2007-01-04

Reiterador

Lua Uma coisa fácil de fazer em Python é um reiterador (para quem gosta de estrangeirismos: «iterador», de iterator). Basta um simples comando yield e está tudo resolvido.

Estava pensando comigo mesmo: e em Lua?

Então resolvi fazer um objeto que retorne indefinidamente e de forma aleatória os elementos de um vetor, mas sem repetir até que todos tenham sido retornados. Em Python a gente resolve isso com uma função:

from random import choice

def rand_new(*args):
assert len(args) > 0
t1 = list(args)
t2 = []
while True:
e = choice(t1)
t1.remove(e)
t2.append(e)
if len(t1) == 0:
t1, t2 = t2, []
yield e


Para resolver este problema (pretensão =P) em Lua, decidi usar uma metatabela.

Vamos então criar a metatabela:

local iter = { sec = {} }
iter.__index = iter
iter.__newindex = function (t, k, v)
error("Chave " .. k .. " não encontrada")
end


Pronto. A chave sec vai funcionar como a lista t2 do código Python. A o próprio objeto (a parte indexada) vai funcionar como a lista t1.

Vamos agora criar o construtor:

function iter:new(o)
o = o or {}
setmetatable(o, self)
return o
end


Como vamos colocar isso dentro de um módulo, vamos criar uma função para retornar o objeto:

function new(t)
if type(t) ~= "table" then
error "Esta função recebe um vetor como parâmetro"
end

if table.getn(t) == 0 then
error "Vetor vazio"
end

return iter:new(t)
end


Vamos criar uma função para reiniciar o contador interno, ou seja, reabilitar todos os elementos do vetor:

function iter:reinit()
while table.getn(self.sec) > 0 do
table.insert(self, table.remove(self.sec, 1))
end
end


Agora vamos criar o next() (como no reiterador de Python):

function iter:next()
local m, resp
m = table.getn(self)

-- Esvaziou? Recomeça do princípio...
if m == 0 then
self:reinit()
m = table.getn(self)
end

resp = table.remove(self, math.random(m))
table.insert(self.sec, resp)

return resp
end


Primeiro determinamos quantos elementos ainda estão no vetor principal (se não tiver nenhum, reinicia). Escolhemos um aleatoriamente, removemos, inserimos no vetor secundário e retornamos o elemento.

E é só isso!

Agora vamos transformar esta festa toda em um módulo!

Se você está usando Lua 5.1 ou possui o Compat-5.1, basta no início do arquivo transformar em locais todas as funções globais que estamos usando e em seguida declarar o módulo:

local error = error
local math = math
local table = table
local setmetatable = setmetatable
local type = type

module "rand"


Se você está usando Lua 5.0 e não possui o módulo Compat-5.1, talvez isto não funcione, então, em vez disso, transforme a função new() em local e no final do arquivo declare a seguinte tabela:

rand = { new = new }


Vamos testar agora:

lua> require "rand"
lua> a = rand.new { 1, 2, 3 }
lua> =a:next()
3
lua> =a:next()
1
lua> =a:next()
2
lua> =a:next()
2
lua> =a:next()
1
lua> =a:next()
3
lua> =a:next()
1


[]'s

3 comentários:

  1. Aviso aos navegantes!!!

    Ontem escrevi os códigos, testei e escrevi o artigo, tudo em menos de meia hora.

    Então, na hora de copiar os códigos pro artigo, dei algumas moscadas (copy no lugar de list, then no lugar de do...).

    Estou corrigindo conforme vou encontrando (ou melhor, conforme o Walter, que está revisando, vai encontro). =P

    []'s

    PS: Valeu Walter!

    ResponderExcluir
  2. O Walter sugeriu o mesmo argumento usando corrotina (um dos recursos de Lua, assim como metatabela), então segue o código:

    local coroutine = coroutine
    local error = error
    local math = math
    local table = table
    local type = type

    module "rand"

    function new(t)
        if type(t) ~= "table" then
            error "Esta função recebe um vetor"
        end
        if table.getn(t) == 0 then
            error "Vetor vazio"
        end
        co = coroutine.create(function (t1)
            local m, e, t2
            t2 = {}
            coroutine.yield(nil)
            while true do
                m = table.getn(t1)
                
                if m == 0 then
                    while table.getn(t2) > 0 do
                        table.insert(t1, table.remove(t2, 1))
                    end
                    m = table.getn(t1)
                end
                
                e = table.remove(t1, math.random(m))
                table.insert(t2, e)
                coroutine.yield(e)
            end
        end)
        
        coroutine.resume(co, t)
        return co
    end

    Agora, em vez de a:next(), deverá ser usado coroutine.resume(a).

    []'s

    ResponderExcluir
  3. Gente,

    Houve um comentário anônimo de um programador que está tendo alguma dificuldade com a linguagem. Ele pediu para quem puder ajudar, adicioná-lo no MSN: kin88_@hotmail.com

    Apesar de ter deixado um endereço de email e MSN, ele não se identificou de qualquer outra forma (nome ou página), e avisei que não permitirei comentários anônimos que não sejam assinados (ou seja, anônimos de verdade, no sentido literal da palavra).

    É uma regra que criei e não posso abrir excessões assim.

    Desculpe amigo. Da próxima, por favor assine o comentário.

    []'s

    ResponderExcluir