Multi-pattern string match using Aho-Corasick
我是擅(倾)长(向)把一篇文章写成杂文的。毕竟,写博客记录生活点滴,比不得发 paper,要求字斟句酌八股结构到位;风格偏杂文一点,也是没人拒稿的。这么说来,arxiv 就好比是 paper 世界的博客,整了篇论文,管他三七二十一,放到 arxiv 上自嗨一番(如果不是自鸣得意的话)再说……
话说在优酷看了个电影《北京爱情故事》。记得当初电视剧的主题曲满大街放的时候,我还不知道有这么一电视剧;机缘巧合,某次宿舍里见朋友在看,才跟着一起看了两集,觉得不错,不过,之后自己也没再看过。今儿晚上看了同名电影,整体感受是不错,低调不煽情,也没有过分矫情,某些包袱抖得很好,情节串联的还行,有些场景有些小触动,具体在此不表……
切入正题。
上周六折腾了一个名字,Sakuc,扩展开来叫做 Swiss Army Knife using C,中文名可姑且叫做「瑞士军刀C系列」…… 主要目的,是完成一些基本的数据结构和常见算法,供平时鼓捣一些东东时方便一点。
众所周知,相对于更加现代的语言如 Python Java 等等,C 的标准库是出了名的短小精悍(也叫「功能稀缺」);当然这是有原因的,毕竟 C 和硬件结合相对紧密,保持短小精悍可以更好的兼顾可移植性和底层开发便利;加上各种委员会们,可能也不太希望把 C 整成另一个 C++,你懂的。总之,C 的世界里,一直都缺乏(被标准化的)常见数据结构和算法实现。
当然,非标准化的,一直都不缺。比如大名鼎鼎的 Gnome 项目衍生出来的 glib,某个多年未更新但质量似乎很高的 c-algorithms 项目等等。那重复造轮子的意义何在?嗯,猛戳此处看 stack exchange 创始人的经典长文。
废话一箩筐之后,我们来说一说多模式字符串匹配的经典算法,Aho-Corasick。最为直接的理解,请参考 wikipedia 的解释。所谓的 suffix link 即蓝色箭头,就好比是 KMP 单模式字符串匹配算法里的回退跳转,dict suffix link 则是多模式匹配里特有的「单次匹配 => 多个关键字」(如 bca 同时匹配了 bca 和 a - 不考虑之前已经匹配了的 bc 关键字)。

听着比较复杂对吗?让我们说的简单一点。
# 假设现在没有什么 aho-corasick 也没有什么 kmp 算法,就给你一个需求「需要在长文本里,同时匹配多个关键字」,不妨动脑筋思考一番。(两分钟过去了)
# 如果没想出来,那再具体一点:需要在长文本里,匹配「我去」「我靠」「我擦」三个关键字。(两分钟过去了)
# 如果还没想出来,那咱本着治病救人的态度,再把需求具体一点:请在文本「哈哈哇塞这次还想不出来就信了你的邪啦」,匹配上述三个关键字。
已经想到解法了吧?三个关键字共有的「我」前缀,在匹配文本里压根就没出现,所以根本不会有任何关键字能够得到匹配;换句话说,将这个思路通用化,即是,通过关键字列表,得到一个先验信息,如构建一颗前缀树,再根据输入流,做依次匹配。
一下子,感觉很简单了对吗?没错,从最初碰到「如果要在一篇文章中匹配十万个关键词怎么办」的帖子,到得到上面的想法,我只用了 5 分钟左右。但是,真正从这个想法,到最终彻底形成清晰的思路(也包括查阅 wikipedia 的解释,相关的 Trie wikipedia 和这篇博客),足足花了一晚上…… 最终的实现,则是花掉了将近一整天时间,正所谓「知易行难」…… 囧
先上接口设计。接口说明和使用,可以分别参考头文件和单元测试例子。有兴趣的,可以读一读算法实现部分。
int sakuc_multi_pattern_build_search_automaton
(struct trie_node **root, const char *keywords[], size_t num,
size_t fifo_init_size); int sakuc_multi_pattern_find_node
(struct trie_node *root, const char *keyword,
struct trie_node **matched); int sakuc_multi_pattern_search
(const struct trie_node *search_db,
enum sakuc_mpm_search_mode search_mode,
const char *input, size_t len,
size_t *matched_pos_suffix, const char **matched_keyword); int sakuc_multi_pattern_destroy_search_automaton
(struct trie_node *root, size_t fifo_init_size);
最后说一下实际字符串搜索匹配接口(上面的第三个接口)的设计。自动机是肯定要指定的,即 search_db,输入的字符流也是要指定的,即 input 和 len。接着自然而然的问题是,如何返回匹配到的结果?直接 printf 输出到终端,显然不靠谱;将所有匹配的结果,存储在一个容器里,想法固然没问题,但不够灵活,一方面,是容器类型必须提前指定,另一方面,万一使用 api 的人只想匹配到了任何一个关键字就中止搜索怎么办(比如在十万个敏感词里,只要匹配到任何一个,就拒绝提交)。
最后,我选择了类似 C++ 的迭代器方案:每次仅给出单个匹配结果,即 api 的最后两个参数;根据函数返回值 0 / 1 / -1 来确定究竟是已经到达了输入字符流结尾、还是匹配 ok 等待下一次迭代、还是参数等出错了;根据函数的 search_mode 参数,决定重启新的搜索,或是继续上一次搜索。
关于这里「迭代器」的实现,有兴趣的可以参考如下的宏。估计很多人看这里会很奇怪:怎么又是宏又是 goto 操作…… 嘿嘿,没错,看着是有一点「冒权威之大不韪」的感觉,但是,善用语言特性,仔细处理边界条件,是能够让生活更美好的…… 囧 做为参考链接,可以考察一下 Contiki 操作系统里的 protoThread 库,更是将 C 的这种「接近汇编」的特色用到了极致。
// only used in function @sakuc_multi_pattern_search
#define _init_keyword_iterate() static int _continue = FALSE #define _continue_last_iter() do { \
_continue = TRUE; \
goto sakuc_mpm_search_iter_continue; \
} while (__LINE__ == -1) #define _get_ready_for_return() _continue = FALSE #define _return_and_wait_for_next_iter() do { \
sakuc_mpm_search_iter_continue: \
if (!_continue) \
return 1; \
} while (__LINE__ == -1)
以上。
Multi-pattern string match using Aho-Corasick的更多相关文章
- Aho - Corasick string matching algorithm
Aho - Corasick string matching algorithm 俗称:多模式匹配算法,它是对 Knuth - Morris - pratt algorithm (单模式匹配算法) 形 ...
- 由Java正则表达式的灾难性回溯引发的高CPU异常:java.util.regex.Pattern$Loop.match
问题与分析 某天领导report了一个问题:线上的CPU自从上一个版本迭代后就一直处于居高不下的状况,领导看着这段时间的曲线图判断是有两条线程在不停的死循环. 接到任务后去查看了AWS的CloudWa ...
- 多模字符串匹配算法-Aho–Corasick
背景 在做实际工作中,最简单也最常用的一种自然语言处理方法就是关键词匹配,例如我们要对n条文本进行过滤,那本身是一个过滤词表的,通常进行过滤的代码如下 for (String document : d ...
- string.match(RegExp) 与 RegExp.exec(string) 深入详解
string.match(RegExp) 与 RegExp.exec(string) 相同点与不同点对比解析: 1. 这两个方法,如果匹配成功,返回一个数组,匹配失败,返回null. 2. 当RegE ...
- LeetCode686——Repeated String Match
题目:Given two strings A and B, find the minimum number of times A has to be repeated such that B is a ...
- LeetCode 942. 增减字符串匹配(DI String Match) 49
942. 增减字符串匹配 942. DI String Match 题目描述 每日一算法2019/6/21Day 49LeetCode942. DI String Match Java 实现 and ...
- LeetCode 686. 重复叠加字符串匹配(Repeated String Match)
686. 重复叠加字符串匹配 686. Repeated String Match 题目描述 给定两个字符串 A 和 B,寻找重复叠加字符串 A 的最小次数,使得字符串 B 成为叠加后的字符串 A 的 ...
- 【Leetcode_easy】942. DI String Match
problem 942. DI String Match 参考 1. Leetcode_easy_942. DI String Match; 完
- 【Leetcode_easy】686. Repeated String Match
problem 686. Repeated String Match solution1: 使用string类的find函数: class Solution { public: int repeate ...
- 【LeetCode】686. Repeated String Match 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
随机推荐
- PhoenixFD插件流体模拟——UI布局【Gird】详解
流体网格 本文主要讲解Grid折叠栏中的内容 主要内容 Overview 综述 Parameters 参数 General 普通参数 Example: Scene Scale Example: Gri ...
- 获取BDC 消息文本的2种方式
第一种 LOOP AT MESSTAB. MOVE MESSTAB-MSGNR TO MSGNO. CALL FUNCTION 'WRITE_MESSAGE' EXPORTING MSGID = ME ...
- 关于逻辑回归是否线性?sigmoid
from :https://www.zhihu.com/question/29385169/answer/44177582 逻辑回归的模型引入了sigmoid函数映射,是非线性模型,但本质上又是一个线 ...
- java实现将包含多个<REC>的文件拆成若干只包含一个<REC>的文件
遍历文件夹里的文件,将包含多个<REC>的文件拆成若干只包含一个<REC>的文件 package com.prepub; import java.io.BufferedRead ...
- python——jieba分词过程
import jieba """函数2:分词函数""" def fenci(training_data): ""&quo ...
- 网络通信实验(1)STM32F4 以太网简介
STM32F4 以太网简介 STM32F407 芯片自带以太网模块,该模块包括带专用 DMA 控制器的 MAC 802.3(介质访问控制)控制器,支持介质独立接口 (MII) 和简化介质独立接口 (R ...
- Ubuntu ROS
设置你的sources.list 将电脑设置为接受来自packages.ros.org的软件 sudo sh -c 'echo "deb http://packages.ros.org/ro ...
- synchronized锁机制 之 代码块锁(转)
synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...
- Spark2.0学习(二)--------RDD详解
添加针对scala文件的编译插件 ------------------------------ <?xml version="1.0" encoding="UTF- ...
- Oracle定时任务Job笔记
定时任务应用场景: 某一个生产系统上面,临时表不断变多,占了不少磁盘空间.由于这套软件是直接买的,不方便修改源代码.所以考虑使用Oracle定时任务直接从数据库层删除临时表. 定时任务业务: 首先写好 ...