00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.