Pour faire l'équivalent de la compréhension de liste Python, je procède comme suit:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Y at-il une meilleure façon de faire cela ... peut-être avec un appel de méthode?
Si vous voulez vraiment, vous pouvez créer une méthode Array # comprehend comme ceci:
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 tu le faisais.
Comment
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Un peu plus propre, du moins à mon goût, et selon un rapide test de performance environ 15% plus rapide que votre version ...
J'ai fait un repère rapide en comparant les trois alternatives et map-compact 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
J'ai discuté de ce sujet avec Rein Henrichs, qui m'a dit que la solution la plus performante est
map { ... }.compact`
Cela a du sens car cela évite de construire des tableaux intermédiaires comme avec l'utilisation immuable de Enumerable#inject
, et cela évite de développer le tableau, ce qui entraîne une allocation. C’est aussi général que les autres, à moins que votre collection ne puisse contenir aucun élément.
Je n'ai pas comparé cela avec
select {...}.map{...}
Il est possible que l'implémentation C de Enumerable#select
par Ruby soit également très bonne.
Il semble y avoir une certaine confusion parmi les programmeurs de Ruby dans ce fil en ce qui concerne la compréhension des listes. Chaque réponse suppose la transformation d’un tableau préexistant. Mais le pouvoir de compréhension de la 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 en 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.
Une solution alternative qui fonctionnera dans chaque implémentation et s'exécutera dans O(n) au lieu de O(2n) est la suivante:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
Je viens de publier le comprendre gem to RubyGems, 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 fois.
Enumerable a une méthode grep
dont le premier argument peut être un processus de prédicat et dont le second argument facultatif est une fonction de mappage; donc les travaux suivants:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Ce n'est pas aussi lisible que quelques suggestions (j'aime bien le simple select.map
ou la gemme à comprendre de l'histocrate anoiaque), mais sa force réside dans le fait qu'elle fait déjà partie de la bibliothèque standard, qu'elle est en un seul passage et qu'elle ne nécessite pas la création d'un intermédiaire temporaire. tableaux, et ne nécessite pas une valeur hors limites comme nil
utilisée dans les suggestions compact
-.
C'est plus concis:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
[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 collect
rend le code plus compréhensible.
select(&:even?).map()
semble en fait mieux, après l'avoir vu ci-dessous.
Comme Pedro l'a mentionné, vous pouvez fusionner les appels chaînés à Enumerable#select
et Enumerable#map
, en évitant une traversée des éléments sélectionnés. Cela est vrai car Enumerable#select
est une spécialisation de fold ou inject
. J'ai posté une introduction précipitée au sujet à la subreddit Ruby.
La fusion manuelle des transformations Array peut être fastidieuse. Quelqu'un pourrait donc jouer avec l'implémentation comprehend
de Robert Gamble pour rendre ce modèle select
/map
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 retourne:
=> [6, 12, 18]
Une autre solution mais peut-être pas la meilleure
some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }
ou
some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.Push(x * 3) : nil }