จะวนซ้ำอักขระแต่ละตัวในสตริง Lua ได้อย่างไร


93

ฉันมีสตริงใน Lua และต้องการย้ำอักขระแต่ละตัวในนั้น แต่ไม่มีรหัสที่ฉันพยายามใช้งานได้และคู่มืออย่างเป็นทางการแสดงวิธีค้นหาและแทนที่สตริงย่อยเท่านั้น :(

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

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

คำตอบ:


131

ใน lua 5.1 คุณสามารถวนซ้ำอักขระของสตริงได้สองวิธี

ลูปพื้นฐานจะเป็น:

สำหรับ i = 1, #str do
    ท้องถิ่น c = str: ย่อย (i, i)
    - ทำอะไรกับค
จบ

แต่อาจมีประสิทธิภาพมากกว่าในการใช้รูปแบบstring.gmatch()เพื่อให้ตัววนซ้ำทับอักขระ:

สำหรับ c ใน str: gmatch "." ทำ
    - ทำอะไรกับค
จบ

หรือแม้กระทั่งใช้string.gsub()เพื่อเรียกใช้ฟังก์ชันสำหรับแต่ละตัวอักษร:

str: gsub (".", ฟังก์ชัน (c)
    - ทำอะไรกับค
จบ)

จากทั้งหมดข้างต้นฉันได้ใช้ประโยชน์จากข้อเท็จจริงที่ว่าstringโมดูลถูกตั้งค่าเป็น metatable สำหรับค่าสตริงทั้งหมดดังนั้นฟังก์ชันของมันจึงสามารถเรียกได้ว่าเป็นสมาชิกโดยใช้:สัญกรณ์ ฉันยังใช้ (ใหม่เป็น 5.1, IIRC) #เพื่อรับความยาวสตริง

คำตอบที่ดีที่สุดสำหรับแอปพลิเคชันของคุณขึ้นอยู่กับหลายปัจจัยและเกณฑ์มาตรฐานคือเพื่อนของคุณหากประสิทธิภาพเป็นสิ่งสำคัญ

คุณอาจต้องการประเมินว่าเหตุใดคุณจึงต้องวนซ้ำบนอักขระและดูหนึ่งในโมดูลนิพจน์ทั่วไปที่เชื่อมโยงกับ Lua หรือสำหรับแนวทางสมัยใหม่ให้ดูโมดูลlpegของ Roberto ซึ่งใช้ Parsing Expression Grammers สำหรับ Lua


ขอบคุณ. เกี่ยวกับโมดูล lpeg ที่คุณพูดถึง - มันบันทึกตำแหน่งโทเค็นในข้อความต้นฉบับหลังจากโทเค็นหรือไม่? งานที่ฉันต้องทำคือการเน้นไวยากรณ์เฉพาะภาษาง่ายๆใน scite ผ่าน lua (โดยไม่มีตัวแยกวิเคราะห์ c ++ ที่คอมไพล์แล้ว) วิธีการติดตั้ง lpeg? ดูเหมือนว่าจะมีแหล่งที่มา. c ในการแจกจ่าย - จำเป็นต้องรวบรวมควบคู่ไปกับ lua หรือไม่?
grigoryvp

การสร้าง lpeg จะสร้าง DLL (หรือ. so) ที่ควรเก็บไว้ในที่ที่ต้องการสามารถค้นหาได้ (เช่นบางที่ระบุโดยเนื้อหาของ global package.cpath ในการติดตั้ง lua ของคุณ) คุณต้องติดตั้ง re.lua โมดูลที่แสดงร่วมด้วยหากคุณต้องการใช้ไวยากรณ์ที่เรียบง่าย จากไวยากรณ์ lpeg คุณสามารถเรียกกลับและจับข้อความได้หลายวิธีและเป็นไปได้อย่างแน่นอนที่จะใช้การจับภาพเพื่อจัดเก็บตำแหน่งของการจับคู่เพื่อใช้ในภายหลัง หากเน้นไวยากรณ์เป็นเป้าหมาย PEG ก็ไม่ใช่ตัวเลือกเครื่องมือที่ไม่ดี
RBerteig

3
ไม่ต้องพูดถึงSciTE รุ่นล่าสุด (ตั้งแต่ 2.22) ได้แก่ Scintillua ซึ่งเป็นคำศัพท์ที่ใช้ LPEG ซึ่งหมายความว่าสามารถทำงานได้ทันทีโดยไม่ต้องมีการรวบรวมซ้ำ
Stuart P. Bentley


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

7

แล้วมีจำนวนมากที่ดีแนวทางในคำตอบให้ ( ที่นี่ , ที่นี่และที่นี่ ) หากความเร็วคือสิ่งที่คุณมองหาเป็นหลักคุณควรพิจารณาทำงานผ่าน 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เวอร์ชันนี้เร็วที่สุดแม้ว่าจะแปลงชาร์โค้ดกลับไปเป็นตัวอักษร (ซึ่งไม่จำเป็นเสมอไปและขึ้นอยู่กับการใช้งาน)

อย่างที่คุณสังเกตเห็นฉันได้ตั้งสมมติฐานตามเกณฑ์มาตรฐานก่อนหน้าของฉันและนำไปใช้กับโค้ด:

  1. ฟังก์ชันไลบรารีควรแปลเป็นภาษาท้องถิ่นเสมอหากใช้ภายในลูปเพราะเร็วกว่ามาก
  2. การใส่องค์ประกอบใหม่ลงในตารางหลัวรวดเร็วยิ่งขึ้นโดยใช้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

"น้อยที่สุด" สำหรับงานอะไร? "ดีที่สุด" สำหรับงานอะไร?
Oleg V. Volkov

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.