Come iterare singoli caratteri nella stringa Lua?


88

Ho una stringa in Lua e voglio iterare i singoli caratteri in essa. Ma nessun codice che ho provato funziona e il manuale ufficiale mostra solo come trovare e sostituire le sottostringhe :(

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

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

Risposte:


125

In lua 5.1, puoi iterare i caratteri di una stringa in un paio di modi.

Il ciclo di base sarebbe:

per i = 1, #str do
    local c = str: sub (i, i)
    - fare qualcosa con c
fine

Ma potrebbe essere più efficiente usare un pattern con string.gmatch()per ottenere un iteratore sui caratteri:

per c in str: gmatch "." fare
    - fare qualcosa con c
fine

O anche da usare string.gsub()per chiamare una funzione per ogni carattere:

str: gsub (".", funzione (c)
    - fare qualcosa con c
fine)

In tutto quanto sopra, ho approfittato del fatto che il stringmodulo è impostato come metatabile per tutti i valori di stringa, quindi le sue funzioni possono essere chiamate come membri usando la :notazione. Ho anche usato il (nuovo per 5.1, IIRC) #per ottenere la lunghezza della stringa.

La risposta migliore per la tua applicazione dipende da molti fattori e i benchmark sono tuoi amici se le prestazioni avranno importanza.

Potresti voler valutare perché devi iterare sui caratteri e guardare uno dei moduli di espressioni regolari che sono stati associati a Lua, o per un approccio moderno guardare nel modulo lpeg di Roberto che implementa Parsing Expression Grammers per Lua.


Grazie. A proposito del modulo lpeg che hai citato: salva le posizioni dei token nel testo originale dopo la tokenizzazione? Il compito che devo eseguire è evidenziare la sintassi di un linguaggio semplice specifico in scite tramite lua (senza parser c ++ compilato). Inoltre, come installare lpeg? Sembra che abbia il sorgente .c nella distribuzione: deve essere compilato insieme a lua?
grigoryvp

La compilazione di lpeg produrrà una DLL (o .so) che dovrebbe essere archiviata dove require può trovarla. (cioè da qualche parte identificato dal contenuto del pacchetto globale.cpath nella tua installazione di lua.) Devi anche installare il suo modulo complementare re.lua se vuoi usare la sua sintassi semplificata. Da una grammatica lpeg, è possibile ottenere callback e acquisire testo in diversi modi, ed è certamente possibile utilizzare acquisizioni per memorizzare semplicemente la posizione della corrispondenza per un uso successivo. Se l'obiettivo è l'evidenziazione della sintassi, un PEG non è una cattiva scelta di strumento.
RBerteig

3
Per non parlare delle ultime versioni di SciTE (dalla 2.22) includono Scintillua, un lexer basato su LPEG, il che significa che può funzionare immediatamente, senza necessità di ricompilazione.
Stuart P. Bentley

11

Se stai usando Lua 5, prova:

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

9

A seconda dell'attività da svolgere potrebbe essere più facile da usare string.byte. È anche il modo più veloce perché evita di creare nuova sottostringa che risulta essere piuttosto costosa in Lua grazie all'hashing di ogni nuova stringa e controllando se è già nota. Puoi pre-calcolare il codice dei simboli che cerchi con lo stesso string.byteper mantenere leggibilità e portabilità.

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

Ci sono già molti buoni approcci nelle risposte fornite ( qui , qui e qui ). Se la velocità è ciò che stai cercando principalmente , dovresti assolutamente considerare di eseguire il lavoro tramite l'API C di Lua, che è molte volte più veloce del codice Lua grezzo. Quando si lavora con blocchi precaricati (es. Funzione di caricamento ), la differenza non è così grande, ma comunque considerevole.

Per quanto riguarda le soluzioni Lua pure , lasciatemi condividere questo piccolo benchmark che ho realizzato. Copre ogni risposta fornita a questa data e aggiunge alcune ottimizzazioni. Tuttavia, la cosa fondamentale da considerare è:

Quante volte dovrai ripetere i caratteri nella stringa?

  • Se la risposta è "una volta", dovresti cercare la prima parte del contrassegno ("velocità grezza").
  • Altrimenti, la seconda parte fornirà una stima più precisa, perché analizza la stringa nella tabella, che è molto più veloce da iterare. Dovresti anche considerare di scrivere una semplice funzione per questo, come suggerito da @Jarriz.

Ecco il codice 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))

Output di esempio (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

Risultato:

Nel mio caso, string.bytee string.subsono stati i più veloci in termini di velocità grezza. Quando si utilizza la tabella della cache e la si riutilizza 10 volte per ciclo, la string.byteversione è risultata più veloce anche durante la riconversione dei charcodes in char (il che non è sempre necessario e dipende dall'utilizzo).

Come probabilmente avrai notato, ho fatto alcune ipotesi basate sui miei precedenti benchmark e le ho applicate al codice:

  1. Le funzioni di libreria dovrebbero essere sempre localizzate se usate all'interno di loop, perché è molto più veloce.
  2. Inserimento nuovo elemento nella tabella lua è molto più veloce con tbl[idx] = valuerispetto table.insert(tbl, value).
  3. Il ciclo attraverso la tabella usando for i = 1, #tblè un po 'più veloce di for k, v in pairs(tbl).
  4. Preferisci sempre la versione con meno chiamate di funzione, perché la chiamata stessa aggiunge un po 'al tempo di esecuzione.

Spero che sia d'aiuto.


0

Tutte le persone suggeriscono un metodo meno ottimale

Sarà il migliore:

    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

"Meno ottimale" per quale compito? "Il migliore" per quale compito?
Oleg V. Volkov

0

Iterando per costruire una stringa e restituendo questa stringa come tabella 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)

Tira fuori...

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.