我是擅(倾)长(向)把一篇文章写成杂文的。毕竟,写博客记录生活点滴,比不得发 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. .net WCF简单练习

    之前一直没接触过WCF这个东西,由于是初学WCF没有深入研究其原理,只是写了一个demo WCF服务用于两个不同项目中的调用,在这里我举例项目A调用WCF服务实现查询数据功能. 第一步:创建数据库,有 ...

  2. c#遍历一个对象中所有的属性和值

    SpDictItem sp = GetCFHObject.GetSpItem("); PropertyInfo[] propertys = sp.GetType().GetPropertie ...

  3. java表达式中运算符优先级

    运算符优先级:运算符*和/(以及%)的优先级高于+和-(优先级越高,越早运算) 在逻辑运算符中,!拥有最高优先级,之后是&&,接下来是||. 一般来说,相同优先级的运算符的运算顺序是从 ...

  4. 转载:IIS 之 连接数、并发连接数、最大并发工作线程数、队列长度、最大工作进程数

    一.IIS连接数 一般购买过虚拟主机的朋友都熟悉购买时,会限制IIS连接数,顾名思义即为IIS服务器可以同时容纳客户请求的最高连接数,准确的说应该叫“IIS限制连接数”. 客户请求的连接内容包括: [ ...

  5. Ubuntu16.04 藍牙連上,但是聲音裏面找不到設備

    解決辦法: 1. sudo apt-get install blueman bluez* 2. sudo vim /etc/pulse/default.pa 注釋掉下面的代碼: #.ifexists ...

  6. ACM学习之路

     2018-10-18 11:03:00 今天开始踏上实现梦想的道路,希望自己不要懈怠. 坚持做简单的事,坚持下来就会变得不简单.

  7. CentOS 7 安装phpredis和redis(接上一篇centos7安装lnmp)

    一.安装扩展phpredis 1.PHP7 安装redis 扩展phpredis cd /root/software wget https://github.com/edtechd/phpredis/ ...

  8. paloalto防火墙执行初始配置

    1.默认情况下,防火墙的 IP 地址为 192.168.1.1,用户名/密码为 admin/admin. 为了安全起见,在继续执行其他防火墙配置任务之前,必须更改这些设置.必须从 MGT 接口(即使计 ...

  9. python—列表生成式

    #原始写法 l=[] for i in range(1,11): l.append(str(i).zfill(2)) print(l) #结果:['01', '02', '03', '04', '05 ...

  10. 关于jqGrid组件数据显示不出问题

    jqGrid组件一开始怎么数据都返回了渲染不出来,查找了一天,最后发现点击搜索之后doSearch()事件触发的方法并不是数据请求接口,而是再次请求了初次登录的接口,从初次登录返回的数据,数据格式没问 ...