Ruby带有方便的<=>
比较运算符,原生基元类型支持它们。我想知道是否有一种简单的方法将它们组合起来比较更复杂的对象,比如Structs。例如,给定
class Datum < Struct.new(:code, :volume); end
datum1 = Datum.new('B',10)
datum2 = Datum.new('A',10)
datum3 = Datum.new('C',11)
data = [datum1, datum2, datum3]
我想按data
对volume
进行排序,如果volume
s相等,则按code
排序。像
data.sort {|a,b| (a.volume <=> b.volume) ??? (a.code <=> b.code)}
我应该在???
中添加什么?
我的解决方案是:
<=>
答案 0 :(得分:4)
对于上述简单案例,您可以使用sort_by
:
data.sort_by {|a| [a.volume, a.code] }
#=> [
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
如果您只按单个属性进行排序,则会更短:
data.sort_by(&:volume)
#=> [
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
其中&:volume
使用Symbol#to_proc
并且是proc {|a| a.volume }
的简写(类似于lambda)。
如果您需要使其更复杂(即左右不同),可以将其扩展为sort
的调用:
data.sort {|a,b| [a.volume, a.code] <=> [b.volume, b.code] }
#=> [
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
这一切都有效,因为<=>
上定义的Array
运算符完全符合您的需要,适用于任意级别。
答案 1 :(得分:2)
我认为你以错误的方式解决这个问题。为什么不为自己省去麻烦并在<=>
添加Datum
运营商?像这样:
class Datum < Struct.new(:code, :volume)
def <=>(other)
if(self.code < other.code)
-1
elsif(self.code > other.code)
1
elsif(self.volume < other.volume)
-1
elsif(self.volume > other.volume)
1
else
0
end
end
end
然后你可以说data.sort
并完成它。一旦您拥有<=>
运算符,您就可以include Comparable
并免费获得<
,<=
,...运算符。
答案 2 :(得分:1)
Ruby链接比较器的方法是使用Numeric.nonzero?
:
data.sort {|a,b| (a.volume <=> b.volume).nonzero? || a.code <=> b.code}
Numeric.nonzero?
的作用是什么?如果它不为零,则返回数字,否则返回nil
。然后||
运算符将处理其余部分,因为在布尔上下文中将nil
视为false
,因此nil || b
与b
相同。 / p>
这就是说,在大多数情况下,使用Enumerable.sort_by
时您会得到较短的代码,并返回一个数组以指示排序顺序:
data.sort_by {|a| [a.volume, a.code] }
颠倒排序顺序比使用比较器还容易:
data.sort {|a,b| (-(a.volume <=> b.volume)).nonzero? || a.code <=> b.code}
# vs.
data.sort_by {|a| [-a.volume, a.code] }
警告:请注意,如果比较器比较慢,sort_by
is slower than sort
in case the comparator is fast和sort_by
会更好。