我需要为排序标准创建lambda。为了简化lambda创建过程,我希望一致地对比较进行排序,例如:
[a.v1, b.v2] <=> [a.v1, b.v2]
[a.v1, b.v2] <=> [-a.v1, b.v2]
[a.v1, b.v2] <=> [a.v1, -b.v2]
[a.v1, b.v2] <=> [-a.v1, -b.v2]
为了确保lambdas按照我期望的方式工作,我编写了以下rspec
:
class Obj
attr_reader :v1, :v2, :v3
def initialize(param1, param2, param3)
@v1 = param1
@v2 = param2
@v3 = param3
end
end
RSpec.describe(Array) do
let(:o1) { Obj.new(1, 1, 1) }
let(:o2) { Obj.new(2, 1, 1) }
let(:o3) { Obj.new(2, 2, 1) }
let(:o4) { Obj.new(3, 2, 1) }
let(:objs) { [o1, o2, o3, o4] }
# See https://ruby-doc.org/3.2.0/Comparable.html
it "uses comparators properly" do
expect([o1.v1] <=> [o2.v1]).to eq(-1)
expect([o1.v1, o1.v2] <=> [o2.v1, o2.v2]).to eq(-1)
expect([o1.v2, o1.v1] <=> [o3.v1, o3.v2]).to eq(-1)
expect([o1.v2, o1.v1] <=> [o3.v1, -o3.v2]).to eq(-1)
expect([o2.v2, o1.v1] <=> [-o2.v2, o2.v1]).to eq(1)
expect([o2.v2, o1.v1] <=> [-o2.v2, -o2.v1]).to eq(1)
end
# See https://ruby-doc.org/3.2.0/Enumerable.html#method-i-sort
it "sorts by 2 keys, both ascending" do
sort_lambda = ->(a, b) { [a.v1, a.v2] <=> [b.v1, b.v2] }
result = objs.sort(&sort_lambda)
expect(result).to eq([o1, o2, o3, o4])
end
it "sorts by 2 keys, both descending" do
sort_lambda = ->(a, b) { [a.v1, a.v2] <=> [-b.v1, -b.v2] }
result = objs.sort(&sort_lambda)
expect(result).to eq([o4, o3, o2, o1])
end
it "sorts by 2 keys, first descending and second ascending" do
sort_lambda = ->(a, b) { [a.v1, b.v2] <=> [-a.v1, b.v2] }
result = objs.sort(&sort_lambda)
expect(result).to eq([o4, o3, o2, o1])
end
# This one fails ... why?
it "sorts by 2 keys, first ascending and second descending" do
sort_lambda = ->(a, b) { [a.v1, b.v2] <=> [a.v1, -b.v2] }
result = objs.sort(&sort_lambda)
expect(result).to eq([o1, o3, o2, o4])
end
end
所有测试均通过,但最后一项测试未通过,原因是:
Failure/Error: expect(result).to eq([o1, o3, o2, o4])
expected: [#<Obj:0x00007fc2ac112940 @v1=1, @v2=1, @v3=1>, #<Obj:0x00007fc2ac112828 @v1=2, @v2=2, @v3=1>, #<Obj:0x00007fc2ac1128c8 @v1=2, @v2=1, @v3=1>, #<Obj:0x00007fc2ac112788 @v1=3, @v2=2, @v3=1>]
got: [#<Obj:0x00007fc2ac112788 @v1=3, @v2=2, @v3=1>, #<Obj:0x00007fc2ac112828 @v1=2, @v2=2, @v3=1>, #<Obj:0x00007fc2ac1128c8 @v1=2, @v2=1, @v3=1>, #<Obj:0x00007fc2ac112940 @v1=1, @v2=1, @v3=1>]
为什么?
3条答案
按热度按时间zkure5ic1#
我想你可能误解了
Array#<=>
(太空船)是如何工作的。(或者这可能只是你测试中的一个打字错误)它的作用是按
a[0] <=> b[0]
排序,如果0
相等,那么它就移到a[1] <=> b[1]
,依此类推,直到(x <=> y) != 0
;然而,它只会比较一个集合一次,这意味着4个元素4次迭代。这给您带来了一个问题,因为这意味着它永远不会有一个比较集,其中a == o2 && b == o3
和a == o3 && b == o2
在您失败的测试中:
a.v1
将始终等于a.v1
,例如返回0
,因此它将继续比较b.v2
和-b.v2
,并且您将以降序结束b.v2
。看来你真正想要的是
示例:
1.你所有的测试都有一个非常特殊的问题,那就是操作符需要同时应用于两端,例如,* it "sorted by 2 keys,both descending"* 看起来通过了,但这仅仅是由于你给出参数的顺序。
[o4, o3, o2, o1]
)不正确,因此测试通过。根据测试描述,预期输出应为[o4, o2, o3, o1]
,lambda主体应更改为[-a.v1, a.v2] <=> [-b.v1, b.v2]
1.使用
sort_by
更容易表达这些测试,例如sort_by {|a| [-a.v1,a.v2]}
flmtquvp2#
下面是@engineersmnky修正后的答案,每个场景都提供了
sort
和等效的sort_by
方法。如果将
Date
类型的属性用作降序排序键,则此解决方案将引发异常。#〈Date:2011年1月1日((2455563 j,0 s,0 n),+0 s,2299161 j)〉'。要查看错误:
1.将
initialize
修改为:1.将
o1
..o4
的定义修改为以下形式:kx1ctssn3#
前面的答案在根据
Date
字段应用降序排序时失败。此解决方案使用@ngineersmnky的建议来反转需要降序排序的字段的比较顺序。请注意,尽管
sort_by
比sort
更便于使用,但只有sort
提供反转比较顺序的功能,因此必须将其用于此解决方案,而不是sort_by
。