Сортировка списка объектов по их атрибутам в Ruby

У меня есть список структур Fruit с именем basket. Каждая структура Fruit имеет name (строку) и calories (целое число). Я хотел бы отсортировать basket так, чтобы:

  1. Первыми появляются Fruit с наибольшим calories. Например, фрукт с 500 калориями появляется перед фруктом с 400 калориями.

  2. Если два Fruit имеют одинаковые calories, то Fruit, name которого идет первым по алфавиту, идет первым, игнорируя регистр. Например, если есть два фрукта с одинаковой калорийностью, один из них с названием «банан» будет стоять перед другим с названием «цитрусовые».

Определение Fruit не является чем-то, что я контролирую, поэтому я бы предпочел решение, которое не включает в себя смешивание чего-либо с Fruit или его изменение. Это возможно?


person Kyle Kaitan    schedule 12.05.2009    source источник


Ответы (4)


Простое решение

basket.sort_by { |f| [-f.calories, f.name] }

Конечно, если это канонический порядок сортировки для фруктов, то он должен определяться с использованием метода <=> и модуля Comparable, смешанного с Fruit.

person Gareth    schedule 12.05.2009
comment
Учитывает ли это тот факт, что f.name следует сравнивать без учета регистра? - person Kyle Kaitan; 12.05.2009
comment
чтобы игнорировать регистр, поменяйте местами f.name.downcase - person rampion; 12.05.2009
comment
Это плюс f.name.downcase было именно то, что мне было нужно. Спасибо! (Нет, для фруктов не существует определенного канонического порядка сортировки; просто так получилось, что именно так их нужно сортировать в данном конкретном случае.) - person Kyle Kaitan; 12.05.2009

Предположим, что ваша корзина представляет собой массив или его подкласс.

Быстрый путь

Enumerable.sort_by

Как указал Гарет, Enumerable (включенный в Array) имеет метод sort_by, который проходит через каждый элемент списка один раз. Это быстрее запускать и быстрее писать, как только вы освоитесь.

# -f.calories to sort descending
# name.downcase to do a case-insensitive sort
basket = basket.sort_by { |f| [-f.calories, f.name.downcase] }

Перл Путь

Array.sort

Исходя из опыта работы с Perl, мой первый порыв — схватить оператора космического корабля ‹=>. Нахальный маленький дьявол. Массив имеет сортировку и сортировку! методы, которые делают его очень полезным. Это решение медленнее, и, поскольку оно длиннее, оно с большей вероятностью приведет к ошибкам. Единственная причина использовать его — если вы имеете дело с людьми, незнакомыми с Ruby и не желающими найти правильный путь в StackOverflow.

baseket.sort! { |a,b|
  if a.calories == b.calories
    a.name.downcase <=> b.name.downcase
  else
    # Reverse the result to sort highest first.
    -(a.calories <=> b.calories)
  end
}
person Adrian Dunston    schedule 12.05.2009

См. Array#sort (документ по API). Вы можете передать блок, который возвращает -1, 0 или 1 для двух объектов Fruit, и ваш блок может определить эти значения, используя любые атрибуты, которые вам нравятся.

person Marc W    schedule 12.05.2009
comment
Проблема с «сортировкой» заключается в том, что она повторно оценивает блок несколько раз для каждой пары сравниваемых элементов. использование sort_by вычисляет ключ сравнения один раз для каждого элемента и сортирует их - person Gareth; 12.05.2009
comment
.. хотя, сказав это, в данном случае это вряд ли будет проблемой, так как в этом примере не будет использоваться медленное вычисление. Но все же я считаю, что sort_by в целом более масштабируемое решение - person Gareth; 12.05.2009

Если вам нужно много сортировать Fruits, вам, вероятно, следует немного поработать заранее и сделать ваши объекты сопоставимыми.

Для этого вам нужно реализовать Spaceship-Operator (<=>) и включить Comparable.

class Fruit
  attr_accessor :name, :color

  def <=>(other)
    # use Array#<=> to compare the attributes
    [self.name.downcase, self.color] <=> [other.name.downcase, other.color]
  end

  include Comparable
end

то вы можете просто сделать:

list_of_fruits.sort

Comparable также бесплатно предоставляет множество других методов (==, <, >), так что вы можете делать такие вещи, как if (apple < banana) (см. документация для модуля Comparable для получения дополнительной информации)

‹=>, указано, чтобы возвращать -1, если self меньше, чем other, +1, если other меньше, и 0, если оба объекта равны.

person levinalex    schedule 12.05.2009