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.
154 lines
4.3 KiB
154 lines
4.3 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.isCircular = exports.countWords = exports.countNodes = exports.findNode = exports.has = exports.createTriFromList = exports.createRoot = exports.iteratorTrieWords = exports.iterateTrie = exports.walk = exports.orderTrie = exports.isWordTerminationNode = exports.insert = void 0; |
|
const gensequence_1 = require("gensequence"); |
|
const TrieNode_1 = require("./TrieNode"); |
|
const walker_1 = require("./walker"); |
|
function insert(text, node = {}) { |
|
if (text.length) { |
|
const head = text[0]; |
|
const tail = text.slice(1); |
|
node.c = node.c || new TrieNode_1.ChildMap(); |
|
node.c.set(head, insert(tail, node.c.get(head))); |
|
} |
|
else { |
|
node.f = (node.f || 0) | TrieNode_1.FLAG_WORD; |
|
} |
|
return node; |
|
} |
|
exports.insert = insert; |
|
function isWordTerminationNode(node) { |
|
return ((node.f || 0) & TrieNode_1.FLAG_WORD) === TrieNode_1.FLAG_WORD; |
|
} |
|
exports.isWordTerminationNode = isWordTerminationNode; |
|
/** |
|
* Sorts the nodes in a trie in place. |
|
*/ |
|
function orderTrie(node) { |
|
if (!node.c) |
|
return; |
|
const nodes = [...node.c].sort(([a], [b]) => (a < b ? -1 : 1)); |
|
node.c = new Map(nodes); |
|
for (const n of node.c) { |
|
orderTrie(n[1]); |
|
} |
|
} |
|
exports.orderTrie = orderTrie; |
|
/** |
|
* Generator an iterator that will walk the Trie parent then children in a depth first fashion that preserves sorted order. |
|
*/ |
|
function walk(node) { |
|
return gensequence_1.genSequence(walker_1.walker(node)); |
|
} |
|
exports.walk = walk; |
|
exports.iterateTrie = walk; |
|
/** |
|
* Generate a Iterator that can walk a Trie and yield the words. |
|
*/ |
|
function iteratorTrieWords(node) { |
|
return walk(node) |
|
.filter((r) => isWordTerminationNode(r.node)) |
|
.map((r) => r.text); |
|
} |
|
exports.iteratorTrieWords = iteratorTrieWords; |
|
function createRoot() { |
|
return {}; |
|
} |
|
exports.createRoot = createRoot; |
|
function createTriFromList(words) { |
|
const root = createRoot(); |
|
for (const word of words) { |
|
if (word.length) { |
|
insert(word, root); |
|
} |
|
} |
|
return root; |
|
} |
|
exports.createTriFromList = createTriFromList; |
|
function has(node, word) { |
|
let h = word.slice(0, 1); |
|
let t = word.slice(1); |
|
while (node.c && node.c.has(h)) { |
|
node = node.c.get(h); |
|
h = t.slice(0, 1); |
|
t = t.slice(1); |
|
} |
|
return !h.length && !!((node.f || 0) & TrieNode_1.FLAG_WORD); |
|
} |
|
exports.has = has; |
|
function findNode(node, prefix) { |
|
let h = prefix.slice(0, 1); |
|
let t = prefix.slice(1); |
|
let n = node; |
|
while (h.length && n && n.c) { |
|
n = n.c.get(h); |
|
h = t.slice(0, 1); |
|
t = t.slice(1); |
|
} |
|
return n; |
|
} |
|
exports.findNode = findNode; |
|
function countNodes(root) { |
|
const seen = new Set(); |
|
function walk(n) { |
|
if (seen.has(n)) |
|
return; |
|
seen.add(n); |
|
if (n.c) { |
|
[...n.c.values()].forEach((n) => walk(n)); |
|
} |
|
} |
|
walk(root); |
|
return seen.size; |
|
} |
|
exports.countNodes = countNodes; |
|
function countWords(root) { |
|
const visited = new Map(); |
|
function walk(n) { |
|
if (visited.has(n)) { |
|
return visited.get(n); |
|
} |
|
let cnt = n.f ? 1 : 0; |
|
// add the node to the set to avoid getting stuck on circular references. |
|
visited.set(n, cnt); |
|
if (!n.c) { |
|
return cnt; |
|
} |
|
for (const c of n.c.values()) { |
|
cnt += walk(c); |
|
} |
|
visited.set(n, cnt); |
|
return cnt; |
|
} |
|
return walk(root); |
|
} |
|
exports.countWords = countWords; |
|
function isCircular(root) { |
|
const seen = new Set(); |
|
const inStack = new Set(); |
|
function walk(n) { |
|
if (seen.has(n)) |
|
return { isCircular: false, allSeen: true }; |
|
if (inStack.has(n)) |
|
return { isCircular: true, allSeen: false }; |
|
inStack.add(n); |
|
let r = { isCircular: false, allSeen: true }; |
|
if (n.c) { |
|
r = [...n.c.values()].reduce((acc, n) => { |
|
if (acc.isCircular) |
|
return acc; |
|
const r = walk(n); |
|
r.allSeen = r.allSeen && acc.allSeen; |
|
return r; |
|
}, r); |
|
} |
|
if (r.allSeen) { |
|
seen.add(n); |
|
} |
|
inStack.delete(n); |
|
return r; |
|
} |
|
return walk(root).isCircular; |
|
} |
|
exports.isCircular = isCircular; |
|
//# sourceMappingURL=util.js.map
|