Iteratori in Lua
Gli iteratori in Lua sono funzioni che permettono di attraversare gli elementi di una collezione uno alla volta. Lua fornisce un meccanismo elegante basato sul ciclo for generico e sulle closure che rende la creazione di iteratori personalizzati semplice e potente. In questa guida esploreremo gli iteratori built-in e impareremo a crearne di personalizzati.
Cosa Sono gli Iteratori
Un iteratore e una funzione che, ogni volta che viene chiamata, restituisce l’elemento successivo di una collezione. Quando non ci sono piu elementi, restituisce nil per segnalare la fine dell’iterazione.
In Lua, gli iteratori vengono tipicamente usati con il ciclo for generico:
for variabili in funzione_iteratore do
-- corpo del ciclo
end
Iteratori Built-in: ipairs()
La funzione ipairs() e l’iteratore standard per gli array (tabelle con chiavi intere consecutive a partire da 1). Restituisce coppie indice, valore in ordine crescente e si ferma al primo nil.
local frutti = {"mela", "banana", "arancia", "uva"}
for indice, frutto in ipairs(frutti) do
print(indice .. ": " .. frutto)
end
-- Output:
-- 1: mela
-- 2: banana
-- 3: arancia
-- 4: uva
ipairs si ferma al primo “buco” nell’array:
local dati = {10, 20, nil, 40, 50}
for i, v in ipairs(dati) do
print(i, v)
end
-- Output:
-- 1 10
-- 2 20
-- (si ferma qui perche dati[3] e nil)
Iteratori Built-in: pairs()
La funzione pairs() itera su tutte le coppie chiave-valore di una tabella, sia con chiavi numeriche che stringa. L’ordine di iterazione non e garantito.
local persona = {
nome = "Luca",
eta = 25,
citta = "Torino",
[1] = "primo",
[2] = "secondo"
}
for chiave, valore in pairs(persona) do
print(tostring(chiave) .. " = " .. tostring(valore))
end
-- Output (ordine variabile):
-- 1 = primo
-- 2 = secondo
-- nome = Luca
-- eta = 25
-- citta = Torino
Il Ciclo For Generico
Il ciclo for generico in Lua ha la forma:
for var1, var2, ... in funzione_iteratore, stato_invariante, valore_iniziale do
-- corpo
end
Internamente, il ciclo chiama ripetutamente la funzione iteratore passandole lo stato invariante e il valore di controllo corrente. Il ciclo termina quando la prima variabile restituita e nil.
Creare un Iteratore con Closure
Il modo piu semplice per creare un iteratore personalizzato e usare una closure: una funzione che cattura variabili locali dal suo ambiente esterno.
-- Iteratore che restituisce i valori di un array
local function valori(t)
local i = 0
return function()
i = i + 1
return t[i]
end
end
local colori = {"rosso", "verde", "blu"}
for colore in valori(colori) do
print(colore)
end
-- Output:
-- rosso
-- verde
-- blu
La funzione valori restituisce una closure che mantiene il proprio stato (i) e ad ogni chiamata restituisce l’elemento successivo.
Iteratore Fibonacci
Un classico esempio di iteratore con closure e la sequenza di Fibonacci.
local function fibonacci(max)
local a, b = 0, 1
return function()
if a > max then
return nil -- fine dell'iterazione
end
local valore = a
a, b = b, a + b
return valore
end
end
print("Numeri di Fibonacci fino a 100:")
for num in fibonacci(100) do
io.write(num .. " ")
end
print()
-- Output: 0 1 1 2 3 5 8 13 21 34 55 89
Iteratori Stateless (Senza Stato)
Un iteratore stateless non usa closure: tutto lo stato e mantenuto nelle variabili del ciclo for generico. Questo puo essere piu efficiente perche non crea una nuova closure ad ogni iterazione.
-- Iteratore stateless per gli elementi di un array
local function iteratore_stateless(t, i)
i = i + 1
local v = t[i]
if v then
return i, v
end
end
local function mio_ipairs(t)
return iteratore_stateless, t, 0
end
local animali = {"gatto", "cane", "pesce"}
for i, animale in mio_ipairs(animali) do
print(i .. ": " .. animale)
end
-- Output:
-- 1: gatto
-- 2: cane
-- 3: pesce
In questo caso, mio_ipairs restituisce tre valori: la funzione iteratore, lo stato invariante (la tabella) e il valore iniziale di controllo (0).
Iteratore su Intervallo Numerico
Un iteratore che genera numeri in un intervallo con un passo configurabile.
local function intervallo(inizio, fine, passo)
passo = passo or 1
local corrente = inizio - passo
return function()
corrente = corrente + passo
if corrente <= fine then
return corrente
end
end
end
for n in intervallo(1, 10, 2) do
io.write(n .. " ")
end
print()
-- Output: 1 3 5 7 9
for n in intervallo(0, 1, 0.25) do
io.write(n .. " ")
end
print()
-- Output: 0 0.25 0.5 0.75 1.0
Iteratore Filtrato
Un iteratore che avvolge un altro iteratore applicando un filtro.
local function filtra(iteratore, predicato)
return function()
while true do
local valore = iteratore()
if valore == nil then
return nil
end
if predicato(valore) then
return valore
end
end
end
end
-- Creare un iteratore che produce numeri da 1 a 20
local function numeri_fino_a(n)
local i = 0
return function()
i = i + 1
if i <= n then return i end
end
end
-- Filtrare solo i numeri pari
local function e_pari(n)
return n % 2 == 0
end
print("Numeri pari da 1 a 20:")
for n in filtra(numeri_fino_a(20), e_pari) do
io.write(n .. " ")
end
print()
-- Output: 2 4 6 8 10 12 14 16 18 20
Iteratore su Righe di una Stringa
Un esempio pratico che itera sulle righe di una stringa multilinea.
local function righe(testo)
local pos = 1
return function()
if pos > #testo then
return nil
end
local inizio = pos
local fine = testo:find("\n", pos)
if fine then
pos = fine + 1
return testo:sub(inizio, fine - 1)
else
pos = #testo + 1
return testo:sub(inizio)
end
end
end
local poesia = [[Prima riga del testo
Seconda riga del testo
Terza e ultima riga]]
local numero = 0
for riga in righe(poesia) do
numero = numero + 1
print(numero .. ": " .. riga)
end
-- Output:
-- 1: Prima riga del testo
-- 2: Seconda riga del testo
-- 3: Terza e ultima riga
Esempio Pratico: Iteratore su Collezione con Trasformazione
-- Iteratore che applica una trasformazione (map)
local function mappa(t, trasforma)
local i = 0
return function()
i = i + 1
if t[i] then
return i, trasforma(t[i])
end
end
end
local prezzi = {10.5, 25.0, 8.99, 42.0, 15.75}
-- Applicare IVA del 22%
print("Prezzi con IVA:")
for i, prezzo_iva in mappa(prezzi, function(p) return p * 1.22 end) do
print(string.format(" Prodotto %d: %.2f EUR", i, prezzo_iva))
end
Conclusioni
Gli iteratori sono un concetto centrale in Lua che permette di attraversare collezioni di dati in modo elegante e flessibile. Le funzioni built-in pairs() e ipairs() coprono i casi d’uso piu comuni, mentre le closure consentono di creare iteratori personalizzati per qualsiasi tipo di sequenza. Comprendere la differenza tra iteratori stateful (con closure) e stateless e fondamentale per scrivere codice Lua efficiente e idiomatico.