大忙人系列-程序猿在想什么 (三) 状态机与正则表达式
一. 传说中的正则表达式
无论你和计算机沾不沾边,“正则表达式”这个词多多少少都会听过,而越是不了解,就越感觉这个词逼格很高。“正则”是什么东西?听上去挺洋气的,正看上去像正规,然后则呢...打则必死。
第一次碰见这玩意儿的时候还是当初学RMXP的时候(也是个上古的东西了)。想当年连程序都没写过就去学Ruby,而那翻译也鬼才把成员变量翻译成“实变量”(至少也应该是实例变量啊),搞了半天都不知道这到底是个什么鬼,为什么前面要加个at。在教程的结尾,写着“附录”“正则表达式”,深深地吸引着年幼的我。
点进去。
从头到尾是各种类字符通配符的列表。
这是个啥?!
二. 正则表达式&正规式
如果你学过编译原理那一定听说过正规式却又没听说过正则表达式...赶紧去学!学会前不要说自己是CS的XD。
这两者概念上似乎有微秒的不同但是本质却是一样的。个人认为,可以把正则表达式看为一种正规式,给定一个模式与一个字符串就可以查找字符串内模式出现的地方。而讨论这些,就不得不讨论状态机。
三. 状态机
状态机是现实事物运行规则抽象而成的一个数学模型。简单而言,就是用一些存储空间来表示当前的状态,然后每一次根据外部的数据或者情况改自己的状态。《ICS》里面举了两个例子,一个是篮球比赛的记分牌,还有一个是转盘密码锁。你都可以把所有可能的状态列举出来,然后根据不同的情况(进球了,或者扭入了一个密码)来变更整个系统的状态。状态机这个模型可以在很多地方使用,包括对一个模式的匹配,最直接的办法就是使用状态机。
四. NFA&DFA
假设现在已经有了一个模式,人肉去做匹配,最简单的就是一个一个看,和普通的字符串匹配有点相似。比如,要匹配ab或者ac,假设没有优化,0号状态就是初始状态,1号状态为0状态下读入a,2号为1号读b;3号为0号读a,4号为3号读c。这个样子,在0号状态计算机并不知道走哪条分支,只能随便走一个,等匹配到后面发现错了,就需要回溯,这就是NFA。
NFA比较直接,但是也比较简陋,需要回溯的话在匹配中会消耗大量资源,但是可以转成DFA(虽然也要不少资源算、转)。DFA就直接,每一步到下一个状态都是确定的。做法很简单,将NFA每个状态下每种输入会得到怎样的结果全部计算一遍。第一步当然是初始状态A0,把不经过任何字符或者经过epsilon(空字符)能到达的状态全部写出来,然后计算经过所有字符后能到达的状态A1 A2...An,然后再把所有的状态作输入,再经过一个字符,得到B(A1)1 B(A1)2...B(An)m。本质上则是提前把所有可能的回溯全部做了。
五. More about 正则表达式
(其实是在水字数)
很多要搜索的地方支持正则表达式搜索,当你对你要搜的东西比较印象比较模糊的时候可以用,会非常方便,比如VS,比如everything。当然,也可以用来处理爬虫爬回来的数据。有的时候,是真的为了快速匹配一些模式,所以要记下一些常用的通配符。
^ 匹配字符串开头
$ 匹配字符串结尾
上面两个很重要,比如你想匹配文件名的话写 .*\.pdf$ 专门指定要以.pdf结尾,而f后面啥也不能接,就可以用$,同理^表示要从开头开始匹配。
.
匹配任意字符,使用频率Max,就连上面那个那么简单的例子都跳脱不了这个符号
* 前面的字符/模式重复任意次(或者零次)
+ 前面的字符/模式重复任意次(一次及以上)
还是见第一个例子, .* 就表示任意字符重复任意次。注意,如果在这两个符号后面加上 ? 表示是惰性匹配,只要满足条件就匹配中,否则系统会尝试匹配尽可能长的串。
aaaaa与表达式 a+? 和 a+ 第一个只会匹配a,而第二个会匹配aaaaa
? 前面的字符/模式可有可无
do(es)? 匹配do或者does
[a-zA-Z] 匹配一个任意大小写字母
中括号表示匹配里面列举的任意一个字符
[^a-zA-Z] 与上面相反,匹配一个字母以外的字符
^放在前面表示匹配列举以外的
(?<=pattern)
(?=pattern)
一个前肯一个后肯。
(?<=Hello)hi(?=Bye)
匹配前有Hello后有Bye的hi
\ 万能的转义
当你感觉你用的字符可能不代表它本身的含义的时候,加上这个。