У меня есть набор объектов Сообщения, и я хочу смочь отсортировать их на основе этих условий:
Некоторые сообщения будут иметь даты (новости и события), у других будут явные положения (лаборатории и портфель).
Я хочу смочь звонить 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
нормально, что позиция
будет определять порядок, но если сравнивать одну дату 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
Если вы используете критерии сравнения только один раз или если эти критерии не универсальны и поэтому не хотите define <=>
, вы можете использовать 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. Вы можете потребовать 'backports / 1.9.2 / array / sort_by'
, чтобы использовать его со старыми Rubies. Post
не является подклассом ActiveRecord :: Base
(в этом случае вы хотите, чтобы сортировка выполнялась сервером базы данных). В качестве альтернативы вы можете выполнить сортировку одним махом в массиве, единственная проблема - обработка случая, когда один из атрибутов равен нулю, хотя это может все равно будут обработаны, если вы знали набор данных, выбрав соответствующую защиту от нуля. Также из вашего псевдо-кода неясно, указаны ли сравнения даты и позиции в порядке приоритета или в одном или другом (т.е. использовать дату, если существует, для обоих, в противном случае используйте позицию). Первое решение предполагает использование, категорию, за которой следует дата, за которой следует позиция
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