Комбинированный оператор сравнения Ruby (‹=›) и функции min/max/minmax

Я понимаю #max, #min, #minmax. Я понимаю ‹=>. Но как это работает в блоке одной из этих функций?

То есть, что происходит в третьей строке ниже? Что ‹=> делает в #min?

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

пример из http://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-min

Какое поведение он будет иметь для массива чисел?


person TheHamCo    schedule 04.09.2015    source источник


Ответы (2)


Как вы, наверное, уже видели, документация для метода min говорит:

min(n) → array

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

Возвращает объект в перечислении с минимальным значением. Первая форма предполагает, что все объекты реализуют Comparable; второй использует блок для возврата a <=> b.

Это означает, что в первой форме min вызывает метод <=> для объектов в массиве и использует результат, чтобы определить, какой элемент является наименьшим.

Во второй форме min вместо этого вызывает блок с обоими элементами, которые он хочет сравнить, и использует возвращаемое значение блока, чтобы определить, какой элемент является наименьшим. По сути, он использует блок, как если бы он был реализацией оператора <=>. Таким образом, x.min {|a,b| a <=> b } будет эквивалентно x.min.

В приведенном примере (a.min { |a, b| a.length <=> b.length } #=> "dog") это означает, что вместо сравнения каждого элемента для определения порядка сортировки он сравнивает длины каждого элемента, чтобы сделать это определение. Поскольку "dog" — самая короткая строка в списке, это значение возвращает min. max, minmax и sort ведут себя аналогично.

Обратите внимание, что приведенный здесь пример немного надуман, так как вы могли бы просто использовать min_by в этой ситуации, чтобы получить тот же результат с более простым кодом: a.min_by { |x| x.length }. Если вам нужен более детальный контроль при определении порядка сортировки, использование min с блоком может быть уместным.

Какое поведение он будет иметь для массива чисел?

min ведет себя одинаково независимо от того, что содержит массив. В этом случае использование блока { |a, b| a.length <=> b.length } не сработает, так как у чисел нет метода length. Вот лучший пример для чисел, который сортируется от меньшего к большему, но всегда считает нечетные числа больше четных:

[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

Результат:

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

Обратите внимание, как четные числа сортируются перед нечетными в конечном массиве? Это результат блока, который мы передали sort. Поведение аналогично для min, max и minmax.

person Ajedi32    schedule 04.09.2015
comment
Хороший ответ. Если вы вставите puts "a=#{a}, b=#{b}", будет напечатано следующее: "a=dog, b=albatross", затем "a=horse, b=dog". По сути, min начинает с предположения, что "dog" будет победителем, затем выполняются попарные сравнения, при этом b всегда относится к победителю до сих пор. - person Cary Swoveland; 05.09.2015

min передает два элемента a и b из массива в блок, и ожидается, что блок вернет -1, 0 или +1 в зависимости от того, меньше, равно или больше a b. Оператор "космического корабля" <=> возвращает эти значения -1, 0 или +1.

Алгоритм прост. Учитывая функцию сравнения:

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

Начнем со сравнения 1-го со 2-м элементом:

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

1 означает, что 'albatros' больше, чем 'dog'. Мы продолжаем с меньшим значением, то есть 'dog', и сравниваем его с 3-м элементом:

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

-1 означает, что 'dog' меньше 'horse'. Элементов больше нет, поэтому результатом является 'dog'.

Вы также можете реализовать этот алгоритм на 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"
person Stefan    schedule 04.09.2015