You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
5.6 KiB

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