Сортировка по нескольким условиям в Ruby
У меня есть коллекция объектов Post, и я хочу иметь возможность сортировать их по следующим условиям:
- Сначала по категориям (новости, события, лаборатории, портфолио и т. Д.)
- Затем по дате, если по дате, или по позиции, если для нее был установлен определенный индекс
Некоторые сообщения будут иметь даты (новости и события), другие будут иметь явные позиции (лаборатории и портфолио).
Я хочу иметь возможность звонить 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!
, Какой самый рубиновый способ сделать это?
2 ответа
Вы должны всегда сортировать по одним и тем же критериям, чтобы обеспечить значимый порядок. Если сравнивать два 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
Вы также хотите сделать сортировку сразу. Для тебя <=>
реализация, использование #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
(в этом случае вы хотите, чтобы сортировка выполнялась сервером БД).
В качестве альтернативы вы можете выполнить сортировку одним махом в массиве, единственное, что нужно, - это обработать случай, когда один из атрибутов равен nil, хотя это все еще можно обработать, если вы знаете набор данных, выбрав соответствующий nil guard. Также из вашего кода псевдо неясно, перечислены ли сравнения даты и позиции в приоритетном порядке или в том или ином (т. Е. Дата использования, если существует для обеих позиций использования). Первое решение предполагает использование, категорию, затем дату, затем позицию
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