Techioz Blog

Ruby ハッシュの配列を反復処理し、その数を数えます。の価値が現れましたか?

概要

構造を持つハッシュの Ruby 配列があります

[{:fruit=>"apple"}, {:fruit=>"apple"}, {:fruit=>"apple"}, {:fruit=>"banana"}, {:fruit=>"banana"}, {:fruit=>"pineapple"}].

次のような最終ハッシュが必要です。

{:apple => 3,:banana=> 2,:pineapple=>1}

これを実現するにはどうすればよいでしょうか?

解決策

このようなヒストグラムを計算するための通常のトリックは、Enumerable#group_by を使用し、次に結果の配列をそのサイズに Enumerable#map して、最後にハッシュに変換することです。

ary = [{ fruit: 'apple' }, { fruit: 'apple' }, { fruit: 'apple' }, 
  { fruit: 'banana' }, { fruit: 'banana' }, { fruit: 'pineapple' }]

ary.
  group_by {|h| h.values.first }.
  map {|fruit, ary| [fruit.to_sym, ary.size]}.
  to_h
# => { apple: 3, banana: 2, pineapple: 1 }

しかし、もっと良い方法があります。MultiSet と呼ばれるデータ構造は、まさに必要なことを実行します。残念ながら、Ruby コア ライブラリまたは stdlib にはそのようなものはありませんが、いくつかの実装が浮遊しているのを見つけることができます。

Multiset[*ary.map {|el| el.values.first.to_sym}]
# => #<Multiset:#3 :apple, #2 :banana, #1 :pineapple>

ただし、シンボルから文字列へのハッシュの配列などのデータ構造がある場合、ほとんどの場合、そこに出てくるオブジェクトが存在します。結局のところ、Ruby はオブジェクト指向言語であり、シンボルのハッシュの配列を文字列に変換する言語ではありません。

class Fruit
  attr_reader :name

  def ==(other)
    name == other.name
  end

  def eql?(other)
    name.eql?(other.name)
  end

  def hash
    name.hash
  end

  def to_s
    name
  end

  def inspect
    "#<Fruit: #{name}>"
  end

  private

  attr_writer :name

  def initialize(name)
    self.name = name
  end
end

Multiset[Fruit.new('apple'), Fruit.new('apple'), Fruit.new('apple'), 
  Fruit.new('banana'), Fruit.new('banana'), Fruit.new('pineapple')]
# => #<Multiset:#3 #<Fruit: apple>, #2 #<Fruit: banana>, #1 #<Fruit: pineapple>>