Liste Verständnis in Ruby
-
10-07-2019 - |
Frage
das Äquivalent von Python Listenkomprehensionen zu tun, ich tue das folgende:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Gibt es einen besseren Weg, dies zu tun ... vielleicht mit einem Methodenaufruf?
Lösung
Wenn Sie wirklich wollen, können Sie ein Array # comprehend Methode wie folgt erstellen:
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
Prints:
6
12
18
Ich würde wahrscheinlich tun es einfach so, wie Sie allerdings tat.
Andere Tipps
Wie wäre:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Ein wenig saubere, zumindest nach meinem Geschmack, und nach einem kurzen Benchmark-Test etwa 15% schneller als die Version ...
Ich habe eine schnelle Benchmark Vergleich die drei Alternativen und Karte-compact scheint wirklich die beste Option zu sein.
Performance-Test (Rails)
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
Ergebnisse
/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
Ich besprach dieses Thema mit Rein Henrichs, der mir sagt, dass die leistungsstärksten Lösung
map { ... }.compact`
Dies ist sinnvoll, weil es vermeidet Zwischen Arrays als mit der unveränderlichen Nutzung von Enumerable#inject
Aufbau und den Array vermeidet wächst, die Zuordnung verursacht. Es ist so allgemein wie alle anderen, es sei denn Ihre Sammlung nil Elemente enthalten kann.
Ich habe nicht verglichen diese mit
select {...}.map{...}
Es ist möglich, dass Rubys C Implementierung von Enumerable#select
ist auch sehr gut.
Es scheint über einige Verwirrung bei den Ruby-Programmierern in diesem Thread zu sein, welche Liste Verständnis ist. Jede einzelne Antwort setzt eine gewisse vorher vorhandenen Array zu transformieren. Aber Kraft der Liste Verständnis liegt in einem Array im laufenden Betrieb mit folgender Syntax erstellt:
squares = [x**2 for x in range(10)]
In dem folgenden würde eine analoge in Ruby (die einzige angemessene Antwort in diesem Thread, Darauf,) sein:
a = Array.new(4).map{rand(2**49..2**50)}
Im obigen Fall, Ich erstelle eine Reihe von Zufallszahlen, aber der Block alles enthalten könnte. Aber das wäre ein Ruby-Liste Verständnis sein.
Eine alternative Lösung, die in jeder Implementierung arbeiten und in O (n) anstelle von O (2n) Zeit laufen ist:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
Ich habe gerade die gem zu begreifen RubyGems, was können Sie tun:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
Es ist in C geschrieben; das Array wird nur einmal durchlaufen.
Enumerable hat eine grep
Methode, deren erstes Argument kann ein Prädikat proc sein und dessen optionale zweite Argument ist eine Abbildungsfunktion; so dass die folgenden Werke:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Das ist nicht so lesbar wie ein paar andere Vorschläge (Ich mag anoiaque ist ganz einfach select.map
oder comprehend Juwel des histocrat), aber seine Stärken sind, dass sie bereits einen Teil der Standard-Bibliothek ist, und ist Single-Pass und ist nicht mit temporäre Zwischenarrays und einen out-of-Grenzen Wert wie nil
nicht in den compact
betriebenen Anregungen verwendet wurde, erfordern.
Dies ist prägnanter:
[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]
Das ist für mich funktioniert. Es ist auch sauber. Ja, es ist das gleiche wie map
, aber ich denke, collect
der Code verständlicher macht.
select(&:even?).map()
sieht eigentlich besser, nachdem sie unten zu sehen.
Wie Pedro erwähnt, können Sie die verkettete Anrufe verschmelzen Enumerable#select
und Enumerable#map
, eine Traversal über die ausgewählten Elemente zu vermeiden. Dies gilt, da Enumerable#select
eine Spezialisierung von Falt- oder inject
ist. Ich schrieb eine zum Thema bei der Ruby subreddit noreferrer"> eilten Einführung rel="nofollow.
Manueller Array-Transformationen Verschmelzen kann langwierig sein, also vielleicht jemand mit Robert Gamble comprehend
Implementierung spielen könnte dies select
/ map
Muster bisschen besser zu machen.
So etwas wie folgt aus:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Nennen Sie es:
lazy (1..6){|x| x * 3 if x.even?}
Welche zurück:
=> [6, 12, 18]
Eine andere Lösung, aber vielleicht nicht die beste
some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }
oder
some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.push(x * 3) : nil }
Ich denke, die meisten Liste Verständnis-esque folgend wäre:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Da Rubin uns erlaubt die bedingte nach dem Ausdruck zu bringen, erhalten wir eine Syntax wie die Python-Version der Liste Verständnis. Da auch die select
Methode nichts enthält, die alle Null-Werte false
entsprechen aus der resultierenden Liste entfernt und kein Aufruf zu kompakt ist notwendig, da der Fall wäre, wenn wir map
oder collect
stattdessen verwendet hatten.