ahocorasick-ts
Version:
ahocorasick implemention
88 lines (74 loc) • 1.83 kB
text/typescript
class TrieNode {
matched: string[]
child: Record<string, TrieNode>
failure: TrieNode
constructor(root?: TrieNode) {
this.matched = []
this.child = {}
if (root) {
this.failure = root
} else {
this.failure = this
}
}
}
export interface SearchResult {
end: number
keys: string[]
}
export class AhoCorasick {
trie: TrieNode
constructor(keyword: string[]) {
this.trie = new TrieNode()
keyword.forEach(s => {
let cur = this.trie
for (const ch of s) {
if (!(ch in cur.child)) {
cur.child[ch] = new TrieNode(this.trie)
}
cur = cur.child[ch]
}
cur.matched.push(s)
})
const queue: TrieNode[] = []
for (const ch in this.trie.child) {
queue.push(this.trie.child[ch])
}
while (queue.length > 0) {
const cur = queue.shift() as TrieNode
for (const ch in cur.child) {
queue.push(cur.child[ch])
let f = cur.failure
while (f !== this.trie && !(ch in f.child)) {
f = f.failure
}
if (ch in f.child) {
cur.child[ch].failure = f.child[ch]
cur.child[ch].matched.push(...cur.failure.matched)
} else {
cur.child[ch].failure = this.trie
}
}
}
}
search(key: string): SearchResult[] {
let cur = this.trie
const res: SearchResult[] = []
key.split('').forEach((ch, i) => {
while (cur !== this.trie && !(ch in cur.child)) {
cur = cur.failure
}
if (!(ch in cur.child)) {
return
}
cur = cur.child[ch]
if (cur.matched.length) {
res.push({
end: i,
keys: cur.matched,
})
}
})
return res
}
}