理解Lua中的模式匹配(Pattern Matching)

您所在的位置:网站首页 素描三级画画内容 理解Lua中的模式匹配(Pattern Matching)

理解Lua中的模式匹配(Pattern Matching)

2023-08-12 19:18| 来源: 网络整理| 查看: 265

最近在学习Lua的过程中,发现Lua中并没有内置正则表达式的功能,而是发明了一种类正则表达式的规则——模式匹配(Pattern Matching)来支持类似功能。其主要原因是出于程序大小的考虑:一个符合POSIX标准的正则表达式实现要大于4000行代码,比整个Lua的标准库都要大,而相比之下,Lua中模式匹配的实现只有不到500行。模式匹配的功能虽然没有正则表达式那般强大,但也足以应对大多数场景。这也算是一个“空间换时间”的思路——程序员们的学习时间。本篇将通过一些示例来帮助理解Lua中的模式匹配。

快速预览

以下示例程序从字符串s中提取日期部分并打印输出:

s = "Deadline is 30/05/1999, firm" date = "%d%d/%d%d/%d%d%d%d" print(string.match(s, date)) --> 30/05/1999

其中字符串data就是用作匹配的“模式”(pattern)。不难发现,%d表示一个数字,告诉string.match:把在字符串s中形如dd/mm/yyyy格式的日期匹配出来。

模式的组成 character classes

类似%d的用法,在Lua中称之为character classes,列举如下:

character class匹配%a字母%c控制字符 (例如回车:\n)%d0-9的数字%l小写字母%p标点符号%s空白字符%u大写字母%w字母和数字%x十六进制%z空字符"\0"

使用character class的大写可以表示该类型的补充。例如%A代表所有非字母字符:

s, _ = string.gsub("hello, up-down!", "%A", ".") print(s) --> hello..up.down.

string.gsub函数过程:匹配"hello, up-down!"中所有非字符字符(%A),并用"."替代这些符。

magic characters

至此,我们发现character class只能匹配一个字符,能力十分有限。不过Lua还引入了类似正则表达式语法,可以通过一些特殊字符实现复杂匹配,这些特殊字符在Lua中称为magic characters,列举如下:

magic character描述备注()标记一个子模式,供后续使用,跟正则的用法类似.匹配所有字符类似正则表达式,只不过正则表达式的.不包括回车"\n"%1. 可用作转义符;2. 声明character classess;3. 跟()结合用于子模式匹配:%N,N是一个数字,表示匹配第N个子串,例如%1,%2 ...,跟正则表达式的1,1,1,2...类似。+匹配前面的模式1次或多次-匹配前面的模式0次或多次,返回最短的匹配结果,模式匹配中的“非贪婪模式”*匹配前面的模式0次或多次?匹配前面的模式0次或1次如果处于模式开头,则表示匹配输入字符串的开始位置,如果放在[]中,表示取补集, 跟正则表达式的用法基本一致$表示匹配输入字符串的结尾位置,跟正则表达式的用法基本一致[ ]表示一个字符集合(char-set),跟正则用法类似例如[%w_]表示匹配字母数字和下划线,[a-f]表示匹配字母a到f 示例

在Lua中,用到模式匹配的函数主要有:

函数签名基本用途string.find(string, pattern [, init, plain])在字符串中查找符合pattern模式的第一个实例,并返回其起止位置string.match(string, pattern [, init])返回字符串中符合pattern模式的第一个实例string.gmatch(string, pattern)返回一个迭代函数,该函数每次调用会返回下一个匹配的实例string.gsub(string, pattern,repl [, n])把字符串中所有被pattern匹配的字符串都替换为repl,返回替换后的字符串

[]中的参数为可选参数,详尽的函数说明请参阅官方文档。 www.lua.org/manual/5.1/…

接下来我们将通过几个示例程序来演示如何使用模式匹配。

基本示例 匹配多次 -- + 匹配1次或多次 print(string.match("hello world 123","%w+ %w+")) --> hello world print(string.match("helle world 123","[%w %d]+")) --> hello world 123 -- ? 匹配0或1次 (匹配一个有符号数) print(string.match("the number is: +123", "[+-]?%d+")) --> +123 print(string.match("the number is: 123", "[+-]?%d+")) --> 123 print(string.match("the number is: -123", "[+-]?%d+")) --> -123 -- * 匹配0次或多次 print(string.match("abc123abc", "%a+%d*%a+")) --> abc123abc print(string.match("abcabc", "%a+%d*%a+")) --> abcabc 使用[]组合 -- 匹配所有字母数字和 "." print(string.match("a1.a2+a3","[%w.]+")) --> a1.a2 -- 匹配所有数字,等同于%d+,这里只是作为示例,实际编码使用%d+即可 print(string.match("abc123","[0-9]+")) --> 123 -- 匹配所有字母和数字,等同于%w+,这里只是作为示例,实际编码中使用%w+即可 print(string.match("abc123","[a-fA-F0-9]+")) --> abc123 -- 匹配0和1 print(string.match("1010345","[01]+")) --> 1010 -- ^取反, 匹配除了字母以外的所有字符 print(string.match("abc123","[^%a]+")) --> 123 使用()和%N来捕获子模式

以下这个例子使用()来捕获子模式,并且返回对应子模式的匹配结果:

date = "17/7/1990" _, _, d, m, y = string.find(date, "(%d+)/(%d+)/(%d+)") print(d, m, y) --> 17 7 1990

这个例子很好理解:我们在模式中使用()定义了三个子模式,string.find将子模式的匹配结果作为返回值分别返回。 使用()搭配%N,还可以直接在模式中使用被捕获的子模式的值,例如提取一个字符串中被双引号"或单引号'包裹的部分,可以这样做:

s = [[then he said: "it's all right"!]] a, b, c, quotedPart = string.find(s, "([\"'])(.-)%1") print(quotedPart) --> it's all right print(c) --> "

模式([\"'])(.-)%1包含两个子模式,%1则表示匹配到的第一个子模式(也就是返回值c),也就是([\"'])匹配到的值,在这个例子中,是双引号"。在这个示例中,不能简单的使用类似[\"'].-[\"']这样的模式,因为显然这条模式只能匹配到it。

非贪婪匹配

+,*,?的匹配规则跟正则表达式中的贪婪匹配类似,总是尽可能多的匹配。正则表达式使用?来表示非贪婪限定符。在Lua中,?不具备这样的功能,而是使用-来表示非贪婪匹配,例如:

print(string.match("helloworld",".+")) --> helloworld print(string.match("helloworld",".-")) --> hello

我们希望匹配HTML元素span之间的内容,在第一条模式中,由于.+是贪婪匹配,所以直接匹配到了结尾的之前,也就是helloworld。第二条模式中,.-会匹配尽可能少的字符,也就是非贪婪匹配模式(懒惰模式),匹配到第一个就停止匹配了。 通过string.gusb来观察匹配结果:

-- 期望将xxx中的xxx替换成lua -- 贪婪匹配,替换了整个字符串 print(string.gsub("helloworld",".+","lua")) --> lua -- 非贪婪匹配,结果符合预期 print(string.gsub("helloworld",".-","lua")) --> lualua

要注意,-用在模式的开头是没有意义的,因为-表示匹配0次或多次,如果放在开头,那意味着永远都表示匹配0次。

string.gmatch

string.match只能匹配字符串中第一个符合pattern模式的字符串,如果我们想得到字符串中所有符合pattern模式的子串,则可以使用string.gmatch,以上述贪婪匹配为例,打印所有span标签中的元素:

for i in string.gmatch("helloworld", "(.-)") do print(i) end

输出结果:

hello world

可以发现,我们对pattern做了一点小手脚,从.-改成了(.-)。这样使得string.gmatch返回的迭代器会直接迭代捕获的子模式,也就是(.-)。如果沿用原本的pattern,则输出为:

hello world

这是gmatch的特性:如果在pattern中出现了(),则迭代器只会迭代()所匹配的子串,如果没有,则返回所有匹配的字符串。利用这个特性,还可以实现读取类似key=value键值对的功能:

s = "from=world, to=Lua" for k, v in string.gmatch(s, "(%w+)=(%w+)") do print("key="..k) print("value="..v) end

输出结果:

key=from value=world key=to value=Lua string.gsub

string.gsub用于字符串的匹配-替换,使用参数repl替换字符串中的所有(或者前n个,如果有传入第三个参数来给定)符合pattern的字符串,其中repl可以是字符串,方法或者是table:

x = string.gsub("hello world", "(%w+)", "%1 %1") --> x="hello hello world world" --%1 表示(%w+)所匹配的字符串,也就是"hello world"。 x = string.gsub("hello world", "%w+", "%0 %0", 1) --> x="hello hello world" --第三个参数表示替换前1一个,也就是第一个,匹配到的字符串,也就是"hello"。 --对于第一次匹配,$0 $0 = "hello hello"。 --因此得到结果为"hello hello world"。 x = string.gsub("hello world", "%w+", "%0 %0", 2) --> x="hello hello world world" --第三个参数表示替换前2个匹配到的字符串,也就是"hello"和"world"。 --对于第一次匹配,$0 $0 = "hello hello". --对于第二次匹配,$0 $0 = "world world" --因此得到结果为"hello hello world world" x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") --> x="world hello Lua from" x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv) --> x="home = /home/roberto, user = roberto" --%$中的%是转义,表示匹配$ --当第二个参数是函数的时候,这个函数会在每次匹配到模式的时候调用,入参为()所匹配的子模式 --函数返回值则则作为替换的字符串。 --在这个示例中,会调用 os.getenv("HOME") 和 os.getenv("USER"),并根据返回值替换原字符串。 x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) return loadstring(s)() end) --> x="4+5 = 9" local t = {name="lua", version="5.1"} x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) --> x="lua-5.1.tar.gz" 执行过程拆解

string.gsub玩法十分丰富,但万变不离其宗。理解了其执行过程,再理解上面的示例就很轻松了。 string.gsub的执行过程就是一个模式匹配+字符串替换的循环,直到模式匹配停止。过程伪代码表示如下:

repeat 执行模式匹配 根据repl规则,对本次循环模式匹配到的字符串进行替换 until (模式匹配结束)

如果不传递第三个参数,string.gsub默认对字符串进行完全匹配,如果我们不需要对字符串进行完全匹配(例如像是string.match和string.find,只匹配第一个结果就结束了),我们可以通过第三个参数来设定要匹配几次——也就是设定模式匹配什么时候结束。在当前匹配-替换循环中,repl只会感知到本次模式匹配的结果:例如%N所捕获的子串的是当次匹配的子串,如果repl是一个函数,那么函数的入参也是当次匹配的字符串,返回值也只替换当次匹配的字符串。

参考

www.lua.org/manual/5.1/… www.lua.org/pil/20.1.ht… www.fhug.org.uk/wiki/wiki/d… www.jianshu.com/p/f141027e1…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3