2010-02-24 4 views
7

En este código, puedo crear una matriz de cadenas "1" a "10000":Equivalente a Ruby Enumerable.collect que devuelve un Enumerable?

array_of_strings = (1..10000).collect {|i| String(i)} 

¿La API de Ruby Core proporciona una forma de obtener un objeto enumerable que me permite enumerar sobre la misma lista, generando los valores de cadena a pedido, en lugar de generar una matriz de cadenas?

Aquí está un ejemplo adicional que se espera aclara lo que estoy tratando de hacer:

def find_me_an_awesome_username 
    awesome_names = (1..1000000).xform {|i| "hacker_" + String(i) } 
    awesome_names.find {|n| not stackoverflow.userexists(n) } 
end 

Dónde xform es el método que yo estoy buscando. awesome_names es un enumerable, por lo que xform no crea una matriz de cadenas de elementos de un millón de unidades, sino que genera y devuelve cadenas de la forma "hacker_ [N]" a petición.

Por cierto, esto es lo que podría ser como en C#:

var awesomeNames = from i in Range(1, 1000000) select "hacker_" + i; 
var name = awesomeNames.First((n) => !stackoverflow.UserExists(n)); 

(una solución)

Aquí es una extensión al enumerador que añade un método xform. Devuelve otro enumerador que itera sobre los valores del enumerador original, con una transformación aplicada.

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} 
+0

... hay que leer '2 a 20' – mackenir

Respuesta

6

Rubí 2.0 introducido Enumerable#lazy que permite una a la cadena map, select, etc ..., y sólo generará los resultados finales al final con to_a, first, etc ... Puede usarlo en cualquier versión de Ruby con 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' 

De lo contrario, puede utilizar Enumerator.new { with_a_block }. Es nuevo en Ruby 1.9, así que require 'backports/1.9.1/enumerator/new' si lo necesita en Ruby 1.8.x.

Como por su ejemplo, lo siguiente no crear una matriz intermedia y sólo construirá las cuerdas necesarias:

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 

Usted puede incluso reemplazar el 100000 por 1,0/0 (es decir, Infinito), si quieres .

Para responder a su comentario, si siempre está correlacionando los valores de uno a uno, usted podría tener 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}"} 
+0

@marc, esto parece! ¿Sabes cómo podría convertir este patrón en un método reutilizable más conciso tomando una 'cosa enumerable' y una 'función transformadora'? En este ejemplo, la 'función de transformador' sería '{| i | "hacker_" + String (i)} ', y la" cosa enumerable "sería' (1..100000) 'o lo que sea. – mackenir

+0

Muchas gracias. Actualicé mi pregunta con una posible implementación que elaboré a partir de su respuesta, y también al leer el enlace que publicó @Telemachus, antes de notar * su * actualización :) ¡Nuevamente, gracias! – mackenir

+0

@mackenir: La función de transformación Enumerable es 'map'. Deberías poder cambiar 'each' a' map' y mantener el algoritmo sin cambios. – Chuck

0

listas tienen una cada método:

(1..100000).each 
+1

... está bien, sigue adelante . :) – mackenir

+1

... bien, ahora comienzas a buscar sobre la iteración de Ruby. – Geo

+0

Pero su código simplemente itera sobre el rango entero. No genera un nuevo enumerable de cadenas. Por favor, intenta ponerte mis zapatos idiotas :). – mackenir

1

Parece que usted desea que un objeto enumerador, pero no exactamente.

Es decir, un objeto enumerador es un objeto que se puede utilizar para llamar next a la carta (en lugar de each lo que hace todo el bucle). (Muchas personas utilizan el lenguaje de interno frente iteradores externos:.. each es interno, y un enumerador es externo Usted lo conduce)

Así es como un enumerador podría ser:

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 

Aquí hay un enlace, a más discusión sobre cómo puede utilizar enumeradores perezosamente en Ruby: http://www.michaelharrison.ws/weblog/?p=163

también hay una sección sobre esto en el libro Pico (de programación Ruby por Dave Thomas).

+0

Gracias. Hmmm. Find definitivamente deja de enumerar cuando encuentra un elemento coincidente. Puede confirmar esto ejecutando find en un rango muy grande, con el predicado 'false' y 'true', el último regresa al instante. Si ambos enumeraran todo, ambos regresarían al mismo tiempo. Re: enumeración con el siguiente, estoy tratando de encontrar la facilidad de 'transformador enumerable' con el fin de escribir más escueto, el código declarativo, y la enumeración manual no logrará realmente eso. Quizás la respuesta sea simplemente implementarlo. – mackenir

+0

Con todos los CR eliminados eso es menos comprensible. Lo que quiero decir es que (1..1000000000000000000) .find {| i | true} es rápido, y (1..1000000000000000000) .find {| i | false} es lento. Significado find solo enumera hasta que 'encuentra'. – mackenir

+0

Enlace útil: creo que lo entiendo, y me ayudó a responder la pregunta. – mackenir

1
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" 

La siguiente cosa que hacer es devolver un enumerador Cuando se invoca sin un bloque ...

Cuestiones relacionadas