web-dev-qa-db-ja.com

Lua文字列の個々の文字を反復する方法は?

Luaに文字列があり、その中の個々の文字を繰り返したい。しかし、私が試したコードは機能せず、公式マニュアルには部分文字列を見つけて置き換える方法のみが示されています:(

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

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

Lua 5.1では、文字列thisの文字をいくつかの方法で繰り返すことができます。

基本的なループは次のとおりです。

 i = 1の場合、#str do 
 local c = str:sub(i、i)
-c 
 end 

ただし、string.gmatch()でパターンを使用して、文字の反復子を取得する方が効率的です。

 str:gmatchのcの場合。 "" do 
-c 
 end 
で何かをします

または、string.gsub()を使用して各文字の関数を呼び出すこともできます。

 str:gsub( "。"、function(c)
-c 
 end)
で何かをする

上記のすべてにおいて、stringモジュールがすべての文字列値のメタテーブルとして設定されているという事実を利用したため、:表記を使用してその関数をメンバーとして呼び出すことができます。文字列の長さを取得するために(5.1の新機能、IIRC)#も使用しました。

アプリケーションの最適な答えは多くの要因に依存します。パフォーマンスが重要になる場合は、ベンチマークが役立ちます。

whyを評価し、Luaにバインドされている正規表現モジュールの1つを調べる必要がある場合、または現代的なアプローチについては、ロベルトの lpeg モジュールを調べて、Lua用の構文解析文法を実装します。

116
RBerteig

Lua 5を使用している場合:

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

手元のタスクによっては、 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
Oleg V. Volkov

提供された回答には、すでに多くの優れたアプローチがあります( herehere 、および here )。速度が主なものを求めている場合、LuaのC APIを使用してジョブを実行することを検討する必要があります。プリロードされたチャンク(たとえば load function )を使用する場合、違いはそれほど大きくありませんが、それでもかなりの違いがあります。

pureLuaソリューションについては、この小さなベンチマークを共有させていただきました。この日付に対して提供されたすべての回答をカバーし、いくつかの最適化を追加します。それでも、考慮すべき基本的なことは次のとおりです。

文字列の文字を何回繰り返す必要があるか?

  • 答えが「1回」の場合は、ベンチマークの最初の部分(「生の速度」)を調べる必要があります。
  • それ以外の場合、2番目の部分は文字列をテーブルに解析し、反復処理がはるかに高速になるため、より正確な推定が提供されます。また、@ 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テーブルへの新しい要素の挿入は、table.insert(tbl, value)よりも_tbl[idx] = value_を使用した方がはるかに高速です。
  3. _for i = 1, #tbl_を使用したテーブルのループは、for k, v in pairs(tbl)よりも少し高速です。
  4. 呼び出し自体が実行時間を少し増やすため、関数呼び出しの少ないバージョンを常に優先してください。

それが役に立てば幸い。

3
Electrix

すべての人は、最適ではない方法を提案します

最高になります:

    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
Jarriz