侧边栏壁纸
博主头像
Terry

『LESSON 5』

  • 累计撰写 90 篇文章
  • 累计创建 21 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

变量替换方案

Terry
2023-09-14 / 0 评论 / 0 点赞 / 139 阅读 / 788 字 / 正在检测是否收录...

原理

把所有的映射构建一个Tire树,然后用ac自动机匹配替换

代码

public class AhoCorasickAutomaton {

    /*AC自动机的根结点,根结点不存储任何字符信息*/
    private final Node root;

    /*待查找的目标字符串集合*/
    private final Map<String, String> target;

    /**
     * @param target 待查找的目标字符串集合
     */
    public AhoCorasickAutomaton(Map<String, String> target) {
        root = new Node();
        this.target = target;
        buildTrieTree();
        buildAcFromTrie();
    }

    /**
     * 用于表示AC自动机的每个结点,在每个结点中我们并没有存储该结点对应的字符
     */
    private static class Node {

        /*如果该结点是一个终点,即,从根结点到此结点表示了一个目标字符串,则str != null, 且str就表示该字符串*/
        String str;

        /*该节点下的子节点*/
        Map<Character, Node> children = new HashMap<>();

        /*当前结点的孩子结点不能匹配文本串中的某个字符时,下一个应该查找的结点*/
        Node fail;

        public boolean isWord() {
            return str != null;
        }

    }

    /**
     * 由目标字符串构建Trie树
     */
    private void buildTrieTree() {
        for (String targetStr : target.keySet()) {
            Node curr = root;
            if (targetStr == null) {
                continue;
            }
            for (int i = 0; i < targetStr.length(); i++) {
                char ch = targetStr.charAt(i);
                Node node = curr.children.get(ch);
                if (node == null) {
                    node = new Node();
                    curr.children.put(ch, node);
                }
                curr = node;
            }
            /*将每个目标字符串的最后一个字符对应的结点变成终点*/
            curr.str = targetStr;
        }
    }

    /**
     * 由Trie树构建AC自动机,本质是一个自动机,相当于构建KMP算法的next数组
     */
    private void buildAcFromTrie() {
        /*广度优先遍历所使用的队列*/
        LinkedList<Node> queue = new LinkedList<>();

        /*单独处理根结点的所有孩子结点*/
        for (Node x : root.children.values()) {
            /*根结点的所有孩子结点的fail都指向根结点*/
            x.fail = root;
            queue.addLast(x);/*所有根结点的孩子结点入列*/
        }

        while (!queue.isEmpty()) {
            /*确定出列结点的所有孩子结点的fail的指向*/
            Node p = queue.removeFirst();
            for (Map.Entry<Character, Node> entry : p.children.entrySet()) {

                /*孩子结点入列*/
                queue.addLast(entry.getValue());
                /*从p.fail开始找起*/
                Node failTo = p.fail;
                while (true) {
                    /*说明找到了根结点还没有找到*/
                    if (failTo == null) {
                        entry.getValue().fail = root;
                        break;
                    }

                    /*说明有公共前缀*/
                    if (failTo.children.get(entry.getKey()) != null) {
                        entry.getValue().fail = failTo.children.get(entry.getKey());
                        break;
                    } else {/*继续向上寻找*/
                        failTo = failTo.fail;
                    }
                }

            }
        }
    }

    /**
     * 在文本串中替换所有的目标字符串
     *
     * @param text          被替换的目标字符串
     * @param stringBuilder 替换后的结果
     */
    public void replace(CharSequence text, StringBuilder stringBuilder) {
        Node curr = root;
        int i = 0;
        while (i < text.length()) {
            /*文本串中的字符*/
            char ch = text.charAt(i);
            /*文本串中的字符和AC自动机中的字符进行比较*/
            Node node = curr.children.get(ch);
            if (node != null) {
                stringBuilder.append(ch);
                /*若相等,自动机进入下一状态*/
                curr = node;
                if (curr.isWord()) {
                    stringBuilder.delete(stringBuilder.length() - curr.str.length(), stringBuilder.length());
                    stringBuilder.append(target.get(curr.str));
                    curr = root;
                }
                /*索引自增,指向下一个文本串中的字符*/
                i++;
            } else {
                /*若不等,找到下一个应该比较的状态*/
                curr = curr.fail;
                /*到根结点还未找到,说明文本串中以ch作为结束的字符片段不是任何目标字符串的前缀,状态机重置,比较下一个字符*/
                if (curr == null) {
                    stringBuilder.append(ch);
                    curr = root;
                    i++;
                }
            }
        }
    }

}
0

评论区