我是擅(倾)长(向)把一篇文章写成杂文的。毕竟,写博客记录生活点滴,比不得发 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的更多相关文章

  1. Aho - Corasick string matching algorithm

    Aho - Corasick string matching algorithm 俗称:多模式匹配算法,它是对 Knuth - Morris - pratt algorithm (单模式匹配算法) 形 ...

  2. 由Java正则表达式的灾难性回溯引发的高CPU异常:java.util.regex.Pattern$Loop.match

    问题与分析 某天领导report了一个问题:线上的CPU自从上一个版本迭代后就一直处于居高不下的状况,领导看着这段时间的曲线图判断是有两条线程在不停的死循环. 接到任务后去查看了AWS的CloudWa ...

  3. 多模字符串匹配算法-Aho–Corasick

    背景 在做实际工作中,最简单也最常用的一种自然语言处理方法就是关键词匹配,例如我们要对n条文本进行过滤,那本身是一个过滤词表的,通常进行过滤的代码如下 for (String document : d ...

  4. string.match(RegExp) 与 RegExp.exec(string) 深入详解

    string.match(RegExp) 与 RegExp.exec(string) 相同点与不同点对比解析: 1. 这两个方法,如果匹配成功,返回一个数组,匹配失败,返回null. 2. 当RegE ...

  5. 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 ...

  6. LeetCode 942. 增减字符串匹配(DI String Match) 49

    942. 增减字符串匹配 942. DI String Match 题目描述 每日一算法2019/6/21Day 49LeetCode942. DI String Match Java 实现 and ...

  7. LeetCode 686. 重复叠加字符串匹配(Repeated String Match)

    686. 重复叠加字符串匹配 686. Repeated String Match 题目描述 给定两个字符串 A 和 B,寻找重复叠加字符串 A 的最小次数,使得字符串 B 成为叠加后的字符串 A 的 ...

  8. 【Leetcode_easy】942. DI String Match

    problem 942. DI String Match 参考 1. Leetcode_easy_942. DI String Match; 完

  9. 【Leetcode_easy】686. Repeated String Match

    problem 686. Repeated String Match solution1: 使用string类的find函数: class Solution { public: int repeate ...

  10. 【LeetCode】686. Repeated String Match 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

随机推荐

  1. SSM商城项目(十三)

    1.   学习计划 1.订单系统 2.提交订单 3.MyCAT 2.   订单系统 2.1. 功能分析 1.在购物车页面点击“去结算”按钮跳转到订单确认页面. a)         展示商品列表 b) ...

  2. 多功能网页刷新工具,刷pv工具

    多功能网页刷新工具,刷pv工具,在线刷流量,刷PV,刷UV小牛刷新助手功能介绍:1.设置多个刷新网页地址.2.设置刷新时间3.开始工作4.其他操作:老板键:打开时自动刷新:置系统托盘5.可手动输入地址 ...

  3. HAProxy 的acl应用

    非常好的博文推荐:http://blog.51cto.com/1992tao/1875563 官方文档:https://cbonte.github.io/haproxy-dconv/1.9/confi ...

  4. Java成员变量与局部变量的区别

    从语法形式上看,成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数:成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所 ...

  5. nginx安装与挂载

    Linux下添加新硬盘,分区及挂载 http://blog.chinaunix.net/uid-25829053-id-3067619.html vim /etc/fstab /dev vdb1  / ...

  6. CentOS-07安装Redis学习笔记

    CentOS-07安装Redis 下载 http://download.redis.io/releases/redis-3.0.0.tar.gz 安装第一步:将下载的Redis源码包上传大奥Linux ...

  7. centos7 安装 redis4.0.8

    1.安装lrzsz yum install lrzsz -y 2.利用rz命令将window中从redis官网下载好的“redis-4.0.8.tar.gz” 拷贝到centos中 redis官网 : ...

  8. swoole和workerman

    作者:韩天峰链接:https://www.zhihu.com/question/47994137/answer/131700752来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  9. python脚本执行报错整理

    people = [ {'name':'alex','age':1000}, {'name':'wuxie','age':100}, {'name':'wangcanghai','age':9000} ...

  10. iOS.redefinition-of-struct-x

    Error: Redefinition of struct x Reference