Сортировка по нескольким условиям в Ruby

У меня есть набор объектов Сообщения, и я хочу смочь отсортировать их на основе этих условий:

  • Во-первых, по категориям (новости, события, лаборатории, портфель, и т.д.)
  • Затем по дате, если дата, или положением, если определенный индекс был установлен для него

Некоторые сообщения будут иметь даты (новости и события), у других будут явные положения (лаборатории и портфель).

Я хочу смочь звонить 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!. Что самый рубиновый путь состоит в том, чтобы сделать это?

8
задан SilentGhost 16 April 2010 в 10:10
поделиться

2 ответа

Вы всегда должны сортировать по одним и тем же критериям, чтобы обеспечить осмысленный порядок. При сравнении двух дат 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 (в этом случае вы хотите, чтобы сортировка выполнялась сервером базы данных).
12
ответ дан 5 December 2019 в 11:23
поделиться

В качестве альтернативы вы можете выполнить сортировку одним махом в массиве, единственная проблема - обработка случая, когда один из атрибутов равен нулю, хотя это может все равно будут обработаны, если вы знали набор данных, выбрав соответствующую защиту от нуля. Также из вашего псевдо-кода неясно, указаны ли сравнения даты и позиции в порядке приоритета или в одном или другом (т.е. использовать дату, если существует, для обоих, в противном случае используйте позицию). Первое решение предполагает использование, категорию, за которой следует дата, за которой следует позиция

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
3
ответ дан 5 December 2019 в 11:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: