使用Ruby按照对象属性对列表进行排序

5
我有一个名为“basket”的Fruit结构体列表。每个Fruit结构体都有一个name(字符串)和一个calories(整数)。我希望对basket进行排序,使得:
  1. 具有最高caloriesFruit首先出现。例如,具有500卡路里的水果先于具有400卡路里的水果。

  2. 如果两个Fruit具有相等的calories,则按字母表顺序排列其名称,忽略大小写。例如,给定两个具有相同卡路里的水果,一个命名为“banana”的水果将在命名为“Citrus”的水果之前出现。

Fruit的定义不是我所控制的,因此我希望不要将任何内容混合到Fruit中或更改它。这可能吗?
4个回答

12

简单的解决方案是

basket.sort_by { |f| [-f.calories, f.name] }

当然,如果这是水果的唯一规范排序顺序,那么它应该使用<=>方法进行定义,并将Comparable模块混入到Fruit中。


这个是否考虑到了应该不区分大小写地比较 f.name 这一事实? - Kyle Kaitan
3
忽略大小写,将f.name转换为小写。 - rampion
这个加上 f.name.downcase 正是我所需要的。谢谢!(不,水果没有定义的规范排序顺序;只是碰巧在这个特定情况下需要按照这种方式排序。) - Kyle Kaitan

2
假设你的篮子是一个数组或其子类。

快速方式

Enumerable.sort_by

正如Gareth所指出的那样,Enumerable(包含在Array中)具有sort_by方法,可以一次运行每个列表项。这种方法更快地运行和更快地编写,一旦掌握了它的使用方法。

# -f.calories to sort descending
# name.downcase to do a case-insensitive sort
basket = basket.sort_by { |f| [-f.calories, f.name.downcase] }

Perl的方式

Array.sort

如果你有Perl的背景,你可能首先想到使用太空船操作符<=>。但是,Ruby的数组提供了sort和sort!方法,这让它非常有用。虽然这种Perl的方法更慢,并且因为它更长,所以更容易引入错误。唯一使用它的原因是,如果你正在处理不熟悉Ruby且不愿意在StackOverflow上寻找正确方法的人。

baseket.sort! { |a,b|
  if a.calories == b.calories
    a.name.downcase <=> b.name.downcase
  else
    # Reverse the result to sort highest first.
    -(a.calories <=> b.calories)
  end
}

1
如果你需要频繁地对水果进行排序,那么最好在一开始就多做一些工作,使你的对象可以进行比较。
为此,你需要实现太空船操作符(<=>)并包含可比较性。
class Fruit
  attr_accessor :name, :color

  def <=>(other)
    # use Array#<=> to compare the attributes
    [self.name.downcase, self.color] <=> [other.name.downcase, other.color]
  end

  include Comparable
end

那么你可以简单地执行:

list_of_fruits.sort

Comparable 还提供了其他许多方法(==, <, >),使您可以免费使用这些方法,例如if (apple < banana)(请查看 可比较模块的文档 获取更多信息)。

<=> 方法被指定为如果 selfother 小,则返回-1,如果 otherself 小,则返回 +1,如果两个对象相等,则返回0


1
请参阅Array#sortAPI文档)。您可以传递一个块,该块返回给定两个Fruit对象的-1、0或1,并且您的块可以使用任何属性来确定这些值。

1
'sort' 的问题在于它会为每一对被比较的元素重新评估代码块多次。而使用 'sort_by' 则会为每个元素计算比较键值 一次,然后对这些键值进行排序。 - Gareth
尽管如此,在这种情况下这不太可能成为一个问题,因为这个例子不会使用缓慢的计算。但我仍然认为,sort_by通常是一种更可扩展的解决方案。 - Gareth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接