Aby zrobić odpowiednik list składanych w Pythonie, wykonuję następujące czynności:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Czy jest lepszy sposób na zrobienie tego ... może za pomocą jednego wywołania metody?
ruby
list-comprehension
Tylko czytać
źródło
źródło
Odpowiedzi:
Jeśli naprawdę chcesz, możesz utworzyć metodę Array # comprehend w następujący sposób:
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
Wydruki:
6 12 18
Jednak prawdopodobnie zrobiłbym to tak, jak ty.
źródło
[nil, nil, nil].comprehend {|x| x }
który zwraca[]
.compact!
zwraca nil zamiast tablicy, gdy żadne elementy nie są zmieniane, więc nie sądzę, aby to działało.Może:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Nieco czystszy, przynajmniej jak na mój gust, i według szybkiego testu porównawczego około 15% szybciej niż twoja wersja ...
źródło
some_array.map{|x| x * 3 unless x % 2}.compact
, który jest prawdopodobnie bardziej czytelny / rubinowy.unless x%2
nie ma żadnego efektu, ponieważ 0 jest prawdą w rubinie. Zobacz: gist.github.com/jfarmer/2647362Dokonałem szybkiego testu porównawczego, porównując trzy alternatywy, a kompresja mapy naprawdę wydaje się być najlepszą opcją.
Test wydajności (szyny)
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
Wyniki
/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
źródło
reduce
w tym teście porównawczym (patrz stackoverflow.com/a/17703276 ).inject
==reduce
Wydaje się, że wśród programistów Rubiego jest pewne zamieszanie w tym wątku, co do tego, czym jest rozumienie list. Każda odpowiedź zakłada przekształcenie jakiejś istniejącej tablicy. Ale siła rozumienia list tkwi w tablicy tworzonej w locie o następującej składni:
squares = [x**2 for x in range(10)]
Poniższy przykład byłby analogiem w Rubim (jedyna odpowiednia odpowiedź w tym wątku, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
W powyższym przypadku tworzę tablicę losowych liczb całkowitych, ale blok może zawierać wszystko. Ale to byłoby zrozumienie listy Ruby.
źródło
Omówiłem ten temat z Reinem Henrichsem, który powiedział mi, że najlepszym rozwiązaniem jest
Ma to sens, ponieważ unika tworzenia tablic pośrednich, jak w przypadku niezmiennego użycia
Enumerable#inject
, i unika powiększania tablicy, co powoduje alokację. Jest tak ogólna, jak każda inna, chyba że Twoja kolekcja może zawierać zero elementów.Nie porównałem tego z
Możliwe, że implementacja języka Ruby w C
Enumerable#select
jest również bardzo dobra.źródło
Alternatywnym rozwiązaniem, które sprawdzi się w każdej implementacji i będzie działało w czasie O (n) zamiast O (2n) jest:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
źródło
2
rzeczyn
razy zamiast1
rzeczyn
razy, a potem inna1
rzeczn
razy :) Jedną z ważnych zaletinject
/reduce
jest to, że zachowuje wszelkienil
wartości w sekwencji wejściowej, co jest zachowaniem bardziej zrozumiałym dla listyWłaśnie opublikowałem comprehend gem w RubyGems, który pozwala ci to zrobić:
require 'comprehend' some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
Jest napisany w C; tablica jest przeszukiwana tylko raz.
źródło
Enumerable ma
grep
metodę, której pierwszym argumentem może być predykat proc, a opcjonalnym drugim argumentem jest funkcja mapująca ; więc działa:some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
To nie jest tak czytelne, jak kilka innych sugestii (lubię prosty
select.map
anoiaque lub zrozumiały klejnot histokraty), ale jego mocne strony polegają na tym, że jest już częścią standardowej biblioteki i jest jednoprzebiegowe i nie obejmuje tworzenia tymczasowych tablic pośrednich i nie wymaga wartości spoza zakresu, takiej jaknil
używana wcompact
sugestiach -using.źródło
To jest bardziej zwięzłe:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
źródło
[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]
To działa dla mnie. Jest również czysty. Tak, to to samo co
map
, ale myślę,collect
że kod jest bardziej zrozumiały.select(&:even?).map()
faktycznie wygląda lepiej, po obejrzeniu tego poniżej.
źródło
Jak wspomniał Pedro, możesz łączyć ze sobą połączone wywołania do
Enumerable#select
iEnumerable#map
, unikając przechodzenia przez wybrane elementy. Jest to prawdą, ponieważEnumerable#select
jest to specjalizacja fold lubinject
. Opublikowałem pośpieszne wprowadzenie do tematu na subreddicie Ruby.Ręcznie zgrzewania transformacje tablic może być uciążliwe, więc może ktoś mógłby grać z Roberta Gamble
comprehend
realizacji uczynienia tegoselect
/map
wzór ładniejsza.źródło
Coś takiego:
def lazy(collection, &blk) collection.map{|x| blk.call(x)}.compact end
Nazwać:
lazy (1..6){|x| x * 3 if x.even?}
Który zwraca:
=> [6, 12, 18]
źródło
lazy
w Array, a następnie:(1..6).lazy{|x|x*3 if x.even?}
Kolejne rozwiązanie, ale może nie najlepsze
some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }
lub
some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.push(x * 3) : nil }
źródło
Oto jeden ze sposobów rozwiązania tego problemu:
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
więc w zasadzie konwertujemy ciąg na odpowiednią składnię ruby dla pętli, a następnie możemy użyć składni Pythona w ciągu, aby wykonać:
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]
lub jeśli nie podoba ci się wygląd łańcucha lub konieczność użycia lambdy, możemy zrezygnować z próby odzwierciedlenia składni Pythona i zrobić coś takiego:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1] # [0, 4, 8, 12, 16]
źródło
Ruby 2.7 wprowadził,
filter_map
który prawie osiąga to, czego chcesz (mapa + kompakt):some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Więcej na ten temat przeczytasz tutaj .
źródło
https://rubygems.org/gems/ruby_list_comprehension
bezwstydna wtyczka do mojego klejnotu Ruby List Compression, aby umożliwić idiomatyczne rozumienie list Ruby
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
źródło
Myślę, że najbardziej rozwinięta lista ze zrozumieniem wyglądałaby następująco:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Ponieważ Ruby pozwala nam umieścić warunek po wyrażeniu, otrzymujemy składnię podobną do wersji list składanych w Pythonie. Ponadto, ponieważ
select
metoda nie zawiera niczego, co jest równoważnefalse
, wszystkie wartości zerowe są usuwane z listy wynikowej i nie jest konieczne wywołanie metody compact, jak byłoby to w przypadku, gdybyśmy użylimap
lubcollect
zamiast tego.źródło