在Ruby中按多个条件排序

时间:2010-04-13 23:53:18

标签: ruby sorting operators comparison-operators spacecraft-operator

我有一组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 个答案:

答案 0 :(得分:12)

您应该始终按相同的标准排序,以确保有意义的订单。如果比较两个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&lt; b&lt; c&lt;一个。那么,哪一个是最小的?

您还想立即进行排序。对于<=>实施,请使用#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_bysort_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的子类(在这种情况下,您希望排序由db服务器完成)。

答案 1 :(得分:3)

或者你可以在一个数组中一举进行排序,唯一的问题是处理其中一个属性为零的情况,尽管如果你通过选择适当的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