基于Aho-Corasick算法的Ahocorasick库的使用

由于最近项目中需要使用PythonAhocorasick库进行模式匹配,因此记录该库所使用的到的Aho-Corasick算法(AC算法)的概要以及该库在Python中的具体使用方法。

概述:Aho-Corasick算法是多模式匹配中的经典算法,目前在实际应用中较多。Aho-Corasick算法对应的数据结构是Aho-Corasick自动机,简称AC自动机Automaton。该算法能够识别出一个给定的语句中包含了哪些词典库中特定的词语,具有很有的模式匹配作用。

1. Aho-Corasick算法

在介绍该算法之前,我们需要明白的一个概念是Trie即字典树(又称为单词查找树或者键树)是一种树形结构作为哈希树的变种,典型的应用就是用于统计和排序大量的字符串(起源于对字符串的研究但是不仅限于字符串例如中文语句的关键词提取);另一个需要知道的概念是KMPKnuth-Morris-Pratt字符串查找算法,常用于在一个文本串S内查找一个模式串P的出现位置,这个算法由Donald KnuthJames H. MorrisVaughan Pratt三人于1977年联合发表,故取这3人的姓氏命名此算法。关于这两个概念的详细信息并不在此赘述,而是通过下图来简单介绍一下这两个概念在AC算法中是如何体现的。
现在假设我们的文本库中存在ACCATCCATGCG四个字符串,那么我们首先可以根据这四个字符串来构造一个Trie即根节点为空节点,并逐单词构造该树。例如ACC那么先生成A这条边指向下一个节点再生成C指向下一个节点再生成C指向最后一个空节点,同理对于ATC而言也是如此,需要注意的是在构造的过程中同一层级某个单词最多只出现一遍,比如ACCATC都共用A这条边指向的节点。同理即可构造出蓝色线条和蓝色节点所生成的字典树。
当生成了字典树后,基于KMP算法来生成所谓的failing link即回溯路线,该概念的含义是如果运行到某个节点处无法继续运行的话,那么就通过回溯路线回溯后继续运行,这样可能有点抽象,但是在接下来的例子讲解中会显得比较清晰。因此,我们需要考虑的是如何生成failing link,一般来说遵循以下的规则:

  1. 根节点Root连向自己同时所有与根节点直接相连的节点都指向根节点。
  2. 对于其他节点Node而言,首先判断其父节点是否存在一条回溯路线且指向的节点存在一个与Node的值相同的子节点,如果存在那么就将自己的回溯路线也指向该子节点,如果不存在那么就继续找该Node的父节点的父节点依次找到Root为止。这一条规则可能会比较绕后续也会结合例子详细讲解。

下图就是根据上述规则生成的字典树以及回溯路线,这里我们挑几个比较具有代表性的节点来看,比如AC节点,沿着AC路线的C节点的父节点的回溯路线指向RootRoot具有值为C的子节点因此AC的回溯路线指向C,沿着ACC路线的C节点的父节点的回溯路线指向CC节点并不存在值为C的子节点反而只有A节点,于是沿着ACC路线的C节点只能去寻找它父亲的父亲即A节点,其回溯路线指向Root并具有一个C节点于是指向该C节点。同理可以构造出所有的回溯路线。
当构造出对应的字典树和回溯路线后即可开始使用AC算法进行字符串的模式匹配,我们假设给定一个GCATCG字符串判断其包含文本库中的哪些字符串,因此我们可以这样运作,首先沿着字典树存在GC路线但是该路线的下一个是G而不是给定字符串中的A于是我们沿着回溯路线回到C节点,此时恰好C节点的下一个节点是A节点同时再下一个节点是T节点于是沿着GCGAT的路线达到了字典树CAT路线的叶子节点,我们将达到叶子节点视为匹配成功即存在CAT字符串,此时虽然已经匹配成功但是为了能够继续运作我们仍然继续执行回溯路线即指向AT节点,那么由于下一个节点刚好C那么沿着GCGATC路线达到了ATC路线的叶子节点因此匹配成功我们认为存在ATC字符串,继续沿着回溯路线回到C节点此时我们给定的字符串中下一个节点是G而运作路线的下一个节点是A于是回溯到根节点此时已经遍历完成所以我们判断原字符串中存在CATATC子串。
下图取自Youtube上某个博主讲解的视频
视频连接为:https://www.youtube.com/watch?v=O7_w001f58c
image.png

2.Python中Ahocorasic库的使用

有了以上的基础后即可开始学习PythonAhocorasic函数库的使用方法了,我的项目需求是通过给定一个文本库中包含各种疾病信息、科室信息、药品信息等等来解析用户的输入中包含了哪些关键词,因此需要使用到该函数库来快速完成需求。

插播一个注意事项,即如果Python的版本在3.7及以上那么可能会发现找不到pyahocorasick函数库而是只显示r-ahocorasicktrie函数库,因此如果需要适应低版本的函数库的话需要适应3.7版本以下的PythonPython3.6

下面主要介绍该函数库的基本使用步骤:
首先需要通过pip install pyahocorasick来安装该库
其次需要根据给定的文本库来将内容添加到actree中来形成词典树

插播一下Pythonenumerate函数的使用结果,就是比如['a','ab','abc']列表作为参数那么就会形成{1:'a',2:'b',3:'c'}这样的结果因此可以用于快速构建索引

add_word函数将第一个作为key构建actree同时第二个参数作为value来作为查询的结果

# 往actree中添加数据
actree = ahocorasick.Automaton()
for index, word in enumerate(wordlist):
	actree.add_word(word, (index, word))  
actree.make_automaton()

最后根据该词典树来查询目标语句中出现在树中的关键词
iter函数相当于通过实参来查找actree中的key并返回该键值对形成的元组

# 使用actree来查询目标语句中出现在actree中的关键词
for i in actree.iter(target_str):
    wd = i[1][1]  # i的形式为(index,(index,word))
    target_wds.append(wd)

补充需要:在某些情况下一个词可能在多个词中出现因此需要将重复出现的词语屏蔽比如如target_wds=['乙肝', '肝硬化', '硬化'],则stop_wds=['硬化']那么在得到最终词表的时候就不会将硬化放在词表中而是选择信息量更大的肝硬化。

# 附加功能:可能出现一个词分成几个部分,把这个部分过滤掉
stop_wds = []
for wd1 in target_wds:
    for wd2 in target_wds:
        if wd1 in wd2 and wd1 != wd2:
            stop_wds.append(wd1)
final_wds = [i for i in target_wds if i not in stop_wds]
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Aho-Corasick算法是一种高效的字符串匹配算法,它可以在一次扫描文本的过程中同时查找多个模式串。该算法通过将模式串预处理为确定有限状态自动机,从而实现快速的匹配过程。与其他算法相比,Aho-Corasick算法的时间复杂度为O(n),与模式串的数量和长度无关。 以下是Aho-Corasick算法的一种Java实现示例: ```java import java.util.*; class AhoCorasick { static class Node { Map<Character, Node> children; Node fail; List<String> outputs; Node() { children = new HashMap<>(); fail = null; outputs = new ArrayList<>(); } } static void buildTrie(Node root, List<String> patterns) { for (String pattern : patterns) { Node curr = root; for (char c : pattern.toCharArray()) { curr.children.putIfAbsent(c, new Node()); curr = curr.children.get(c); } curr.outputs.add(pattern); } } static void buildFailure(Node root) { Queue<Node> queue = new LinkedList<>(); for (Node child : root.children.values()) { child.fail = root; queue.add(child); } while (!queue.isEmpty()) { Node curr = queue.poll(); for (Map.Entry<Character, Node> entry : curr.children.entrySet()) { char c = entry.getKey(); Node child = entry.getValue(); queue.add(child); Node failNode = curr.fail; while (failNode != null && !failNode.children.containsKey(c)) { failNode = failNode.fail; } child.fail = failNode != null ? failNode.children.get(c) : root; child.outputs.addAll(child.fail.outputs); } } } static List<String> search(Node root, String text) { List<String> matches = new ArrayList<>(); Node curr = root; for (char c : text.toCharArray()) { while (curr != null && !curr.children.containsKey(c)) { curr = curr.fail; } curr = curr != null ? curr.children.get(c) : root; for (String output : curr.outputs) { matches.add(output); } } return matches; } public static void main(String[] args) { List<String> patterns = Arrays.asList("he", "she", "his", "hers"); String text = "ushers"; Node root = new Node(); buildTrie(root, patterns); buildFailure(root); List<String> matches = search(root, text); System.out.println(matches); // 输出:[she, he, hers] } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值