Wie iteriere ich einzelne Zeichen in der Lua-Zeichenfolge?

87

Ich habe eine Zeichenfolge in Lua und möchte einzelne Zeichen darin wiederholen. Aber kein Code, den ich ausprobiert habe, funktioniert und das offizielle Handbuch zeigt nur, wie Teilzeichenfolgen gefunden und ersetzt werden :(

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

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

Antworten:

122

In Lua 5.1 können Sie die Zeichen einer Zeichenfolge auf verschiedene Arten durchlaufen.

Die Grundschleife wäre:

für i = 1 ist #str do
    lokal c = str: sub (i, i)
    - etwas mit c machen
Ende

Es kann jedoch effizienter sein, ein Muster zu verwenden string.gmatch(), um einen Iterator über die Zeichen zu erhalten:

für c in str: gmatch "." machen
    - etwas mit c machen
Ende

Oder sogar, um string.gsub()eine Funktion für jedes Zeichen aufzurufen:

str: gsub (".", Funktion (c)
    - etwas mit c machen
Ende)

In all dem habe ich die Tatsache ausgenutzt, dass das stringModul als Metatable für alle Zeichenfolgenwerte festgelegt ist, sodass seine Funktionen unter Verwendung der :Notation als Mitglieder aufgerufen werden können . Ich habe auch die (neu in 5.1, IIRC) verwendet #, um die Stringlänge zu erhalten.

Die beste Antwort für Ihre Anwendung hängt von vielen Faktoren ab, und Benchmarks sind Ihr Freund, wenn es auf die Leistung ankommt.

Vielleicht möchten Sie bewerten, warum Sie die Zeichen durchlaufen müssen, und sich eines der an Lua gebundenen Module für reguläre Ausdrücke ansehen oder einen modernen Ansatz für das LPEG- Modul von Roberto ansehen, das Parsing Expression Grammers für Lua implementiert.

RBerteig
quelle
Vielen Dank. Über das von Ihnen erwähnte LPEG-Modul: Speichert es nach der Tokenisierung die Token-Positionen im Originaltext? Die Aufgabe, die ich ausführen muss, besteht darin, eine bestimmte einfache Sprache in scite via lua (ohne kompilierten c ++ - Parser) in der Syntax hervorzuheben. Wie installiere ich lpeg? Scheint, als hätte es eine .c-Quelle in der Distribution - muss es zusammen mit lua kompiliert werden?
Grigoryvp
Wenn Sie lpeg erstellen, wird eine DLL (oder .so) erstellt, die dort gespeichert werden soll, wo sie benötigt wird. (dh irgendwo, der durch den Inhalt des globalen package.cpath in Ihrer lua-Installation identifiziert wird.) Sie müssen auch das Begleitmodul re.lua installieren, wenn Sie die vereinfachte Syntax verwenden möchten. Mit einer LPEG-Grammatik können Sie Rückrufe abrufen und Text auf verschiedene Arten erfassen, und es ist sicherlich möglich, Captures zu verwenden, um einfach den Ort der Übereinstimmung für die spätere Verwendung zu speichern. Wenn Syntax-Highlight das Ziel ist, ist ein PEG keine schlechte Wahl.
RBerteig
3
Ganz zu schweigen von den neuesten Versionen von SciTE (seit 2.22), einschließlich Scintillua, einem LPEG-basierten Lexer, was bedeutet, dass es sofort funktioniert, ohne dass eine Neukompilierung erforderlich ist.
Stuart P. Bentley
11

Wenn Sie Lua 5 verwenden, versuchen Sie:

for i = 1, string.len(str) do
    print( string.sub(str, i, i) )
end
Aaron Saarela
quelle
9

Abhängig von der jeweiligen Aufgabe ist die Verwendung möglicherweise einfacher string.byte. Dies ist auch der schnellste Weg, da dadurch vermieden wird, dass neue Teilzeichenfolgen erstellt werden, die in Lua sehr teuer werden, da jede neue Zeichenfolge gehasht und überprüft wird, ob sie bereits bekannt ist. Sie können den Code der gesuchten Symbole vorab berechnen string.byte, um die Lesbarkeit und Portabilität zu gewährleisten.

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
Oleg V. Volkov
quelle
4

In den bereitgestellten Antworten gibt es bereits viele gute Ansätze ( hier , hier und hier ). Wenn Sie in erster Linie nach Geschwindigkeit suchen, sollten Sie auf jeden Fall in Betracht ziehen, die Arbeit über die C-API von Lua zu erledigen, die um ein Vielfaches schneller ist als roher Lua-Code. Bei der Arbeit mit vorinstallierten Chunks (z. B. Ladefunktion ) ist der Unterschied nicht so groß, aber dennoch beträchtlich.

Lassen Sie mich bei den reinen Lua-Lösungen diesen kleinen Benchmark teilen, den ich erstellt habe. Es deckt jede Antwort auf dieses Datum ab und fügt einige Optimierungen hinzu. Das Grundlegende ist jedoch:

Wie oft müssen Sie Zeichen in Zeichenfolgen durchlaufen?

  • Wenn die Antwort "einmal" lautet, sollten Sie den ersten Teil des Banchmarks nachschlagen ("rohe Geschwindigkeit").
  • Andernfalls liefert der zweite Teil eine genauere Schätzung, da die Zeichenfolge in die Tabelle analysiert wird, was viel schneller zu durchlaufen ist. Sie sollten auch in Betracht ziehen, eine einfache Funktion dafür zu schreiben, wie von @Jarriz vorgeschlagen.

Hier ist der vollständige Code:

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

Beispielausgabe (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

Ergebnis:

In meinem Fall waren die string.byteund string.subin Bezug auf die Rohgeschwindigkeit am schnellsten. Bei Verwendung der Cache-Tabelle und deren string.bytezehnmaliger Wiederverwendung pro Schleife war die Version selbst beim Konvertieren von Zeichencodes zurück in Zeichen am schnellsten (was nicht immer erforderlich ist und von der Verwendung abhängt).

Wie Sie wahrscheinlich bemerkt haben, habe ich einige Annahmen getroffen, die auf meinen vorherigen Benchmarks basieren, und sie auf den Code angewendet:

  1. Bibliotheksfunktionen sollten immer lokalisiert werden, wenn sie in Schleifen verwendet werden, da sie viel schneller sind.
  2. Das Einfügen eines neuen Elements in die Lua-Tabelle ist mit viel schneller tbl[idx] = valueals table.insert(tbl, value).
  3. Das Durchlaufen der Tabelle mit for i = 1, #tblist etwas schneller als for k, v in pairs(tbl).
  4. Bevorzugen Sie immer die Version mit weniger Funktionsaufrufen, da der Aufruf selbst die Ausführungszeit etwas verlängert.

Ich hoffe es hilft.

Electrix
quelle
0

Alle Menschen schlagen eine weniger optimale Methode vor

Wird am besten sein:

    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
Jarriz
quelle
"Weniger optimal" für welche Aufgabe? "Best" für welche Aufgabe?
Oleg V. Volkov