Enumerator: a classe que facilita iterações em ruby
A primeira coisa que devem estar se perguntando é: Mas como assim preciso de uma classe pra fazer um for
?!, mas fique calmo, vai dar tudo certo.. espero..
Na implementação da linguagem Ruby, existe sim a palavra reservada (keyword) for
, então sim, ela pode sim ser utilizada para iterar em coleções
de modo similar a outras linguagens de programação:
Reparem o último caracter que aparece na tela é ?
, isso acontece porque a variável c
definida dentro do loop continua definida após a execução do bloco.
No entanto, em linguagens mais modernas, que são mais idiomáticas, existem outras maneiras de iterar em uma coleção, por exemplo, utilizando o each
que é a
principal forma de iteração da linguagem onde muitos dos métodos a chamam internamente:
Neste caso, a variável c
existe apenas dentro do escopo do laço, caso tentássemos acessar seu valor fora do bloco, teríamos um erro: NameError: undefined local variable or method 'c' for main:Object
Mas existem outras formas mais performáticas e interessantes de percorrer um Array
. Através da classe: Enumerator
.
Na verdade não existiria nada de excepcional nessa classe, se não fosse a inclusão do módulo Enumerable
que implementa diversos métodos públicos para facilitar
a vida dos programadores.
map
e collect
Este dois métodos (um alias do outro) são muito utilizados no ruby: com eles é possível que um novo Array
seja retornado contendo o valor retornado por cada iteração do bloco.
Isso significa que com eles conseguimos aplicar um trecho de código em todos os elementos da nossa coleção.
Enumerator
Para fazer buscas, os dois métodos mais usados nessa classe são find_all
e select
onde ambos retornam uma coleção com os dados que atendem ao critério passado dentro do bloco. Vale notar que caso queria apenas um resultado, é recomendado que outros métodos sejam usados tais como o find
por exemplo, que tem sua execução interrompida quando a condição é atingida.
Dentro da classe Array
temos ainda o método bsearch
que na verdade acaba sendo mais performático por utilizar um algoritmo de busca binária em sua implementação:
reduce
e inject
Certo.. mas e se eu quiser acessar o resultado da iteração anterior? Para isso que esses métodos foram feitos! Pois na execução de seu bloco, é possível determinar um valor inicial (que também pode ser passado como parâmetro para os métodos):
Estes são só alguns dos métodos contidos neste módulo, existem vários outros métodos utilitários (i.e. .any?
, .reject
, take
…).
Na dúvida de sintaxe ou de qual método utilizar ou também se quiser saber mais, sempre recomendo visitar a documentação do ruby.
A partir da versão 2.0 do ruby, a funcionalidade Enumerator::Lazy
foi implementada, o que permite a iteração em uma lista infinita acessando somente os elementos desejados.
A magia negra explicação muito bem detalhada de como a implementação disso foi possível está muito bem escrita nesse post
, onde o autor mostra que por ele ser um Enumerator
e responder para os métodos do módulo Enumerable
recebe e expõe dados do mesmo tipo. Com isso é possível que uma lazy evaluation seja feita, onde, em um conjunto encadeado de operações, o ruby consegue
orquestrar as chamadas de métodos para que apenas uma quantidade necessária de execuções seja feita, veja no exemplo:
1
2
3
4
5
6
require 'prime'
infinite_range = (1..Float::INFINITY)
infinite_range.lazy.select do |n|
n.prime?
end.take(50).to_a
# Returns the first 50 prime numbers
Parece mágico, não?
O que acontence na lazy evaluation é que a execução deste trecho de código, a linha 5
controla a quantidade de chamadas que o bloco select
é executado.
Assim a execução é feita praticamente de traz pra frente.
Neste exemplo, o método take
começa a iteração pelo infinite_range
e quando tem a quantidade de elementos suficiente, gera uma exceção que interrompe o laço.
Espero que tenham entendido um pouco sobre as formas mais comuns de iteração em coleções em Ruby. Qualquer dúvida que tiverem postem nos comentários ou entrem em contato.
Além das que foram passadas no texto, abaixo vou colocar também umas outras boas referências sobre o assunto.
Abraços!