Ruby dizisindeki aynı dizge elemanlarını nasıl sayılır


91

Şunlara sahibim Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

Her bir özdeş öğe için bir sayımı nasıl üretirim ?

Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?

veya bir hash üretin Nerede:

Nerede: hash = {"Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1}


2
Ruby 2.7'den itibaren kullanabilirsiniz Enumerable#tally. Daha fazla bilgi burada .
SRack

Yanıtlar:


82
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = Hash.new(0)
names.each { |name| counts[name] += 1 }
# => {"Jason" => 2, "Teresa" => 1, ....

127
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}

sana verir

{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

3
+1 Seçilen cevap gibi, ancak ben enjekte kullanmayı tercih ediyorum ve "harici" değişken kullanmıyorum.

18
Onun each_with_objectyerine kullanırsanız , blokta inject( ;total) dönmek zorunda değilsiniz .
mfilej

12
Gelecek nesil için @mfilej şu anlama geliyor:array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
Gon Zifroni

2
Ruby 2.7 itibaren, basitçe yapabilirsiniz: names.tally.
Hallgeir Wilhelmsen

99

Ruby v2.7 + (en son)

Ruby v2.7.0'dan (Aralık 2019'da yayınlandı) itibaren, temel dil artık şunları içeriyor Enumerable#tally- özellikle bu sorun için tasarlanmış yeni bir yöntem :

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.tally
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.4 + (şu anda destekleniyor, ancak daha eski)

Aşağıdaki kod, bu soru ilk sorulduğunda (Şubat 2011) standart Ruby'de mümkün değildi, çünkü kullandı:

Ruby'ye yapılan bu modern eklemeler aşağıdaki uygulamayı mümkün kılar:

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.group_by(&:itself).transform_values(&:count)
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.2 + (kullanımdan kaldırıldı)

Yukarıda belirtilen Hash#transform_valuesyönteme erişim olmadan daha eski bir Ruby sürümü kullanıyorsanız Array#to_h, bunun yerine Ruby v2.1.0'a (Aralık 2013'te piyasaya sürüldü) eklenmiş olanı kullanabilirsiniz :

names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Daha eski ruby ​​sürümleri için ( <= 2.1), bunu çözmenin birkaç yolu vardır, ancak (bence) kesin bir "en iyi" yol yoktur. Bu gönderiye verilen diğer cevapları görün.


Göndermek üzereydim: P. Kullanma arasında gözle görülür bir fark var mı countyerine size/ ' length?
buz ツ

1
@SagarPandya Hayır, fark yok. Farklı olarak Array#sizeve Array#length, Array#count olabilir , isteğe bağlı bir argüman ya da blok almak; ancak ikisiyle de kullanılmazsa uygulaması aynıdır. Daha spesifik olarak, üç yöntem LONG2NUM(RARRAY_LEN(ary))de başlık altında çağırır : sayı / uzunluk
Tom Lord

1
Bu, deyimsel Ruby'nin çok güzel bir örneğidir. Mükemmel cevap.
slhck

1
Ekstra kredi! .group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
Abram

2
@ Abram yapabilirsin sort_by{ |k, v| -v}, gerek yok reverse! ;-)
Sony Santos

26

Artık Ruby 2.2.0'ı kullanarak bu itselfyöntemden yararlanabilirsiniz .

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = {}
names.group_by(&:itself).each { |k,v| counts[k] = v.length }
# counts > {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

3
Kabul ediyorum, ancak isimleri biraz tercih ederim.group_by (&: kendisi) .map {| k, v | [k, v.count]}. to_h, böylece bir hash nesnesi bildirmek zorunda kalmazsınız
Andy Day

8
@andrewkday Bunu bir adım daha ileri götürerek ruby ​​v2.4 Hash#transform_values, kodunuzu daha da basitleştirmemizi sağlayan yöntemi ekledi :names.group_by(&:itself).transform_values(&:count)
Tom Lord

Ayrıca, bu çok ince bir noktadır (muhtemelen gelecekteki okuyucular için geçerli değildir!), Ancak kodunuzun da kullandığını unutmayın Array#to_h- ki bu Ruby v2.1.0'a eklenmiştir (Aralık 2013'te yayınlandı - yani orijinal sorudan neredeyse 3 yıl sonra) soruldu!)
Tom Lord

17

Bunu yapan bir veri yapısı aslında var: MultiSet.

Ne yazık ki, MultiSetRuby çekirdek kitaplığında veya standart kitaplıkta uygulama yoktur , ancak web'de dolaşan birkaç uygulama vardır.

Bu, bir veri yapısı seçiminin bir algoritmayı nasıl basitleştirebileceğinin harika bir örneğidir. Aslında, bu özel örnekte, algoritma tamamen ortadan kalkıyor. Kelimenin tam anlamıyla sadece:

Multiset.new(*names)

Ve bu kadar. Örnek, https://GitHub.Com/Josh/Multimap/ kullanarak :

require 'multiset'

names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison]

histogram = Multiset.new(*names)
# => #<Multiset: {"Jason", "Jason", "Teresa", "Judah", "Judah", "Judah", "Michelle", "Allison"}>

histogram.multiplicity('Judah')
# => 3

Http://maraigue.hhiro.net/multiset/index-en.php kullanarak örnek :

require 'multiset'

names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison]

histogram = Multiset[*names]
# => #<Multiset:#2 'Jason', #1 'Teresa', #3 'Judah', #1 'Michelle', #1 'Allison'>

MultiSet kavramı matematikten mi yoksa başka bir programlama dilinden mi kaynaklanıyor?
Andrew Grimm

2
@Andrew Grimm: Hem "multiset" kelimesi (de Bruijn, 1970'ler) ve kavram (Dedekind 1888) matematikte ortaya çıktı. Multisetkatı matematiksel kurallarla yönetilir ve bazı önemli yasalar yapsa da, tipik küme işlemlerini (birleşim, kesişim, tamamlama, ...) çoğunlukla "normal" matematiksel küme teorisinin aksiyomları, yasaları ve teoremleriyle tutarlı bir şekilde destekler. değil sen MULTISETS onları genelleme çalıştığınızda tutun. Ama bu konuyu anlamamın çok ötesinde. Bunları matematiksel bir kavram olarak değil, bir programlama veri yapısı olarak kullanıyorum.
Jörg W Mittag

Bu noktayı biraz genişletmek gerekirse : "... aksiyomlarla çoğunlukla tutarlı bir şekilde ..." : "Normal" kümeler genellikle resmi olarak "Zermelo-Frankel küme teorisi" adı verilen bir dizi aksiyom (varsayım) ile tanımlanır. ". Bununla birlikte, bu aksiyomlardan biri: genişlemenin aksiyomu, bir kümenin kesin olarak üyeleri tarafından tanımlandığını belirtir - örneğin {A, A, B} = {A, B}. Bu açıkça çoklu kümelerin tanımına aykırıdır!
Tom Lord

... Fakat (bu bir yazılım forum değil ileri matematik! Olduğu gibi) çok fazla ayrıntıya girmeden, tek yapabilirsiniz resmen matematiksel Crisp setleri, Peano aksiyomları ve diğer MultiSet özgü aksiyomların aksiyomlarına aracılığıyla çoklu setleri tanımlar.
Tom Lord

13

Enumberable#each_with_object sizi son hash'i geri vermekten kurtarır.

names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }

İadeler:

=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Kabul ediyorum, each_with_objectvaryant bana göre daha okunabilirinject
Lev Lukomsky

9

Ruby 2.7+

Ruby 2.7 Enumerable#tallytam da bu amaç için geliyor. Burada iyi bir özet var .

Bu kullanım durumunda:

array.tally
# => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }

Yayınlanan özelliklerle ilgili dokümanlar burada .

Umarım bu birine yardımcı olur!


Harika haberler!
tadman

6

Bu çalışıyor.

arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
result = {}
arr.uniq.each{|element| result[element] = arr.count(element)}

2
+1 Farklı bir yaklaşım için - her ne kadar bu daha kötü bir teorik karmaşıklığa sahip olsa da - O(n^2)(bazı değerleri için önemli olacak n) ve fazladan iş yapıyor (örneğin "Yahuda" için 3x sayılmalıdır) !. Bunun eachyerine map(harita sonucu

Bunun için teşekkürler! Haritayı her biri için değiştirdim.Ayrıca, diziyi incelemeden önce benzersiz hale getirdim. Belki şimdi karmaşıklık sorunu çözüldü?
Shreyas

6

Aşağıdakiler biraz daha işlevsel bir programlama stilidir:

array_with_lower_case_a = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
hash_grouped_by_name = array_with_lower_case_a.group_by {|name| name}
hash_grouped_by_name.map{|name, names| [name, names.length]}
=> [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]

Bunun bir avantajı, group_byeşdeğer öğeleri gruplamak için kullanabilmeniz, ancak tam olarak aynı öğeleri kullanabilmenizdir:

another_array_with_lower_case_a = ["Jason", "jason", "Teresa", "Judah", "Michelle", "Judah Ben-Hur", "JUDAH", "Allison"]
hash_grouped_by_first_name = another_array_with_lower_case_a.group_by {|name| name.split(" ").first.capitalize}
hash_grouped_by_first_name.map{|first_name, names| [first_name, names.length]}
=> [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]

İşlevsel programlama duydum mu? +1 :-) Hafıza açısından verimli olmadığı tartışılsa da, bu kesinlikle en iyi yoldur. Ayrıca Fasetlerin Numaralandırılabilir # frekansına sahip olduğuna dikkat edin.
tokland

5
a = [1, 2, 3, 2, 5, 6, 7, 5, 5]
a.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 }

# => {1=>1, 2=>2, 3=>1, 5=>3, 6=>1, 7=>1}

Kredi Frank Wambutt


3
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Hash[names.group_by{|i| i }.map{|k,v| [k,v.size]}]
# => {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

2

Burada birçok harika uygulama var.

Ancak yeni başlayan biri olarak bunu okuması ve uygulaması en kolay yöntem olarak görüyorum

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

name_frequency_hash = {}

names.each do |name|
  count = names.count(name)
  name_frequency_hash[name] = count  
end
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Attığımız adımlar:

  • hash'i biz yarattık
  • namesdizinin üzerinden geçtik
  • namesdizide her ismin kaç kez göründüğünü saydık
  • nameve değerini kullanarak bir anahtar oluşturduk .count

Biraz daha ayrıntılı olabilir (ve performans açısından, geçersiz kılan anahtarlarla bazı gereksiz işler yapacaksınız), ancak bence ulaşmak istediğiniz şeyi okumak ve anlamak daha kolay


2
Bunun kabul edilen cevaptan daha kolay okunmasının ne kadar kolay olduğunu anlamıyorum ve açıkça daha kötü bir tasarım (çok fazla gereksiz iş yapmak).
Tom Lord

@Tom Lord - Performans konusunda sizinle aynı fikirdeyim (cevabımda bundan bahsetmiştim bile) - ancak gerekli olan gerçek kodu ve adımları anlamaya çalışan bir acemi olarak, daha ayrıntılı olmanın ve daha sonra iyileştirmek için yeniden düzenleme yapmanın yardımcı olduğunu görüyorum performans ve kodu daha açıklayıcı hale getirin
Sami Birnbaum

1
@ SamiBirnbaum'a biraz katılıyorum. Bu, neredeyse hiç özel yakut bilgisi kullanmayan tek kişidir Hash.new(0). Sözde koda en yakın olanı. Bu okunabilirlik açısından iyi bir şey olabilir ama aynı zamanda gereksiz işler yapmak okuyucular için okunabilirliğe zarar verebilir çünkü daha karmaşık durumlarda, neden yapıldığını anlamaya çalışırken delireceklerini düşünerek biraz zaman harcayacaklar.
Adamantish

1

Bu bir cevaptan çok bir yorumdur, ancak bir yorum bunu doğru yapmaz. Bunu yaparsanız Array = foo, en az bir IRB uygulamasını çökertirsiniz:

C:\Documents and Settings\a.grimm>irb
irb(main):001:0> Array = nil
(irb):1: warning: already initialized constant Array
=> nil
C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3177:in `rl_redisplay': undefined method `new' for nil:NilClass (NoMethodError)
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3873:in `readline_internal_setup'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4704:in `readline_internal'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4727:in `readline'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/readline.rb:40:in `readline'
        from C:/Ruby19/lib/ruby/1.9.1/irb/input-method.rb:115:in `gets'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:139:in `block (2 levels) in eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:271:in `signal_status'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:138:in `block in eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `call'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `buf_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:103:in `getc'
        from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:205:in `match_io'
        from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:75:in `match'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:287:in `token'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:263:in `lex'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:234:in `block (2 levels) in each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `loop'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `block in each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `catch'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:153:in `eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:70:in `block in start'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `catch'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `start'
        from C:/Ruby19/bin/irb:12:in `<main>'

C:\Documents and Settings\a.grimm>

Çünkü Arraybir sınıftır.


1
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}

Geçen süre 0,028 milisaniye

ilginç bir şekilde, stupidgeek'in uygulaması kıyaslandı:

Geçen süre 0,041 milisaniye

ve kazanan cevap:

Geçen süre 0,011 milisaniye

:)

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.