aho-corasick.ts 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  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. const len = key.length;
  14. let node = root;
  15. for (let idx = 0; idx < len; idx++) {
  16. const char = key[idx];
  17. if (node.has(char)) {
  18. node = node.get(char)!;
  19. } else {
  20. const newNode = new Node(false, undefined);
  21. node.set(char, newNode);
  22. node = newNode;
  23. }
  24. }
  25. // If a new node is created, mark it as a word end when loop finish
  26. if (node !== root) {
  27. node.wordEnd = true;
  28. }
  29. };
  30. keys.forEach(put);
  31. // const build = () => {
  32. const queue: Node[] = [root];
  33. while (queue.length) {
  34. const beginNode = queue.pop()!;
  35. beginNode.forEach((node, char) => {
  36. let failNode = beginNode.fail;
  37. while (failNode && !failNode.has(char)) {
  38. failNode = failNode.fail;
  39. }
  40. node.fail = failNode ? failNode.get(char) : root;
  41. queue.push(node);
  42. });
  43. }
  44. // };
  45. // build();
  46. return (text: string) => {
  47. let node: Node | undefined = root;
  48. for (let i = 0, textLen = text.length; i < textLen; i++) {
  49. const char = text[i];
  50. while (node && !node.has(char)) {
  51. node = node.fail;
  52. }
  53. node = node ? node.get(char)! : root;
  54. if (node.wordEnd) {
  55. return true;
  56. }
  57. }
  58. return false;
  59. };
  60. }
  61. export default createKeywordFilter;