Ruby tally と dig

先日やった問題で、もっといいやり方を教えてもらったのでやってみる。

masuyama13.hatenablog.com

3. 配列(リスト)の重複カウント (paizaランク D 相当)

指定された配列(リスト)の定義の中で、同じ要素の数をカウントして、その数を出力してください。

入力される値
なし

"HND", "NRT", "KIX", "NGO", "NGO", "NGO", "NGO", "NGO"
を要素に持つ配列(リスト)をプログラムで定義し、使用すること。
ただし、2つ以上同じ要素が出現するのは、1種類の文字列についてだけです。

期待する出力
同じ要素の数をカウントして、その数を出力してください。

元々の自分の解答

ary = ["HND", "NRT", "KIX", "NGO", "NGO", "NGO", "NGO", "NGO"]

def find_plural(array)
  plural =
    array.group_by(&:itself).map do | k, v |
      [k, v.size]
    end.to_h.select do | k, v |
      v > 1
    end
  plural.values
end

puts find_plural(ary)
#=> 5

tally!!

前になんかそういうメソッドがあるという話を見かけた気がして探し回ってドキュメントも検索してみたが見つけられなかったやつ。教えてもらってありがたい。

ということで、やり直してみた。

tally は、レシーバの要素ごとの数を数えて Hash で返してくれるメソッド。

Enumerable#tally (Ruby 2.7.0 リファレンスマニュアル)

array = ['HND', 'NRT', 'KIX', 'NGO', 'NGO', 'NGO', 'NGO', 'NGO']
p array.tally
#=> {"HND"=>1, "NRT"=>1, "KIX"=>1, "NGO"=>5}

返ってくるハッシュから、value が 1 より大きいものを探す。

p array.tally.find { |k, v| v > 1 }
#=> ["NGO", 5]

該当するものが配列として返ってくるので、インデックスが1の要素を取り出して出力すれば OK。

解答コード

array = ['HND', 'NRT', 'KIX', 'NGO', 'NGO', 'NGO', 'NGO', 'NGO']
puts array.tally.find { |k, v| v > 1 }[1]
#=> 5

dig

今回の問題の場合はこれで正解できたが、find で該当するものがないケースだと、下のようにエラーになる。

array = ['HND', 'NRT', 'KIX', 'NGO']
puts array.tally.find { |k, v| v > 1 }[1]
#=> Main.rb:2:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)

nil[]というメソッドはない、というエラー。

p array.tally.find { |k, v| v > 1 }
#=> nil

find で該当するものがない場合はnilが返ってくるため。

dig というメソッドを使えば、エラーを回避できる。

array = ['HND', 'NRT', 'KIX', 'NGO']
puts array.tally.find { |k, v| v > 1 }&.dig(1)

Array#dig (Ruby 2.7.0 リファレンスマニュアル)

余談:5日前から使っている Scrapbox が快適すぎて、ブログから移行しようと思い始めた(ブログをやめるわけではないけど)。