Сортировка по нескольким условиям в Ruby
-
26-09-2019 - |
Вопрос
У меня есть коллекция пост объектов, и я хочу иметь возможность сортировать их на основе этих условий:
- Во-первых, по категориям (новостями, событиями, лабораториями, портфолио и т. Д.)
- Затем по дате, если дата или позицией, если определенный индекс был установлен для него
Некоторые посты будут иметь даты (новости и события), другие будут иметь явные позиции (лаборатории и портфель).
Я хочу быть в состоянии позвонить posts.sort!
, так я переопределил <=>
, но ищу самый эффективный способ сортировки по этим условиям. Ниже приведен псевдопользователь:
def <=>(other)
# first, everything is sorted into
# smaller chunks by category
self.category <=> other.category
# then, per category, by date or position
if self.date and other.date
self.date <=> other.date
else
self.position <=> other.position
end
end
Похоже, мне придется на самом деле сортировать два отдельных времена, а не все втягивать в этот один метод. Что-то вроде sort_by_category
, потом sort!
. Отказ Какой самый рубливый способ сделать это?
Решение
Вы всегда должны сортировать по тем же критериям для обеспечения значимого порядка. Если сравнивать два nil
даты, хорошо, что position
будет судить о заказе, но если сравнивать один nil
Дата с установленной датой, вы должны решить, что идет первым, независимо от должности (например, сопоставлением nil
до дня в прошлом).
В противном случае представьте следующее:
a.date = nil ; a.position = 1
b.date = Time.now - 1.day ; b.position = 2
c.date = Time.now ; c.position = 0
По вашим оригинальным критериям вы бы имели: a <b <c <a. Итак, какой из них самый маленький ??
Вы также хотите сделать сортировку одновременно. Для тебя <=>
Реализация, использование #nonzero?
:
def <=>(other)
return nil unless other.is_a?(Post)
(self.category <=> other.category).nonzero? ||
((self.date || AGES_AGO) <=> (other.date || AGES_AGO)).nonzero? ||
(self.position <=> other.position).nonzero? ||
0
end
Если вы используете свои критерии сравнения только один раз, или если эти критерии не универсальны и, таким образом, не хотите определять <=>
, вы могли бы использовать sort
с блоком:
post_ary.sort{|a, b| (a.category <=> ...).non_zero? || ... }
Лучше еще, есть sort_by
и sort_by!
Что вы можете использовать для создания массива для чего сравнивать, в каком приоритете:
post_ary.sort_by{|a| [a.category, a.date || AGES_AGO, a.position] }
Помимо короче, используя sort_by
У вас есть преимущество, что вы можете получить только заказанные критерии.
Примечания:
sort_by!
был введен в Ruby 1.9.2. Ты сможешьrequire 'backports/1.9.2/array/sort_by'
использовать его со старыми рубинами.- Я предполагаю, что
Post
не подклассActiveRecord::Base
(в этом случае вы хотите, чтобы сорт был сделан сервером БД).
Другие советы
В качестве альтернативы вы можете сделать то, что в одном падающем массиве в массиве, единственным GOTCHA обрабатывает случай, когда один из атрибутов является Nil, хотя это все еще можно обрабатывать, если вы знаете данные, установленные, выбрав соответствующий охранник Nil. Также из вашего PSuedo Codeo не ясно, если сравнения даты и положения перечислены в порядке приоритета или одно или другое (т. Е. Дата использования, если существует как для положения использования как иначе). Первое решение предполагает использование, категория, а затем дату, а затем позиция
def <=>(other)
[self.category, self.date, self.position] <=> [other.category, other.date, other.position]
end
Второе предполагает, что это дата или позиция
def <=>(other)
if self.date && other.date
[self.category, self.date] <=> [other.category, other.date]
else
[self.category, self.position] <=> [other.category, other.position]
end
end