¿Cómo iterar caracteres individuales en la cadena Lua?


88

Tengo una cadena en Lua y quiero iterar caracteres individuales en ella. Pero ningún código que he probado funciona y el manual oficial solo muestra cómo encontrar y reemplazar subcadenas :(

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

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

Respuestas:


125

En lua 5.1, puede iterar los caracteres de una cadena de varias formas.

El bucle básico sería:

para i = 1, #str do
    local c = str: sub (i, i)
    - haz algo con c
fin

Pero puede ser más eficiente usar un patrón con string.gmatch()para obtener un iterador sobre los caracteres:

para c en str: gmatch "." hacer
    - haz algo con c
fin

O incluso para usar string.gsub()para llamar a una función para cada carácter:

str: gsub (".", función (c)
    - haz algo con c
fin)

En todo lo anterior, aproveché el hecho de que el stringmódulo está configurado como una metatabla para todos los valores de cadena, por lo que sus funciones se pueden llamar como miembros usando la :notación. También he usado (nuevo en 5.1, IIRC) #para obtener la longitud de la cadena.

La mejor respuesta para su aplicación depende de muchos factores, y los puntos de referencia son sus amigos si el rendimiento va a importar.

Es posible que desee evaluar por qué necesita iterar sobre los caracteres y mirar uno de los módulos de expresión regular que se han vinculado a Lua, o para un enfoque moderno, busque en el módulo lpeg de Roberto que implementa Parsing Expression Grammers para Lua.


Gracias. Acerca del módulo lpeg que ha mencionado: ¿guarda las posiciones de los tokens en el texto original después de la tokenización? La tarea que debo realizar es resaltar la sintaxis en un lenguaje simple específico en scite a través de lua (sin un analizador de C ++ compilado). Además, ¿cómo instalar lpeg? Parece que tiene una fuente .c en la distribución, ¿es necesario compilarlo junto con lua?
grigoryvp

La construcción de lpeg producirá un archivo DLL (o .so) que debe almacenarse donde requiera encontrarlo. (es decir, en algún lugar identificado por el contenido del package.cpath global en su instalación de lua). También necesita instalar su módulo complementario re.lua si desea utilizar su sintaxis simplificada. Desde una gramática lpeg, puede obtener devoluciones de llamada y capturar texto de varias formas, y ciertamente es posible usar capturas para almacenar simplemente la ubicación de la coincidencia para su uso posterior. Si el objetivo es el resaltado de sintaxis, entonces un PEG no es una mala elección de herramienta.
RBerteig

3
Sin mencionar que las últimas versiones de SciTE (desde 2.22) incluyen Scintillua, un lexer basado en LPEG, lo que significa que puede funcionar de inmediato, sin necesidad de volver a compilarlo.
Stuart P. Bentley

11

Si está utilizando Lua 5, intente:

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

9

Dependiendo de la tarea en cuestión, podría ser más fácil de usar string.byte. También es la forma más rápida porque evita la creación de una nueva subcadena que resulta ser bastante cara en Lua gracias al hash de cada nueva cadena y al comprobar si ya se conoce. Puede calcular previamente el código de los símbolos que busca con el mismo string.bytepara mantener la legibilidad y la portabilidad.

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

5

Ya hay muchos buenos enfoques en las respuestas proporcionadas ( aquí , aquí y aquí ). Si la velocidad es lo que busca principalmente , definitivamente debería considerar hacer el trabajo a través de la API C de Lua, que es muchas veces más rápida que el código Lua sin formato. Cuando se trabaja con fragmentos precargados (por ejemplo , función de carga ), la diferencia no es tan grande, pero sigue siendo considerable.

En cuanto a las soluciones Lua puras , permítanme compartirles este pequeño punto de referencia que hice. Cubre todas las respuestas proporcionadas hasta esta fecha y agrega algunas optimizaciones. Aún así, lo básico a considerar es:

¿Cuántas veces necesitará iterar sobre caracteres en una cadena?

  • Si la respuesta es "una vez", entonces debería buscar la primera parte del banchmark ("velocidad bruta").
  • De lo contrario, la segunda parte proporcionará una estimación más precisa, porque analiza la cadena en la tabla, que es mucho más rápida de iterar. También debería considerar escribir una función simple para esto, como sugirió @Jarriz.

Aquí está el código completo:

-- 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))

Salida de ejemplo (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

Resultado:

En mi caso, los string.bytey string.subfueron los más rápidos en términos de velocidad bruta. Al usar la tabla de caché y reutilizarla 10 veces por ciclo, la string.byteversión fue la más rápida incluso al convertir los códigos de caracteres de nuevo a caracteres (lo que no siempre es necesario y depende del uso).

Como probablemente haya notado, hice algunas suposiciones basadas en mis puntos de referencia anteriores y las apliqué al código:

  1. Las funciones de la biblioteca siempre deben estar localizadas si se usan dentro de bucles, porque es mucho más rápido.
  2. Insertar un nuevo elemento en la tabla lua es mucho más rápido usando tbl[idx] = valueque table.insert(tbl, value).
  3. Recorrer la tabla usando for i = 1, #tbles un poco más rápido que for k, v in pairs(tbl).
  4. Prefiera siempre la versión con menos llamadas a funciones, porque la llamada en sí aumenta un poco el tiempo de ejecución.

Espero eso ayude.


0

Todas las personas sugieren un método menos óptimo.

Será mejor:

    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

¿"Menos óptimo" para qué tarea? ¿"Mejor" para qué tarea?
Oleg V. Volkov

0

Iterando para construir una cadena y devolviendo esta cadena como una tabla con 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)

Pone fuera...

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.