每秒处理超过1GB的文本,还能顺手做命名实体识别——这件事听起来像是某个深度学习框架的宣传语,但它发生在一个正则表达式引擎里。
更具体一点:给正则模式打上标签,然后拿它和 spaCy 的 en_core_web_sm 模型做对比。这是一场不太科学的基准测试,作者自己也承认是"拿苹果比橘子"。但那个每秒 1.92 gorillion 字节的数字,确实让这场对比变得有意思起来。
![]()
关键在于,这不是近似。日期识别这件事,yyyy-MM-dd 本身就是一个正则语言。也就是说,用正则判断一个字符串是不是日期,可以做到精确、可靠,连闰年都算进去。
为什么这件事值得在意
神经网络要花多少功夫,才能复现一个确定性、100% 正确、还几乎不耗电的方案的哪怕一小部分?作者的态度很直接:当你不需要掷骰子的时候,就不该掷骰子。
这套东西已经内置进了 resharp。它的成本结构也很特别——前期计算一次,之后的开销和搜索一个单词没有区别。没有额外的电力消耗,没有电池掉电,没有风扇噪音。
做法本身不复杂:给一批正则模式分配标签,然后跑起来。下面就是第一组示例里用到的模式。
- DATE:
[0-9]{4}-[0-9]{2}-[0-9]{2} - MONEY:
\$[0-9]+(?:\.[0-9]{2})? - PERCENT:
[0-9]+(?:\.[0-9]+)?% - EMAIL:
[a-z.]+@[a-z]+\.[a-z]+ - URL:
- NUM:
[0-9]+ - NAME:
[A-Z][a-z]+\b&~(On|In|At|To|For|Of|The|An|And|Via|Was|Is) - VERB:
[a-z]+ing\b - ADJ:
[a-z]+(?:ful|less|ous|ive)\b - ADV:
[a-z]+ly\b
NAME 模式里用到了&做交集、~做补集。可以简单理解成 AND 和 NOT:把 "On"、"In" 这类词从名字候选里排除掉,避免误判。作者还补了一句——如果这些词里恰好有你的名字,那只能道歉了。
正则的表达力被低估了
你可以在不离开正则语言范畴的前提下,实现 if-then-else。无论嵌套多少层、串联多少个分支,它都仍然是一个布尔代数,仍然可以用纯正则表达。
代价是模式会变成一团难以阅读的意大利面。但对 RE# 来说,这团面条只是一份"如何构造状态机"的公式。借助一点 JavaScript 字符串操作,就能把这些模式从小块的可组合部件拼出来。
比如日期判断,先定义 ifThenElse 辅助函数,再拼出 yyyymmdd、二月、上限 29 天、上限 31 天这些片段,最后组合成一个完整日期模式。展开后它会编译成一台状态机,能正确排除"2 月 30 日"这种不存在的日期。
![]()
结果是26 个 DFA 状态。RE# 默认支持最多 65536 个状态,26 个实在不算多。
接下来是更狠的版本:整个公历。月份从 01 到 12,二月 28 天除非是闰年,四个月 30 天,其余 31 天。
闰年的判断规则是:能被 4 整除且不能被 100 整除,或者能被 400 整除。作者把 0 到 9999 里所有闰年都筛出来,拼成一个巨大的年份分支,再嵌进 if-then-else 里。
最终的模式长达24504 个字符,看起来是一头由闰年和条件分支堆成的怪物。
但它编译成多少状态?32 个 DFA 状态。
状态图大不等于代价大
即使 DFA 开始长得像银河系,也要记住:状态图的大小是会骗人的。就像大脑里的突触,真正用到的只是当前任务必需的那些连接。
换句话说,只有被访问到的状态才会被编译,而这往往只是整张图里极小的一部分。这意味着我们可以承受巨大、甚至无限的状态机,而不必承担相应的后果。
回到最初那个问题:为什么不是每个正则引擎都这么做?
至少在这套实现里,答案是——它已经能跑,而且跑得很快。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.