|
import { bench } from "vitest"; |
|
import Fs from "node:fs"; |
|
|
|
class AhoCorasick { |
|
constructor(keywords: string[]) { |
|
this._buildTables(keywords); |
|
} |
|
|
|
_buildTables(keywords: string[]) { |
|
var gotoFn = { |
|
0: {}, |
|
}; |
|
var output = {}; |
|
|
|
var state = 0; |
|
keywords.forEach(function (word) { |
|
var curr = 0; |
|
for (var i = 0; i < word.length; i++) { |
|
var l = word[i]; |
|
if (gotoFn[curr] && l in gotoFn[curr]) { |
|
curr = gotoFn[curr][l]; |
|
} else { |
|
state++; |
|
gotoFn[curr][l] = state; |
|
gotoFn[state] = {}; |
|
curr = state; |
|
output[state] = []; |
|
} |
|
} |
|
|
|
output[curr].push(word); |
|
}); |
|
|
|
var failure = {}; |
|
var xs = []; |
|
|
|
// f(s) = 0 for all states of depth 1 (the ones from which the 0 state can transition to) |
|
for (var l in gotoFn[0]) { |
|
state = gotoFn[0][l]; |
|
failure[state] = 0; |
|
xs.push(state); |
|
} |
|
|
|
while (xs.length) { |
|
var r = xs.shift(); |
|
// for each symbol a such that g(r, a) = s |
|
for (var l in gotoFn[r]) { |
|
var s = gotoFn[r][l]; |
|
xs.push(s); |
|
|
|
// set state = f(r) |
|
state = failure[r]; |
|
while (state > 0 && !(l in gotoFn[state])) { |
|
state = failure[state]; |
|
} |
|
|
|
if (l in gotoFn[state]) { |
|
var fs = gotoFn[state][l]; |
|
failure[s] = fs; |
|
output[s] = output[s].concat(output[fs]); |
|
} else { |
|
failure[s] = 0; |
|
} |
|
} |
|
} |
|
|
|
this.gotoFn = gotoFn; |
|
this.output = output; |
|
this.failure = failure; |
|
} |
|
|
|
search(string: string) { |
|
var state = 0; |
|
var results = []; |
|
for (var i = 0; i < string.length; i++) { |
|
var l = string[i]; |
|
while (state > 0 && !(l in this.gotoFn[state])) { |
|
state = this.failure[state]; |
|
} |
|
if (!(l in this.gotoFn[state])) { |
|
continue; |
|
} |
|
|
|
state = this.gotoFn[state][l]; |
|
|
|
if (this.output[state].length) { |
|
var foundStrs = this.output[state]; |
|
results.push([i, foundStrs]); |
|
} |
|
} |
|
|
|
return results; |
|
} |
|
} |
|
|
|
const schema = Fs.readFileSync("./shared/schema.ts", "utf8"); |
|
const searchWords = ["hello", "hi", "index", "nothing", "string"]; |
|
const searchRegex = new RegExp(`(${searchWords.join("|")})`); |
|
const corasick = new AhoCorasick(searchWords); |
|
|
|
bench( |
|
"regex match", |
|
() => { |
|
schema.match(searchRegex); |
|
}, |
|
{ time: 1000 } |
|
); |
|
|
|
bench( |
|
"regex test", |
|
() => { |
|
searchRegex.test(schema); |
|
}, |
|
{ time: 1000 } |
|
); |
|
|
|
bench( |
|
"indexOf loop", |
|
() => { |
|
let found = true; |
|
for (let word of searchWords) { |
|
if (schema.indexOf(word) !== -1) found = true; |
|
} |
|
}, |
|
{ time: 1000 } |
|
); |
|
|
|
bench( |
|
"includes loop", |
|
() => { |
|
let found = true; |
|
for (let word of searchWords) { |
|
if (schema.includes(word)) found = true; |
|
} |
|
}, |
|
{ time: 1000 } |
|
); |
|
|
|
bench( |
|
"corasick", |
|
() => { |
|
corasick.search(schema); |
|
}, |
|
{ time: 1000 } |
|
); |