Come iterare i singoli caratteri nella stringa Lua?
-
06-07-2019 - |
Domanda
Ho una stringa in Lua e voglio iterare singoli caratteri al suo interno. 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
Soluzione
In lua 5.1, puoi iterare i caratteri di una stringa in un paio di modi.
Il ciclo di base sarebbe:
for i = 1, #str do local c = str:sub(i,i) -- do something with c end
Ma potrebbe essere più efficiente utilizzare un modello con string.gmatch ()
per ottenere un iteratore sui caratteri:
for c in str:gmatch"." do -- do something with c end
O anche per usare string.gsub ()
per chiamare una funzione per ogni carattere:
str:gsub(".", function(c) -- do something with c end)
In tutto quanto sopra, ho approfittato del fatto che il modulo stringa
è impostato come misurabile per tutti i valori di stringa, quindi le sue funzioni possono essere chiamate come membri usando il #
per ottenere la lunghezza della stringa.
La migliore risposta per la tua applicazione dipende da molti fattori e i benchmark sono i tuoi amici se le prestazioni contano.
Potresti voler valutare perché devi iterare sui personaggi e guardare uno dei moduli di espressione regolare che sono stati associati a Lua, o per un approccio moderno esaminare il < a href = "http://www.inf.puc-rio.br/~roberto/lpeg.html" rel = "noreferrer"> lpeg che implementa Parsing Expression Grammers per Lua.
Altri suggerimenti
Se stai usando Lua 5, prova:
for i = 1, string.len(str) do
print( string.sub(str, i, i) )
end
A seconda dell'attività da svolgere, potrebbe essere più semplice utilizzare string.byte
. È anche il modo più veloce perché evita di creare nuove sottostringhe che a Lua sono abbastanza costose grazie all'hash di ogni nuova stringa e al controllo se è già noto. Puoi pre-calcolare il codice dei simboli che cerchi con lo stesso string.byte
per mantenere la leggibilità e la 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
Esistono già molti buoni approcci nelle risposte fornite ( qui , qui e qui ). Se la velocità è ciò che stai principalmente , dovresti assolutamente considerare di fare il lavoro tramite l'API C di Lua, che è molte volte più veloce del codice Lua grezzo. Quando si lavora con blocchi precaricati (ad es. funzione di caricamento ), la differenza non è così grande, ma comunque considerevole.
Per quanto riguarda le soluzioni pure Lua, vorrei 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 scorrere i caratteri nella stringa?
- Se la risposta è "una volta", allora dovresti cercare la prima parte del contrassegno ("quotazione non elaborata").
- Altrimenti, la seconda parte fornirà una stima più precisa, perché analizza la stringa nella tabella, che è molto più veloce su cui scorrere. 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))
Esempio di output (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.byte
e string.sub
erano i più veloci in termini di velocità raw. Quando si utilizza la tabella della cache e la si riutilizza 10 volte per ciclo, la versione string.byte
è stata la più veloce anche durante la riconversione di charcodes in char (che non è sempre necessario e dipende dall'uso).
Come probabilmente avrai notato, ho fatto alcune ipotesi basate sui miei benchmark precedenti e le ho applicate al codice:
- Le funzioni di libreria dovrebbero essere sempre localizzate se utilizzate all'interno dei loop, perché è molto più veloce.
- L'inserimento di un nuovo elemento nella tabella lua è molto più veloce usando
tbl [idx] = value
rispetto atable.insert (tbl, value)
. - Scorrere la tabella usando
per i = 1, #tbl
è un po 'più veloce diper k, v in coppie (tbl)
. - 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.
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