In Progress
Unit 1, Lesson 1
In Progress

Min By

Video transcript & code

Say we have a list of Marathon runners. Each has a name and a best time, in seconds.

Runner = Struct.new(:name, :time)

RUNNERS = [
  Runner.new("Catherine Ndereba", 8327),
  Runner.new("Paula Radcliff", 8125),
  Runner.new("Naoko Takahashi", 8386)
]

We want to find the runner with the best time, in order to solicit them for a lucrative sponsorship deal. This is easy enough; we'll just map over the objects and grab their times. Then we'll use #min to discover the fastest time. Simple and quick.

require "./runners"

RUNNERS.map(&:time)             # => [8327, 8125, 8386]
RUNNERS.map(&:time).min         # => 8125

There's just one teensy tinsy problem: now we have the fastest time, but we don't know which entry it belongs to. By mapping just the time values, we've lost the context of those attributes.

Remembering what we learned in episode #181, we switch tactics and decide to sort the list of runners by their time attributes, and then pick the first one in the sorted list.

require "./runners"

RUNNERS.sort_by(&:time)
# => [#<struct Runner name="Paula Radcliff", time=8125>,
#     #<struct Runner name="Catherine Ndereba", time=8327>,
#     #<struct Runner name="Naoko Takahashi", time=8386>]

RUNNERS.sort_by(&:time).first
# => #<struct Runner name="Paula Radcliff", time=8125>

This works nicely, but as usual in Ruby, we can do even better. Instead of a sort followed by #first, we can combine both into a single operation with #min_by.

require "./runners"

RUNNERS.min_by(&:time)
# => #<struct Runner name="Paula Radcliff", time=8125>

As you've probably guessed, there is also a matching #max_by method.

require "./runners"

RUNNERS.max_by(&:time)
# => #<struct Runner name="Naoko Takahashi", time=8386>

We've been using symbol-to-proc shorthands, but just like #sort_by, these methods can accept any block in order to determine the criterion for sorting. For instance, if for some reason we wanted to sort our runners by number of letters in their names, we could.

require "./runners"

RUNNERS.max_by{ |r| r.name.size }
# => #<struct Runner name="Catherine Ndereba", time=8327>

These min and max methods are part of the Enumerable module, so they are available on any Ruby collection. If instead of an array we have a hash of string keys to numeric values, we could find the key/value pair with the lowest value using #min_by and the #last attribute. This works because when hashes are iterated over, each key and value pair is represented in the form of a two-element array. The #last element of each pair is the value, in this case representing milligrams of caffeine.

caffeine = {
  "coffee"   => 200,
  "espresso" => 75,
  "tea"      => 70
}

caffeine.min_by(&:last)         # => ["tea", 70]

Bottom line, when we want to pick out the item in a collection which is highest or lowest in some property, #min_by and #max_by are great methods to know about. Happy hacking!

Responses