TheHamCo
TheHamCo

Reputation: 13

Ruby combined comparison operator (<=>) and min / max / minmax functions

I understand #max, #min, #minmax. I understand <=>. But how does it work in a block within one of those functions?

That is, what is happening in the third line below? What is <=> doing in #min?

a = %w(albatross dog horse)
a.min                                   #=> "albatross"
a.min { |a, b| a.length <=> b.length }  #=> "dog"

example from http://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-min

What behavior would it have for an array of numbers?

Upvotes: 1

Views: 770

Answers (2)

Stefan
Stefan

Reputation: 114218

min passes two elements a and b from the array to the block and the block is expected to return -1, 0, or +1 depending on whether a is less than, equal to, or greater than b. The "spaceship" operator <=> returns these -1, 0, or +1 values.

The algorithm is easy. Given a comparison function:

cmp = -> (a, b) { a.length <=> b.length }

We start by comparing the 1st with the 2nd element:

cmp.call 'albatros', 'dog'
#=> 1

1 means 'albatros' is greater than 'dog'. We continue with the lesser value, i.e. 'dog' and compare it with the 3rd element:

cmp.call 'dog', 'horse'
#=> -1

-1 means 'dog' is less than 'horse'. There are no more elements, so 'dog' is the result.

You could also implement this algorithm in Ruby:

def my_min(ary)
  ary.inject { |min, x| yield(x, min) == -1 ? x : min }
end

ary = %w(albatross dog horse)
my_min(ary) { |a, b| a.length <=> b.length }
#=> "dog"

Upvotes: 2

Ajedi32
Ajedi32

Reputation: 48418

As you've probably already seen, the documentation for the min method says:

min(n) → array

min(n) {| a,b | block } → array

Returns the object in enum with the minimum value. The first form assumes all objects implement Comparable; the second uses the block to return a <=> b.

This means that, in the first form, min is calling the <=> method on objects in the array and using the result to determine which element is the smallest.

In the second form, min instead calls the block with both of the elements it wants to compare, and uses the block's return value to determine which element is the smallest. Basically, it's using the block as if it were an implementation of the <=> operator. So x.min {|a,b| a <=> b } would be equivalent to x.min.

In the example given (a.min { |a, b| a.length <=> b.length } #=> "dog"), this means that instead of comparing each element to determine sort order, it's comparing the lengths of each element to make that determination. Since "dog" is the shortest string in the list, that's the value that gets returned by min. max, minmax, and sort behave similarly.

Note that the example there is a bit contrived, since you could just use min_by in that situation to achieve the same result with simpler code: a.min_by { |x| x.length }. If you want more fine-grained control though when determining sort order, using min with a block might be appropriate.

What behavior would it have for an array of numbers?

min behaves the same way regardless of what the array contains. In this case though using the block { |a, b| a.length <=> b.length } wouldn't work since numbers don't have a length method on them. Here's a better example for numbers, which sorts by smallest to biggest, but always counts odd numbers as being bigger than even numbers:

[2, 10, 9, 7, 6, 1, 5, 3, 8, 4].sort do |a, b|
  if a.odd? && b.even?
    1
  elsif a.even? && b.odd?
    -1
  else
    a <=> b
  end
end

Result:

[2, 4, 6, 8, 10, 1, 3, 5, 7, 9]

Notice how even numbers are sorted before odd numbers in the final array? That's the result of the block we passed to sort. The behavior is similar for min, max, and minmax.

Upvotes: 6

Related Questions