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}.compact
tartışmasız daha okunabilir / yakut-esque olan.
unless x%2
hiç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
reduce
Bu 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#inject
ve 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#select
da ç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 n
yerine kez 1
şey n
süreleri ve daha sonra başka bir 1
şey n
kez :) önemli bir avantajı ait inject
/ reduce
herhangi korur olmasıdır nil
fazla 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, grep
ilk 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.map
veya 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 nil
kullanı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#select
ve Enumerable#map
seçilen elemanları üzerinde bir geçişi kaçınarak. Bu doğrudur, çünkü Enumerable#select
katlama 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 comprehend
kılmak için Robert Gamble'ın uygulamasıyla oynayabilir .select
map
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]
lazy
Dizi ü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, select
yö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 map
ya da collect
onun yerine kullanmış olsaydık olduğu gibi herhangi bir kompakt çağrısı gerekli değildir .