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

J’ai une chaîne dans Lua et je veux y parcourir 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 

En 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 do
     local c = str: sub (i, i)
     - faire quelque chose avec c
 fin

Mais il peut être plus efficace d’utiliser un motif avec ssortingng.gmatch() pour obtenir un iterator sur les caractères:

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

Ou même d’utiliser ssortingng.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 module de ssortingng est défini comme une 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 pour 5.1, IIRC) # pour obtenir la longueur de la chaîne.

La meilleure réponse à votre application dépend de nombreux facteurs, et les tests de performance sont vos amis si les performances sont importantes.

Vous voudrez peut-être évaluer la raison pour laquelle vous devez parcourir les caractères, et regarder l’un des modules d’expression régulière liés à Lua, ou pour une approche moderne, voir le module lpeg de Roberto qui implémente Parsing Expression Grammers pour Lua.

Si vous utilisez Lua 5, essayez:

 for i = 1, ssortingng.len(str) do print( ssortingng.sub(str, i, i) ) end 

Selon la tâche à accomplir, il peut être plus facile d’utiliser ssortingng.byte . C’est aussi le moyen le plus rapide, car cela évite de créer de nouvelles sous-chaînes qui coûtent assez cher en Lua, grâce au hachage de chaque nouvelle chaîne et en vérifiant si elle est déjà connue. Vous pouvez pré-calculer le code des symboles que vous recherchez avec la même ssortingng.byte pour conserver la lisibilité et la portabilité.

 local str = "ab/cd/ef" local target = ssortingng.byte("/") for idx = 1, #str do if str:byte(idx) == target then print("Target found at:", idx) end end 

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 beaucoup plus rapide que le code Lua brut. Lorsque vous travaillez avec des blocs préchargés (par exemple, la fonction de chargement ), la différence n’est pas si importante, mais elle rest considérable.

En ce qui concerne les solutions pures de Lua, laissez-moi partager ce petit benchmark, j’ai fait. Il couvre toutes les réponses fournies à cette date et ajoute quelques optimisations. Pourtant, la chose fondamentale à considérer est:

Combien de fois devrez-vous itérer sur les caractères en chaîne?

  • Si la réponse est “une fois”, vous devriez rechercher la première partie du repère (“vitesse brute”).
  • Sinon, la seconde partie fournira une estimation plus précise, car elle parsing la chaîne dans la table, ce qui est beaucoup plus rapide à parcourir. Vous devriez également envisager d’écrire une fonction simple pour cela, comme suggéré par @Jarriz.

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 ssortingngbyte, ssortingngchar, ssortingngsub, ssortingnggsub, ssortingnggmatch = ssortingng.byte, ssortingng.char, ssortingng.sub, ssortingng.gsub, ssortingng.gmatch print("-----------------------") print("Raw speed:") print("-----------------------") -- Version 1 - ssortingng.sub in loop x = os.clock() for j = 1, attempts do for i = 1, #str do c = ssortingngsub(str, i) end end elapsed = os.clock() - x print(ssortingng.format("V1: elapsed time: %.3f", elapsed)) -- Version 2 - ssortingng.gmatch loop x = os.clock() for j = 1, attempts do for c in ssortingnggmatch(str, ".") do end end elapsed = os.clock() - x print(ssortingng.format("V2: elapsed time: %.3f", elapsed)) -- Version 3 - ssortingng.gsub callback x = os.clock() for j = 1, attempts do ssortingnggsub(str, ".", function(c) end) end elapsed = os.clock() - x print(ssortingng.format("V3: elapsed time: %.3f", elapsed)) -- For version 4 local str2table = function(str) local ret = {} for i = 1, #str do ret[i] = ssortingngsub(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(ssortingng.format("V4: elapsed time: %.3f", elapsed)) -- Version 5 - ssortingng.byte x = os.clock() for j = 1, attempts do tbl = {ssortingngbyte(str, 1, #str)} -- Note: This is about 15% faster than calling ssortingng.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(ssortingng.format("V5: elapsed time: %.3f", elapsed)) -- Version 5b - ssortingng.byte + conversion back to chars x = os.clock() for j = 1, attempts do tbl = {ssortingngbyte(str, 1, #str)} -- Note: This is about 15% faster than calling ssortingng.byte for every character. for i = 1, #tbl do c = ssortingngchar(tbl[i]) end end elapsed = os.clock() - x print(ssortingng.format("V5b: elapsed time: %.3f", elapsed)) print("-----------------------") print("Creating cache table ("..reuses.." reuses):") print("-----------------------") -- Version 1 - ssortingng.sub in loop x = os.clock() for k = 1, attempts do tbl = {} for i = 1, #str do tbl[i] = ssortingngsub(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(ssortingng.format("V1: elapsed time: %.3f", elapsed)) -- Version 2 - ssortingng.gmatch loop x = os.clock() for k = 1, attempts do tbl = {} local tblc = 1 -- Note: This is faster than table.insert for c in ssortingnggmatch(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(ssortingng.format("V2: elapsed time: %.3f", elapsed)) -- Version 3 - ssortingng.gsub callback x = os.clock() for k = 1, attempts do tbl = {} local tblc = 1 -- Note: This is faster than table.insert ssortingnggsub(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(ssortingng.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(ssortingng.format("V4: elapsed time: %.3f", elapsed)) -- Version 5 - ssortingng.byte to create table x = os.clock() for k = 1, attempts do tbl = {ssortingngbyte(str,1,#str)} for j = 1, reuses do for i = 1, #tbl do c = tbl[i] end end end elapsed = os.clock() - x print(ssortingng.format("V5: elapsed time: %.3f", elapsed)) -- Version 5b - ssortingng.byte to create table + ssortingng.char loop to convert bytes to chars x = os.clock() for k = 1, attempts do tbl = {ssortingngbyte(str, 1, #str)} for i = 1, #tbl do tbl[i] = ssortingngchar(tbl[i]) end for j = 1, reuses do for i = 1, #tbl do c = tbl[i] end end end elapsed = os.clock() - x print(ssortingng.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, ssortingng.byte et ssortingng.sub étaient les plus rapides en termes de vitesse brute. Lors de l’utilisation de la table cache et de sa réutilisation 10 fois par boucle, la version ssortingng.byte était la plus rapide, même lors de la conversion des codes de caractères en caractères (ce qui n’est pas toujours nécessaire et dépend de l’utilisation).

Comme vous l’avez probablement remarqué, j’ai fait des suppositions basées sur mes benchmarks précédents et les ai appliqués au code:

  1. Les fonctions de bibliothèque doivent toujours être localisées si elles sont utilisées à l’intérieur de 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] = value que table.insert(tbl, value) .
  3. Boucler la table en utilisant for i = 1, #tbl est un peu plus rapide que for k, v in pairs(tbl) .
  4. Préférez toujours la version avec moins d’appels de fonctions, car l’appel lui-même ajoute un peu de temps à l’exécution.

J’espère que cela aide.

Toutes les personnes suggèrent une méthode moins optimale

Sera le meilleur:

  function chars(str) strc = {} for i = 1, #str do table.insert(strc, ssortingng.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