简单的模式匹配示例:
var str = string.match("字符串","这里是模式串")
模式串有两个基本的元素:
模式元是模式串里的最小匹配单位,用于表示字符或某一类的字符(或连续的字符序列)。
字面值指的是 "a" 表示 "a", "b" 表示 "b ",字面意思是什么就表示什么,这个不难理解。
var str = string.match("abcd","abc")
.
匹配任意单字节字符( 与正则表达式类似 )。 :
匹配任意多字节字符( 例如中文字符,正则表达式都是用单个点表示 ) 。正则表达式兼容的字符类:
\n
换行符
\r
回车符
\w
字母和数字
\s
空白符
\d
数字
\f
换页符 '\x0c'
\v
匹配一个垂直制表符。等价于 '\x0b'
\t
制表符
其他字符类:
\a
字母
\c
控制字符
\i
是否ASCII字符( 字节码 < 0x80 )
\l
小写字母
\p
标点字符
\u
大写字母
\x
十六进制数字(正则表达式里用于16进制字符前缀,即\xhh
)
\z
表示 '\0'
大写表示反义( 与正则表达式相同 ),例如\D
表示不是数字的字符。
import console;
var str = string.match("12345678","\d")
console.log( str ) //显示 1
console.pause();
例如[abcd]
表示字符是 abcd 其中一个,或者[a-z]
表示 a 到 z 的任意小写字母字符。
//还是返回 1
var str = string.match("12345678","[1230-9]")
参考:模式语法 - 字符集合
例如 <a-zabcd>
元序列的语法与**自定义字符类**类似,但是**元序列**表示的是一连串的字符**序列**。
// 显示 1234
var str = string.match("12345678","<1230-9>")
要注意元序列内部执行贪婪与激进的匹配规则,只要能匹配到内容就会尽力向后走并且不会回溯(这在需要局部禁止回溯时非常有用)。
元序列实际上是一种更精简速度更快的模式匹配迷你版本,元序列可将一个长度不限的字符序列转换为模式元(可对其应用其他模式式运算符),并且在内部可以支持大多数其他模式语法(不能在元序列内创建捕获组,可以引用捕获组)。
aardio 通过『元序列』限制并避免了在使用模式匹配时复杂度无限上升、并以此换取最大化的匹配速度,更精简更可控的匹配语法。
测试数据: string.regex 耗时 455 毫秒的查询,preg 库正则表达式需要 49 毫秒,而模式匹配仅需要 1.4 毫秒。
参考:模式语法 - 元序列
运算符是指定一个模式应当怎样去匹配的特殊符号。
运算符只能用于模式元。
如果将『模式元』理解为编程语言里的操作数,那么『运算符』就相当于编程语言里的操作符。
运算符有很多种,而最常用的就是用来指定匹配次数的量词。
p{2,3}
表示模式元 p 出现 2 到 3次//匹配结果为 12345
var str = string.match("12345678","\d{2,5}")
p+
表示模式元 p 出现 1 次到任意次数,等价于 p{1,}
//匹配结果为 12345678
var str = string.match("12345678","\d+")
p{0,}
//匹配结果还是 12345678
var str = string.match("12345678","\d+\s*") //这里的"\s*"匹配零个或多个空格
其他运算符请参考:模式语法 - 运算符
模式匹配对正则表达式进行了简化,保留基本语法,牺牲一些功能换取效率。
例如模式串 \d+
包含两个部分:
\d
表示数字。这里的\d
还可以换成[0-9]
,它们的意思是一样的。[0-9]
等价于 [0123456789]
。 中括号表示自定义的一个字符集合,只要目标字符是其中的一个就匹配成功。+
表示匹配一次或多次,也可以写为 {1,}
意思是一样的。\d+
前面的模式元 \d
指定匹配什么样的字符,后面运算符 +
指定怎样去匹配、匹配多少个字符。
再例如身份证匹配的模式 \d{15,18
} 用于匹配 15 到 18 个数字。
自定义字符集合
但我们遇到的一个问题是,最后一位可能不是数值,也有一种可能是"x",也就是说**可能是数值也可能是x**,那么我们就要改为:
\d{14,17}[\dx]
但是别人还有可能把 x 大写啊,所以继续修改为:
\d{14,17}[\dxX]
元序列
身份证还不能是 16 位, 17 位数字。所以我们继续修改为:
var str = string.match("身份证号码","\d{14,14}<\d\d\d>*[\dxX]" )
上面的 <\d\d\d>
是一个自定义的元序列( metasequence )。
像这些,多动手折腾很快会明白,别人灌输给你的知识永远没有自已探索到的理解深刻。
首尾锚点
更进一步,19 位,20 位的数值都可能匹配成功,因为他只要匹配其中18位就成功了,这时候我们还要限定他前面后面都不能有其他的字符,这时就要指定首尾锚点,用 ^
表示文本开始位置的锚点,用 $
表示文本结束位置的锚点。
例如:
var str = string.match("身份证号码","^\d{14,14}<\d\d\d>*[\dxX]$" )
跳过空白字符
但是这样还有一个问题,如果身份证号码前后可能有空格怎么办呢?可以用 \s
表示所有空白字符,加上匹配零到多次的量词运算符就是\s*
,再改进如下:
var str = string.match("身份证号码","^\s*\d{14,14}<\d\d\d>*[\dxX]\s*$" )
边界断言
如果身份证前后不仅仅是空格,还可能有别的字符,哪怎么办呢?
这里我们可以接触一个新的概念:边界断言。
边界断言是在模式元前面加一个表示否定的感叹号,表示从不匹配(左侧)到匹配(右侧)的边界。
例如 !\d
表示不是数字到数字交界的位置,这是一个试探性的零宽匹配,匹配的仅仅是边界,消耗的字符串长度为 0。
加上边界断言后的代码如下:
import console;
var idNumber = "sfz612323198608110000fgd"
idNumber = string.match(idNumber,"!\d(\d{14}<\d\d\d>*[\dxX])![^\dxX]")
console.log(idNumber);
console.pause(true);
边界断言是一种左右双向的零宽断言(Zero-width Assertions)语法,
结合向左回顾断言(逻辑取反) + 向右预测断言以实现边界检测,在字符串首尾两端只检测有字符的内侧边界。
这些需要在**实践中嗑碰出来的知识,如流水无形,无一定之规**。
可以熟练使用模式匹配中的括号,表示你精通了模式匹配。
[ab]
中括号创建自定义字符集合,用于匹配指定字符中的**任意一个**。<ab>
尖括号创建元序列,匹配一连串的字符**序列**。p{2,3}
大括号作为运算符指定模式元重复匹配的次数。(p)
而圆括号则对模式匹配结果进行分组,每增加一对圆括号,模式匹配函数就**多一个返回值**。参考: