LazyBucketSortedSet.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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. this._getKey = getKey;
  54. this._innerArgs = args;
  55. this._leaf = args.length <= 1;
  56. this._keys = new SortableSet(undefined, comparator);
  57. /** @type {Map<K, Entry<T, K>>} */
  58. this._map = new Map();
  59. /** @type {Set<T>} */
  60. this._unsortedItems = new Set();
  61. this.size = 0;
  62. }
  63. /**
  64. * Adds an item to the unsorted staging area so sorting can be deferred until
  65. * an ordered pop is requested.
  66. * @param {T} item an item
  67. * @returns {void}
  68. */
  69. add(item) {
  70. this.size++;
  71. this._unsortedItems.add(item);
  72. }
  73. /**
  74. * Inserts an item into the correct nested bucket, creating intermediate
  75. * bucket structures on demand.
  76. * @param {K} key key of item
  77. * @param {T} item the item
  78. * @returns {void}
  79. */
  80. _addInternal(key, item) {
  81. let entry = this._map.get(key);
  82. if (entry === undefined) {
  83. entry = this._leaf
  84. ? new SortableSet(
  85. undefined,
  86. /** @type {Comparator<T>} */
  87. (this._innerArgs[0])
  88. )
  89. : new LazyBucketSortedSet(
  90. .../** @type {[GetKey<T, K>, Comparator<K>]} */
  91. (this._innerArgs)
  92. );
  93. this._keys.add(key);
  94. this._map.set(key, entry);
  95. }
  96. entry.add(item);
  97. }
  98. /**
  99. * Removes an item from either the unsorted staging area or its resolved
  100. * bucket and prunes empty buckets as needed.
  101. * @param {T} item an item
  102. * @returns {void}
  103. */
  104. delete(item) {
  105. this.size--;
  106. if (this._unsortedItems.has(item)) {
  107. this._unsortedItems.delete(item);
  108. return;
  109. }
  110. const key = this._getKey(item);
  111. const entry = /** @type {Entry<T, K>} */ (this._map.get(key));
  112. entry.delete(item);
  113. if (entry.size === 0) {
  114. this._deleteKey(key);
  115. }
  116. }
  117. /**
  118. * Removes an empty bucket key and its corresponding nested entry.
  119. * @param {K} key key to be removed
  120. * @returns {void}
  121. */
  122. _deleteKey(key) {
  123. this._keys.delete(key);
  124. this._map.delete(key);
  125. }
  126. /**
  127. * Removes and returns the smallest item according to the configured bucket
  128. * order, sorting only the portions of the structure that are needed.
  129. * @returns {T | undefined} an item
  130. */
  131. popFirst() {
  132. if (this.size === 0) return;
  133. this.size--;
  134. if (this._unsortedItems.size > 0) {
  135. for (const item of this._unsortedItems) {
  136. const key = this._getKey(item);
  137. this._addInternal(key, item);
  138. }
  139. this._unsortedItems.clear();
  140. }
  141. this._keys.sort();
  142. const key = /** @type {K} */ (first(this._keys));
  143. const entry = this._map.get(key);
  144. if (this._leaf) {
  145. const leafEntry = /** @type {SortableSet<T>} */ (entry);
  146. leafEntry.sort();
  147. const item = /** @type {T} */ (first(leafEntry));
  148. leafEntry.delete(item);
  149. if (leafEntry.size === 0) {
  150. this._deleteKey(key);
  151. }
  152. return item;
  153. }
  154. const nodeEntry =
  155. /** @type {LazyBucketSortedSet<T, K>} */
  156. (entry);
  157. const item = nodeEntry.popFirst();
  158. if (nodeEntry.size === 0) {
  159. this._deleteKey(key);
  160. }
  161. return item;
  162. }
  163. /**
  164. * Begins an in-place update for an item and returns a completion callback
  165. * that can either reinsert it under a new key or remove it entirely.
  166. * @param {T} item to be updated item
  167. * @returns {(remove?: true) => void} finish update
  168. */
  169. startUpdate(item) {
  170. if (this._unsortedItems.has(item)) {
  171. return (remove) => {
  172. if (remove) {
  173. this._unsortedItems.delete(item);
  174. this.size--;
  175. }
  176. };
  177. }
  178. const key = this._getKey(item);
  179. if (this._leaf) {
  180. const oldEntry = /** @type {SortableSet<T>} */ (this._map.get(key));
  181. return (remove) => {
  182. if (remove) {
  183. this.size--;
  184. oldEntry.delete(item);
  185. if (oldEntry.size === 0) {
  186. this._deleteKey(key);
  187. }
  188. return;
  189. }
  190. const newKey = this._getKey(item);
  191. if (key === newKey) {
  192. // This flags the sortable set as unordered
  193. oldEntry.add(item);
  194. } else {
  195. oldEntry.delete(item);
  196. if (oldEntry.size === 0) {
  197. this._deleteKey(key);
  198. }
  199. this._addInternal(newKey, item);
  200. }
  201. };
  202. }
  203. const oldEntry =
  204. /** @type {LazyBucketSortedSet<T, K>} */
  205. (this._map.get(key));
  206. const finishUpdate = oldEntry.startUpdate(item);
  207. return (remove) => {
  208. if (remove) {
  209. this.size--;
  210. finishUpdate(true);
  211. if (oldEntry.size === 0) {
  212. this._deleteKey(key);
  213. }
  214. return;
  215. }
  216. const newKey = this._getKey(item);
  217. if (key === newKey) {
  218. finishUpdate();
  219. } else {
  220. finishUpdate(true);
  221. if (oldEntry.size === 0) {
  222. this._deleteKey(key);
  223. }
  224. this._addInternal(newKey, item);
  225. }
  226. };
  227. }
  228. /**
  229. * Appends iterators for every stored bucket and leaf to support unordered
  230. * traversal across the entire structure.
  231. * @param {Iterator<T>[]} iterators list of iterators to append to
  232. * @returns {void}
  233. */
  234. _appendIterators(iterators) {
  235. if (this._unsortedItems.size > 0) {
  236. iterators.push(this._unsortedItems[Symbol.iterator]());
  237. }
  238. for (const key of this._keys) {
  239. const entry = this._map.get(key);
  240. if (this._leaf) {
  241. const leafEntry = /** @type {SortableSet<T>} */ (entry);
  242. const iterator = leafEntry[Symbol.iterator]();
  243. iterators.push(iterator);
  244. } else {
  245. const nodeEntry =
  246. /** @type {LazyBucketSortedSet<T, K>} */
  247. (entry);
  248. nodeEntry._appendIterators(iterators);
  249. }
  250. }
  251. }
  252. /**
  253. * Iterates over all stored items without imposing bucket sort order.
  254. * @returns {Iterator<T>} the iterator
  255. */
  256. [Symbol.iterator]() {
  257. /** @type {Iterator<T>[]} */
  258. const iterators = [];
  259. this._appendIterators(iterators);
  260. iterators.reverse();
  261. let currentIterator =
  262. /** @type {Iterator<T>} */
  263. (iterators.pop());
  264. return {
  265. next: () => {
  266. const res = currentIterator.next();
  267. if (res.done) {
  268. if (iterators.length === 0) return res;
  269. currentIterator = /** @type {Iterator<T>} */ (iterators.pop());
  270. return currentIterator.next();
  271. }
  272. return res;
  273. }
  274. };
  275. }
  276. }
  277. module.exports = LazyBucketSortedSet;