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

以上。

「2014-3-18」multi-pattern string match using aho-corasick的更多相关文章

  1. 「模拟8.18」字符串(卡特兰数)·乌鸦喝水(树状数组,二分)·所驼门王的宝藏(tarjan,拓扑)

    最近好颓啊,所以啥都做不出来 简单说一下这次考试,分机房了,还分不同考卷,果然我还是留在二机房的蒟蒻, 大概也只有这样的简单题,才能勉强水个rank 3吧........ 其实不必管在哪个机房,努力便 ...

  2. 「10.17-10.18」liu_runda’s模拟

    暂咕 $day1$ A. 位运算 分类讨论,贡献分离. B. 集合论 维护类似时间戳的东西 C. 连连看 考场思路太局限了,考虑容斥. 我们可以看出两个方块能作出贡献,实际上是一个极大联通块(白块)所 ...

  3. 「查缺补漏」巩固你的Redis知识体系

    Windows Redis 安装 链接: https://pan.baidu.com/s/1MJnzX_qRuNXJI09euzkPGA 提取码: 2c6w 复制这段内容后打开百度网盘手机App,操作 ...

  4. [译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并修改其中的Value 学习笔记——异步 程序员常说的「哈希表」是个什么鬼?

    [译]聊聊C#中的泛型的使用(新手勿入)   写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发 ...

  5. [转帖]「知乎知识库」— 5G

    「知乎知识库」— 5G 甜草莓 https://zhuanlan.zhihu.com/p/55998832 ​ 通信 话题的优秀回答者 已关注 881 人赞同了该文章 谢 知识库 邀请~本文章是几个答 ...

  6. 面试都在问的「微服务」「RPC」「服务治理」「下一代微服务」一文带你彻底搞懂!

    ❝ 文章每周持续更新,各位的「三连」是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) ❞ 单体式应用程序 与微服务相对的另一个概念是传统的「单体式应用程 ...

  7. 企业运营对 DevOps 的「傲慢与偏见」

    摘要:出于各种原因,并非所有人都信任 DevOps .有些人觉得 DevOps 只不过给开发者改善产品提供了一个途径而已,还有的人觉得 DevOps 是一堆悦耳的空头支票,甚至有人认为 DevOps ...

  8. 「造个轮子」——cicada 设计一个配置模块

    前言 在前两次的 cicada 版本中其实还不支持读取配置文件,比如对端口.路由的配置. 因此我按照自己的想法创建了一个 issue ,也收集到了一些很不错的建议. 最终其实还是按照我之前的想法来做了 ...

  9. 「造个轮子」——cicada 源码分析

    前言 两天前写了文章<「造个轮子」--cicada(轻量级 WEB 框架)> 向大家介绍了 cicada 之后收到很多反馈,也有许多不错的建议. 同时在 GitHub 也收获了 80 几颗 ...

随机推荐

  1. jsp 9大内置对象

    http://www.cnblogs.com/xrong/p/3168616.html

  2. 修改订单的收货时间(MySQL)脚本

    需求 下单时间为21:00-23:59,并且收货时间为第二天的订单,将这些订单的收货时间改为第三天: #!/bin/bash ###time:2016-04-26 ###This script is ...

  3. sql server 2008 外键关联的设置和取消

    直接上图片 选中表右击-设计 找到需要设置外键的字段.右击-关系,在弹出的对话框中点击添加 选择右边的小按钮点击.选择主键表和关联的主键ID,以及外建表的关联字段. 建立外键完成. 删除的话选中某个外 ...

  4. Brophp框架开发时连接数据库读取UTF8乱码的解决(转)

    Brophp框架开发时连接数据库读取UTF8乱码的解决办法 (2012-09-15 10:41:22) 转载▼ 标签: 杂谈 it php 分类: 建站技术 Brophp框架开发时连接数据库读取UTF ...

  5. 解决UBUNTU update KEY错误的问题

    114down voteaccepted Run the following in your terminal, sudo apt-key adv --keyserver keyserver.ubun ...

  6. 使用ajax实现无刷新改变页面内容

    如何使用ajax实现无刷新改变页面内容(也就是ajax异步请求刷新页面),下面通过一个小demo说明一下,前端页面代码如下所示 1 <%@ Page Language="C#" ...

  7. Splinter学习--初探3,两种方式登录QQ邮箱

    目前,qq邮箱的登录方式有: 1.利用账号.密码登录 2.快捷登录,前提是你本地已有qq账号登录中 和前面一样,还是先到qq邮箱登录首页,审查页面元素,找到我们进行登录操作所相关的链接.按钮或是输入框 ...

  8. 伸缩盒 Flexible Box(新)

    flex   flex-grow    flex-shrink   flex-basis  flex-flow   flex-direction   flex-wrap  align-content  ...

  9. export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH

    如题,临时修改程序运行时动态库的搜索路径,平时经常会用到,记录之!

  10. apache结合svn创建svn资源库

    1.在登录过程中可以查看error日志,如果发生以下提示: (13)Permission denied: Could not open password file 2.运行:chcon -R -h - ...