Comment itérer des caractères individuels dans la chaîne Lua?

87

J'ai une chaîne dans Lua et je souhaite y itérer des caractères individuels. Mais aucun code que j'ai essayé ne fonctionne et le manuel officiel montre seulement comment trouver et remplacer des sous-chaînes :(

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

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

Réponses:

123

Dans lua 5.1, vous pouvez itérer les caractères d'une chaîne de plusieurs manières.

La boucle de base serait:

pour i = 1, #str faire
    local c = str: sous (i, i)
    - faire quelque chose avec c
fin

Mais il peut être plus efficace d'utiliser un modèle avec string.gmatch()pour obtenir un itérateur sur les caractères:

pour c dans str: gmatch "." faire
    - faire quelque chose avec c
fin

Ou même à utiliser string.gsub()pour appeler une fonction pour chaque caractère:

str: gsub (".", fonction (c)
    - faire quelque chose avec c
fin)

Dans tout ce qui précède, j'ai profité du fait que le stringmodule est défini comme métatable pour toutes les valeurs de chaîne, de sorte que ses fonctions peuvent être appelées en tant que membres en utilisant la :notation. J'ai également utilisé le (nouveau à 5.1, IIRC) #pour obtenir la longueur de la chaîne.

La meilleure réponse pour votre application dépend de nombreux facteurs, et les points de repère sont vos amis si les performances comptent.

Vous voudrez peut-être évaluer pourquoi vous devez itérer sur les caractères et examiner l'un des modules d'expressions régulières qui ont été liés à Lua, ou pour une approche moderne, regardez le module lpeg de Roberto qui implémente Parsing Expression Grammers pour Lua.

RBerteig
la source
Merci. À propos du module lpeg que vous avez mentionné - enregistre-t-il les positions des jetons dans le texte d'origine après la tokenisation? La tâche que je dois effectuer est de mettre en évidence la syntaxe d'un langage simple spécifique dans scite via lua (sans parseur C ++ compilé). Aussi, comment installer lpeg? Il semble qu'il a une source .c dans la distribution - doit-il être compilé avec lua?
grigoryvp
Construire lpeg produira une DLL (ou .so) qui devrait être stockée là où require peut la trouver. (c'est-à-dire quelque part identifié par le contenu du package global.cpath dans votre installation lua.) Vous devez également installer son module compagnon re.lua si vous souhaitez utiliser sa syntaxe simplifiée. À partir d'une grammaire lpeg, vous pouvez obtenir des rappels et capturer du texte de différentes manières, et il est certainement possible d'utiliser des captures pour simplement stocker l'emplacement de la correspondance pour une utilisation ultérieure. Si la mise en évidence de la syntaxe est l'objectif, alors un PEG n'est pas un mauvais choix d'outil.
RBerteig
3
Sans oublier que les dernières versions de SciTE (depuis 2.22) incluent Scintillua, un lexer basé sur LPEG, ce qui signifie qu'il peut fonctionner dès la sortie de la boîte, aucune recompilation requise.
Stuart P. Bentley
11

Si vous utilisez Lua 5, essayez:

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

Selon la tâche à accomplir, il peut être plus facile à utiliser string.byte. C'est aussi le moyen le plus rapide car cela évite de créer une nouvelle sous-chaîne qui s'avère être assez coûteuse en Lua grâce au hachage de chaque nouvelle chaîne et à la vérification si elle est déjà connue. Vous pouvez pré-calculer le code des symboles que vous recherchez avec le même string.bytepour maintenir la lisibilité et la portabilité.

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
Oleg V. Volkov
la source
4

Il y a déjà beaucoup de bonnes approches dans les réponses fournies ( ici , ici et ici ). Si la vitesse est ce que vous recherchez principalement , vous devriez certainement envisager de faire le travail via l'API C de Lua, qui est plusieurs fois plus rapide que le code Lua brut. Lorsque vous travaillez avec des blocs préchargés (par exemple, fonction de chargement ), la différence n'est pas si grande, mais tout de même considérable.

En ce qui concerne les solutions pures Lua, permettez-moi de partager cette petite référence que j'ai faite. Il couvre toutes les réponses fournies à cette date et ajoute quelques optimisations. Pourtant, la chose de base à considérer est:

Combien de fois devrez-vous parcourir les caractères de la chaîne?

  • Si la réponse est "une fois", vous devriez rechercher la première partie de la marque de commerce ("vitesse brute").
  • Sinon, la deuxième partie fournira une estimation plus précise, car elle analyse la chaîne dans la table, qui est beaucoup plus rapide à parcourir. Vous devriez également envisager d'écrire une fonction simple pour cela, comme @Jarriz l'a suggéré.

Voici le code complet:

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

Exemple de sortie (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

Résultat:

Dans mon cas, le string.byte et string.subétaient les plus rapides en termes de vitesse brute. Lors de l'utilisation de la table de cache et de sa réutilisation 10 fois par boucle, lestring.byte version était la plus rapide même lors de la conversion des charcodes en chars (ce qui n'est pas toujours nécessaire et dépend de l'utilisation).

Comme vous l'avez probablement remarqué, j'ai fait quelques hypothèses basées sur mes précédents benchmarks et les ai appliquées au code:

  1. Les fonctions de bibliothèque doivent toujours être localisées si elles sont utilisées dans des boucles, car elles sont beaucoup plus rapides.
  2. L'insertion d'un nouvel élément dans la table lua est beaucoup plus rapide en utilisant tbl[idx] = valuequetable.insert(tbl, value) .
  3. Faire une boucle dans la table en utilisant for i = 1, #tblest un peu plus rapide quefor k, v in pairs(tbl) .
  4. Préférez toujours la version avec moins d'appels de fonction, car l'appel lui-même ajoute un peu au temps d'exécution.

J'espère que ça aide.

Electrix
la source
0

Tout le monde suggère une méthode moins optimale

Sera le meilleur:

    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
Jarriz
la source
"Moins optimal" pour quelle tâche? "Meilleur" pour quelle tâche?
Oleg V. Volkov