UNPKG

@thi.ng/trie

Version:

Trie-based ES6-like Map data structures with prefix search/query support

176 lines (175 loc) 4.07 kB
class MultiTrie { constructor(pairs, opts) { this.opts = opts; pairs && this.into(pairs); } opts; next = /* @__PURE__ */ new Map(); vals; get size() { let size = 0; const stack = [this]; while (stack.length) { const node = stack.pop(); size += node.vals?.size ?? 0; if (node.next) stack.push(...node.next.values()); } return size; } [Symbol.iterator]() { return this.iterate( (key, node) => [...node.vals].map((v) => [key, v]) ); } keys(prefix = [], includePrefix = true) { return this.iterate((key) => [key], prefix, includePrefix); } values(prefix) { return this.iterate((_, node) => node.vals, prefix); } entries(prefix, includePrefix = true) { return this.iterate( (key, node) => [...node.vals].map((v) => [key, v]), prefix, includePrefix ); } clear() { this.next.clear(); this.vals = void 0; } has(key) { return !!this.get(key); } hasPrefix(prefix) { return !!this.find(prefix); } get(key) { const node = this.find(key); return node ? node.vals : void 0; } find(key) { let node = this; for (let i = 0, n = key.length; i < n; i++) { node = node.next.get(key[i]); if (!node) return; } return node; } /** * Returns longest known prefix for `key` as array. If array is * empty, the given key has no partial matches. * * @param key - */ knownPrefix(key) { let node = this; const prefix = []; for (let i = 0, n = key.length; i < n; i++) { const k = key[i]; const next = node.next.get(k); if (!next) break; prefix.push(k); node = next; } return prefix; } hasKnownPrefix(key) { return this.knownPrefix(key).length > 0; } add(key, val) { let node = this; for (let i = 0, n = key.length; i < n; i++) { const k = key[i]; const next = node.next.get(k); if (!next) { const newNode = new MultiTrie(null, this.opts); node.next.set(k, newNode); node = newNode; } else { node = next; } } if (!node.vals) { node.vals = this.opts?.values?.() ?? /* @__PURE__ */ new Set(); } node.vals.add(val); } into(pairs) { for (const [k, v] of pairs) { this.add(k, v); } } delete(prefix, val) { const n = prefix.length; if (n < 1) return false; const path = []; const key = []; let i = 0; let node = this; for (; i < n; i++) { const k = prefix[i]; key.push(k); path.push(node); node = node.next.get(k); if (!node) return false; } if (val !== void 0) { const vals = node.vals; if (vals?.has(val)) { vals.delete(val); if (vals.size > 0) return true; } else { return false; } } while (node = path[--i]) { node.next.delete(key[i]); if (node.next.size) break; } return true; } toJSON() { return { next: [...this.next].reduce( (acc, [k, v]) => (acc[k] = v.toJSON(), acc), {} ), vals: this.vals ? [...this.vals] : void 0 }; } *iterate(fn, prefix = [], includePrefix = true) { const root = this.find(prefix); if (!root) return; const stack = [ [includePrefix ? prefix : [], root] ]; while (stack.length) { const [key, node] = stack.pop(); if (node.vals) yield* fn(key, node); for (let [k, v] of node.next) { stack.push([key.concat(k), v]); } } } } const defMultiTrie = (pairs, opts) => new MultiTrie(pairs, opts); const defMultiTrieFromJSON = (src, opts) => { const res = defMultiTrie(null, opts); const queue = [[[], src]]; while (queue.length) { const [key, node] = queue.pop(); if (node.vals) { for (const v of node.vals) res.add(key, v); } for (const [k, child] of Object.entries(node.next)) { queue.push([[...key, k], child]); } } return res; }; export { MultiTrie, defMultiTrie, defMultiTrieFromJSON };