"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TrieBuilder = exports.buildTrie = void 0; const trie_1 = require("./trie"); const consolidate_1 = require("./consolidate"); function buildTrie(words) { return new TrieBuilder(words).build(); } exports.buildTrie = buildTrie; class TrieBuilder { constructor(words) { this.count = 0; this.signatures = new Map(); this.cached = new Map(); this.transforms = new Map(); this._eow = Object.freeze({ f: 1 }); /** position 0 of lastPath is always the root */ this.lastPath = [{ s: '', n: { f: undefined, c: undefined } }]; this.tails = new Map([['', this._eow]]); this._canBeCached(this._eow); // this line is just for coverage reasons this.signatures.set(this.signature(this._eow), this._eow); this.cached.set(this._eow, this.count++); if (words) { this.insert(words); } } set _root(n) { this.lastPath[0].n = n; } get _root() { return this.lastPath[0].n; } signature(n) { const isWord = n.f ? '*' : ''; const ref = n.c ? JSON.stringify([...n.c.entries()].map(([k, n]) => [k, this.cached.get(n)])) : ''; return isWord + ref; } _canBeCached(n) { if (!n.c) return true; for (const v of n.c) { if (!this.cached.has(v[1])) return false; } return true; } tryCacheFrozen(n) { if (this.cached.has(n)) return n; this.cached.set(n, this.count++); return n; } freeze(n) { if (Object.isFrozen(n)) return n; // istanbul ignore else if (n.c) { const c = [...n.c] .sort((a, b) => (a[0] < b[0] ? -1 : 1)) .map(([k, n]) => [k, this.freeze(n)]); n.c = new Map(c); Object.freeze(n.c); } return Object.freeze(n); } tryToCache(n) { if (!this._canBeCached(n)) { return n; } const sig = this.signature(n); const ref = this.signatures.get(sig); if (ref !== undefined) { return this.tryCacheFrozen(ref); } this.signatures.set(sig, this.freeze(n)); return n; } storeTransform(src, s, result) { var _a; if (!Object.isFrozen(result) || !Object.isFrozen(src)) return; const t = (_a = this.transforms.get(src)) !== null && _a !== void 0 ? _a : new Map(); t.set(s, result); this.transforms.set(src, t); } addChild(node, head, child) { var _a, _b; if (((_a = node.c) === null || _a === void 0 ? void 0 : _a.get(head)) !== child) { if (!node.c || Object.isFrozen(node)) { node = { ...node, c: new Map((_b = node.c) !== null && _b !== void 0 ? _b : []) }; } node.c.set(head, child); } return Object.isFrozen(child) ? this.tryToCache(node) : node; } buildTail(s) { if (this.tails.has(s)) { return this.tails.get(s); } const head = s[0]; const tail = s.slice(1); const t = this.tails.get(tail); const c = t || this.buildTail(tail); const n = this.addChild({ f: undefined, c: undefined }, head, c); if (!t) { return n; } const cachedNode = this.tryCacheFrozen(Object.freeze(n)); this.tails.set(s, cachedNode); return cachedNode; } _insert(node, s, d) { var _a, _b; const orig = node; if (Object.isFrozen(node)) { const n = (_a = this.transforms.get(node)) === null || _a === void 0 ? void 0 : _a.get(s); if (n) { return this.tryCacheFrozen(n); } } if (!s) { if (!node.c) { return this._eow; } else { node = copyIfFrozen(node); node.f = this._eow.f; return node; } } const head = s[0]; const tail = s.slice(1); const cNode = (_b = node.c) === null || _b === void 0 ? void 0 : _b.get(head); const child = cNode ? this._insert(cNode, tail, d + 1) : this.buildTail(tail); node = this.addChild(node, head, child); this.storeTransform(orig, s, node); this.lastPath[d] = { s: head, n: child }; return node; } insertWord(word) { let d = 1; for (const s of word.split('')) { const p = this.lastPath[d]; if ((p === null || p === void 0 ? void 0 : p.s) !== s) break; d++; } // remove the remaining part of the path because it doesn't match this word. if (word.length < d) { d = word.length; } this.lastPath.length = d; d -= 1; const { n } = this.lastPath[d]; const tail = word.slice(d); this.lastPath[d].n = this._insert(n, tail, d + 1); while (d > 0) { const { s, n } = this.lastPath[d]; d -= 1; const parent = this.lastPath[d]; const pn = parent.n; parent.n = this.addChild(pn, s, n); if (pn === parent.n) break; const tail = word.slice(d); this.storeTransform(pn, tail, parent.n); } } insert(words) { for (const w of words) { w && this.insertWord(w); } } /** * Resets the builder */ reset() { this._root = { f: undefined, c: undefined }; this.cached.clear(); this.signatures.clear(); } build(consolidateSuffixes = false) { const root = this._root; // Reset the builder to prevent updating the trie in the background. this.reset(); return new trie_1.Trie(consolidateSuffixes ? consolidate_1.consolidate(root) : root); } } exports.TrieBuilder = TrieBuilder; function copyIfFrozen(n) { if (!Object.isFrozen(n)) return n; const c = n.c ? new Map(n.c) : undefined; return { f: n.f, c }; } //# sourceMappingURL=TrieBuilder.js.map