2010-02-24 6 views
7

Dans ce code, je crée un tableau de chaînes « 1 » à « 10000 »:Equivalent de Ruby Enumerable.collect qui retourne un Enumerable?

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

L'API Ruby Core fournit un moyen d'obtenir un objet dénombrable qui me permet d'énumérer sur la même liste, la génération les valeurs de chaîne à la demande, plutôt que de générer un tableau des chaînes?

Voici un autre exemple qui précise, espérons-ce que je suis en train de faire:

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

xform est la méthode que je cherche. awesome_names est un Enumerable, donc xform ne crée pas un tableau de chaînes de 1 million d'éléments, mais génère et retourne simplement des chaînes de la forme "hacker_ [N]" à la demande.

D'ailleurs, voici ce qu'il pourrait ressembler en C#:

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

(une solution)

Voici une extension recenseur qui ajoute une méthode xform. Il renvoie un autre énumérateur qui itère sur les valeurs de l'énumérateur d'origine, avec une transformation qui lui est appliquée.

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

... devrait lire « 2 à 20 » – mackenir

Répondre

6

Ruby 2.0 introduit Enumerable#lazy qui permet une chaîne à map, select, etc ..., et seulement générer les résultats finaux à la fin avec to_a, first, etc ... Vous pouvez l'utiliser dans n'importe quelle version Ruby avec 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' 

Sinon, vous pouvez utiliser Enumerator.new { with_a_block }. C'est nouveau dans Ruby 1.9, donc require 'backports/1.9.1/enumerator/new' si vous en avez besoin dans Ruby 1.8.x.

Comme par votre exemple, ce qui suit ne crée pas un tableau intermédiaire et ne construire les cordes nécessaires:

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 

Vous pouvez même remplacer les 100000 de 1,0/0 (Infinity), si vous voulez .

Pour répondre à votre commentaire, si vous mappez toujours vos valeurs un pour un, vous pourriez avoir quelque chose comme:

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, ça ressemble à ça! Savez-vous comment je pourrais transformer ce modèle en une méthode réutilisable plus concise prenant une «chose énumérable», et une «fonction de transformateur»? Dans cet exemple, la 'fonction de transformateur' serait '{| i | "hacker_" + String (i)} ', et la 'chose énumérable' serait' (1..1000000) 'ou quoi que ce soit. – mackenir

+0

Merci beaucoup. J'ai mis à jour ma question avec une implémentation possible que j'ai élaborée à partir de votre réponse, et aussi en lisant le lien que @Telemachus a posté, avant que je remarque * votre * mise à jour :) Encore une fois, merci! – mackenir

+0

@mackenir: La fonction de transformation Enumerable est 'map'. Vous devriez simplement pouvoir changer 'each' en' map' et garder l'algorithme inchangé. – Chuck

0

listes ont une chaque méthode:

(1..100000).each 
+1

... OK, continuez . :) – mackenir

+1

... ok, maintenant vous commencez à chercher sur l'itération de Ruby. – Geo

+0

Mais votre code itère juste sur la plage entière. Il ne génère pas de nouvelle énumération de chaînes. S'il vous plaît essayez et mettez-vous dans mes chaussures idiot :). – mackenir

1

On dirait que vous voulez un objet recenseur, mais pas exactement.

En d'autres termes, un objet Enumérateur est un objet que vous pouvez utiliser pour appeler next à la demande (plutôt que each qui exécute la boucle entière). (Beaucoup de gens utilisent la langue interne par rapport itérateurs externes:.. each est interne, et un recenseur est externe Vous conduisez)

Voilà comment un recenseur pourrait ressembler:

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 

est ici un lien, à une discussion plus approfondie de la façon dont vous pouvez utiliser Ruby énumérateurs paresseusement: http://www.michaelharrison.ws/weblog/?p=163

Il y a aussi une section sur ce dans le livre Pioche (programmation Ruby par Dave Thomas).

+0

Merci. Hmmm. Trouver définitivement arrête d'énumérer quand il trouve un élément correspondant. Vous pouvez le confirmer en lançant find sur une très large plage, avec le prédicat 'false' et 'true' ce dernier retourne instantanément. Si les deux énuméraient tout, ils reviendraient tous les deux en même temps. Re: enumeration avec next, j'essaie de trouver la fonction 'enumerable transformateur' afin d'écrire un code déclaratif plus laconique, et l'énumération manuelle ne permettra pas vraiment d'y parvenir. Peut-être que la réponse est de simplement l'implémenter. – mackenir

+0

Avec tous les CR retirés, c'est moins compréhensible. Ce que je veux dire, c'est que (1..1000000000000000000) .find {| i | true} est rapide, et (1..1000000000000000000) .find {| i | false} est lent. Signification find énumère juste jusqu'à ce qu'il «trouve». – mackenir

+0

Lien utile - Je pense que je le comprends, et cela m'a aidé à répondre à la question. – 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 prochaine chose à faire est de retourner un recenseur lorsqu'il est appelé sans bloc ...