web-dev-qa-db-ja.com

Ruby配列内の同一の文字列要素をカウントする方法

次のArray = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]があります

同一要素ごとにカウントを生成するにはどうすればよいですか?

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

またはハッシュを生成ここで:

ここで:hash = {"Jason" => 2、 "Judah" => 3、 "Allison" => 1、 "Teresa" => 1、 "Michelle" => 1}

68
user398520
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = Hash.new(0)
names.each { |name| counts[name] += 1 }
# => {"Jason" => 2, "Teresa" => 1, ....
70
Dylan Markow
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}

あなたにあげる

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

Ruby v2.4 +(現在)

次のコードは、標準のRubyではこの質問が最初に尋ねられたとき(2011年2月)では使用できなかったため、不可能でした。

  • Object#itself 、これはRuby v2.2.0(2014年12月リリース)に追加されました。
  • Hash#transform_values 、これはRuby v2.4.0(2016年12月リリース)に追加されました。

Rubyへのこれらの最新の追加により、次の実装が可能になります。

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 +(非推奨)

古いRubyバージョンを使用している場合、上記のHash#transform_valuesメソッド、代わりに Array#to_h 、これはRuby v2.1.0(2013年12月リリース)に追加されました:

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

さらに古いRubyバージョン(<= 2.1)、これを解決する方法はいくつかありますが、(私の意見では)明確な「最良の」方法はありません。この投稿に対する他の回答を参照してください。


(2019年2月)編集:

Ruby v2.7 +(まだリリースされていません)

このコメントを将来のプレースホルダーと考えてください。 Ruby 2.7.0がリリースされ(2019年12月に予定)、メソッドがコア言語であるかどうかを確認します。

最近の 言語の拡張 がありました。すべてが計画どおりに進んだ場合、新しいメソッドEnumerable#tally、Ruby v2.7.0に追加。このメソッドは、この問題専用の新しい構文を追加します。

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

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

Ruby 2.2.0を使用すると、 itselfメソッド を活用できます。

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}
25
Ahmed Fahmy

実際にこれを行うデータ構造があります:MultiSet

残念ながら、Rubyコアライブラリまたは標準ライブラリにはMultiSet実装はありませんが、Webにはさまざまな実装があります。

これは、データ構造の選択がアルゴリズムをどのように単純化できるかの良い例です。実際、この特定の例では、アルゴリズムもcompletelyはなくなります。文字通りただ:

Multiset.new(*names)

以上です。 https://GitHub.Com/Josh/Multimap/ を使用した例:

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 を使用した例

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'>
16
Jörg W Mittag

Enumberable#each_with_object は、最終的なハッシュを返すことを防ぎます。

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

戻り値:

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

以下は、もう少し機能的なプログラミングスタイルです。

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]]

group_byの利点の1つは、それを使用して、同等ではあるがまったく同一ではないアイテムをグループ化できることです。

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]]
6
Andrew Grimm

これは動作します。

arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
result = {}
arr.uniq.each{|element| result[element] = arr.count(element)}
5
Shreyas
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}

クレジット Frank Wambutt

4
narzero

Ruby 2.7 +

Ruby 2.7は、まさにこの目的のためにEnumerable#tallyを導入しています。良い要約があります こちら

このユースケースでは:

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

リリースされている機能に関するドキュメントは こちら です。

これが誰かを助けることを願っています!

3
SRack

ここにはたくさんの素晴らしい実装があります。

ただし、初心者としては、これを読みやすく実装しやすいと考えています

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}

行った手順:

  • ハッシュを作成しました
  • names配列をループしました
  • names配列に各名前が現れる回数をカウントしました
  • nameを使用してキーを作成し、countを使用して値を作成しました

それはもう少し冗長かもしれません(そしてパフォーマンスに関しては、オーバーライドキーでいくつかの不必要な作業をするでしょう)が、私の意見では、あなたが達成したいことを読みやすく理解しやすいです

2
Sami Birnbaum
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
Arup Rakshit

これは回答というよりもコメントですが、コメントは正義ではありません。もしあなたがそうするなら Array = foo、IRBの少なくとも1つの実装をクラッシュさせます。

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>

Arrayがクラスだからです。

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

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

経過時間0.028ミリ秒

興味深いことに、stupidgeekの実装のベンチマーク:

経過時間0.041ミリ秒

そして勝利の答え:

経過時間0.011ミリ秒

:)

0