UNPKG

froebel

Version:
116 lines (98 loc) 3.13 kB
/** * Behaves like a regular JavaScript `Map`, but its iteration order is dependant * on the `compare` function supplied in the constructor. * * Note: The item's sort position is only computed automatically on insertion. * If you update one of the values that the `compare` function depends on, you * must call the `update(key)` method afterwards to ensure the map stays sorted. */ export default class SortedMap { constructor(compare, entries) { this.#cmp = compare; if (!(entries !== null && entries !== void 0 && entries.length)) return; const sorted = [...entries].sort(([ka, va], [kb, vb]) => compare(va, vb, ka, kb)); for (let i = 0; i < entries.length; i++) { this.#map.set(...sorted[i]); this.#order.push(i); } } #cmp; #map = new Map(); #order = []; #bind = method => this.#map[method].bind(this.#map); clear = this.#bind("clear"); get = this.#bind("get"); has = this.#bind("has"); set(key, value) { if (this.#map.has(key)) this.delete(key); let i = 0; for (const [k, v] of this) { if (this.#cmp(value, v, key, k) < 0) break; i++; } this.#order.splice(i, 0, this.size); this.#map.set(key, value); return this; } delete(key) { if (this.#map.has(key)) { const i = [...this.#map.keys()].indexOf(key); this.#order.splice(this.#order.indexOf(i), 1); this.#order = this.#order.map(n => n < i ? n : n - 1); } return this.#map.delete(key); } /** * Update the sort position of the element at `key` if necessary. * * This method should be called to notify the SortedMap that one of the * parameters that the `compare` function depends on has been updated and * consequently the sort order must be verified/updated. * * @returns `true` if the sort position of the element with `key` had to be * updated, `false` if not. */ update(key) { const entries = [...this.#map.entries()]; const i = entries.findIndex(([k]) => k === key); const oi = this.#order.indexOf(i); const e = entries[i]; const l = entries[this.#order[oi - 1]]; const r = entries[this.#order[oi + 1]]; if ((!l || this.#cmp(l[1], e[1], l[0], e[0]) <= 0) && (!r || this.#cmp(e[1], r[1], e[0], r[0]) <= 0)) { return false; } this.set(...e); return true; } forEach(callback) { for (const [k, v] of this.entries()) callback(v, k, this); } map(callback) { return [...this].map(([k, v]) => callback(v, k, this)); } get size() { return this.#map.size; } #orderIter(iter) { const items = [...iter]; let i = -1; return { next: () => { i++; return { done: i >= items.length, value: items[this.#order[i]] }; }, [Symbol.iterator]() { return this; } }; } [Symbol.iterator] = () => this.#orderIter(this.#map); entries = () => this.#orderIter(this.#map.entries()); keys = () => this.#orderIter(this.#map.keys()); values = () => this.#orderIter(this.#map.values()); [Symbol.toStringTag] = this.#map[Symbol.toStringTag]; }