Оператор ruby's <=> и метод сортировки

player1 = Player.new("moe")
player2 = Player.new("larry",60)
player3 = Player.new("curly", 125)
@players = [player1, player2, player3]

Выше я создал несколько объектов игрока и добавил их в ранее пустой массив @players.

Затем я переопределил <=>:

def <=>(other)
    other.score <=> score
end 

Затем я могу запустить этот код

@players.sort

и мой массив объектов игрока в @players отсортирован с высокой оценки на низкую оценку. Я думаю, это выглядит немного черным ящиком для меня. Мне немного непонятно, что здесь происходит. Как мне узнать, что происходит за кулисами?

Все, что я знаю, это то, что если вы берете два значения и используете оператор космического корабля / общий оператор сравнения:

2 <=> 1   
=> 1

1 <=> 2
=> -1

1 <=> 1
=>0

Иногда кажется, что в Ruby происходит много вещей более низкого уровня, которые я не могу видеть на высоком уровне, на котором я программирую. Это кажется естественным... но этот случай кажется особенно удаленным из действий более низкого уровня метод сортировки. Как сортировка использует оператор космического корабля? Почему переопределение оператора космического корабля так, как мы это сделали, позволяет нам сортировать объекты сейчас?

3 ответа

Решение

Прежде чем вы сможете понять сортировку объектов. Вы должны понимать метод.sort в Ruby. Если бы вам пришлось отсортировать 5 карт с номерами на них, вы могли бы взглянуть на все из них, легко найти самую низкую и просто выбрать эту в качестве своей первой карты (при условии, что вы сортируете от самой низкой к самой высокой, что в Ruby). делает). Когда ваш мозг сортируется, он может смотреть на все и сортировать оттуда.

Здесь есть 2 основных элемента путаницы, которые редко рассматриваются:

1) Руби не может сортировать так, как вы думаете о слове "сортировать". Ruby может только "поменять" элементы массива, и он может "сравнить" элементы массива.

2) Ruby использует оператор сравнения, называемый космическим кораблем, чтобы присвоить числа, чтобы помочь ему "сортировать". Эти цифры -1,0,1. Люди ошибочно думают, что эти 3 числа помогают ему "сортировать" (например, если бы существовал массив с 3 числами, например 10,20,30, то 10 было бы -1, 20 a 0 и 30 a 1, а Ruby просто упрощает сортировку, уменьшая ее до -1,0,1. Это неправильно. Ruby не может "сортировать". Он не может только сравнивать).

Посмотрите на оператора космического корабля. Это 3 отдельных оператора, объединенных в один, <, the = и>. Когда Ruby сравнивает две переменные, это приводит к одному из этих чисел.

Оператор космического корабля

Тем не менее, что означает "результаты"? Это не означает, что одной из переменных присваивается 0,1,-1. Это просто способ, которым Ruby может взять две переменные и что-то с ними сделать. Теперь, если вы просто запустите:

puts 4 <=> 5

Вы получите результат -1, поскольку любая "часть" (например, <, = или>) оператора сравнения (космический корабль) имеет значение "истина", получает присвоенное ему число (как видно на рисунке выше), Когда Ruby видит это <=> с массивом, у него есть 2 вещи, которые он будет делать только с массивом: оставьте массив в покое ИЛИ поменяйте местами элементы массива.

Если Ruby использует <=> и получает 1, он поменяет 2 элемента массива. Если Ruby получит результат -1 или 0, он оставит массив в покое.

Например, если Ruby видит массив [2,1]. При использовании метода sort эти цифры будут отображаться как 2<=>1. Так как часть космического корабля (если вы хотите думать об этом так), которая является истинной, является> (т.е. 2>1 является истинной), результатом будет '1' из Ruby. Когда Ruby видит 1 результат от космического корабля, он меняет 2 элемента массива. Теперь массив равен [1,2].

Надеемся, что в этот момент вы видите, что Ruby сравнивает только с оператором <=>, а затем заменяет (или оставляет в покое) 2 элемента в массиве, который он сравнивает.

Понимать, что метод.sort - это итеративный метод, означающий, что это метод, который многократно выполняет блок кода. Большинство людей знакомятся с методом.sort только после того, как они увидели такие методы, как.each или.upto (вам не нужно знать, что они делают, если вы о них не слышали), но эти методы проходят через Массив 1 раз ТОЛЬКО. Метод.sort отличается тем, что он будет проходить через ваш массив столько раз, сколько необходимо для сортировки (под сортировкой мы подразумеваем сравнение и обмен).

Чтобы убедиться, что вы понимаете синтаксис Ruby:

foo = [4, 5, 6]
puts foo.sort {|a,b| a <=> b}

Блок кода (в окружении {}) - это то, что Ruby будет делать в любом случае, когда сортирует по убыванию. Но достаточно сказать, что первая итерация метода.sort назначит переменные между каналами (a, b) первыми двумя элементами массива. Таким образом, для первой итерации a=4 и b=5, а так как 4<5, это приводит к -1, что в Ruby означает НЕ поменять местами массив. Он делает это для второй итерации, то есть a=5 и b=6, видит, что 5<6, приводит к -1 и оставляет массив в покое. Поскольку все результаты <=> были равны -1, Ruby прекращает цикл и чувствует, что массив отсортирован в [4,5,6].

Мы можем сортировать от высокого к низкому, просто меняя порядок переменных.

bar = [5, 1, 9]
puts bar.sort {|a,b| b <=> a}   

Вот что делает Ruby:

Итерация 1: Массив [5,1, 9]. а = 5, б =1. Руби видит b<=>a и говорит, что 1 < 5? Да. Это приводит к -1. Оставайся таким же.

Итерация 2: Массив [5,1,9]. а =1, б =9. Руби видит b<=>a и говорит, что 9 < 1? Нет. Это приводит к 1. Поменяйте местами 2 элемента массива. Массив теперь [5,9,1]

Итерация 3: Массив [5,9, 1]. Начиная с b/c, в массиве был результат +1, прежде чем пройти через все это. а = 5, б =9. Руби видит b<=>a, говорит, что 9<5? Это приводит к 1. Обмен. [9, 5, 1]

Итерация 4: Массив [9,5,1]. а = 5, б =1. Руби видит b<=>a, говорит 1<5? Да. Это приводит к -1. Поэтому обмен не выполняется. Готово. [9,5,1].

Представьте себе массив с номером 50 для первых 999 элементов и 1 для элемента 1000. Вы полностью поймете метод сортировки, если поймете, что Ruby должен был пройти через этот массив тысячи раз, выполняя ту же самую простую процедуру сравнения и замены для сдвига это 1 вплоть до начала массива.

Теперь мы можем, наконец, взглянуть на.sort, когда дело доходит до объекта.

def <=>(other)
    other.score <=> score
end 

Теперь это должно иметь немного больше смысла. Когда метод.sort вызывается для объекта, например, когда вы запускаете:

@players.sort

он вызывает метод "def <=>" с параметром (например, "other"), который имеет текущий объект из @players (например, "независимо от того, является ли текущий объект экземпляра" @players ", так как это метод sort в конечном итоге он будет проходить через все элементы массива '@players'). Это так же, как когда вы пытаетесь запустить метод put для класса, он автоматически вызывает метод to_s внутри этого класса. То же самое для метода.sort, автоматически ищущего метод <=>.

Глядя на код внутри метода <=>, в этом классе должна быть переменная экземпляра.score (с методом доступа) или просто метод.score. И результат этого метода.score должен (надеюсь) быть строкой или числом - две вещи, которые ruby ​​может "отсортировать". Если это число, то Ruby использует операцию <=> 'sort', чтобы переставить все эти объекты, теперь, когда он знает, какую часть этих объектов сортировать (в данном случае это результат метода.score или переменной экземпляра).).

В качестве окончательного лабиринта, Ruby сортирует в алфавитном порядке, также преобразовывая его в числовые значения. Он просто считает, что любой букве присваивается код из ASCII (то есть, поскольку заглавные буквы имеют меньшие числовые значения на кодовой диаграмме ASCII, заглавные буквы будут отсортированы по умолчанию первыми).

Надеюсь это поможет!

В вашем примере

@players.sort

эквивалентно

@players.sort { |x, y| x <=> y }

Элементы сортируются в зависимости от возврата <=> метод. Если <=> возвращается -1 первый элемент сортируется перед вторым, если он возвращает 1 вторая сортируется перед первой. Если вы измените возвращаемое значение (например, поменяйте местами элементы), то порядок изменится в соответствии с возвращаемыми значениями.

сортировка на самом деле метод Enumerable, который опирается на реализацию <=>, Из самого документа Ruby:

Если используется Enumerable#max, #min или #sort, объекты в коллекции также должны реализовывать содержательный оператор <=>, так как эти методы основаны на упорядочении между членами коллекции.

Попробуй сам:

class Player
  attr_accessor :name, :score

  def initialize(name, score=0)
    @name = name
    @score = score  
  end

  def <=> other
    puts caller[0].inspect
    other.score <=> score
  end
end

player1 = Player.new("moe")
player2 = Player.new("larry",60)
player3 = Player.new("curly", 125)
@players = [player1, player2, player3]
puts @players.sort.inspect

#=> "player.rb:19:in `sort'"
#=> "player.rb:19:in `sort'"
#=> [#<Player:0x007fe87184bbb8 @name="curly", @score=125>, #<Player:0x007fe87184bc08 @name="larry", @score=60>, #<Player:0x007fe87184bc58 @name="moe", @score=0>]

Вы видите, когда мы используем sort на @players массив, объект Player называется с <=>, если вы не реализуете его, то, вероятно, получите:

player.rb: 14: в sort': comparison of Player with Player failed (ArgumentError) from player.rb:14:in'

Что имеет смысл, так как объект не знает, как иметь дело с <=>,

Другие вопросы по тегам