Как перебирать отдельные символы в строке Lua?

88

У меня есть строка в Lua, и я хочу перебирать в ней отдельные символы. Но ни один код, который я пробовал, не работает, а официальное руководство показывает только, как найти и заменить подстроки :(

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

for i = 1, str:len() do
  print( str[ i ] ) -- nil
end
Григорьевп
источник

Ответы:

125

В lua 5.1 вы можете перебирать символы строки this двумя способами.

Базовый цикл будет:

для i = 1, #str do
    местный c = str: sub (i, i)
    - сделать что-нибудь с c
конец

Но может быть более эффективным использовать шаблон с, string.gmatch()чтобы получить итератор по символам:

для c в str: gmatch "." делать
    - сделать что-нибудь с c
конец

Или даже использовать string.gsub()для вызова функции для каждого символа:

str: gsub (".", функция (c)
    - сделать что-нибудь с c
конец)

Во всем вышеперечисленном я воспользовался тем фактом, что stringмодуль установлен как метатаблица для всех строковых значений, поэтому его функции можно вызывать как члены с использованием :нотации. Я также использовал (новое для 5.1, IIRC), #чтобы получить длину строки.

Лучший ответ для вашего приложения зависит от множества факторов, и тесты производительности - ваш друг, если производительность имеет значение.

Возможно, вы захотите оценить, почему вам нужно перебирать символы, и взглянуть на один из модулей регулярных выражений, привязанных к Lua, или, чтобы узнать о современном подходе, загляните в модуль lpeg Роберто, который реализует грамматики синтаксического анализа для Lua.

RBerteig
источник
Спасибо. О модуле lpeg, о котором вы упомянули - сохраняет ли он позиции токенов в исходном тексте после токенизации? Задача, которую мне нужно выполнить, - выделить синтаксис конкретного простого языка в scite через lua (без скомпилированного парсера C ++). Также как установить lpeg? Похоже, в его дистрибутиве есть исходный код .c - нужно ли его компилировать вместе с lua?
grigoryvp 07
Сборка lpeg создаст DLL (или .so), которая должна храниться там, где require может ее найти. (т.е. где-то, идентифицированное содержимым глобального package.cpath в вашей установке lua.) Вам также необходимо установить его сопутствующий модуль re.lua, если вы хотите использовать его упрощенный синтаксис. Из грамматики lpeg вы можете получать обратные вызовы и захватывать текст несколькими способами, и, безусловно, можно использовать захваты, чтобы просто сохранить местоположение совпадения для последующего использования. Если целью является выделение синтаксиса, тогда PEG - неплохой выбор.
RBerteig
3
Не говоря уже о последних выпусках SciTE (начиная с 2.22), включая Scintillua, лексер на основе LPEG, что означает, что он может работать прямо из коробки, повторная компиляция не требуется.
Стюарт П. Бентли
11

Если вы используете Lua 5, попробуйте:

for i = 1, string.len(str) do
    print( string.sub(str, i, i) )
end
Аарон Саарела
источник
9

В зависимости от поставленной задачи им может быть проще пользоваться string.byte. Это также самый быстрый способ, поскольку он позволяет избежать создания новой подстроки, которая оказывается довольно дорогой в Lua благодаря хешированию каждой новой строки и проверке, известна ли она уже. Вы можете предварительно рассчитать код символов, которые вы ищете, для string.byteобеспечения удобочитаемости и переносимости.

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

В предоставленных ответах ( здесь , здесь и здесь ) уже есть много хороших подходов . Если скорость - это то, что вы в первую очередь ищете, вам определенно следует рассмотреть возможность выполнения этой работы через Lua C API, который во много раз быстрее, чем необработанный код Lua. При работе с предварительно загруженными фрагментами (например, функция загрузки ) разница не такая большая, но все же значительная.

Что касается чистого решений Lua, позвольте мне поделиться этим небольшим тестом, который я сделал. Он охватывает все предоставленные ответы на эту дату и добавляет несколько оптимизаций. Тем не менее, главное, что нужно учитывать:

Сколько раз вам нужно будет перебирать символы в строке?

  • Если ответ «один раз», то вам следует найти первую часть метки («сырая скорость»).
  • В противном случае вторая часть предоставит более точную оценку, потому что она анализирует строку в таблице, что намного быстрее для перебора. Вам также следует подумать о написании простой функции для этого, например, @Jarriz.

Вот полный код:

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

Пример вывода (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

Результат:

В моем случае string.byteи string.subбыли самыми быстрыми с точки зрения чистой скорости. При использовании таблицы кеширования и повторном ее использовании 10 раз за циклstring.byte версия была самой быстрой даже при преобразовании кодов обратно в символы (что не всегда необходимо и зависит от использования).

Как вы, наверное, заметили, я сделал некоторые предположения на основе своих предыдущих тестов и применил их к коду:

  1. Библиотечные функции всегда должны быть локализованы, если они используются внутри циклов, потому что это намного быстрее.
  2. Вставка нового элемента в таблицу lua выполняется намного быстрее, tbl[idx] = valueчем при использовании table.insert(tbl, value).
  3. Цикл по таблице с использованием for i = 1, #tblнемного быстрее, чем for k, v in pairs(tbl).
  4. Всегда предпочитайте версию с меньшим количеством вызовов функций, потому что сам вызов немного увеличивает время выполнения.

Надеюсь, это поможет.

Электрикс
источник
0

Все люди предлагают менее оптимальный метод

Лучше всего будет:

    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
Джаррис
источник
«Менее оптимально» для какой задачи? «Лучший» для какой задачи?
Олег В. Волков
0

Итерация для создания строки и возврат этой строки в виде таблицы с 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)

Вылезает ...

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
Koyaanisqatsi
источник