Как перебирать отдельные символы в строке Lua?
-
06-07-2019 - |
Вопрос
У меня есть строка в Lua, и я хочу перебирать в ней отдельные символы.Но ни один код, который я пробовал, не работает, а официальное руководство показывает только, как найти и заменить подстроки :(
str = "abcd"
for char in str do -- error
print( char )
end
for i = 1, str:len() do
print( str[ i ] ) -- nil
end
Решение
В lua 5.1 вы можете перебирать символы строки this несколькими способами.
Основной цикл будет следующим:
for i = 1, #str do local c = str:sub(i,i) -- do something with c end
Но может быть более эффективно использовать шаблон с string.gmatch ()
, чтобы получить итератор для символов:
for c in str:gmatch"." do -- do something with c end
Или даже использовать string.gsub ()
для вызова функции для каждого символа:
str:gsub(".", function(c) -- do something with c end)
Во всем вышесказанном я воспользовался тем, что модуль string
установлен как метатабельный для всех строковых значений, поэтому его функции можно вызывать как члены, используя :
. Я также использовал (new to 5.1, IIRC) #
, чтобы получить длину строки.
Лучший ответ для вашего приложения зависит от множества факторов, и тесты - ваш друг, если производительность будет иметь значение.
Возможно, вы захотите оценить почему вам нужно перебрать символы и взглянуть на один из модулей регулярных выражений, связанных с Lua, или для современного подхода заглянуть в Роберто < модуль href = "http://www.inf.puc-rio.br/~roberto/lpeg.html" rel = "noreferrer"> lpeg , который реализует грамматики синтаксического анализа для Lua.
Другие советы
Если вы используете Lua 5, попробуйте:
for i = 1, string.len(str) do
print( string.sub(str, i, i) )
end
В зависимости от поставленной задачи может быть проще использовать <код> 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
В предоставленных ответах уже есть много хороших подходов (здесь, здесь и здесь).Если скорость - это то, что ты в первую очередь В поисках вам обязательно следует рассмотреть возможность выполнения этой работы через C API Lua, который во много раз быстрее, чем необработанный код 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
версия была самой быстрой даже при преобразовании кодов обратно в символы (что не всегда необходимо и зависит от использования).
Как вы, наверное, заметили, я сделал некоторые предположения на основе своих предыдущих тестов и применил их к коду:
- Библиотечные функции всегда должны быть локализованы, если они используются внутри циклов, потому что это намного быстрее.
- Вставка нового элемента в таблицу Lua происходит намного быстрее, используя
tbl[idx] = value
чемtable.insert(tbl, value)
. - Цикл по таблице с использованием
for i = 1, #tbl
немного быстрее, чемfor k, v in pairs(tbl)
. - Всегда отдавайте предпочтение версии с меньшим количеством вызовов функций, потому что сам вызов немного увеличивает время выполнения.
Надеюсь, поможет.
Все люди предлагают менее оптимальный метод
Будет лучше:
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