Pour faire l'équivalent de la compréhension de liste Python, je fais ce qui suit:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Y a-t-il une meilleure façon de faire cela ... peut-être avec un seul appel de méthode?
Pour faire l'équivalent de la compréhension de liste Python, je fais ce qui suit:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Y a-t-il une meilleure façon de faire cela ... peut-être avec un seul appel de méthode?
Réponses:
Si vous le souhaitez vraiment, vous pouvez créer une méthode Array # comprehend comme celle-ci:
class Array
def comprehend(&block)
return self if block.nil?
self.collect(&block).compact
end
end
some_array = [1, 2, 3, 4, 5, 6]
new_array = some_array.comprehend {|x| x * 3 if x % 2 == 0}
puts new_array
Impressions:
6
12
18
Je le ferais probablement comme vous l'avez fait.
[nil, nil, nil].comprehend {|x| x }
qui retourne []
.
compact!
renvoie nil au lieu du tableau lorsqu'aucun élément n'est modifié, donc je ne pense pas que cela fonctionne.
Que diriez-vous:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Légèrement plus propre, du moins à mon goût, et selon un rapide test de référence environ 15% plus rapide que votre version ...
some_array.map{|x| x * 3 unless x % 2}.compact
, qui est sans doute plus lisible / rubis-esque.
unless x%2
n'a aucun effet puisque 0 est la vérité en ruby. Voir: gist.github.com/jfarmer/2647362
J'ai fait une comparaison rapide des trois alternatives et la carte compacte semble vraiment être la meilleure option.
require 'test_helper'
require 'performance_test_help'
class ListComprehensionTest < ActionController::PerformanceTest
TEST_ARRAY = (1..100).to_a
def test_map_compact
1000.times do
TEST_ARRAY.map{|x| x % 2 == 0 ? x * 3 : nil}.compact
end
end
def test_select_map
1000.times do
TEST_ARRAY.select{|x| x % 2 == 0 }.map{|x| x * 3}
end
end
def test_inject
1000.times do
TEST_ARRAY.inject([]) {|all, x| all << x*3 if x % 2 == 0; all }
end
end
end
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/performance/list_comprehension_test.rb" -- --benchmark
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
ListComprehensionTest#test_inject (1230 ms warmup)
wall_time: 1221 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.ListComprehensionTest#test_map_compact (860 ms warmup)
wall_time: 855 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.ListComprehensionTest#test_select_map (961 ms warmup)
wall_time: 955 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.
Finished in 66.683039 seconds.
15 tests, 0 assertions, 0 failures, 0 errors
reduce
ce benchmark (voir stackoverflow.com/a/17703276 ).
inject
==reduce
Il semble y avoir une certaine confusion parmi les programmeurs Ruby dans ce fil concernant ce qu'est la compréhension de liste. Chaque réponse suppose un tableau préexistant à transformer. Mais le pouvoir de la compréhension de liste réside dans un tableau créé à la volée avec la syntaxe suivante:
squares = [x**2 for x in range(10)]
Ce qui suit serait un analogue de Ruby (la seule réponse adéquate dans ce fil, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
Dans le cas ci-dessus, je crée un tableau d'entiers aléatoires, mais le bloc peut contenir n'importe quoi. Mais ce serait une compréhension de la liste Ruby.
J'ai discuté de ce sujet avec Rein Henrichs, qui me dit que la solution la plus performante est
map { ... }.compact
Cela a du bon sens car cela évite de créer des tableaux intermédiaires comme avec l'utilisation immuable de Enumerable#inject
, et cela évite de développer le tableau, ce qui provoque une allocation. C'est aussi général que n'importe lequel des autres, sauf si votre collection peut contenir des éléments nuls.
Je n'ai pas comparé ça avec
select {...}.map{...}
Il est possible que l'implémentation en C de Ruby Enumerable#select
soit également très bonne.
Une solution alternative qui fonctionnera dans chaque implémentation et fonctionnera en temps O (n) au lieu de O (2n) est:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
2
choses n
fois au lieu de 1
chose n
fois, puis une autre 1
chose n
fois :) Un avantage important de inject
/ reduce
est qu'il conserve toutes les nil
valeurs dans la séquence d'entrée qui est plus le comportement de liste comprehensionly
Je viens de publier le joyau de compréhension sur RubyGems, ce qui vous permet de faire ceci:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
C'est écrit en C; le tableau n'est parcouru qu'une seule fois.
Enumerable a une grep
méthode dont le premier argument peut être un prédicat proc, et dont le deuxième argument facultatif est une fonction de mappage; donc ce qui suit fonctionne:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Ce n'est pas aussi lisible que quelques autres suggestions (j'aime le joyau simple select.map
ou compréhensible de l'histocrate d'anoiaque), mais ses points forts sont qu'il fait déjà partie de la bibliothèque standard, et qu'il est en un seul passage et n'implique pas la création de tableaux intermédiaires temporaires , et ne nécessite pas de valeur hors limites comme celle nil
utilisée dans les compact
suggestions -using.
C'est plus concis:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact
=> [6, 12, 18]
Ça marche pour moi. C'est aussi propre. Oui, c'est la même chose que map
, mais je pense que cela collect
rend le code plus compréhensible.
select(&:even?).map()
semble vraiment mieux, après l'avoir vu ci-dessous.
Comme Pedro l'a mentionné, vous pouvez fusionner les appels chaînés vers Enumerable#select
et Enumerable#map
, en évitant une traversée des éléments sélectionnés. Cela est vrai car il Enumerable#select
s'agit d'une spécialisation de pli ou inject
. J'ai posté une introduction hâtive au sujet dans le sous-répertoire Ruby.
La fusion manuelle des transformations Array peut être fastidieuse, alors peut-être que quelqu'un pourrait jouer avec l' comprehend
implémentation de Robert Gamble pour rendre ce select
/ map
pattern plus joli.
Quelque chose comme ça:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Appeler:
lazy (1..6){|x| x * 3 if x.even?}
Qui renvoie:
=> [6, 12, 18]
lazy
sur Array et ensuite:(1..6).lazy{|x|x*3 if x.even?}
Voici une façon d'aborder ceci:
c = -> x do $*.clear
if x['if'] && x[0] != 'f' .
y = x[0...x.index('for')]
x = x[x.index('for')..-1]
(x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
x.insert(x.length, "end; $*")
eval(x)
$*)
elsif x['if'] && x[0] == 'f'
(x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << x")
x.insert(x.length, "end; $*")
eval(x)
$*)
elsif !x['if'] && x[0] != 'f'
y = x[0...x.index('for')]
x = x[x.index('for')..-1]
(x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
x.insert(x.length, "end; $*")
eval(x)
$*)
else
eval(x.split[3]).to_a
end
end
Donc, fondamentalement, nous convertissons une chaîne en syntaxe ruby appropriée pour la boucle, puis nous pouvons utiliser la syntaxe python dans une chaîne à faire:
c['for x in 1..10']
c['for x in 1..10 if x.even?']
c['x**2 for x in 1..10 if x.even?']
c['x**2 for x in 1..10']
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# [2, 4, 6, 8, 10]
# [4, 16, 36, 64, 100]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
ou si vous n'aimez pas l'apparence de la chaîne ou si vous devez utiliser un lambda, nous pourrions renoncer à essayer de refléter la syntaxe python et faire quelque chose comme ceci:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
Ruby 2.7 introduit filter_map
qui réalise à peu près ce que vous voulez (carte + compact):
some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Vous pouvez en savoir plus ici .
https://rubygems.org/gems/ruby_list_comprehension
plug sans vergogne pour mon joyau de compréhension de liste Ruby pour permettre la compréhension idiomatique de la liste Ruby
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
Je pense que la plus grande compréhension de la liste serait la suivante:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Puisque Ruby nous permet de placer le conditionnel après l'expression, nous obtenons une syntaxe similaire à la version Python de la compréhension de liste. De plus, comme la select
méthode n'inclut rien qui équivaut à false
, toutes les valeurs nulles sont supprimées de la liste résultante et aucun appel à compact n'est nécessaire comme ce serait le cas si nous avions utilisé map
ou à la collect
place.