在lua中使用指定的分隔符拆分字符串

8

我正在尝试使用lua创建一个split()函数,使其具有自定义分隔符的功能,而默认情况下为空格。 如果没有指定分隔符,它可以正常工作。但是当我给函数指定分隔符时,则会出现问题。由于某个原因,它无法返回最后一个子字符串。 该函数:

function split(str,sep)
if sep == nil then
    words = {}
    for word in str:gmatch("%w+") do table.insert(words, word) end
    return words
end
return {str:match((str:gsub("[^"..sep.."]*"..sep, "([^"..sep.."]*)"..sep)))} -- BUG!! doesnt return last value
end

我尝试运行这个:

local str = "a,b,c,d,e,f,g"
local sep = ","
t = split(str,sep)
for i,j in ipairs(t) do
    print(i,j)
end

我得到:

1   a
2   b
3   c
4   d
5   e
6   f

无法找出错误所在...


1
这是因为您在字符串末尾没有使用sep。但模式是 [^,]*,。附注:为了宣传 https://github.com/moteus/lua-split。 - moteus
请参阅此链接:https://dev59.com/4-Xys4cB2Jgan1znM5zr#36958689 - tonypdmtr
4个回答

12

分割字符串时,避免出现边角情况的最简单方法是在你知道字符串不会以分隔符结尾时将分隔符附加到字符串后面:

str = "a,b,c,d,e,f,g"
str = str .. ','
for w in str:gmatch("(.-),") do print(w) end

或者,您可以使用带有可选分隔符的模式:

str = "a,b,c,d,e,f,g"
for w in str:gmatch("([^,]+),?") do print(w) end

实际上,由于我们正在捕获非定界符,所以不需要可选的分隔符:

str = "a,b,c,d,e,f,g"
for w in str:gmatch("([^,]+)") do print(w) end

如此简单而优雅...谢谢! - DrorNohi
1
我认为将分隔符附加到字符串中在任何情况下都有效,不仅仅是当字符串不能以分隔符结尾时。如果字符串以分隔符结尾,则会获得一个空的最后匹配,这应该是可以预料的。 - tonypdmtr
@tonypdmtr,当然可以,只要你表达清楚就可以。 - lhf
@lhf,为什么您在模式的末尾使用了,??即使没有它,结果也是相同的。 - Basilio German
1
这应该被添加到官方的Lua文档中。网上有太多的帖子把这个简单的问题变成了一个代码高尔夫倒置。 - Klaatu von Schlacker

8

这是我常用的split()函数:

-- split("a,b,c", ",") => {"a", "b", "c"}
function split(s, sep)
    local fields = {}
    
    local sep = sep or " "
    local pattern = string.format("([^%s]+)", sep)
    string.gsub(s, pattern, function(c) fields[#fields + 1] = c end)
    
    return fields
end

1
"

\"[^\"..sep..\"]*\"..sep 这是导致问题的原因。您正在匹配一个不是分隔符的字符字符串,后跟分隔符。但是,您想要匹配的最后一个子字符串 (g) 不是由分隔符字符而是由字符串结尾跟随。

最快的解决方法是也将 \\0 视为分隔符 (\"[^\"..sep..\"\\0]*\"..sep),因为它表示字符串的开头和/或结尾。这样,g 就算不是由分隔符而是由字符串结尾跟随,仍然被视为匹配项。

总的来说,我认为您的方法过于复杂;首先,您可以只匹配不包含分隔符的单个子字符串;其次,您可以使用 gmatch 函数在 for 循环中完成此操作。

"
local result = {}
for field in your_string:gsub(("[^%s]+"):format(your_separator)) do
  table.insert(result, field)
end
return result

编辑:上面的代码变得更简单了。
local pattern = "[^%" .. your_separator .. "]+"
for field in string.gsub(your_string, pattern) do
-- ...and so on (The rest should be easy enough to understand)

编辑2:请注意,您还应该转义分隔符。像%这样的分隔符如果不转义为%%可能会导致问题。

function escape(str)
  return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
end

0
我为那些不想使用正则表达式的人添加了一个选项。
local function split(str, sep)
  assert(type(str) == 'string' and type(sep) == 'string', 'The arguments must be <string>')
  if sep == '' then return {str} end
  
  local res, from = {}, 1
  repeat
    local pos = str:find(sep, from)
    res[#res + 1] = str:sub(from, pos and pos - 1)
    from = pos and pos + #sep
  until not from
  return res
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接