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