分割合并 |
|
如以下所述,在 Lua 中设计和实现这些函数有各种方法。
使用 Lua 5.x,您可以使用 table.concat[3] 进行合并:table.concat(tbl, delimiter_str)
。
table.concat({"a", "b", "c"}, ",") --> "a,b,c"
其他接口是可能的,很大程度上取决于分割接口的选择,因为合并通常被认为是分割的逆运算。
首先,虽然 Lua 的标准库中没有分割函数,但它确实有 string.gmatch
[4],它可以在许多情况下用作分割函数。与分割函数不同,string.gmatch
使用模式来匹配非分隔符文本,而不是分隔符本身。
local example = "an example string" for i in string.gmatch(example, "%S+") do print(i) end -- output: -- an -- example -- string
split
[1] 函数将字符串分成子字符串列表,在某些分隔符(字符、字符集或模式)的出现处分割原始字符串。设计字符串分割函数有各种方法。下面列出了设计决策的摘要。
分割应该返回一个表数组、列表还是迭代器?
split("a,b,c", ",") --> {"a", "b", "c"} split("a,b,c", ",") --> "a","b","c" (not scalable: Lua has a limit of a few thousand return values) for x in split("a,b,c", ",") do ..... end
分隔符应该是字符串、Lua 模式、LPeg 模式还是正则表达式?
split("a +b c", " +") --> {"a ", "b c"} split("a +b c", " +") --> {"a", "+b", "c"} split("a +b c", some_other_object) --> .....
如何处理空分隔符?
split("abc", "") --> {"a", "b", "c"} split("abc", "") --> {"", "a", "b", "c", ""} split("abc", "") --> error split("abc", "%d*") --> what about patterns that can evaluate to empty strings?
split(s,"")
是将字符串拆分为字符的便捷习惯用法。在 Lua 中,我们可以选择执行 for c in s:gmatch"." do ..... end
。
如何处理空值?
split(",,a,b,c,", ",") --> {"a", "b", "c"} split(",,a,b,c,", ",") --> {"", "", "a", "b", "c", ""} split(",", ",") --> {} or {""} or {"", ""} ? split("", ",") --> {} or {""} ?
join({"",""}, "")
、join({""}, "")
和 join({}, "")
都将生成相同的字符串 ""
。因此,split("", "")
的逆运算应该返回什么并不立即清楚。
是否应该有一个参数来限制分割的次数?
split("a,b,c", ",", 2) --> {"a", "b,c"}
是否应该返回分隔符?当分隔符是模式时,这更有用,在这种情况下,分隔符可能会有所不同。
split("a b c", " +") --> {"a", " ", "b", " ", "c"}
string.gmatch
[5] 在某种程度上是 split
的对偶,它返回与模式匹配的子字符串,并丢弃它们之间的字符串,而不是相反。一个返回两者结果的函数有时被称为 partition
[6]。
string.gsub
/string.match
按模式分割在单个字符出现的地方将字符串分解。如果字段数量已知
str:match( ("([^"..sep.."]*)"..sep):rep(nsep) )
如果字段数量未知
fields = {str:match((str:gsub("[^"..sep.."]*"..sep, "([^"..sep.."]*)"..sep)))}
有些人可能会称以上为 hack :) 如果 sep
是模式元字符,则需要对其进行转义,并且您可能最好预先计算和/或记忆模式。它会省略最后一个分隔符后的值。例如,"a,b,c" 返回 "a" 和 "b",但不返回 "c"
string.gsub
fields = {} str:gsub("([^"..sep.."]*)"..sep, function(c) table.insert(fields, c) end)
无法按预期工作
str, sep = "1:2:3", ":" fields = {} str:gsub("([^"..sep.."]*)"..sep, function(c) table.insert(fields, c) end) for i,v in ipairs(fields) do print(i,v) end -- output: -- 1 1 -- 2 2
修复
function string:split(sep) local sep, fields = sep or ":", {} local pattern = string.format("([^%s]+)", sep) self:gsub(pattern, function(c) fields[#fields+1] = c end) return fields end
示例:将字符串拆分为单词,或返回 nil
function justWords(str) local t = {} local function helper(word) table.insert(t, word) return "" end if not str:gsub("%w+", helper):find"%S" then return t end end
这使用模式 sep
分割字符串。它为每个段调用 func
。当调用 func
时,第一个参数是段,其余参数是 sep
中的捕获(如果有)。在最后一个段上,func
将只带一个参数被调用。(这可以用作标志,或者您可以使用两个不同的函数)。sep
必须不匹配空字符串。增强功能留作练习 :)
func((str:gsub("(.-)("..sep..")", func)))
示例:将字符串拆分为以 DOS 或 Unix 行结束符分隔的行,并从结果中创建一个表。
function lines(str) local t = {} local function helper(line) table.insert(t, line) return "" end helper((str:gsub("(.-)\r?\n", helper))) return t end
使用 gsub 的问题是,它无法处理分隔符模式未出现在字符串末尾的情况。在这种情况下,最终的 "(.-)" 永远无法捕获字符串的末尾,因为整体模式无法匹配。为了处理这种情况,您必须做一些更复杂的事情。下面的 split 函数的行为或多或少类似于 perl 或 python 中的 split。特别是,字符串开头和结尾的单个匹配不会创建新元素。连续的多个匹配会创建空字符串元素。
-- Compatibility: Lua-5.1 function split(str, pat) local t = {} -- NOTE: use {n = 0} in Lua-5.0 local fpat = "(.-)" .. pat local last_end = 1 local s, e, cap = str:find(fpat, 1) while s do if s ~= 1 or cap ~= "" then table.insert(t, cap) end last_end = e+1 s, e, cap = str:find(fpat, last_end) end if last_end <= #str then cap = str:sub(last_end) table.insert(t, cap) end return t end
示例:将文件路径字符串拆分为组件。
function split_path(str) return split(str,'[\\/]+') end parts = split_path("/usr/local/bin") --> {'usr','local','bin'}
测试用例
split('foo/bar/baz/test','/') --> {'foo','bar','baz','test'} split('/foo/bar/baz/test','/') --> {'foo','bar','baz','test'} split('/foo/bar/baz/test/','/') --> {'foo','bar','baz','test'} split('/foo/bar//baz/test///','/') --> {'foo','bar','','baz','test','',''} split('//foo////bar/baz///test///','/+') --> {'foo','bar','baz','test'} split('foo','/+') --> {'foo'} split('','/+') --> {} split('foo','') -- opps! infinite loop!
在邮件列表中讨论了这个话题后,我创建了自己的函数……不知不觉中,我采用了与上面函数类似的方法,只是我使用 gfind 来迭代,并且我看到字符串开头和结尾的单个匹配项为空字段。如上所述,多个连续的分隔符会创建空字符串元素。
-- Compatibility: Lua-5.0 function Split(str, delim, maxNb) -- Eliminate bad cases... if string.find(str, delim) == nil then return { str } end if maxNb == nil or maxNb < 1 then maxNb = 0 -- No limit end local result = {} local pat = "(.-)" .. delim .. "()" local nb = 0 local lastPos for part, pos in string.gfind(str, pat) do nb = nb + 1 result[nb] = part lastPos = pos if nb == maxNb then break end end -- Handle the last field if nb ~= maxNb then result[nb + 1] = string.sub(str, lastPos) end return result end
测试用例
ShowSplit("abc", '') --> { [1] = "", [2] = "", [3] = "", [4] = "", [5] = "" } -- No infite loop... but garbage in, garbage out... ShowSplit("", ',') --> { [1] = "" } ShowSplit("abc", ',') --> { [1] = "abc" } ShowSplit("a,b,c", ',') --> { [1] = "a", [2] = "b", [3] = "c" } ShowSplit("a,b,c,", ',') --> { [1] = "a", [2] = "b", [3] = "c", [4] = "" } ShowSplit(",a,b,c,", ',') --> { [1] = "", [2] = "a", [3] = "b", [4] = "c", [5] = "" } ShowSplit("x,,,y", ',') --> { [1] = "x", [2] = "", [3] = "", [4] = "y" } ShowSplit(",,,", ',') --> { [1] = "", [2] = "", [3] = "", [4] = "" } ShowSplit("x!yy!zzz!@", '!', 4) --> { [1] = "x", [2] = "yy", [3] = "zzz", [4] = "@" } ShowSplit("x!yy!zzz!@", '!', 3) --> { [1] = "x", [2] = "yy", [3] = "zzz" } ShowSplit("x!yy!zzz!@", '!', 1) --> { [1] = "x" } ShowSplit("a:b:i:p:u:random:garbage", ":", 5) --> { [1] = "a", [2] = "b", [3] = "i", [4] = "p", [5] = "u" } ShowSplit("hr , br ; p ,span, div", '%s*[;,]%s*') --> { [1] = "hr", [2] = "br", [3] = "p", [4] = "span", [5] = "div" }
许多人错过了 Lua 中类似 Perl 的 split/join 函数。以下是我的函数
-- Concat the contents of the parameter list, -- separated by the string delimiter (just like in perl) -- example: strjoin(", ", {"Anna", "Bob", "Charlie", "Dolores"}) function strjoin(delimiter, list) local len = getn(list) if len == 0 then return "" end local string = list[1] for i = 2, len do string = string .. delimiter .. list[i] end return string end -- Split text into a list consisting of the strings in text, -- separated by strings matching delimiter (which may be a pattern). -- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores") function strsplit(delimiter, text) local list = {} local pos = 1 if strfind("", delimiter, 1) then -- this would result in endless loops error("delimiter matches empty string!") end while 1 do local first, last = strfind(text, delimiter, pos) if first then -- found? tinsert(list, strsub(text, pos, first-1)) pos = last+1 else tinsert(list, strsub(text, pos)) break end end return list end
这是我自己的 split 函数,用于比较。它与上面的函数基本相同;不是那么 DRY,但(IMO)稍微干净一些。它不使用 gfind(如以下建议),因为我想能够为 split 字符串指定一个模式,而不是为数据部分指定一个模式。如果速度至关重要,可以通过将 string.find 缓存为局部 'strfind' 变量来提高速度,如上面所做的那样。
--Written for 5.0; could be made slightly cleaner with 5.1 --Splits a string based on a separator string or pattern; --returns an array of pieces of the string. --(May optionally supply a table as the third parameter which will be filled with the results.) function string:split( inSplitPattern, outResults ) if not outResults then outResults = { } end local theStart = 1 local theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) while theSplitStart do table.insert( outResults, string.sub( self, theStart, theSplitStart-1 ) ) theStart = theSplitEnd + 1 theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) end table.insert( outResults, string.sub( self, theStart ) ) return outResults end
将字符串使用分隔符分解成表格(从 TableUtils 移动)
-- explode(seperator, string) function explode(d,p) local t, ll t={} ll=0 if(#p == 1) then return {p} end while true do l = string.find(p, d, ll, true) -- find the next d in the string if l ~= nil then -- if "not not" found then.. table.insert(t, string.sub(p,ll,l-1)) -- Save it in our array. ll = l + 1 -- save just after where we found it for searching next time. else table.insert(t, string.sub(p,ll)) -- Save what's left in our array. break -- Break at end, as it should be, according to the lua manual. end end return t end
这是我版本的 PHP 风格 explode,支持限制
function explode(sep, str, limit) if not sep or sep == "" then return false end if not str then return false end limit = limit or mhuge if limit == 0 or limit == 1 then return {str}, 1 end local r = {} local n, init = 0, 1 while true do local s,e = strfind(str, sep, init, true) if not s then break end r[#r+1] = strsub(str, init, s - 1) init = e + 1 n = n + 1 if n == limit - 1 then break end end if init <= strlen(str) then r[#r+1] = strsub(str, init) else r[#r+1] = "" end n = n + 1 if limit < 0 then for i=n, n + limit + 1, -1 do r[i] = nil end n = n + limit end return r, n end
此函数使用元表的 __index 函数来填充拆分部分的表格。此函数不会尝试(正确地)反转模式,因此实际上不按大多数字符串拆分函数的方式工作。
--[[ written for Lua 5.1 split a string by a pattern, take care to create the "inverse" pattern yourself. default pattern splits by white space. ]] string.split = function(str, pattern) pattern = pattern or "[^%s]+" if pattern:len() == 0 then pattern = "[^%s]+" end local parts = {__index = table.insert} setmetatable(parts, parts) str:gsub(pattern, parts) setmetatable(parts, nil) parts.__index = nil return parts end -- example 1 str = "no separators in this string" parts = str:split( "[^,]+" ) print( # parts ) table.foreach(parts, print) --[[ output: 1 1 no separators in this string ]] -- example 2 str = " split, comma, separated , , string " parts = str:split( "[^,%s]+" ) print( # parts ) table.foreach(parts, print) --[[ output: 4 1 split 2 comma 3 separated 4 string ]]
这是 Python 的行为
Python 2.5.1 (r251:54863, Jun 15 2008, 18:24:51) [GCC 4.3.0 20080428 (Red Hat 4.3.0-8)] on linux2 >>> 'x!yy!zzz!@'.split('!') ['x', 'yy', 'zzz', '@'] >>> 'x!yy!zzz!@'.split('!', 3) ['x', 'yy', 'zzz', '@'] >>> 'x!yy!zzz!@'.split('!', 2) ['x', 'yy', 'zzz!@'] >>> 'x!yy!zzz!@'.split('!', 1) ['x', 'yy!zzz!@']
IMO,此 Lua 函数实现了此语义
function string:split(sSeparator, nMax, bRegexp) assert(sSeparator ~= '') assert(nMax == nil or nMax >= 1) local aRecord = {} if self:len() > 0 then local bPlain = not bRegexp nMax = nMax or -1 local nField, nStart = 1, 1 local nFirst,nLast = self:find(sSeparator, nStart, bPlain) while nFirst and nMax ~= 0 do aRecord[nField] = self:sub(nStart, nFirst-1) nField = nField+1 nStart = nLast+1 nFirst,nLast = self:find(sSeparator, nStart, bPlain) nMax = nMax-1 end aRecord[nField] = self:sub(nStart) end return aRecord end
观察使用简单字符串或正则表达式作为分隔符的可能性。
测试用例
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio ... > for k,v in next, string.split('x!yy!zzz!@', '!') do print(v) end x yy zzz @ > for k,v in next, string.split('x!yy!zzz!@', '!', 3) do print(v) end x yy zzz @ > for k,v in next, string.split('x!yy!zzz!@', '!', 2) do print(v) end x yy zzz!@ > for k,v in next, string.split('x!yy!zzz!@', '!', 1) do print(v) end x yy!zzz!@
function gsplit(s,sep) return coroutine.wrap(function() if s == '' or sep == '' then coroutine.yield(s) return end local lasti = 1 for v,i in s:gmatch('(.-)'..sep..'()') do coroutine.yield(v) lasti = i end coroutine.yield(s:sub(lasti)) end) end -- same idea without coroutines function gsplit2(s,sep) local lasti, done, g = 1, false, s:gmatch('(.-)'..sep..'()') return function() if done then return end local v,i = g() if s == '' or sep == '' then done = true return s end if v == nil then done = true return s:sub(lasti) end lasti = i return v end end The {{gsplit()}} above returns an iterator, so other API variants can be easily derived from it: {{{!Lua function iunpack(i,s,v1) local function pass(...) local v1 = i(s,v1) if v1 == nil then return ... end return v1, pass(...) end return pass() end function split(s,sep) return iunpack(gsplit(s,sep)) end function accumulate(t,i,s,v) for v in i,s,v do t[#t+1] = v end return t end function tsplit(s,sep) return accumulate({}, gsplit(s,sep)) end
请注意,上面的实现不允许在分隔符中捕获。为了允许这样做,必须创建另一个闭包来传递额外的捕获字符串(参见 VarargTheSecondClassCitizen)。语义也会变得模糊(我认为一个用例是想要知道每个字符串的实际分隔符是什么,例如,对于像 [%.,;] 这样的分隔符模式)。
function gsplit(s,sep) local i, done, g = 1, false, s:gmatch('(.-)'..sep..'()') local function pass(...) if ... == nil then done = true return s:sub(i) end i = select(select('#',...),...) return ... end return function() if done then return end if s == '' or sep == '' then done = true return s end return pass(g()) end end
上面实现的问题是,无论多么易于阅读,Lua 中的 (.-) 模式在性能上都很糟糕,因此有了以下基于 string.find 的实现(允许在分隔符中捕获,并添加第三个参数“plain”,类似于 string.find)
function string.gsplit(s, sep, plain) local start = 1 local done = false local function pass(i, j, ...) if i then local seg = s:sub(start, i - 1) start = j + 1 return seg, ... else done = true return s:sub(start) end end return function() if done then return end if sep == '' then done = true return s end return pass(s:find(sep, start, plain)) end end
单元测试
local function test(s,sep,expect) local t={} for c in s:gsplit(sep) do table.insert(t,c) end assert(#t == #expect) for i=1,#t do assert(t[i] == expect[i]) end test(t, expect) end test('','',{''}) test('','asdf',{''}) test('asdf','',{'asdf'}) test('', ',', {''}) test(',', ',', {'',''}) test('a', ',', {'a'}) test('a,b', ',', {'a','b'}) test('a,b,', ',', {'a','b',''}) test(',a,b', ',', {'','a','b'}) test(',a,b,', ',', {'','a','b',''}) test(',a,,b,', ',', {'','a','','b',''}) test('a,,b', ',', {'a','','b'}) test('asd , fgh ,; qwe, rty. ,jkl', '%s*[,.;]%s*', {'asd','fgh','','qwe','rty','','jkl'}) test('Spam eggs spam spam and ham', 'spam', {'Spam eggs ',' ',' and ham'})
-- single char string splitter, sep *must* be a single char pattern -- *probably* escaped with % if it has any special pattern meaning, eg "%." not "." -- so good for splitting paths on "/" or "%." which is a common need local function csplit(str,sep) local ret={} local n=1 for w in str:gmatch("([^"..sep.."]*)") do ret[n] = ret[n] or w -- only set once (so the blank after a string is ignored) if w=="" then n = n + 1 end -- step forwards on a blank but not a string end return ret end -- the following is true of any string, csplit will do the reverse of a concat local str="" print(str , assert( table.concat( csplit(str,"/") , "/" ) == str ) ) local str="only" print(str , assert( table.concat( csplit(str,"/") , "/" ) == str ) ) local str="/test//ok/" print(str , assert( table.concat( csplit(str,"/") , "/" ) == str ) ) local str=".test..ok." print(str , assert( table.concat( csplit(str,"%.") , "." ) == str ) )
在 Lua 5.3.2 之前,在大多数情况下,拆分都很棘手,因为 string.gmatch
和 string.gsub
会引入额外的虚假空字段(如 Perl 中一样)。从 Lua 5.3.3 开始,它们不再这样做,它们现在表现得像 Python 一样。因此,以下极简的拆分函数现在是 table.concat
的真正逆函数;以前它不是。
-- splits 'str' into pieces matching 'pat', returns them as an array local function split(str,pat) local tbl = {} str:gsub(pat, function(x) tbl[#tbl+1]=x end) return tbl end local str = "a,,b" -- comma-separated list local pat = "[^,]*" -- everything except commas assert (table.concat(split(str, pat), ",") == str)
'.-'
搜索模式引起的所有性能问题'.-'
(非贪婪)模式的糟糕性能可以通过将其锚定到搜索字符串的起始位置来解决(如果我们使用 string.find()
以及它的第三个参数,起始位置不一定是字符串的第一个位置),这样匹配分隔符的子模式就可以是贪婪的(但请注意,如果分隔符是匹配空字符串的模式,则在文本开始之前会找到一个空匹配,其中包含一个空分隔字符串和一个空分隔符,因此它可能会产生无限循环:不要为可以匹配空字符串的分隔符指定任何模式)。
因此,要从起始位置 p
在字符串 str
中搜索第一个分隔符 ;
,我们可以使用
q, r = str:find('^.-;', p)
同样,当分隔符是静态的时,我们不需要任何捕获来调用 string.find()
:如果存在匹配,q 将等于 p(因为模式在开始处锚定),而 r 将位于分隔符的最后一个字符上。由于我们可以在循环扫描要分割的字符串之前通过简单的 k = #sep
初始化来确定分隔符的长度,因此新的分隔词将是 str:sub(q, r - k)
。但是,静态的普通分隔符必须首先通过在它的“魔法字符”前面加上 '%'
前缀来转换为搜索模式。
但是,如果分隔符必须是模式,则找到的有效分隔符可能具有可变长度,因此您需要捕获分隔符之前的文本,并且要搜索的完整模式是 ('^(.-)' .. sep)
q, r, s = str:find('^(.-);', p)
如果存在匹配,q
将等于起始位置 p
,r
将是分隔符最后一个字符的位置(用于下一个循环),而 s
将是第一个捕获,即从位置 p
(或 q
)开始但在未捕获的分隔符之前的词。
这给出了以下高效函数
function split(str, sep, plain, max) local result, count, first, found, last, word = {}, 1, 1 if plain then sep = sep:gsub('[$%%()*+%-.?%[%]^]', '%%%0') end sep = '^(.-)' .. sep repeat found, last, word = str:find(sep, first) if q then result[count], count, first = word, count + 1, last + 1 else result[count] = str:sub(first) break end until count == max return result end
与之前的函数一样,您可以传递一个可选参数 plain
,将其设置为 true 以搜索普通分隔符(它将被转换为模式),以及一个 max
参数来限制返回数组中的项目数量(如果达到此限制,则返回的最后一个分隔词不包含分隔符的任何出现,因此在此实现中忽略了其余文本)。还要注意,分隔符分隔的空字符串可能会被返回(最多与找到的分隔符的出现次数一样多的空字符串)。
所以
split(';;A', ';')
将返回 {"", "", "A"}
split(';;A', ';', true, 2)
将返回 {"", ""}
使用普通分隔符(例如带有单一编码的'\n'
换行符,或单个';'
分号,或单个'\t'
制表符,或更长的序列如'--'
)的最紧凑的拆分函数是这个。
local function splitByPlainSeparator(str, sep, max) local z = #sep; sep = '^.-'..sep:gsub('[$%%()*+%-.?%[%]^]', '%%%0') local t,n,p, q,r = {},1,1, str:find(sep) while q and n~=max do t[n],n,p = s:sub(q,r-z),n+1,r+1 q,r = str:find(sep,p) end t[n] = str:sub(p) return t end
使用模式分隔符(例如可变换行符如'\r?[\n\v\f]'
或任何空格序列如'%s+'
,或可选地由贪婪空格包围的逗号如'%s*,%s*'
)的最紧凑的拆分函数是这个。
local function splitByPatternSeparator(str, sep, max) sep = '^(.-)'..sep local t,n,p, q,r,s = {},1,1, str:find(sep) while q and n~=max do t[n],n,p = s,n+1,r+1 q,r,s = str:find(sep,p) end t[n] = str:sub(p) return t end
然而,最后一个函数仍然不支持可以是多个备选方案之一的分隔符(因为 Lua 在其模式中没有|
),但是您可以通过使用多个模式来规避此限制,并在内部子循环中使用str:find()
来定位每个可能的备选方案并使用一个小循环在每个备选模式上获取找到的最小位置(例如,使用扩展模式'\r?\n|\r|<br%s*/?>'
拆分)。
local function splitByExtendedPatternSeparator(str, seps, max) -- Split the extended pattern into a sequence of Lua patterns, using the function defined above. -- Note: '|' cannot be part of any subpattern alternative for the separator (no support here for any escape). -- Alternative: just pass "seps" as a sequence of standard Lua patterns built like below, with a non-greedy -- pattern anchored at start for the contextual text accepted in the returned texts betweeen separators, -- and the empty capture '()' just before the pattern for a single separator. if type(seps) == 'string' then seps = splitByPlainSeparator(sep, '|') -- Adjust patterns for i, sep in ipairs(seps) do seps[i] = '^.-()' .. sep end end -- Now the actual loop to split the first string parameter local t, n, p = {}, 1, 1 while n ~= max do -- locate the nearest subpatterns that match a separator in str:sub(p); -- if two subpatterns match at same nearest position, keep the longest one local first, last = nil for i, sep in ipairs(seps) do local q, r, s = str:find(sep, p) if q then -- A possible separator (not necessarily the neareast) was found in str:sub(s, r) -- Here: q~=nil, r~=nil, s~=nil, q==p <= s <= r) if not first or s < first then first, last = s, r -- this also overrides any longer pattern, but located later elseif r > last then last = r -- prefer the longest pattern at the same position end end end if not first then break end -- the nearest separator (with the longest length) was found in str:sub(first, last) t[n], n, p = str:sub(p, first - 1), n + 1, last + 1 end t[n] = str:sub(p) return t end
最后三个函数(几乎等效,但目的不完全相同)都允许搜索任何分隔符的所有出现(不限于一个字符),它们还有一个可选的max
参数(仅在单个while
语句的条件中使用)。
max
设置为大于 1 的正整数,则返回的子字符串数量不会超过此数字,最后一个子字符串包含文本的其余部分(包括分隔符),因此它在找到(max-1)
个第一个出现后停止拆分;
max==1
,它们都返回原始文本;
max
为 nil,或不是整数,或为负数或零,则将扫描所有分隔符,如果源文本包含k个分隔符出现,则返回的表将具有K+1个字符串项,或者如果它根本不包含分隔符出现,则返回单个字符串,与输入文本相同。
str
和sep
是必需的,并且都应该是字符串而不是 nil;但max
是可选的,要生效,它应该是一个正整数。
如果您永远不需要max
参数(即表现得好像它在上面是nil
,因此拆分整个文本以删除所有普通或模式分隔符的出现),只需删除这些while
语句第一行中的条件and n~=max
。
还要注意,以上这些函数在返回表中会去除所有分隔符。您可能希望有一个名为“separators”的变量,以便在需要时获取副本以实现不同的行为。修改很简单:在上面三个函数的 `while` 循环中,只需追加两个字符串,而不是一个:分隔后的单词将位于返回表中的奇数位置(从 1 开始),而分隔符将位于偶数位置(从 2 开始),如果有出现的话。
这允许创建一个简单的词法分析器,其中“分隔符”(定义为如上所述的“扩展模式”或模式表)将是词法标记,而“非分隔符”将是额外的可选空格,不会被标记匹配。在下面的示例代码中,扩展模式使用空字符(在 Lua 字符串字面量中为 `'\000'`)而不是管道来分隔匹配单个标记的备用子模式。
local function splitTokens(str, tokens, max) -- Split the extended pattern into a sequence of Lua patterns, using the function defined above. -- Note: '\000' cannot be part of any subpattern alternative for the separator (no support here for any escape). -- Alternative: just pass "seps" as a sequence of standard Lua patterns built like below, with a non-greedy -- pattern anchored at start for the contextual text accepted in the returned texts betweeen separators, -- and the empty capture '()' just before the pattern for a single separator. if type(tokens) == 'string' then tokens = splitByPlainSeparator(tokens, '\000') -- Adjust patterns for i, token in ipairs(tokens) do tokens[i] = '^.-()' .. token end end -- Now the actual loop to split the first string parameter local t, n, p = {}, 1, 1 while n ~= max do -- locate the nearest subpatterns that match a separator in str:sub(p); -- if two subpatterns match at same nearest position, keep the longest one local first, last = nil for i, token in ipairs(tokens) do local q, r, s = str:find(token, p) if q then -- A possible token (not necessarily the neareast) was found in str:sub(s, r) -- Here: q~=nil, r~=nil, s~=nil, q==p <= s <= r) if not first or s < first then first, last = s, r -- this also overrides any longer pattern, but located later elseif r > last then last = r -- prefer the longest pattern at the same position end end end if not first then break end -- The nearest token (with the longest length) was found in str:sub(first, last). -- Store the non-token part (possibly empty) at odd position, and the token at the next even position t[n], t[n + 1], n, p = str:sub(p, first - 1), str:sub(first, last), n + 2, last + 1 end t[n] = str:sub(p) -- Store the last non-token (possibly empty) at odd position return t end
因此,您可以调用它来对包含标识符、整数或浮点数(如 Lua 语法中的那些)或孤立的非空格符号的文本进行标记(您可以通过向扩展模式添加更多备选方案来添加更长的符号的标记,或支持其他字面量)。
splitTokens(str, '[%a_][%w_]+' .. '\000' .. '%d+[Ee][%-%+]?%d+' .. '\000' .. '%d+%.?%d*' .. '\000' .. '%.%d+[Ee][%-%+]?%d+' .. '\000' .. '%.%d+' .. '\000' .. '[^%w_%s]')
(verdy_p)
当然,我无意冒犯,但是.. 真的有人拥有一个没有像无限循环、错误匹配或错误情况这样的故障的正常 split 函数吗?所有这些“尝试”在这里有什么帮助吗? -- CosminApreutesei
试试 Rici Lake 的 split 函数:LuaList:2006-12/msg00414.html -- J�rg Richter