Ruby多参数排序Lambda最后一个RSpec测试失败原因咨询
排序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]
编写RSpec测试验证逻辑时,最后一个测试(按v1升序、v2降序排序)失败。测试代码核心部分:
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] } # 失败的测试用例 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>]
失败原因
你写的Lambda逻辑完全错误:
- 对比数组的第一个元素都是
a.v1,意味着无论a和b的v1值差异是什么,第一个元素的对比结果永远为0(相等); - 实际起作用的是第二个元素的对比:
b.v2 <=> -b.v2,这个结果只和b自身的v2值有关,和a完全无关; - 测试数据中
v2都是正数,此时b.v2 > -b.v2,对比结果为1,排序算法会判定a应该排在b后面,最终导致整个数组被完全倒序,和预期的v1升序、v2降序逻辑完全不符。
正确的Lambda写法
要实现**v1升序,v2降序**的排序逻辑,需保持对比的对称性:将a的属性放在左边,b的对应属性(降序则取反)放在右边,正确写法如下:
sort_lambda = ->(a, b) { [a.v1, -a.v2] <=> [b.v1, -b.v2] }
或者更贴合Ruby排序常规逻辑的等价写法:
sort_lambda = ->(a, b) { cmp = a.v1 <=> b.v1 cmp.zero? ? (-a.v2 <=> -b.v2) : cmp }
这两种写法都能保证:
- 优先按
v1升序对比,v1值小的元素排在前面; - 当
v1值相等时,按v2降序对比,v2值大的元素排在前面。
修改后测试结果会符合预期:[o1, o3, o2, o4]。
内容的提问来源于stack exchange,提问作者Mike Slinn
相关产品推荐
相关产品推荐

