Ruby'de anlama listesi


93

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?


3
Hem senin hem de Glenn Mcdonald'ın cevapları bana iyi görünüyor ... Her ikisinden de daha kısa olmaya çalışarak ne kazanacağını anlamıyorum.
Pistos

1
bu çözüm listeyi iki kez ters çevirir. Enjekte etmiyor.
Pedro Rolo

2
Burada bazı harika cevaplar var, ancak birden fazla koleksiyonda listeyi anlama fikirlerini görmek de harika olurdu.
Bo Jeanes

Yanıtlar:


55

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.


2
Kompakt kullanabilirsiniz! biraz optimize etmek için
Alexey

9
Bu aslında doğru değil, düşünün: [nil, nil, nil].comprehend {|x| x }hangisi geri dönüyor []?
Ted Kaplan

alexey, belgelere göre, 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.
İkili Phile

90

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ı ...


4
ve some_array.map{|x| x * 3 unless x % 2}.compacttartışmasız daha okunabilir / yakut-esque olan.
havuzu

5
@nightpool'un unless x%2hiçbir etkisi yoktur, çünkü 0 Ruby'de doğru. Bakınız: gist.github.com/jfarmer/2647362
Abhinav Srivastava

30

Üç 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.

Performans testi (Raylar)

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

Sonuçlar

/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

1
reduceBu karşılaştırmada da görmek ilginç olurdu (bkz. Stackoverflow.com/a/17703276 ).
Adam Lindberg

3
inject==reduce
ben.snape

map_compact daha hızlı olabilir ama yeni bir dizi yaratıyor. enjekte map.compact ve select.map daha uzay verimlidir
bibstha

11

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.


1
OP'nin yapmaya çalıştığı şeyi nasıl yaparsınız?
Andrew Grimm

2
Aslında şimdi OP'nin kendisinin yazarın dönüştürmek istediği bazı mevcut listeleri olduğunu görüyorum. Ancak arketipsel liste anlama anlayışı, bazı yinelemelere atıfta bulunarak daha önce var olmayan bir dizi / liste oluşturmayı içerir. Ama aslında, bazı resmi tanımlar liste anlamanın haritayı hiç kullanamayacağını söylüyor, bu yüzden benim versiyonum bile koşer değil - ama sanırım Ruby'de bir tanesi olabildiğince yakın.
Mark

6
Ruby örneğinizin Python örneğinizin bir benzeri olması gerektiğini anlamıyorum. Ruby kodu şöyle olmalıdır: squares = (0..9) .map {| x | x ** 2}
michau

4
@Michau haklı olsa da, listeyi anlamanın (Mark'ın ihmal ettiği) tüm noktası, listeyi anlamanın kendisinin dizileri oluşturmamasıdır - tüm hesaplamaları, depolama alanı ayırmadan akışlı bir şekilde yapmak için jeneratörleri ve ortak rutinleri kullanır (hariç temp değişkenleri) kadar (iff) sonuçlar bir dizi değişkenine denk gelir - bu python örneğindeki köşeli parantezlerin amacı bir sonuç kümesine daraltmaktır. Ruby'nin jeneratörlere benzer bir tesisi yoktur.
Guss

5
Oh evet, var (Ruby 2.0'dan beri): squares_of_all_natural_numbers = (0..Float :: INFINITY) .lazy.map {| x | x ** 2}; p squares_of_all_natural_numbers.take (10] .to_a
michau

11

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.


9

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}

11
Listeyi yalnızca bir kez geçiyor demek istiyorsun. Resmi tanıma göre giderseniz, O (n) eşittir O (2n). Sadece
nitpicking

1
@Daniel Harper :) Sadece haklı değilsiniz, aynı zamanda ortalama durum için, listeyi bir kez bazı girişleri atmak için ve ardından bir işlem gerçekleştirmek için bir kez çevirmek ortalama durumlarda gerçekten daha iyi olabilir :)
Pedro Rolo

Başka bir deyişle, yaptığınız 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
John La Rooy

8

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.


7

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.


4

Bu daha kısa:

[1,2,3,4,5,6].select(&:even?).map{|x| x*3}

2
Ya da daha fazla noktasız mükemmellik için[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
Jörg W Mittag

4
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact
=> [6, 12, 18]

Bu benim için çalışıyor. Aynı zamanda temiz. Evet, aynı mapşey ama bence collectkodu daha anlaşılır kılıyor.


select(&:even?).map()

aslında aşağıda gördükten sonra daha iyi görünüyor.


2

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


2

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?}
Guss

1

Başka bir çözüm ama belki de en iyisi değil

some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }

veya

some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.push(x * 3) : nil }

0

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]

0

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 .



-4

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 .


7
Bu işe yaramıyor gibi görünüyor. En azından Ruby 1.8.6'da, [1,2,3,4,5,6] .select {| x | x * 3 eğer x% 2 == 0} [2, 4, 6] olarak değerlendirilirse Numaralandırılabilir # select, yalnızca bloğun AFAIK olarak çıktı verdiği değer değil, doğru veya yanlış olarak değerlendirilip değerlendirilmediğiyle ilgilenir.
Greg Campbell
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.