Як виконати ітерацію окремих символів у рядку 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

Відповіді:


124

У lua 5.1 ви можете виконати ітерацію символів рядка кількома способами.

Основним циклом буде:

для i = 1, #str зробити
    локальна c = str: sub (i, i)
    - зробити щось із с
кінець

Але може бути ефективніше використовувати шаблон, string.gmatch()щоб отримати ітератор над символами:

для c в str: gmatch "." робити
    - зробити щось із с
кінець

Або навіть використовувати string.gsub()для виклику функції для кожного символу:

str: gsub (".", функція (c)
    - зробити щось із с
кінець)

У всьому вищесказаному я скористався тим, що stringмодуль встановлюється як метатаблиця для всіх рядкових значень, тому його функції можна викликати як члени, використовуючи :позначення. Я також використовував (новий для 5.1, IIRC), #щоб отримати довжину рядка.

Найкраща відповідь для вашої програми залежить від багатьох факторів, і еталони - це ваш друг, якщо продуктивність буде мати значення.

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


Дякую. Про модуль lpeg, про який ви згадали, - чи зберігає він позиції токенів у оригінальному тексті після маркування? Завдання, яке мені потрібно виконати, - це виділити синтаксис конкретної простої мови в scite через lua (без скомпільованого синтаксичного аналізатора c ++). Крім того, як встановити lpeg? Здається, у нього є джерело .c у розповсюдженні - чи потрібно його компілювати разом з lua?
grigoryvp

Створення lpeg створить DLL (або .so), який слід зберігати там, де його можна знайти. (тобто десь ідентифікується вмістом глобального package.cpath у вашій інсталяції lua.) Вам також потрібно встановити його супутній модуль re.lua, якщо ви хочете використовувати його спрощений синтаксис. З граматики lpeg ви можете отримувати зворотні виклики та захоплювати текст різними способами, і, звичайно, можна використовувати захоплення, щоб просто зберегти місце збігу для подальшого використання. Якщо метою є виділення синтаксису, то PEG - це не поганий вибір інструменту.
RBerteig

3
Не кажучи вже про останні випуски SciTE (починаючи з 2.22), включають Scintillua, лексер на основі LPEG, тобто він може працювати зразу, не потрібно повторної компіляції.
Стюарт П. Бентлі,


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

У наданих відповідях вже є багато хороших підходів ( тут , тут і тут ). Якщо швидкість - це те, що ви в першу чергу шукаєте, вам, безумовно, слід подумати про те, щоб виконати роботу за допомогою API Lua C, що в рази швидше, ніж вихідний код 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.