プログラミング漫遊記

思ったことや、勉強したことをつらつらと。

Enumerableモジュールのメソッドツアー - max_byメソッド

Enumerableモジュールのメソッドを1記事1メソッドで雑に紹介していくコーナー第3弾です。

前回はmaxメソッドを紹介しました。

haruguchi-yuma.hatenablog.com

今回はmaxメソッドにとても似ているmax_byメソッドについて紹介します。

max_byメソッド

まずはるりま*1の確認から。

module Enumerable (Ruby 3.1 リファレンスマニュアル)

各要素を順番にブロックに渡して実行し、その評価結果を <=> で比較して、最大であった値に対応する元の要素、もしくは最大の n 要素が降順で入った配列を返します。 引数を指定しない形式では要素が存在しなければ nil を返します。引数を指定する形式では、空の配列を返します。該当する要素が複数存在する場合、どの要素を返すかは不定です。 Enumerable#max と Enumerable#max_by の違いは Enumerable#sort と Enumerable#sort_by の違いと同じです。

「各要素をブロックに渡して実行し、〜」とあるようにブロック付きで呼ぶのが基本となるメソッドみたいです。 maxメソッドと同じく、なんらかの観点で比較したときの、最大の要素を引数の数だけ取得するのが基本の使い方になります。

ちなみに、ブロックなしで呼び出した場合はEnumeratorオブジェクトが返ってきます。

では挙動を確認していきます。

irbで挙動の確認

Ruby 3.1.2で実行しています。

ブロックありでの呼び出し

# 一の位が最大である要素を取得
[8, 32,4].max_by{ |n| n % 10}
# => 8

# イメージ
8 % 10 <=> 32 % 10で比較
8が大きい
8 % 10 <=> 4 % 10 で比較
8が大きい
8を返す

# 最大の要素がない場合はnil
[].max_by{ |n| n }
=> nil

引数を指定すると最大の要素を引数の数だけ取得することができます。 また、返り値は配列になります。

# 文字数で比較
%w(panda monkey dog cat).max_by(2) { |str| str.length }
=> ["monkey", "panda"]

# 引数に負数を渡すとArgumentError
%w(panda monkey dog cat).max_by(-1) { |str| str.length }
`max_by': negative size (-1) (ArgumentError)

ちょっとだけ実用的な例を見てみましょう。 数学のテストの点数が一番高いユーザーのテスト結果を調べます。

students = [
  {name: 'taro', japanese: 30, math: 50, english: 90},
  {name: 'jiro', japanese: 80, math: 23, english: 20},
  {name: 'saburo', japanese: 88, math: 92, english: 79},
]

students.max_by { |student| student[:math] }
=> {:name=>"saburo", :japanese=>88, :math=>92, :english=>79}

ブロックなしで呼び出した場合

ブロックなしで呼び出した場合はEnumeratorオブジェクトを返します。

('a'..'c').max_by
=> <Enumerator: ...>


('a'..'c').max_by(2)
=> <Enumerator: ...>

maxメソッドとmax_byメソッドの違い

言葉で説明するのは難しいのですが、

maxメソッドは要素を2つずつ取り、明示的に<=>メソッドで比較を行います。(正確には左辺が大きいときは正の数、右辺が大きいときは負の数、等しいときは0を返せば<=>メソッドでなくても良いです)

一方、max_byメソッドは比較基準だけ示しておけば、内部的に<=>メソッドで比較を行ってくれます。

パフォーマンスの観点からどちらが優れているというのは未検証のため、言及は控えておきます。

# maxメソッドの場合
%w(panda monkey dog cat).max { |animal1, animal2| animal1.length <=> animal2.length }
=> "monkey"


# max_byメソッドの場合
%w(panda monkey dog cat).max_by { |animal| animal.length }
=> "monkey"

何かしらの観点で最大の要素を取得したい場合はmax_byを使った方が記述量は少なくすみそうです。

まとめ

  • max_byは最大の要素を取得する
  • ブロックを評価した後<=>で比較処理が行われる
  • 引数に正整数を渡すとその数だけ最大の要素を取得できる
  • ブロックなしで呼び出した場合はEnumeratorオブジェクトが返ってくる

以上です!

*1:Rubyリファレンスマニュアルの略