2009-03-04 9 views
3

J'essaie de trouver une manière élégante de créer une liste à partir d'une fonction qui génère des valeurs dans Python et Ruby.Créer des listes en utilisant yield dans Ruby et Python

En Python:

def foo(x): 
    for i in range(x): 
     if bar(i): yield i 
result = list(foo(100)) 

Dans Ruby:

def foo(x) 
    x.times {|i| yield i if bar(i)} 
end 
result = [] 
foo(100) {|x| result << x} 

Bien que j'aime travailler dans les deux langues, je l'ai toujours été un peu dérangé par la version Ruby ayant pour initialiser la liste et Puis remplissez-le. Les résultats yield de Python donnent une itération simple, ce qui est génial. Le yield de Ruby invoque un bloc, ce qui est aussi génial, mais quand je veux juste remplir une liste, ça me semble un peu maladroit.

Y a-t-il une manière plus élégante de Ruby?

MISE À JOUR Retravaillé l'exemple pour montrer que le nombre de valeurs générées par la fonction n'est pas nécessairement égal à x.

+3

Les "rendements" de Ruby et Python sont sémantiquement différents. Le rendement de Python est presque plus proche de celui de Ruby que celui de Ruby. – rampion

Répondre

10

Alors, pour votre nouvel exemple, essayez ceci:

def foo(x) 
    (0..x).select { |i| bar(i) } 
end 

Fondamentalement, sauf si vous écrivez un itérateur de votre propre, vous n'avez pas besoin yield très souvent dans Ruby. Vous ferez probablement beaucoup mieux si vous arrêtez d'essayer d'écrire des idiomes Python en utilisant la syntaxe Ruby.

+2

D'accord. Les deux langues ont une approche complètement différente des problèmes équivalents dans beaucoup de cas. – bojo

+2

Vous n'avez même pas besoin de to_a - La plage inclut Enumerable. Juste (1..x) .sélectionnez {| i | bar i}. – Chuck

+1

Je ne l'appellerais même pas un idiome de Python, voir les suggestions de Python ci-dessous. – FogleBird

1

Je sais que ce n'est pas exactement ce que vous recherchez, mais plus élégante façon d'exprimer votre exemple en rubis est:

result = Array.new(100) {|x| x*x} 
+0

Vous avez raison, ce n'est pas tout à fait ce que je cherche.La seule raison pour laquelle je céderais d'une méthode serait si je ne savais pas exactement combien de valeurs seraient cédées. – jcrossley3

1
def squares(x) 
    (0..x).map { |i| i * i } 
end 

Tout ce qui concerne une gamme de valeurs est meilleure façon de traiter avec, bien , une plage, plutôt que times et la génération de tableau.

+0

L'exemple a été inventé. La question concerne toute fonction qui donne des valeurs, qu'une gamme soit impliquée ou non. – jcrossley3

7

Pour la version Python j'utiliser une expression de générateur comme:

(i for i in range(x) if bar(i)) 

Ou pour ce cas spécifique des valeurs de filtrage, encore plus simplement

itertools.ifilter(bar,range(x)) 
+0

liste compréhensions ftw! –

1

Pour la liste Python version compréhension affichée par stbuton utiliser xrange au lieu de la gamme si vous voulez un générateur. range va créer toute la liste en mémoire.

+0

Pas en python 3.0 .. –

1

yield signifie différentes choses ruby ​​et python. Dans ruby, vous devez spécifier un bloc de rappel si je me souviens bien, alors que les générateurs en python peuvent être transmis et céder à celui qui les détient.

+0

Générateurs IMO en python sont un gâchis. Ils «ressemblent» à des fonctions mais ne sont rien comme eux --- et vous devez chercher dans la fonction à la recherche du «rendement» avant de pouvoir les identifier comme des générateurs plutôt que comme des fonctions. Et puisque les 'fonctions' du générateur renvoient des objets générateurs de toute façon ... pourquoi ne pas faire de l'API de manière explicite sur la construction des objets générateurs comme dans Ruby? Il semble que Ruby soit plus explicite ici que Python - avec python essayant de les déguiser en fonction (ce qui est complètement la mauvaise abstraction). – horseyguy

+0

@banister: J'ai eu une réaction différente à la différence. Pour autant que je sache sur ruby, (pas très loin) une construction équivalente aux générateurs de ruby ​​peut être accomplie en acceptant simplement un paramètre de référence de fonction, et en l'appelant successivement avec chaque valeur "générée". Cela ne nécessite que des fonctions de première classe, ce que Python a également. L'approche python utilise des continuations, ce qui donne quelque chose de plus profond. La syntaxe magique autour de 'yield' ne devrait pas être trop importante pour les développeurs, car il est aussi possible de retourner des objets générateurs à partir de fonctions régulières, comme avec les compréhensions de générateurs. – recursive

+0

@recursive, je ne comprends pas comment fonctionne la construction équivalente en python, pourriez-vous donner un exemple? POUR L'ENREGISTREMENT Les générateurs Ruby sont mis en œuvre en utilisant des fibres, qui sont des co-routines supportées par le langage à part entière. En outre, ruby ​​a des suites de première classe: P – horseyguy

5

L'équivalent exact de votre code Python (en utilisant des générateurs Ruby) serait:

def foo(x) 
    Enumerator.new do |yielder| 
     (0..x).each { |v| yielder.yield(v) if bar(v) } 
    end 
end 

result = Array(foo(100)) 

Dans ce qui précède, la liste est générée paresseusement (comme dans l'exemple Python); voir:

def bar(v); v % 2 == 0; end 

f = foo(100) 
f.next #=> 0 
f.next #=> 2 
Questions connexes