Um das Äquivalent zum Verständnis der Python-Liste zu erreichen, gehe ich wie folgt vor:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Gibt es einen besseren Weg, dies zu tun ... vielleicht mit einem Methodenaufruf?
Um das Äquivalent zum Verständnis der Python-Liste zu erreichen, gehe ich wie folgt vor:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Gibt es einen besseren Weg, dies zu tun ... vielleicht mit einem Methodenaufruf?
Antworten:
Wenn Sie wirklich möchten, können Sie eine Array # -Verständnismethode 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
Drucke:
6
12
18
Ich würde es wahrscheinlich genauso machen, wie du es getan hast.
[nil, nil, nil].comprehend {|x| x }
welche zurückgibt []
.
compact!
gibt alexey nil anstelle des Arrays zurück, wenn keine Elemente geändert werden. Ich denke also nicht, dass dies funktioniert.
Wie wäre es mit:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Etwas sauberer, zumindest nach meinem Geschmack, und laut einem schnellen Benchmark-Test etwa 15% schneller als Ihre Version ...
some_array.map{|x| x * 3 unless x % 2}.compact
, was wohl besser lesbar / rubinartig ist.
unless x%2
hat keine Auswirkung, da 0 in Ruby wahr ist. Siehe: gist.github.com/jfarmer/2647362
Ich habe einen schnellen Benchmark erstellt, bei dem die drei Alternativen mit Map-Compact verglichen wurden scheint wirklich die beste Option zu sein.
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
in diesem Benchmark zu sehen (siehe stackoverflow.com/a/17703276 ).
inject
==reduce
In diesem Thread scheint es unter Ruby-Programmierern einige Verwirrung darüber zu geben, was Listenverständnis ist. Jede einzelne Antwort setzt voraus, dass ein bereits vorhandenes Array transformiert wird. Die Stärke des Listenverständnisses liegt jedoch in einem Array, das im laufenden Betrieb mit der folgenden Syntax erstellt wird:
squares = [x**2 for x in range(10)]
Das Folgende wäre ein Analogon in Ruby (die einzig angemessene Antwort in diesem Thread, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
Im obigen Fall erstelle ich ein Array von zufälligen Ganzzahlen, aber der Block kann alles enthalten. Dies wäre jedoch ein Verständnis der Ruby-Liste.
Ich habe dieses Thema mit Rein Henrichs besprochen, der mir sagt, dass die Lösung mit der besten Leistung ist
map { ... }.compact
Dies ist sinnvoll, da das Erstellen von Zwischen-Arrays wie bei der unveränderlichen Verwendung von Enumerable#inject
vermieden wird und das Array nicht vergrößert wird, was zu einer Zuweisung führt. Es ist so allgemein wie alle anderen, es sei denn, Ihre Sammlung kann keine Elemente enthalten.
Ich habe das nicht mit verglichen
select {...}.map{...}
Es ist möglich, dass Rubys C-Implementierung Enumerable#select
auch sehr gut ist.
Eine alternative Lösung, die in jeder Implementierung funktioniert und in O (n) anstelle von O (2n) ausgeführt wird, ist:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
2
Dinge n
mal statt , 1
was n
mal und dann noch 1
etwas n
mal :) Ein wichtiger Vorteil von inject
/ reduce
ist , dass es keine bewahrt nil
Werte in der Eingangssequenz , die mehr list-comprehensionly Verhalten
Ich habe gerade das Comprehens-Juwel in RubyGems veröffentlicht, mit dem Sie Folgendes tun können:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
Es ist in C geschrieben; Das Array wird nur einmal durchlaufen.
Enumerable verfügt über eine grep
Methode, deren erstes Argument ein Prädikat proc sein kann und deren optionales zweites Argument eine Zuordnungsfunktion ist. so funktioniert folgendes:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Dies ist nicht so lesbar wie einige andere Vorschläge (ich mag Anoiaques einfaches select.map
oder histokratisches Juwel), aber seine Stärken sind, dass es bereits Teil der Standardbibliothek ist, in einem Durchgang und ohne temporäre Zwischenarrays und erfordert keinen Wert außerhalb der Grenzen, wie nil
er in den compact
Vorschlägen zur Verwendung verwendet wird.
Dies ist prägnanter:
[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]
Das ist für mich in Ordnung. Es ist auch sauber. Ja, es ist das gleiche wie map
, aber ich denke, collect
macht den Code verständlicher.
select(&:even?).map()
sieht eigentlich besser aus, nachdem ich es unten gesehen habe.
Wie Pedro bereits erwähnt hat, können Sie die verketteten Anrufe mit Enumerable#select
und zusammenführen Enumerable#map
, um ein Durchlaufen der ausgewählten Elemente zu vermeiden. Dies ist wahr, weil Enumerable#select
eine Spezialisierung von Falte oder inject
. Ich habe eine hastige Einführung gepostet in das Thema im Ruby-Subreddit veröffentlicht.
Das manuelle Zusammenführen von Array-Transformationen kann mühsam sein. Vielleicht könnte jemand mit der comprehend
Implementierung von Robert Gamble spielen , um dieses select
/ map
Muster schöner zu machen .
Etwas wie das:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Nennen:
lazy (1..6){|x| x * 3 if x.even?}
Welches kehrt zurück:
=> [6, 12, 18]
lazy
auf Array zu definieren und dann:(1..6).lazy{|x|x*3 if x.even?}
Dies ist eine Möglichkeit, dies zu erreichen:
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
Im Grunde genommen konvertieren wir einen String in die richtige Ruby-Syntax für die Schleife. Dann können wir die Python-Syntax in einem String verwenden, um Folgendes zu tun:
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]
oder wenn Ihnen das Aussehen der Zeichenfolge nicht gefällt oder Sie kein Lambda verwenden müssen, können Sie auf den Versuch verzichten, die Python-Syntax zu spiegeln und so etwas zu tun:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
Ruby 2.7 wurde eingeführt, filter_map
das so ziemlich das erreicht, was Sie wollen (Karte + Kompakt):
some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Sie können mehr darüber lesen Sie hier .
https://rubygems.org/gems/ruby_list_comprehension
Schamloser Plug für mein Ruby List Comprehension-Juwel, um idiomatisches Ruby List-Verständnis zu ermöglichen
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
Ich denke, das Listenverständnis wäre das Folgende:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Da Ruby es uns ermöglicht, die Bedingung nach dem Ausdruck zu platzieren, erhalten wir eine Syntax ähnlich der Python-Version des Listenverständnisses. Da die select
Methode nichts enthält, was gleichbedeutend ist false
, werden alle Nullwerte aus der resultierenden Liste entfernt, und es ist kein Aufruf von compact erforderlich, wie dies der Fall wäre, wenn wir map
oder collect
stattdessen verwendet hätten.