LazySet.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const makeSerializable = require("./makeSerializable");
  7. /**
  8. * @template T
  9. * @param {Set<T>} targetSet set where items should be added
  10. * @param {Set<Iterable<T>>} toMerge iterables to be merged
  11. * @returns {void}
  12. */
  13. const merge = (targetSet, toMerge) => {
  14. for (const set of toMerge) {
  15. for (const item of set) {
  16. targetSet.add(item);
  17. }
  18. }
  19. };
  20. /**
  21. * @template T
  22. * @param {Set<Iterable<T>>} targetSet set where iterables should be added
  23. * @param {Array<LazySet<T>>} toDeepMerge lazy sets to be flattened
  24. * @returns {void}
  25. */
  26. const flatten = (targetSet, toDeepMerge) => {
  27. for (const set of toDeepMerge) {
  28. if (set._set.size > 0) targetSet.add(set._set);
  29. if (set._needMerge) {
  30. for (const mergedSet of set._toMerge) {
  31. targetSet.add(mergedSet);
  32. }
  33. flatten(targetSet, set._toDeepMerge);
  34. }
  35. }
  36. };
  37. /**
  38. * @template T
  39. * @typedef {import("typescript-iterable").SetIterator<T>} SetIterator
  40. */
  41. /**
  42. * Like Set but with an addAll method to eventually add items from another iterable.
  43. * Access methods make sure that all delayed operations are executed.
  44. * Iteration methods deopts to normal Set performance until clear is called again (because of the chance of modifications during iteration).
  45. * @template T
  46. */
  47. class LazySet {
  48. /**
  49. * @param {Iterable<T>=} iterable init iterable
  50. */
  51. constructor(iterable) {
  52. /** @type {Set<T>} */
  53. this._set = new Set(iterable);
  54. /** @type {Set<Iterable<T>>} */
  55. this._toMerge = new Set();
  56. /** @type {Array<LazySet<T>>} */
  57. this._toDeepMerge = [];
  58. this._needMerge = false;
  59. this._deopt = false;
  60. }
  61. _flatten() {
  62. flatten(this._toMerge, this._toDeepMerge);
  63. this._toDeepMerge.length = 0;
  64. }
  65. _merge() {
  66. this._flatten();
  67. merge(this._set, this._toMerge);
  68. this._toMerge.clear();
  69. this._needMerge = false;
  70. }
  71. _isEmpty() {
  72. return (
  73. this._set.size === 0 &&
  74. this._toMerge.size === 0 &&
  75. this._toDeepMerge.length === 0
  76. );
  77. }
  78. get size() {
  79. if (this._needMerge) this._merge();
  80. return this._set.size;
  81. }
  82. /**
  83. * @param {T} item an item
  84. * @returns {LazySet<T>} itself
  85. */
  86. add(item) {
  87. this._set.add(item);
  88. return this;
  89. }
  90. /**
  91. * @param {Iterable<T> | LazySet<T>} iterable a immutable iterable or another immutable LazySet which will eventually be merged into the Set
  92. * @returns {LazySet<T>} itself
  93. */
  94. addAll(iterable) {
  95. if (this._deopt) {
  96. const _set = this._set;
  97. for (const item of iterable) {
  98. _set.add(item);
  99. }
  100. } else {
  101. if (iterable instanceof LazySet) {
  102. if (iterable._isEmpty()) return this;
  103. this._toDeepMerge.push(iterable);
  104. this._needMerge = true;
  105. if (this._toDeepMerge.length > 100000) {
  106. this._flatten();
  107. }
  108. } else {
  109. this._toMerge.add(iterable);
  110. this._needMerge = true;
  111. }
  112. if (this._toMerge.size > 100000) this._merge();
  113. }
  114. return this;
  115. }
  116. clear() {
  117. this._set.clear();
  118. this._toMerge.clear();
  119. this._toDeepMerge.length = 0;
  120. this._needMerge = false;
  121. this._deopt = false;
  122. }
  123. /**
  124. * @param {T} value an item
  125. * @returns {boolean} true, if the value was in the Set before
  126. */
  127. delete(value) {
  128. if (this._needMerge) this._merge();
  129. return this._set.delete(value);
  130. }
  131. /**
  132. * @returns {SetIterator<[T, T]>} entries
  133. */
  134. entries() {
  135. this._deopt = true;
  136. if (this._needMerge) this._merge();
  137. return this._set.entries();
  138. }
  139. /**
  140. * @template K
  141. * @param {(value: T, value2: T, set: Set<T>) => void} callbackFn function called for each entry
  142. * @param {K} thisArg this argument for the callbackFn
  143. * @returns {void}
  144. */
  145. forEach(callbackFn, thisArg) {
  146. this._deopt = true;
  147. if (this._needMerge) this._merge();
  148. // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-method-this-argument
  149. this._set.forEach(callbackFn, thisArg);
  150. }
  151. /**
  152. * @param {T} item an item
  153. * @returns {boolean} true, when the item is in the Set
  154. */
  155. has(item) {
  156. if (this._needMerge) this._merge();
  157. return this._set.has(item);
  158. }
  159. /**
  160. * @returns {SetIterator<T>} keys
  161. */
  162. keys() {
  163. this._deopt = true;
  164. if (this._needMerge) this._merge();
  165. return this._set.keys();
  166. }
  167. /**
  168. * @returns {SetIterator<T>} values
  169. */
  170. values() {
  171. this._deopt = true;
  172. if (this._needMerge) this._merge();
  173. return this._set.values();
  174. }
  175. /**
  176. * @returns {SetIterator<T>} iterable iterator
  177. */
  178. [Symbol.iterator]() {
  179. this._deopt = true;
  180. if (this._needMerge) this._merge();
  181. return this._set[Symbol.iterator]();
  182. }
  183. /* istanbul ignore next */
  184. get [Symbol.toStringTag]() {
  185. return "LazySet";
  186. }
  187. /**
  188. * @param {import("../serialization/ObjectMiddleware").ObjectSerializerContext} context context
  189. */
  190. serialize({ write }) {
  191. if (this._needMerge) this._merge();
  192. write(this._set.size);
  193. for (const item of this._set) write(item);
  194. }
  195. /**
  196. * @template T
  197. * @param {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} context context
  198. * @returns {LazySet<T>} lazy set
  199. */
  200. static deserialize({ read }) {
  201. const count = read();
  202. const items = [];
  203. for (let i = 0; i < count; i++) {
  204. items.push(read());
  205. }
  206. return new LazySet(items);
  207. }
  208. }
  209. makeSerializable(LazySet, "webpack/lib/util/LazySet");
  210. module.exports = LazySet;