Rubyのオブジェクト配列にて、各要素の同一属性で同じ値が何個あるかを数える

Rubyのオブジェクト配列にて、各要素の同一属性で同じ値が何個あるかを数えたくなる機会がありました。

例えば

class Apple
  attr_reader :name, :grower

  def initialize(name, grower)
    @name = name
    @grower = grower
  end
end

apples = [
  Apple.new('シナノゴールド', 'Aさん'),
  Apple.new('秋映', 'Aさん'),
  Apple.new('シナノゴールド', 'Bさん'),
  Apple.new('秋映', 'Cさん'),
  Apple.new('奥州ロマン', 'Cさん'),
  Apple.new('シナノゴールド', 'Dさん'),
]

というオブジェクト配列があったときに、りんごの名前 (name) ごとの生産者(grower) の人数の取得方法を知りたくなったのでした。

そこで、調べたときのメモを残します。

 
目次

 

環境

 

gropu_by + transform_values を使う

調べてみたところ、以下の記事を参考になりました。ありがとうございました。
配列に同じ要素が何個あるかを数える - patorashのブログ

 
そこで、記事に記載のあった通り、 gropu_by 後に transform_values を使って実装してみました。

 
すると、欲しい結果が得られました。

apples = [
  Apple.new('シナノゴールド', 'Aさん'),
  Apple.new('秋映', 'Aさん'),
  Apple.new('シナノゴールド', 'Bさん'),
  Apple.new('秋映', 'Cさん'),
  Apple.new('奥州ロマン', 'Cさん'),
  Apple.new('シナノゴールド', 'Dさん'),
]

p apples.map(&:name).group_by(&:itself).transform_values(&:size)
# => {"シナノゴールド"=>3, "秋映"=>2, "奥州ロマン"=>1}

 
ただ、これだけだとメソッドチェーンの途中でどのような形になっているか、まだ理解できていませんでした。

そこで、途中結果を表示してみた上で、自分向けのメモも残してみます。

 

途中経過のメモ

まずは map を使い、オブジェクトの name 属性の配列にします。

r1 = apples.map(&:name)
p r1
# => ["シナノゴールド", "秋映", "シナノゴールド", "秋映", "奥州ロマン", "シナノゴールド"]

 
続いて、group_by(&:itself) にて

  • キー
    • ブロックの中で、オブジェクトの itself メソッドを使うことで得られた、 シナノゴールド秋映奥州ロマン
    • itself の結果を、キーごとに配列化

という形のハッシュにします。

r2 = r1.group_by(&:itself)
p r2
# => {"シナノゴールド"=>["シナノゴールド", "シナノゴールド", "シナノゴールド"], "秋映"=>["秋映", "秋映"], "奥州ロマン"=>["奥州ロマン"]}

 
最後に、 transform_values(&:size) で、ハッシュの値をブロックの結果 (&:size による配列の要素数) へと差し替えます。

p r2.transform_values(&:size)
# => {"シナノゴールド"=>3, "秋映"=>2, "奥州ロマン"=>1}

 

ソースコード全体

以下のソースコードmain.rb で保存し、ruby main.rb と実行することで、同じ結果が得られます。

class Apple
  attr_reader :name, :grower

  def initialize(name, grower)
    @name = name
    @grower = grower
  end
end

apples = [
  Apple.new('シナノゴールド', 'Aさん'),
  Apple.new('秋映', 'Aさん'),
  Apple.new('シナノゴールド', 'Bさん'),
  Apple.new('秋映', 'Cさん'),
  Apple.new('奥州ロマン', 'Cさん'),
  Apple.new('シナノゴールド', 'Dさん'),
]

# 途中経過版
r1 = apples.map(&:name)
p r1
# => ["シナノゴールド", "秋映", "シナノゴールド", "秋映", "奥州ロマン", "シナノゴールド"]

r2 = r1.group_by(&:itself)
p r2
# => {"シナノゴールド"=>["シナノゴールド", "シナノゴールド", "シナノゴールド"], "秋映"=>["秋映", "秋映"], "奥州ロマン"=>["奥州ロマン"]}

p r2.transform_values(&:size)
# => {"シナノゴールド"=>3, "秋映"=>2, "奥州ロマン"=>1}

# チェーン版
puts '=' * 30
p apples.map(&:name).group_by(&:itself).transform_values(&:size)

 
実行結果は以下です。

% ruby --version   
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin21]

% ruby main.rb     
["シナノゴールド", "秋映", "シナノゴールド", "秋映", "奥州ロマン", "シナノゴールド"]
{"シナノゴールド"=>["シナノゴールド", "シナノゴールド", "シナノゴールド"], "秋映"=>["秋映", "秋映"], "奥州ロマン"=>["奥州ロマン"]}
{"シナノゴールド"=>3, "秋映"=>2, "奥州ロマン"=>1}
==============================
{"シナノゴールド"=>3, "秋映"=>2, "奥州ロマン"=>1}