Equivalente a ruby enumerable.
-
22-09-2019 - |
Pergunta
Neste código, crio uma matriz de strings "1" para "10000":
array_of_strings = (1..10000).collect {|i| String(i)}
A API do Ruby Core fornece uma maneira de obter um objeto enumerável que me permite enumerar sobre a mesma lista, gerando os valores da string sob demanda, em vez de gerar uma matriz das cordas?
Aqui está um exemplo adicional que, esperançosamente, esclarece o que estou tentando fazer:
def find_me_an_awesome_username
awesome_names = (1..1000000).xform {|i| "hacker_" + String(i) }
awesome_names.find {|n| not stackoverflow.userexists(n) }
end
Onde xform
é o método que estou procurando. Awesome_names é um enumerável, então xform
Não está criando uma matriz de 1 milhão de elementos de cordas, mas apenas gerando e retornando strings da forma "hacker_ [n] sob demanda.
A propósito, eis como pode ser em C#:
var awesomeNames = from i in Range(1, 1000000) select "hacker_" + i;
var name = awesomeNames.First((n) => !stackoverflow.UserExists(n));
(Uma solução)
Aqui está uma extensão para o enumerador que adiciona um método xform. Ele retorna outro enumerador que itera sobre os valores do enumerador original, com uma transformação aplicada a ele.
class Enumerator
def xform(&block)
Enumerator.new do |yielder|
self.each do |val|
yielder.yield block.call(val)
end
end
end
end
# this prints out even numbers from 2 to 10:
(1..10).each.xform {|i| i*2}.each {|i| puts i}
Solução
Ruby 2.0 introduzido Enumerable#lazy
o que permite cadeia map
, select
, etc ... e apenas gerar os resultados finais no final com to_a
, first
, etc ... você pode usá -lo em qualquer versão rubi com require 'backports/2.0.0/enumerable/lazy'
.
require 'backports/2.0.0/enumerable/lazy'
names = (1..Float::INFINITY).lazy.map{|i| "hacker_" + String(i) }
names.first # => 'hacker_1'
Caso contrário, você pode usar Enumerator.new { with_a_block }
. É novo em Ruby 1.9, então require 'backports/1.9.1/enumerator/new'
Se você precisar no Ruby 1.8.x.
De acordo com o seu exemplo, o seguinte não criará uma matriz intermediária e só construirá as seqüências necessárias:
require 'backports/1.9.1/enumerator/new'
def find_me_an_awesome_username
awesome_names = Enumerator.new do |y|
(1..1000000).each {|i| y.yield "hacker_" + String(i) }
end
awesome_names.find {|n| not stackoverflow.userexists(n) }
end
Você pode até substituir o 100000 por 1,0/0 (ou seja, infinito), se quiser.
Para responder ao seu comentário, se você está sempre mapeando seus valores um a um, pode ter algo como:
module Enumerable
def lazy_each
Enumerator.new do |yielder|
each do |value|
yielder.yield(yield value)
end
end
end
end
awesome_names = (1..100000).lazy_each{|i| "hacker_#{i}"}
Outras dicas
Parece que você deseja um objeto enumerador, mas não exatamente.
Isto é, um objeto enumerador é um objeto que você pode usar para ligar next
sob demanda (ao invés de each
que faz todo o loop). (Muitas pessoas usam o idioma dos iteradores internos versus externos: each
é interno e um enumerador é externo. Você dirige.)
Veja como um enumerador pode parecer:
awesome_names = Enumerator.new do |y|
number = 1
loop do
y.yield number
number += 1
end
end
puts awesome_names.next
puts awesome_names.next
puts awesome_names.next
puts awesome_names.next
Aqui está um link, para mais discussões sobre como você pode usar os enumeradores preguiçosamente em Ruby: http://www.michaelharrison.ws/weblog/?p=163
Há também uma seção sobre isso no livro de picareta (Programação rubi por Dave Thomas).
class T < Range
def each
super { |i| yield String(i) }
end
end
T.new(1,3).each { |s| p s }
$ ruby rsc.rb
"1"
"2"
"3"
A próxima coisa a fazer é devolver um enumerador quando chamado sem um bloco ...
As listas têm um método:
(1..100000).each