使用Ruby中的属性对对象列表进行排序

我有一个叫做basketFruit结构清单。 每个Fruit结构都有一个name (一个字符串)和一个calories (一个整数)。 我想对basket进行分类,以便:

  1. 具有最高caloriesFruit首先出现。 例如,具有500卡路里的水果出现在具有400卡路里的水果之前。

  2. 如果两个Fruit具有相同的calories ,那么name首先按字母顺序排列的Fruit首先出现,忽略大小写。 例如,给定两个具有相同卡路里的水果,一个名为“香蕉”的水果将在一个名为“Citrus”之前出现。

Fruit的定义不是我控制的,所以我更喜欢一种不涉及将任何东西混合到Fruit或改变Fruit的解决方案。 这可能吗?

简单的解决方案是

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

当然,如果这是水果规范排序, 则应使用<=>方法定义并将Comparable模块混合到Fruit

我们假设您的购物篮是一个数组或其子类。

快速的方式

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 Way

的Array.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 } 

请参阅Array#sort ( API doc )。 在给定两个Fruit对象的情况下,您可以传入一个返回-1,0或1的块,并且您的块可以使用您喜欢的任何属性来确定这些值。

如果你需要经常对Fruit进行分类,你可能应该先做一些工作并使你的对象具有可比性。

为此,您需要实施Spaceship-Operator( <=> )并包含Comparable。

 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) (有关更多信息,请参阅Comparable Module的文档 )

<=>,如果self小于other ,则指定返回-1如果other小则指定为+1 0如果两个对象相等则指定为0