rubyguides

Array#max_by

arr.max_by { |item| ... }

Array#max_by returns the element whose block value is the largest under <=> comparison. The method itself is defined on Enumerable, and Array inherits it unchanged. Reach for it when you need the “best” element according to a derived value such as length, a hash key, or a computed score.

Signature

The method has four call shapes:

arr.max_by                          # => Enumerator
arr.max_by { |item| ... }           # => element | nil
arr.max_by(n)                       # => Array
arr.max_by(n) { |item| ... }        # => Array

The n-argument form was added in Ruby 2.2.0. With n, the method returns an Array of the top elements ordered by decreasing block value.

Return Value

With a block and no n, the method returns the array element whose block value is greatest, or nil if the array is empty. Without a block and no n, you get an Enumerator, which is chainable with with_index and friends. With n as the argument, you get an Array of the top n elements ordered by block value in descending order; an empty array returns [].

Examples

The longest word in a list

%w[cat elephant dog].max_by { |w| w.length }
# => "elephant"

The block returns a numeric length, and max_by compares those lengths via <=>. The original string still wins because max_by returns the element, not the block value. This pattern generalises to any block whose return value is comparable with <=>, which is most numeric, string, or symbol keys.

The record with the highest score

records = [
  { name: "Ada",   score: 92 },
  { name: "Bjorn", score: 87 },
  { name: "Cleo",  score: 95 }
]

records.max_by { |r| r[:score] }
# => {:name=>"Cleo", :score=>95}

Closest value to a target

max_by finds the maximum, so to find the value closest to a target, use a negated distance as the key. The negation flips the order, so smaller distances become larger (less negative) scores, and the highest of those wins. This trick is the standard way to repurpose a “maximum” search as a “closest match” search without sorting.

target = 50
[12, 47, 53, 60, 81].max_by { |n| -((n - target).abs) }
# => 47

When the distance is tied (47 and 53 are both 3 away), the first element in iteration order wins. If you need a true nearest neighbor, sort by (n - target).abs and pick the first.

The top N elements (Ruby 2.2+)

records.max_by(3) { |r| r[:score] }
# => [{:name=>"Cleo", :score=>95}, {:name=>"Ada", :score=>92}, {:name=>"Bjorn", :score=>87}]

Pass 0 to get an empty Array; pass a value larger than the array’s size and you get the whole array sorted by block value.

Enumerator form for chaining

Calling max_by without a block returns an Enumerator, which is the standard Enumerable behavior:

%w[ant bee cat].max_by.with_index { |w, i| w.length * 10 - i }
# => "ant"

with_index lowers the score of later elements slightly, breaking length ties in favor of earlier positions.

max_by vs max

Array#max compares the elements themselves using their own <=>. Array#max_by lets you supply a comparison key via the block. Use max_by whenever the rule for “largest” depends on something you have to compute, such as a string’s length, a hash value, a database column, or a score you derive on the fly. Use max when the elements already compare the way you want (numbers, strings, etc.).

[3, 1, 2].max                  # => 3
[3, 1, 2].max_by(&:itself)     # => 3

Gotchas

  • It returns the element, not the block value. If you want the block value too, recompute it on the result: best = arr.max_by { |x| score(x) }; [best, score(best)].
  • Ties go to the first element. The implementation only replaces the current best when the new key is strictly greater. If tie order matters, sort and pick, or pre-filter the array.
  • No block, no n returns an Enumerator. Forgetting the block in tight code surfaces as a NoMethodError later when the enumerator is consumed. Pass a block or call .first / .to_a explicitly.
  • The block value must be comparable via <=>. If the block can return different types (sometimes Integer, sometimes nil), Ruby raises ArgumentError: comparison of NilClass with Integer failed. Filter or coerce first.
  • The n-arg form may return more than n items on ties. This matches the documented Enumerable#max_by contract, so don’t rely on a strict length assertion without sorting.
  • The block runs once per element. O(n) calls, but if the block does heavy work (network calls, parsing), cache the key first with group_by or build a hash.

See Also