如何迭代Lua字符串中的单个字符?

Posted

技术标签:

【中文标题】如何迭代Lua字符串中的单个字符?【英文标题】:How to iterate individual characters in Lua string? 【发布时间】:2010-10-24 03:57:09 【问题描述】:

我在 Lua 中有一个字符串,想在其中迭代单个字符。但是我尝试过的代码都没有,官方手册只显示了如何查找和替换子字符串:(

str = "abcd"
for char in str do -- error
  print( char )
end

for i = 1, str:len() do
  print( str[ i ] ) -- nil
end

【问题讨论】:

【参考方案1】:

在 lua 5.1 中,您可以通过多种方式迭代字符串 this 的字符。

基本循环是:

for i = 1, #str do
    local c = str:sub(i,i)
    -- do something with c
end

但是使用带有string.gmatch() 的模式来获取字符的迭代器可能更有效:

for c in str:gmatch"." do
    -- do something with c
end

甚至可以使用string.gsub() 为每个字符调用一个函数:

str:gsub(".", function(c)
    -- do something with c
end)

在上述所有内容中,我利用了 string 模块被设置为所有字符串值的元表这一事实,因此可以使用 : 表示法将其函数作为成员调用。我还使用 (new to 5.1, IIRC) # 来获取字符串长度。

您的应用程序的最佳答案取决于很多因素,如果性能很重要,基准测试是您的朋友。

您可能想要评估为什么您需要遍历字符,并查看已绑定到 Lua 的正则表达式模块之一,或者对于现代方法查看 Roberto 的 @ 987654321@ 实现 Lua 解析表达式语法的模块。

【讨论】:

谢谢。关于您提到的 lpeg 模块 - 它是否在标记化后将标记位置保存在原始文本中?我需要执行的任务是通过 lua 语法突出显示 scite 中的特定简单语言(没有编译的 c++ 解析器)。另外,如何安装lpeg?似乎它在分发中有 .c 源 - 它需要与 lua 一起编译吗? 构建 lpeg 将生成一个 DLL(或 .so),该 DLL(或 .so)应该存储在 require 可以找到的地方。 (即,在你的 lua 安装中由全局 package.cpath 的内容标识的某个地方。)如果你想使用它的简化语法,你还需要安装它的配套模块 re.lua。从 lpeg 语法中,您可以通过多种方式获取回调和捕获文本,当然也可以使用捕获来简单地存储匹配的位置以供以后使用。如果语法高亮是目标,那么 PEG 是不错的工具选择。 更不用说latest releases of SciTE(自 2.22 起)包括基于 LPEG 的词法分析器 Scintillua,这意味着它可以直接工作,无需重新编译。 所有这些都不适用于非 ASCII 字符。【参考方案2】:

根据手头的任务,使用string.byte 可能更容易。这也是最快的方法,因为它避免了创建在 Lua 中非常昂贵的新子字符串,这要归功于每个新字符串的散列并检查它是否已知。您可以使用相同的string.byte 预先计算您要查找的符号代码,以保持可读性和可移植性。

local str = "ab/cd/ef"
local target = string.byte("/")
for idx = 1, #str do
   if str:byte(idx) == target then
      print("Target found at:", idx)
   end
end

【讨论】:

【参考方案3】:

如果您使用的是 Lua 5,请尝试:

for i = 1, string.len(str) do
    print( string.sub(str, i, i) )
end

【讨论】:

【参考方案4】:

在提供的答案(here、here 和here)中已经有很多好的方法。如果速度是您主要寻找的东西,那么您绝对应该考虑通过 Lua 的 C API 来完成这项工作,这比原始 Lua 代码快很多倍。使用预加载的块(例如load function)时,差异不是很大,但仍然相当大。

至于 Lua 解决方案,让我分享一下我所做的这个小基准测试。它涵盖了迄今为止提供的所有答案,并添加了一些优化。不过,要考虑的基本事项是:

您需要迭代字符串中的字符多少次?

如果答案是“一次”,那么您应该查找基准的第一部分(“原始速度”)。 否则,第二部分将提供更精确的估计,因为它将字符串解析到表中,这样迭代起来要快得多。您还应该考虑为此编写一个简单的函数,就像 @Jarriz 建议的那样。

这里是完整的代码:

-- Setup locals
local str = "Hello World!"
local attempts = 5000000
local reuses = 10 -- For the second part of benchmark: Table values are reused 10 times. Change this according to your needs.
local x, c, elapsed, tbl
-- "Localize" funcs to minimize lookup overhead
local stringbyte, stringchar, stringsub, stringgsub, stringgmatch = string.byte, string.char, string.sub, string.gsub, string.gmatch

print("-----------------------")
print("Raw speed:")
print("-----------------------")

-- Version 1 - string.sub in loop
x = os.clock()
for j = 1, attempts do
    for i = 1, #str do
        c = stringsub(str, i)
    end
end
elapsed = os.clock() - x
print(string.format("V1: elapsed time: %.3f", elapsed))

-- Version 2 - string.gmatch loop
x = os.clock()
for j = 1, attempts do
    for c in stringgmatch(str, ".") do end
end
elapsed = os.clock() - x
print(string.format("V2: elapsed time: %.3f", elapsed))

-- Version 3 - string.gsub callback
x = os.clock()
for j = 1, attempts do
    stringgsub(str, ".", function(c) end)
end
elapsed = os.clock() - x
print(string.format("V3: elapsed time: %.3f", elapsed))

-- For version 4
local str2table = function(str)
    local ret = 
    for i = 1, #str do
        ret[i] = stringsub(str, i) -- Note: This is a lot faster than using table.insert
    end
    return ret
end

-- Version 4 - function str2table
x = os.clock()
for j = 1, attempts do
    tbl = str2table(str)
    for i = 1, #tbl do -- Note: This type of loop is a lot faster than "pairs" loop.
        c = tbl[i]
    end
end
elapsed = os.clock() - x
print(string.format("V4: elapsed time: %.3f", elapsed))

-- Version 5 - string.byte
x = os.clock()
for j = 1, attempts do
    tbl = stringbyte(str, 1, #str) -- Note: This is about 15% faster than calling string.byte for every character.
    for i = 1, #tbl do
        c = tbl[i] -- Note: produces char codes instead of chars.
    end
end
elapsed = os.clock() - x
print(string.format("V5: elapsed time: %.3f", elapsed))

-- Version 5b - string.byte + conversion back to chars
x = os.clock()
for j = 1, attempts do
    tbl = stringbyte(str, 1, #str) -- Note: This is about 15% faster than calling string.byte for every character.
    for i = 1, #tbl do
        c = stringchar(tbl[i])
    end
end
elapsed = os.clock() - x
print(string.format("V5b: elapsed time: %.3f", elapsed))

print("-----------------------")
print("Creating cache table ("..reuses.." reuses):")
print("-----------------------")

-- Version 1 - string.sub in loop
x = os.clock()
for k = 1, attempts do
    tbl = 
    for i = 1, #str do
        tbl[i] = stringsub(str, i) -- Note: This is a lot faster than using table.insert
    end
    for j = 1, reuses do
        for i = 1, #tbl do
            c = tbl[i]
        end
    end
end
elapsed = os.clock() - x
print(string.format("V1: elapsed time: %.3f", elapsed))

-- Version 2 - string.gmatch loop
x = os.clock()
for k = 1, attempts do
    tbl = 
    local tblc = 1 -- Note: This is faster than table.insert
    for c in stringgmatch(str, ".") do
        tbl[tblc] = c
        tblc = tblc + 1
    end
    for j = 1, reuses do
        for i = 1, #tbl do
            c = tbl[i]
        end
    end
end
elapsed = os.clock() - x
print(string.format("V2: elapsed time: %.3f", elapsed))

-- Version 3 - string.gsub callback
x = os.clock()
for k = 1, attempts do
    tbl = 
    local tblc = 1 -- Note: This is faster than table.insert
    stringgsub(str, ".", function(c)
        tbl[tblc] = c
        tblc = tblc + 1
    end)
    for j = 1, reuses do
        for i = 1, #tbl do
            c = tbl[i]
        end
    end
end
elapsed = os.clock() - x
print(string.format("V3: elapsed time: %.3f", elapsed))

-- Version 4 - str2table func before loop
x = os.clock()
for k = 1, attempts do
    tbl = str2table(str)
    for j = 1, reuses do
        for i = 1, #tbl do -- Note: This type of loop is a lot faster than "pairs" loop.
            c = tbl[i]
        end
    end
end
elapsed = os.clock() - x
print(string.format("V4: elapsed time: %.3f", elapsed))

-- Version 5 - string.byte to create table
x = os.clock()
for k = 1, attempts do
    tbl = stringbyte(str,1,#str)
    for j = 1, reuses do
        for i = 1, #tbl do
            c = tbl[i]
        end
    end
end
elapsed = os.clock() - x
print(string.format("V5: elapsed time: %.3f", elapsed))

-- Version 5b - string.byte to create table + string.char loop to convert bytes to chars
x = os.clock()
for k = 1, attempts do
    tbl = stringbyte(str, 1, #str)
    for i = 1, #tbl do
        tbl[i] = stringchar(tbl[i])
    end
    for j = 1, reuses do
        for i = 1, #tbl do
            c = tbl[i]
        end
    end
end
elapsed = os.clock() - x
print(string.format("V5b: elapsed time: %.3f", elapsed))

示例输出(Lua 5.3.4,Windows)

-----------------------
Raw speed:
-----------------------
V1: elapsed time: 3.713
V2: elapsed time: 5.089
V3: elapsed time: 5.222
V4: elapsed time: 4.066
V5: elapsed time: 2.627
V5b: elapsed time: 3.627
-----------------------
Creating cache table (10 reuses):
-----------------------
V1: elapsed time: 20.381
V2: elapsed time: 23.913
V3: elapsed time: 25.221
V4: elapsed time: 20.551
V5: elapsed time: 13.473
V5b: elapsed time: 18.046

结果:

就我而言,string.bytestring.sub 在原始速度方面是最快的。当使用缓存表并在每个循环中重复使用 10 次时,string.byte 版本即使在将 charcode 转换回 chars 时也是最快的(这并不总是必要的,取决于使用情况)。

您可能已经注意到,我根据之前的基准做了一些假设,并将它们应用到代码中:

    如果在循环内部使用库函数,则应始终进行本地化,因为它要快得多。 使用tbl[idx] = value 将新元素插入lua 表比table.insert(tbl, value) 快得多。 使用for i = 1, #tbl 循环遍历表比for k, v in pairs(tbl) 快一点。 总是更喜欢函数调用较少的版本,因为调用本身会增加一点执行时间。

希望对你有帮助。

【讨论】:

【参考方案5】:

迭代构造一个字符串并用 load() 将该字符串作为表返回...

itab=function(char)
local result
for i=1,#char do
 if i==1 then
  result=string.format('%s','')
 end
result=result..string.format('\'%s\'',char:sub(i,i))
 if i~=#char then
  result=result..string.format('%s',',')
 end
 if i==#char then
  result=result..string.format('%s','')
 end
end
 return load('return '..result)()
end

dump=function(dump)
for key,value in pairs(dump) do
 io.write(string.format("%s=%s=%s\n",key,type(value),value))
end
end

res=itab('KOYAANISQATSI')

dump(res)

放出来……

1=string=K
2=string=O
3=string=Y
4=string=A
5=string=A
6=string=N
7=string=I
8=string=S
9=string=Q
10=string=A
11=string=T
12=string=S
13=string=I

【讨论】:

【参考方案6】:

所有人都建议一种不太理想的方法

会是最好的:

    function chars(str)
        strc = 
        for i = 1, #str do
            table.insert(strc, string.sub(str, i, i))
        end
        return strc
    end

    str = "Hello world!"
    char = chars(str)
    print("Char 2: "..char[2]) -- prints the char 'e'
    print("-------------------\n")
    for i = 1, #str do -- testing printing all the chars
        if (char[i] == " ") then
            print("Char "..i..": [[space]]")
        else
            print("Char "..i..": "..char[i])
        end
    end

【讨论】:

“不太理想”适合什么任务?最适合什么任务?

以上是关于如何迭代Lua字符串中的单个字符?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 R 中迭代以同时保存多个文件并避免“absolute_path(target) 中的错误:'x' 必须是单个字符串”?

如何并行运行单个Lua脚本对多个Redis值?

如何将单个字符转换为字符串?

如何将单个字符串转换为字符串?

lua中如何按照key顺序遍历table

Lua教程