LazyBucketSortedSet.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { first } = require("./SetHelpers");
  7. const SortableSet = require("./SortableSet");
  8. /**
  9. * Callback that extracts the grouping key for an item at one bucket layer.
  10. * @template T
  11. * @template K
  12. * @typedef {(item: T) => K} GetKey
  13. */
  14. /**
  15. * Comparison function used to order keys or leaf items.
  16. * @template T
  17. * @typedef {(a: T, n: T) => number} Comparator
  18. */
  19. /**
  20. * Internal bucket entry, either another nested bucket set or a sorted leaf set.
  21. * @template T
  22. * @template K
  23. * @typedef {LazyBucketSortedSet<T, K> | SortableSet<T>} Entry
  24. */
  25. /**
  26. * Constructor argument accepted for nested bucket layers or the final leaf
  27. * comparator.
  28. * @template T
  29. * @template K
  30. * @typedef {GetKey<T, K> | Comparator<K> | Comparator<T>} Arg
  31. */
  32. /**
  33. * Multi layer bucket sorted set:
  34. * Supports adding non-existing items (DO NOT ADD ITEM TWICE),
  35. * Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET),
  36. * Supports popping the first items according to defined order,
  37. * Supports iterating all items without order,
  38. * Supports updating an item in an efficient way,
  39. * Supports size property, which is the number of items,
  40. * Items are lazy partially sorted when needed
  41. * @template T
  42. * @template K
  43. */
  44. class LazyBucketSortedSet {
  45. /**
  46. * Creates a lazily sorted, potentially multi-level bucket structure whose
  47. * order is only fully resolved when items are popped.
  48. * @param {GetKey<T, K>} getKey function to get key from item
  49. * @param {Comparator<K>=} comparator comparator to sort keys
  50. * @param {...Arg<T, K>} args more pairs of getKey and comparator plus optional final comparator for the last layer
  51. */
  52. constructor(getKey, comparator, ...args) {
  53. /** @type {GetKey<T, K>} */
  54. this._getKey = getKey;
  55. /** @type {Arg<T, K>[]} */
  56. this._innerArgs = args;
  57. /** @type {boolean} */
  58. this._leaf = args.length <= 1;
  59. /** @type {SortableSet<K>} */
  60. this._keys = new SortableSet(undefined, comparator);
  61. /** @type {Map<K, Entry<T, K>>} */
  62. this._map = new Map();
  63. /** @type {Set<T>} */
  64. this._unsortedItems = new Set();
  65. /** @type {number} */
  66. this.size = 0;
  67. }
  68. /**
  69. * Adds an item to the unsorted staging area so sorting can be deferred until
  70. * an ordered pop is requested.
  71. * @param {T} item an item
  72. * @returns {void}
  73. */
  74. add(item) {
  75. this.size++;
  76. this._unsortedItems.add(item);
  77. }
  78. /**
  79. * Inserts an item into the correct nested bucket, creating intermediate
  80. * bucket structures on demand.
  81. * @param {K} key key of item
  82. * @param {T} item the item
  83. * @returns {void}
  84. */
  85. _addInternal(key, item) {
  86. let entry = this._map.get(key);
  87. if (entry === undefined) {
  88. entry = this._leaf
  89. ? new SortableSet(
  90. undefined,
  91. /** @type {Comparator<T>} */
  92. (this._innerArgs[0])
  93. )
  94. : new LazyBucketSortedSet(
  95. .../** @type {[GetKey<T, K>, Comparator<K>]} */
  96. (this._innerArgs)
  97. );
  98. this._keys.add(key);
  99. this._map.set(key, entry);
  100. }
  101. entry.add(item);
  102. }
  103. /**
  104. * Removes an item from either the unsorted staging area or its resolved
  105. * bucket and prunes empty buckets as needed.
  106. * @param {T} item an item
  107. * @returns {void}
  108. */
  109. delete(item) {
  110. this.size--;
  111. if (this._unsortedItems.has(item)) {
  112. this._unsortedItems.delete(item);
  113. return;
  114. }
  115. const key = this._getKey(item);
  116. const entry = /** @type {Entry<T, K>} */ (this._map.get(key));
  117. entry.delete(item);
  118. if (entry.size === 0) {
  119. this._deleteKey(key);
  120. }
  121. }
  122. /**
  123. * Removes an empty bucket key and its corresponding nested entry.
  124. * @param {K} key key to be removed
  125. * @returns {void}
  126. */
  127. _deleteKey(key) {
  128. this._keys.delete(key);
  129. this._map.delete(key);
  130. }
  131. /**
  132. * Removes and returns the smallest item according to the configured bucket
  133. * order, sorting only the portions of the structure that are needed.
  134. * @returns {T | undefined} an item
  135. */
  136. popFirst() {
  137. if (this.size === 0) return;
  138. this.size--;
  139. if (this._unsortedItems.size > 0) {
  140. for (const item of this._unsortedItems) {
  141. const key = this._getKey(item);
  142. this._addInternal(key, item);
  143. }
  144. this._unsortedItems.clear();
  145. }
  146. this._keys.sort();
  147. const key = /** @type {K} */ (first(this._keys));
  148. const entry = this._map.get(key);
  149. if (this._leaf) {
  150. const leafEntry = /** @type {SortableSet<T>} */ (entry);
  151. leafEntry.sort();
  152. const item = /** @type {T} */ (first(leafEntry));
  153. leafEntry.delete(item);
  154. if (leafEntry.size === 0) {
  155. this._deleteKey(key);
  156. }
  157. return item;
  158. }
  159. const nodeEntry =
  160. /** @type {LazyBucketSortedSet<T, K>} */
  161. (entry);
  162. const item = nodeEntry.popFirst();
  163. if (nodeEntry.size === 0) {
  164. this._deleteKey(key);
  165. }
  166. return item;
  167. }
  168. /**
  169. * Begins an in-place update for an item and returns a completion callback
  170. * that can either reinsert it under a new key or remove it entirely.
  171. * @param {T} item to be updated item
  172. * @returns {(remove?: true) => void} finish update
  173. */
  174. startUpdate(item) {
  175. if (this._unsortedItems.has(item)) {
  176. return (remove) => {
  177. if (remove) {
  178. this._unsortedItems.delete(item);
  179. this.size--;
  180. }
  181. };
  182. }
  183. const key = this._getKey(item);
  184. if (this._leaf) {
  185. const oldEntry = /** @type {SortableSet<T>} */ (this._map.get(key));
  186. return (remove) => {
  187. if (remove) {
  188. this.size--;
  189. oldEntry.delete(item);
  190. if (oldEntry.size === 0) {
  191. this._deleteKey(key);
  192. }
  193. return;
  194. }
  195. const newKey = this._getKey(item);
  196. if (key === newKey) {
  197. // This flags the sortable set as unordered
  198. oldEntry.add(item);
  199. } else {
  200. oldEntry.delete(item);
  201. if (oldEntry.size === 0) {
  202. this._deleteKey(key);
  203. }
  204. this._addInternal(newKey, item);
  205. }
  206. };
  207. }
  208. const oldEntry =
  209. /** @type {LazyBucketSortedSet<T, K>} */
  210. (this._map.get(key));
  211. const finishUpdate = oldEntry.startUpdate(item);
  212. return (remove) => {
  213. if (remove) {
  214. this.size--;
  215. finishUpdate(true);
  216. if (oldEntry.size === 0) {
  217. this._deleteKey(key);
  218. }
  219. return;
  220. }
  221. const newKey = this._getKey(item);
  222. if (key === newKey) {
  223. finishUpdate();
  224. } else {
  225. finishUpdate(true);
  226. if (oldEntry.size === 0) {
  227. this._deleteKey(key);
  228. }
  229. this._addInternal(newKey, item);
  230. }
  231. };
  232. }
  233. /**
  234. * Appends iterators for every stored bucket and leaf to support unordered
  235. * traversal across the entire structure.
  236. * @param {Iterator<T>[]} iterators list of iterators to append to
  237. * @returns {void}
  238. */
  239. _appendIterators(iterators) {
  240. if (this._unsortedItems.size > 0) {
  241. iterators.push(this._unsortedItems[Symbol.iterator]());
  242. }
  243. for (const key of this._keys) {
  244. const entry = this._map.get(key);
  245. if (this._leaf) {
  246. const leafEntry = /** @type {SortableSet<T>} */ (entry);
  247. const iterator = leafEntry[Symbol.iterator]();
  248. iterators.push(iterator);
  249. } else {
  250. const nodeEntry =
  251. /** @type {LazyBucketSortedSet<T, K>} */
  252. (entry);
  253. nodeEntry._appendIterators(iterators);
  254. }
  255. }
  256. }
  257. /**
  258. * Iterates over all stored items without imposing bucket sort order.
  259. * @returns {Iterator<T>} the iterator
  260. */
  261. [Symbol.iterator]() {
  262. /** @type {Iterator<T>[]} */
  263. const iterators = [];
  264. this._appendIterators(iterators);
  265. iterators.reverse();
  266. let currentIterator =
  267. /** @type {Iterator<T>} */
  268. (iterators.pop());
  269. return {
  270. next: () => {
  271. const res = currentIterator.next();
  272. if (res.done) {
  273. if (iterators.length === 0) return res;
  274. currentIterator = /** @type {Iterator<T>} */ (iterators.pop());
  275. return currentIterator.next();
  276. }
  277. return res;
  278. }
  279. };
  280. }
  281. }
  282. module.exports = LazyBucketSortedSet;