| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const SortableSet = require("./SortableSet"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-10-13 02:16:58 +08:00
										 |  |  |  * Multi layer bucket sorted set: | 
					
						
							|  |  |  |  * Supports adding non-existing items (DO NOT ADD ITEM TWICE), | 
					
						
							|  |  |  |  * Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET), | 
					
						
							|  |  |  |  * Supports popping the first items according to defined order, | 
					
						
							|  |  |  |  * Supports iterating all items without order, | 
					
						
							|  |  |  |  * Supports updating an item in an efficient way, | 
					
						
							|  |  |  |  * Supports size property, which is the number of items, | 
					
						
							|  |  |  |  * Items are lazy partially sorted when needed | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  |  * @template T | 
					
						
							|  |  |  |  * @template K | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class LazyBucketSortedSet { | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {function(T): K} getKey function to get key from item | 
					
						
							|  |  |  | 	 * @param {function(K, K): number} comparator comparator to sort keys | 
					
						
							|  |  |  | 	 * @param  {...((function(T): any) | (function(any, any): number))} args more pairs of getKey and comparator plus optional final comparator for the last layer | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	constructor(getKey, comparator, ...args) { | 
					
						
							|  |  |  | 		this._getKey = getKey; | 
					
						
							|  |  |  | 		this._innerArgs = args; | 
					
						
							|  |  |  | 		this._leaf = args.length <= 1; | 
					
						
							|  |  |  | 		this._keys = new SortableSet(undefined, comparator); | 
					
						
							|  |  |  | 		/** @type {Map<K, LazyBucketSortedSet<T, any> | SortableSet<T>>} */ | 
					
						
							|  |  |  | 		this._map = new Map(); | 
					
						
							|  |  |  | 		this._unsortedItems = new Set(); | 
					
						
							|  |  |  | 		this.size = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {T} item an item | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	add(item) { | 
					
						
							|  |  |  | 		this.size++; | 
					
						
							|  |  |  | 		this._unsortedItems.add(item); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {K} key key of item | 
					
						
							|  |  |  | 	 * @param {T} item the item | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_addInternal(key, item) { | 
					
						
							|  |  |  | 		let entry = this._map.get(key); | 
					
						
							|  |  |  | 		if (entry === undefined) { | 
					
						
							|  |  |  | 			entry = this._leaf | 
					
						
							|  |  |  | 				? new SortableSet(undefined, this._innerArgs[0]) | 
					
						
							|  |  |  | 				: new /** @type {any} */ (LazyBucketSortedSet)(...this._innerArgs); | 
					
						
							|  |  |  | 			this._keys.add(key); | 
					
						
							|  |  |  | 			this._map.set(key, entry); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		entry.add(item); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {T} item an item | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	delete(item) { | 
					
						
							|  |  |  | 		this.size--; | 
					
						
							|  |  |  | 		if (this._unsortedItems.has(item)) { | 
					
						
							|  |  |  | 			this._unsortedItems.delete(item); | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		const key = this._getKey(item); | 
					
						
							|  |  |  | 		const entry = this._map.get(key); | 
					
						
							|  |  |  | 		entry.delete(item); | 
					
						
							|  |  |  | 		if (entry.size === 0) { | 
					
						
							|  |  |  | 			this._deleteKey(key); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {K} key key to be removed | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_deleteKey(key) { | 
					
						
							|  |  |  | 		this._keys.delete(key); | 
					
						
							|  |  |  | 		this._map.delete(key); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {T | undefined} an item | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	popFirst() { | 
					
						
							|  |  |  | 		if (this.size === 0) return undefined; | 
					
						
							|  |  |  | 		this.size--; | 
					
						
							|  |  |  | 		if (this._unsortedItems.size > 0) { | 
					
						
							|  |  |  | 			for (const item of this._unsortedItems) { | 
					
						
							|  |  |  | 				const key = this._getKey(item); | 
					
						
							|  |  |  | 				this._addInternal(key, item); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			this._unsortedItems.clear(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this._keys.sort(); | 
					
						
							|  |  |  | 		const key = this._keys.values().next().value; | 
					
						
							|  |  |  | 		const entry = this._map.get(key); | 
					
						
							|  |  |  | 		if (this._leaf) { | 
					
						
							|  |  |  | 			const leafEntry = /** @type {SortableSet<T>} */ (entry); | 
					
						
							|  |  |  | 			leafEntry.sort(); | 
					
						
							|  |  |  | 			const item = leafEntry.values().next().value; | 
					
						
							|  |  |  | 			leafEntry.delete(item); | 
					
						
							|  |  |  | 			if (leafEntry.size === 0) { | 
					
						
							|  |  |  | 				this._deleteKey(key); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return item; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry); | 
					
						
							|  |  |  | 			const item = nodeEntry.popFirst(); | 
					
						
							|  |  |  | 			if (nodeEntry.size === 0) { | 
					
						
							|  |  |  | 				this._deleteKey(key); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return item; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {T} item to be updated item | 
					
						
							|  |  |  | 	 * @returns {function(true=): void} finish update | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	startUpdate(item) { | 
					
						
							|  |  |  | 		if (this._unsortedItems.has(item)) { | 
					
						
							|  |  |  | 			return remove => { | 
					
						
							|  |  |  | 				if (remove) { | 
					
						
							|  |  |  | 					this._unsortedItems.delete(item); | 
					
						
							|  |  |  | 					this.size--; | 
					
						
							|  |  |  | 					return; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		const key = this._getKey(item); | 
					
						
							|  |  |  | 		if (this._leaf) { | 
					
						
							|  |  |  | 			const oldEntry = /** @type {SortableSet<T>} */ (this._map.get(key)); | 
					
						
							|  |  |  | 			return remove => { | 
					
						
							|  |  |  | 				if (remove) { | 
					
						
							|  |  |  | 					this.size--; | 
					
						
							|  |  |  | 					oldEntry.delete(item); | 
					
						
							|  |  |  | 					if (oldEntry.size === 0) { | 
					
						
							|  |  |  | 						this._deleteKey(key); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				const newKey = this._getKey(item); | 
					
						
							|  |  |  | 				if (key === newKey) { | 
					
						
							|  |  |  | 					// This flags the sortable set as unordered
 | 
					
						
							|  |  |  | 					oldEntry.add(item); | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					oldEntry.delete(item); | 
					
						
							|  |  |  | 					if (oldEntry.size === 0) { | 
					
						
							|  |  |  | 						this._deleteKey(key); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					this._addInternal(newKey, item); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			const oldEntry = /** @type {LazyBucketSortedSet<T, any>} */ (this._map.get( | 
					
						
							|  |  |  | 				key | 
					
						
							|  |  |  | 			)); | 
					
						
							|  |  |  | 			const finishUpdate = oldEntry.startUpdate(item); | 
					
						
							|  |  |  | 			return remove => { | 
					
						
							|  |  |  | 				if (remove) { | 
					
						
							|  |  |  | 					this.size--; | 
					
						
							|  |  |  | 					finishUpdate(true); | 
					
						
							|  |  |  | 					if (oldEntry.size === 0) { | 
					
						
							|  |  |  | 						this._deleteKey(key); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				const newKey = this._getKey(item); | 
					
						
							|  |  |  | 				if (key === newKey) { | 
					
						
							|  |  |  | 					finishUpdate(); | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					finishUpdate(true); | 
					
						
							|  |  |  | 					if (oldEntry.size === 0) { | 
					
						
							|  |  |  | 						this._deleteKey(key); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					this._addInternal(newKey, item); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {Iterator<T>[]} iterators list of iterators to append to | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_appendIterators(iterators) { | 
					
						
							|  |  |  | 		if (this._unsortedItems.size > 0) | 
					
						
							|  |  |  | 			iterators.push(this._unsortedItems[Symbol.iterator]()); | 
					
						
							|  |  |  | 		for (const key of this._keys) { | 
					
						
							|  |  |  | 			const entry = this._map.get(key); | 
					
						
							|  |  |  | 			if (this._leaf) { | 
					
						
							|  |  |  | 				const leafEntry = /** @type {SortableSet<T>} */ (entry); | 
					
						
							|  |  |  | 				const iterator = leafEntry[Symbol.iterator](); | 
					
						
							|  |  |  | 				iterators.push(iterator); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry); | 
					
						
							|  |  |  | 				nodeEntry._appendIterators(iterators); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {Iterator<T>} the iterator | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	[Symbol.iterator]() { | 
					
						
							|  |  |  | 		const iterators = []; | 
					
						
							|  |  |  | 		this._appendIterators(iterators); | 
					
						
							|  |  |  | 		iterators.reverse(); | 
					
						
							|  |  |  | 		let currentIterator = iterators.pop(); | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			next: () => { | 
					
						
							|  |  |  | 				const res = currentIterator.next(); | 
					
						
							|  |  |  | 				if (res.done) { | 
					
						
							|  |  |  | 					if (iterators.length === 0) return res; | 
					
						
							|  |  |  | 					currentIterator = iterators.pop(); | 
					
						
							|  |  |  | 					return currentIterator.next(); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return res; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = LazyBucketSortedSet; |