aho-corasick.ts 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. class Node extends Map<string, Node> {
  2. constructor(
  3. public wordEnd: boolean,
  4. public fail: Node | undefined
  5. ) {
  6. super();
  7. }
  8. }
  9. function createKeywordFilter(keys: string[] | Set<string>) {
  10. const root = new Node(false, undefined);
  11. // Create a trie with extra fields and information
  12. const put = (key: string) => {
  13. let node = root;
  14. for (let idx = 0, len = key.length; idx < len; idx++) {
  15. const char = key[idx];
  16. if (node.has(char)) {
  17. node = node.get(char)!;
  18. } else {
  19. const newNode = new Node(false, undefined);
  20. node.set(char, newNode);
  21. node = newNode;
  22. }
  23. }
  24. // If a new node is created, mark it as a word end when loop finish
  25. if (node !== root) {
  26. node.wordEnd = true;
  27. }
  28. };
  29. keys.forEach(put);
  30. // const build = () => {
  31. const queue: Node[] = [root];
  32. while (queue.length) {
  33. const beginNode = queue.pop()!;
  34. beginNode.forEach((node, char) => {
  35. let failNode = beginNode.fail;
  36. while (failNode && !failNode.has(char)) {
  37. failNode = failNode.fail;
  38. }
  39. node.fail = failNode ? failNode.get(char) : root;
  40. queue.push(node);
  41. });
  42. }
  43. // };
  44. // build();
  45. return (text: string) => {
  46. let node: Node | undefined = root;
  47. for (let i = 0, textLen = text.length; i < textLen; i++) {
  48. const char = text[i];
  49. while (node && !node.has(char)) {
  50. node = node.fail;
  51. }
  52. node = node ? node.get(char)! : root;
  53. if (node.wordEnd) {
  54. return true;
  55. }
  56. }
  57. return false;
  58. };
  59. }
  60. export default createKeywordFilter;