Python liste anlamalarının eşdeğerini yapmak için şunları yapıyorum:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Bunu yapmanın daha iyi bir yolu var mı ... belki bir yöntem çağrısıyla?
Python liste anlamalarının eşdeğerini yapmak için şunları yapıyorum:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Bunu yapmanın daha iyi bir yolu var mı ... belki bir yöntem çağrısıyla?
Yanıtlar:
Gerçekten istiyorsanız, bunun gibi bir Array # kavrama yöntemi oluşturabilirsiniz:
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
Baskılar:
6
12
18
Muhtemelen senin yaptığın gibi yapardım.
[nil, nil, nil].comprehend {|x| x }hangisi geri dönüyor []?
compact!hiçbir öğe değiştirilmediğinde dizi yerine nil döndürür, bu yüzden bunun işe yaradığını düşünmüyorum.
Ne dersin?
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Biraz daha temiz, en azından benim zevkime göre ve hızlı bir kıyaslama testine göre sürümünüzden yaklaşık% 15 daha hızlı ...
some_array.map{|x| x * 3 unless x % 2}.compacttartışmasız daha okunabilir / yakut-esque olan.
unless x%2hiçbir etkisi yoktur, çünkü 0 Ruby'de doğru. Bakınız: gist.github.com/jfarmer/2647362
Üç alternatifi karşılaştırarak hızlı bir kıyaslama yaptım ve harita-kompakt gerçekten en iyi seçenek gibi görünüyor.
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
reduceBu karşılaştırmada da görmek ilginç olurdu (bkz. Stackoverflow.com/a/17703276 ).
inject==reduce
Bu ileti dizisinde Ruby programcıları arasında liste anlamanın ne olduğu konusunda bazı karışıklıklar var gibi görünüyor. Her bir yanıt, önceden var olan bazı dizilerin dönüşeceğini varsayar. Ancak listeyi anlamanın gücü, aşağıdaki sözdizimi ile anında oluşturulan bir dizide yatmaktadır:
squares = [x**2 for x in range(10)]
Aşağıdakiler Ruby'de bir analog olacaktır (bu başlıktaki tek uygun cevap, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
Yukarıdaki durumda, rastgele bir tamsayı dizisi oluşturuyorum, ancak blok herhangi bir şey içerebilir. Ancak bu bir Ruby listesi anlayışı olacaktır.
Bu konuyu, bana en iyi performans gösteren çözümün olduğunu söyleyen Rein Henrichs ile tartıştım.
map { ... }.compact
Bu mantıklıdır çünkü değişmez kullanımında olduğu gibi ara Diziler oluşturmayı önler Enumerable#injectve ayırmaya neden olan Diziyi büyütmekten kaçınır. Koleksiyonunuz sıfır öğe içermediği sürece, diğerleri kadar geneldir.
Bunu karşılaştırmadım
select {...}.map{...}
Ruby'nin C uygulamasının Enumerable#selectda çok iyi olması mümkündür.
Her uygulamada çalışacak ve O (2n) zamanı yerine O (n) ile çalışacak alternatif bir çözüm şudur:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
2şeyleri nyerine kez 1şey nsüreleri ve daha sonra başka bir 1şey nkez :) önemli bir avantajı ait inject/ reduceherhangi korur olmasıdır nilfazla liste-comprehensionly davranıştır giriş dizisindeki değerleri
Kavrama mücevherini RubyGems'te yayınladım , bu da bunu yapmanıza izin veriyor:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
C ile yazılmıştır; dizi yalnızca bir kez geçilir.
Enumerable, grepilk argümanı bir yüklem prok olabilen ve isteğe bağlı ikinci argümanı bir eşleme işlevi olan bir yönteme sahiptir ; bu nedenle aşağıdakiler çalışır:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Bu, diğer birkaç öneri kadar okunabilir değildir (anoiaque'in basit select.mapveya histokratın kavrayış mücevherini severim), ancak güçlü yönleri, zaten standart kütüphanenin bir parçası olması ve tek geçişli olması ve geçici ara diziler oluşturmayı içermemesidir. ve -using önerilerinde nilkullanıldığı gibi sınır dışı bir değer gerektirmez compact.
Bu daha kısa:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
Pedro belirttiğimiz gibi, zincirlenmiş aramaları birlikte ilerleyebilir Enumerable#selectve Enumerable#mapseçilen elemanları üzerinde bir geçişi kaçınarak. Bu doğrudur, çünkü Enumerable#selectkatlama veya inject. Ruby alt dizininde konuya hızlı bir giriş gönderdim .
Dizi dönüşümlerini manuel olarak birleştirmek sıkıcı olabilir, bu nedenle belki birisi bunu / kalıbı daha güzel comprehendkılmak için Robert Gamble'ın uygulamasıyla oynayabilir .selectmap
Bunun gibi bir şey:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Bunu aramak:
lazy (1..6){|x| x * 3 if x.even?}
Hangi döndürür:
=> [6, 12, 18]
lazyDizi üzerinde tanımlamada yanlış olan şey ve ardından:(1..6).lazy{|x|x*3 if x.even?}
Bu, buna yaklaşmanın bir yolu:
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
Yani temelde bir dizgeyi döngü için uygun ruby sözdizimine dönüştürüyoruz, sonra bunu yapmak için bir dizede python sözdizimini kullanabiliriz:
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]
veya dizenin görünümünü beğenmezseniz veya bir lambda kullanmak zorunda kalırsanız, python sözdizimini yansıtma girişiminden vazgeçebilir ve şöyle bir şey yapabiliriz:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
filter_mapİstediğinizi hemen hemen elde eden Ruby 2.7 tanıtıldı (harita + kompakt):
some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Bu konuda daha fazla bilgi bulabilirsiniz burada .
https://rubygems.org/gems/ruby_list_comprehension
Ruby Listesini Anlama cevheri için utanmaz eklenti deyimsel Ruby listesi anlayışlarına izin verir
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
Bence en çok liste anlayışı şudur:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Ruby, ifadeden sonra koşulu yerleştirmemize izin verdiği için, liste anlamanın Python sürümüne benzer bir sözdizimi elde ederiz. Ayrıca, selectyöntem eşit olan herhangi bir şey içermediğinden false, tüm nil değerleri sonuç listesinden kaldırılır ve kullanmış olsaydık mapya da collectonun yerine kullanmış olsaydık olduğu gibi herhangi bir kompakt çağrısı gerekli değildir .