LazyBucketSortedSet.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. * Multi layer bucket sorted set:
  10. * Supports adding non-existing items (DO NOT ADD ITEM TWICE),
  11. * Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET),
  12. * Supports popping the first items according to defined order,
  13. * Supports iterating all items without order,
  14. * Supports updating an item in an efficient way,
  15. * Supports size property, which is the number of items,
  16. * Items are lazy partially sorted when needed
  17. * @template T
  18. * @template K
  19. */
  20. class LazyBucketSortedSet {
  21. /**
  22. * @param {function(T): K} getKey function to get key from item
  23. * @param {function(K, K): number} comparator comparator to sort keys
  24. * @param {...((function(T): any) | (function(any, any): number))} args more pairs of getKey and comparator plus optional final comparator for the last layer
  25. */
  26. constructor(getKey, comparator, ...args) {
  27. this._getKey = getKey;
  28. this._innerArgs = args;
  29. this._leaf = args.length <= 1;
  30. this._keys = new SortableSet(undefined, comparator);
  31. /** @type {Map<K, LazyBucketSortedSet<T, any> | SortableSet<T>>} */
  32. this._map = new Map();
  33. this._unsortedItems = new Set();
  34. this.size = 0;
  35. }
  36. /**
  37. * @param {T} item an item
  38. * @returns {void}
  39. */
  40. add(item) {
  41. this.size++;
  42. this._unsortedItems.add(item);
  43. }
  44. /**
  45. * @param {K} key key of item
  46. * @param {T} item the item
  47. * @returns {void}
  48. */
  49. _addInternal(key, item) {
  50. let entry = this._map.get(key);
  51. if (entry === undefined) {
  52. entry = this._leaf
  53. ? new SortableSet(undefined, this._innerArgs[0])
  54. : new /** @type {any} */ (LazyBucketSortedSet)(...this._innerArgs);
  55. this._keys.add(key);
  56. this._map.set(key, entry);
  57. }
  58. entry.add(item);
  59. }
  60. /**
  61. * @param {T} item an item
  62. * @returns {void}
  63. */
  64. delete(item) {
  65. this.size--;
  66. if (this._unsortedItems.has(item)) {
  67. this._unsortedItems.delete(item);
  68. return;
  69. }
  70. const key = this._getKey(item);
  71. const entry = this._map.get(key);
  72. entry.delete(item);
  73. if (entry.size === 0) {
  74. this._deleteKey(key);
  75. }
  76. }
  77. /**
  78. * @param {K} key key to be removed
  79. * @returns {void}
  80. */
  81. _deleteKey(key) {
  82. this._keys.delete(key);
  83. this._map.delete(key);
  84. }
  85. /**
  86. * @returns {T | undefined} an item
  87. */
  88. popFirst() {
  89. if (this.size === 0) return undefined;
  90. this.size--;
  91. if (this._unsortedItems.size > 0) {
  92. for (const item of this._unsortedItems) {
  93. const key = this._getKey(item);
  94. this._addInternal(key, item);
  95. }
  96. this._unsortedItems.clear();
  97. }
  98. this._keys.sort();
  99. const key = first(this._keys);
  100. const entry = this._map.get(key);
  101. if (this._leaf) {
  102. const leafEntry = /** @type {SortableSet<T>} */ (entry);
  103. leafEntry.sort();
  104. const item = first(leafEntry);
  105. leafEntry.delete(item);
  106. if (leafEntry.size === 0) {
  107. this._deleteKey(key);
  108. }
  109. return item;
  110. } else {
  111. const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
  112. const item = nodeEntry.popFirst();
  113. if (nodeEntry.size === 0) {
  114. this._deleteKey(key);
  115. }
  116. return item;
  117. }
  118. }
  119. /**
  120. * @param {T} item to be updated item
  121. * @returns {function(true=): void} finish update
  122. */
  123. startUpdate(item) {
  124. if (this._unsortedItems.has(item)) {
  125. return remove => {
  126. if (remove) {
  127. this._unsortedItems.delete(item);
  128. this.size--;
  129. return;
  130. }
  131. };
  132. }
  133. const key = this._getKey(item);
  134. if (this._leaf) {
  135. const oldEntry = /** @type {SortableSet<T>} */ (this._map.get(key));
  136. return remove => {
  137. if (remove) {
  138. this.size--;
  139. oldEntry.delete(item);
  140. if (oldEntry.size === 0) {
  141. this._deleteKey(key);
  142. }
  143. return;
  144. }
  145. const newKey = this._getKey(item);
  146. if (key === newKey) {
  147. // This flags the sortable set as unordered
  148. oldEntry.add(item);
  149. } else {
  150. oldEntry.delete(item);
  151. if (oldEntry.size === 0) {
  152. this._deleteKey(key);
  153. }
  154. this._addInternal(newKey, item);
  155. }
  156. };
  157. } else {
  158. const oldEntry = /** @type {LazyBucketSortedSet<T, any>} */ (
  159. this._map.get(key)
  160. );
  161. const finishUpdate = oldEntry.startUpdate(item);
  162. return remove => {
  163. if (remove) {
  164. this.size--;
  165. finishUpdate(true);
  166. if (oldEntry.size === 0) {
  167. this._deleteKey(key);
  168. }
  169. return;
  170. }
  171. const newKey = this._getKey(item);
  172. if (key === newKey) {
  173. finishUpdate();
  174. } else {
  175. finishUpdate(true);
  176. if (oldEntry.size === 0) {
  177. this._deleteKey(key);
  178. }
  179. this._addInternal(newKey, item);
  180. }
  181. };
  182. }
  183. }
  184. /**
  185. * @param {Iterator<T>[]} iterators list of iterators to append to
  186. * @returns {void}
  187. */
  188. _appendIterators(iterators) {
  189. if (this._unsortedItems.size > 0)
  190. iterators.push(this._unsortedItems[Symbol.iterator]());
  191. for (const key of this._keys) {
  192. const entry = this._map.get(key);
  193. if (this._leaf) {
  194. const leafEntry = /** @type {SortableSet<T>} */ (entry);
  195. const iterator = leafEntry[Symbol.iterator]();
  196. iterators.push(iterator);
  197. } else {
  198. const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
  199. nodeEntry._appendIterators(iterators);
  200. }
  201. }
  202. }
  203. /**
  204. * @returns {Iterator<T>} the iterator
  205. */
  206. [Symbol.iterator]() {
  207. const iterators = [];
  208. this._appendIterators(iterators);
  209. iterators.reverse();
  210. let currentIterator = iterators.pop();
  211. return {
  212. next: () => {
  213. const res = currentIterator.next();
  214. if (res.done) {
  215. if (iterators.length === 0) return res;
  216. currentIterator = iterators.pop();
  217. return currentIterator.next();
  218. }
  219. return res;
  220. }
  221. };
  222. }
  223. }
  224. module.exports = LazyBucketSortedSet;