You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
6.1 KiB
196 lines
6.1 KiB
"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
|