Ruby 3.1: Enumerable#tallyにカウント集計用のオプションハッシュを渡せる(翻訳)
enumeration(列挙)とは、オブジェクト内にあるものを順にたどることを指します。RubyにあるArray
やHash
やRange
などのクラスは、Enumerable
モジュールをインクルードすることで列挙の機能を獲得しています。
このEnumerable
モジュールには、#include?
や#count
や#map
や#select
や#uniq
など、私たちがよく使うさまざまなメソッドが提供されています。本記事で取り上げる#tally
メソッドもそのひとつです。#tally
は、コレクション内に存在する要素の出現回数をカウントして、すべての要素の出現回数を含むハッシュを返します。
Ruby 2.7で導入されたEnumerable#tally
についてもう少し知りたい方は、過去記事(英語)をご覧ください。
サンプルコード
文房具屋を例にとって考えてみましょう。店員は、店の商品を1個単位で販売します。
#tally
メソッドは、1週間の間に販売された商品のトータルの個数を集計するような問題に最適の候補となります。
店の顧客が、以下のように商品を個別に追加して注文をかけるとします。
order_one = %i{ pen pencil eraser sharpener pen pen }
order_two = %i{ sharpener eraser eraser eraser eraser sharpener sharpener sharpener sharpener }
変更前
各商品の数量を以下のように集計します。
tally_one = order_one.tally
# => { pen: 3, pencil: 1, eraser: 1, sharpener: 1 }
tally_two = order_two.tally
# => { sharpener: 5, eraser: 4 }
weekly_tally = (order_one + order_two).tally # order_one, order_two, ..., order_seven
# => { pen: 3, pencil: 1, eraser: 5, sharpener: 6 }
上の例でわかるように、消しゴムと鉛筆削りの個数が増えています。
ここではいったん配列order_one
と配列order_two
を結合し、それから改めて#tally
を呼んで結合後の注文個数の総計を求めています。
変更後
実行中に総計を算出するために、Ruby 3.1のEnumerable#tally
に出現回数集計用のオプションハッシュを渡せるようになりました(#4318)。
この場合、集計途中の項目数を保存する変数を用意して、それを#tally
メソッドに渡せるようになります。
order_one = %i{ pen pencil eraser sharpener pen pen }
order_two = %i{ sharpener eraser eraser eraser eraser sharpener sharpener sharpener sharpener }
weekly_tally = {}
weekly_tally = order_one.tally(weekly_tally)
# => {pen: 3, pencil: 1, eraser: 1, sharpener: 1}
weekly_tally = order_two.tally(weekly_tally)
# => {pen: 3, pencil: 1, eraser: 5, sharpener: 6}
# (以下同様)
上のRuby 3.1コード例でわかるように、項目の出現回数を保存するweekly_tally
ハッシュを引数として#tally
メソッドに渡しています。
order_two
にある消しゴムと鉛筆削りの個数がweekly_tally
ハッシュに追加され、追加後の項目とその個数の組み合わせを返しています。
メモ
#tally
メソッドにはデフォルト値付きのハッシュも渡せます。配列に既に存在するキーはデフォルトのハッシュ値を無視し、配列に存在しないキーはnil
の代わりにそのデフォルト値を返します。
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally(Hash.new 5)
# => { pen: 3, pencil: 1, eraser: 1, sharpener: 1 }
tally[:ruler]
# => 5
#tally
メソッドにはProc
も渡せます。配列内にキーが存在する場合はProc
を無視し、キーが存在しない場合はProc
が実行されます。
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally(Hash.new { puts "Proc called" })
# => { pen: 3, pencil: 1, eraser: 1, sharpener: 1 }
tally[:ruler]
Proc called
# => nil
- 配列に存在しないキーは、結果のハッシュとマージされます。たとえば、以下では
first_name
とlast_name
が配列order
にない場合にマージされています。
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally({ first_name: "Sam", last_name: "Example" }) # 可能
# => { first_name: "Sam", last_name: "Example", pen: 3, pencil: 1, eraser: 1, sharpener: 1 }
配列内に存在するキーは、整数型の値だけを取る必要があります。そうでない場合はTypeError
になります。
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally({ pencil: "colored" }) # できない
# => TypeError: wrong argument type String (expected Integer)
概要
原著者の許諾を得て翻訳・公開いたします。