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) の人数の取得方法を知りたくなったのでした。
そこで、調べたときのメモを残します。
目次
環境
- Ruby 3.3.0
gropu_by + transform_values を使う
調べてみたところ、以下の記事を参考になりました。ありがとうございました。
配列に同じ要素が何個あるかを数える - patorashのブログ
そこで、記事に記載のあった通り、 gropu_by
後に transform_values
を使って実装してみました。
- Enumerable#group_by (Ruby 3.3 リファレンスマニュアル)
- Object#itself (Ruby 3.3 リファレンスマニュアル)
- Hash#transform_values (Ruby 3.3 リファレンスマニュアル)
- Array#length (Ruby 3.3 リファレンスマニュアル)
すると、欲しい結果が得られました。
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}