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