Reiterador
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