Java实现Aho-Corasick字符串匹配算法?

时间:2017-10-24 23:01:51

标签: java algorithm data-structures trie aho-corasick

现在我知道有关此算法的先前问题,但老实说,我没有遇到过简单的java实现。许多人在他们的GitHub配置文件中复制并粘贴了相同的代码,这让我感到恼火。

因此,为了进行面试练习,我计划使用不同的方法来制定和实施算法。

算法往往非常具有挑战性。老实说,我很失落如何去做。这个逻辑没有意义。我差不多花了4天时间草拟这种方法,但无济于事。

因此请用你的智慧启发我们。

我主要是根据这些信息Intuition behind the Aho-Corasick string matching algorithm

来做算法

如果可以在这里实施自己的解决方案,那将是一个很大的好处。

但是这里有以下不完整和无法正常工作的解决方案,我真的陷入了困境:

如果您对代码进行过度训练,主要问题在于Aho-Corasick的主要算法。我们已经很好地创建了字典树。

但问题是,现在我们已经开始实施。我们如何开始实施。

没有资源有用。

public class DeterminingDNAHealth {
  private Trie tree;
  private String[] dictionary;
  private Node FailedNode;


  private DeterminingDNAHealth() {

  }

  private void buildMatchingMachine(String[] dictionary) {
    this.tree = new Trie();
    this.dictionary = dictionary;

    Arrays.stream(dictionary).forEach(tree::insert);

  }

  private void searchWords(String word, String[] dictionary) {

    buildMatchingMachine(dictionary);

    HashMap < Character, Node > children = tree.parent.getChildren();

    String matchedString = "";

    for (int i = 0; i < 3; i++) {
      char C = word.charAt(i);

      matchedString += C;

      matchedChar(C, matchedString);

    }

  }

  private void matchedChar(char C, String matchedString) {


    if (tree.parent.getChildren().containsKey(C) && dictionaryContains(matchedString)) {

      tree.parent = tree.parent.getChildren().get(C);

    } else {

      char suffix = matchedString.charAt(matchedString.length() - 2);

      if (!tree.parent.getParent().getChildren().containsKey(suffix)) {
        tree.parent = tree.parent.getParent();

      }


    }
  }

  private boolean dictionaryContains(String word) {

    return Arrays.asList(dictionary).contains(word);

  }


  public static void main(String[] args) {

    DeterminingDNAHealth DNA = new DeterminingDNAHealth();

    DNA.searchWords("abccab", new String[] {
      "a",
      "ab",
      "bc",
      "bca",
      "c",
      "caa"
    });


  }
}

我已经设置了一个工作正常的trie数据结构。所以这里没问题

trie.java

public class Trie {
  public Node parent;
  public Node fall;

  public Trie() {
    parent = new Node('⍜');
    parent.setParent(new Node());
  }

  public void insert(String word) {...}

  private boolean delete(String word) {...}

  public boolean search(String word) {...}

  public Node searchNode(String word) {...}

  public void printLevelOrderDFS(Node root) {...}

  public static void printLevel(Node node, int level) {...}

  public static int maxHeight(Node root) {...}

  public void printTrie() {...}

}

Node也是如此。

Node.java

public class Node {

  private char character;
  private Node parent;
  private HashMap<Character, Node> children = new HashMap<Character, Node>();
  private boolean leaf;

  // default case
  public Node() {}

  // constructor accepting the character
  public Node(char character) {
    this.character = character;
  }

  public void setCharacter(char character) {...}

  public char getCharacter() {...}

  public void setParent(Node parent) {...}

  public Node getParent() {...}

  public HashMap<Character, Node> getChildren() {...}

  public void setChildren(HashMap<Character, Node> children) {...}

  public void resetChildren() {...}

  public boolean isLeaf() {...}

  public void setLeaf(boolean leaf) {...}
}

2 个答案:

答案 0 :(得分:4)

通过阅读一些源代码,您无法很好地理解Aho-Corasick string matching algorithm。而且你不会发现一个简单的实现,因为算法并不重要。

原始论文Efficient String Matching: An Aid to Bibliographic Search写得很好,非常平易近人。我建议你下载PDF,仔细阅读,稍微考虑一下,再读一遍。 研究论文。

您可能还会发现阅读其他人很有用&#39;算法的描述。有许多页面包含文字说明,图表,Powerpoint幻灯片等。

在尝试实施之前,您可能希望至少花一两天时间研究这些资源。因为如果你试图在没有完全理解它如何工作的情况下实现它,那么你将会迷失方向,并且你的实现将会显示它。该算法并不简单,但它非常平易近人。

如果你只想要一些代码,那么这里有一个很好的实现:https://codereview.stackexchange.com/questions/115624/aho-corasick-for-multiple-exact-string-matching-in-java

答案 1 :(得分:2)

我通常每隔一年就开设一门高级数据结构课程,在探索字符串数据结构时,我们会介绍Aho-Corasick自动机。有可用的幻灯片here显示如何通过优化几个较慢的算法来开发算法。

一般来说,我将实施分为四个步骤:

  1. 构建特里。 Aho-Corasick自动机的核心是一个带有一些额外箭头的trie。算法的第一步是构造这个trie,好消息是它就像普通的trie结构一样。事实上,我建议只是假装你只是制作一个特里而不做任何事情来预测后面的步骤来实现这一步。

  2. 添加后缀(失败)链接。算法中的这一步添加了重要的失败链接,匹配器在遇到无法用于跟踪边缘的字符时使用该链接。关于这些工作如何在链接讲座幻灯片中的最佳解释。该算法的该步骤被实现为在线索上的广度优先搜索步行。在您编写此代码之前,我建议您手动完成一些示例,以确保获得一般模式。一旦这样做,编码起来并不是特别棘手。然而,试图在不完全了解其工作原理的情况下对其进行编码会使调试成为一场噩梦!

  3. 添加输出链接。在此步骤中,您可以添加用于报告在trie中给定节点上匹配的所有字符串的链接。它是通过对trie进行第二次广度优先搜索来实现的,而且,我对它的工作方式的最佳解释是在幻灯片中。好消息是,这一步实际上比后缀链接构建更容易实现,因为您将更熟悉如何执行BFS以及如何向下和向上走。同样,不要尝试对其进行编码,除非您可以手动轻松地完成此操作!您不需要min代码,但是您不希望调试那些您不理解的高级行为的代码。

  4. 实施匹配器。这一步也不错!你只需从输入中读取trie读取字符,输出每一步的所有匹配,并在遇到卡住时使用失败链接,不能向前推进。

  5. 我希望这会给你一个更模块化的任务细分,以及关于整个过程应该如何工作的参考。祝你好运!