Jak iterować poszczególne znaki w łańcuchu Lua?


88

Mam ciąg w Lua i chcę iterować w nim poszczególne znaki. Ale żaden kod, który próbowałem, nie działa, a oficjalna instrukcja pokazuje tylko, jak znaleźć i zamienić podciągi :(

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

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

Odpowiedzi:


125

W lua 5.1 możesz iterować znaki łańcucha na kilka sposobów.

Podstawowa pętla wyglądałaby tak:

dla i = 1, #str do
    lokalny c = str: sub (i, i)
    - zrób coś z c
koniec

Ale wydajniejsze może być użycie wzorca z, string.gmatch()aby uzyskać iterator po znakach:

dla c in str: gmatch "." zrobić
    - zrób coś z c
koniec

Lub nawet użyć string.gsub()do wywołania funkcji dla każdego znaku:

str: gsub (".", funkcja (c)
    - zrób coś z c
koniec)

We wszystkich powyższych przypadkach wykorzystałem fakt, że stringmoduł jest ustawiony jako metatable dla wszystkich wartości łańcuchowych, więc jego funkcje można wywoływać jako elementy :składowe przy użyciu notacji. Użyłem również (nowość w 5.1, IIRC), #aby uzyskać długość struny.

Najlepsza odpowiedź dla Twojej aplikacji zależy od wielu czynników, a testy porównawcze są Twoim przyjacielem, jeśli wydajność ma mieć znaczenie.

Możesz chcieć ocenić, dlaczego musisz iterować po znakach i przyjrzeć się jednemu z modułów wyrażeń regularnych, które zostały powiązane z Lua, lub przyjrzeć się nowemu podejściu do modułu lpeg Roberta , który implementuje Parsing Expression Grammers for Lua.


Dzięki. O module lpeg, o którym wspomniałeś - czy zapisuje on pozycje tokenów w oryginalnym tekście po tokenizacji? Zadanie, które muszę wykonać, to podświetlić składnię konkretnego prostego języka w scite za pośrednictwem lua (bez skompilowanego parsera C ++). Ponadto, jak zainstalować lpeg? Wygląda na to, że w dystrybucji znajduje się źródło .c - czy musi być kompilowane razem z lua?
grigoryvp

Budowanie lpeg spowoduje powstanie biblioteki DLL (lub .so), która powinna być przechowywana tam, gdzie require może ją znaleźć. (tj. gdzieś zidentyfikowane przez zawartość global package.cpath w twojej instalacji lua.) Musisz także zainstalować jego moduł towarzyszący re.lua, jeśli chcesz używać jego uproszczonej składni. Z gramatyki lpeg można uzyskać wywołania zwrotne i przechwycić tekst na wiele sposobów, a na pewno można użyć przechwytywania, aby po prostu zapisać lokalizację dopasowania do późniejszego wykorzystania. Jeśli celem jest podświetlanie składni, to PEG nie jest złym wyborem narzędzia.
RBerteig

3
Nie wspominając o najnowszych wydaniach SciTE (od 2.22), w tym Scintillua, lekser oparty na LPEG, co oznacza, że ​​może działać od razu po wyjęciu z pudełka, bez konieczności ponownej kompilacji.
Stuart P. Bentley

11

Jeśli używasz Lua 5, spróbuj:

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

9

W zależności od wykonywanego zadania może być łatwiejszy w użyciu string.byte. Jest to również najszybszy sposób, ponieważ pozwala uniknąć tworzenia nowego podciągu, który zdarza się być dość drogi w Lua, dzięki haszowaniu każdego nowego ciągu i sprawdzaniu, czy jest już znany. Możesz wstępnie obliczyć kod symboli, których szukasz, string.byteaby zachować czytelność i przenośność.

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

W udzielonych odpowiedziach ( tutaj , tutaj i tutaj ) jest już wiele dobrych podejść . Jeśli przede wszystkim szukasz szybkości , zdecydowanie powinieneś rozważyć wykonanie tej pracy za pośrednictwem interfejsu API Lua C, który jest wielokrotnie szybszy niż surowy kod Lua. Podczas pracy z wstępnie załadowanymi fragmentami (np. Funkcja ładowania ) różnica nie jest tak duża, ale nadal znaczna.

Jeśli chodzi o czyste rozwiązania Lua, podzielę się tym małym testem, który stworzyłem. Obejmuje każdą udzieloną odpowiedź do tej daty i dodaje kilka optymalizacji. Jednak podstawową rzeczą do rozważenia jest:

Ile razy będziesz potrzebować iterować po znakach w ciągu?

  • Jeśli odpowiedź brzmi „raz”, to powinieneś sprawdzić pierwszą część znaku banchmark („surowa prędkość”).
  • W przeciwnym razie druga część zapewni dokładniejsze oszacowanie, ponieważ analizuje łańcuch w tabeli, co jest znacznie szybsze do iteracji. Powinieneś także rozważyć napisanie prostej funkcji do tego, jak zasugerował @Jarriz.

Oto pełny kod:

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

Przykładowe dane wyjściowe (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

Wynik:

W moim przypadku string.bytei string.subbyły najszybsze pod względem szybkości surowej. Podczas korzystania z tabeli pamięci podręcznej i ponownego jej używania 10 razy na pętlę, string.bytewersja była najszybsza nawet przy konwersji kodów znaków z powrotem na znaki (co nie zawsze jest konieczne i zależy od użycia).

Jak zapewne zauważyłeś, poczyniłem pewne założenia w oparciu o moje poprzednie testy porównawcze i zastosowałem je do kodu:

  1. Funkcje biblioteczne powinny być zawsze zlokalizowane, jeśli są używane w pętlach, ponieważ jest o wiele szybszy.
  2. Wstawianie nowego elementu do tabeli lua jest znacznie szybsze przy użyciu tbl[idx] = valueniż table.insert(tbl, value).
  3. Zapętlanie za pomocą tabeli for i = 1, #tbljest nieco szybsze niż for k, v in pairs(tbl).
  4. Zawsze preferuj wersję z mniejszą liczbą wywołań funkcji, ponieważ samo wywołanie nieco wydłuża czas wykonywania.

Mam nadzieję, że to pomoże.


0

Wszyscy sugerują mniej optymalną metodę

Najlepiej będzie:

    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

„Mniej optymalne” do jakiego zadania? „Najlepsze” do jakiego zadania?
Oleg V. Volkov

0

Iterowanie w celu skonstruowania łańcucha i zwrócenie tego ciągu jako tabeli za pomocą funkcji 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)

Wystawia ...

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.