使用红宝石中的太空船运算符进行自定义排序

时间:2019-03-20 09:24:44

标签: arrays ruby sorting

我正在实现自定义排序。有一个太空船操作员<=>处理数组的排序:

myArray.sort { |a, b| a <=> b }
    a <=> b大于1时,
  • b返回a,并且两个元素被交换。
  • a <=> b等于0时,
  • a返回b,并且两个元素保持原始位置。
  • a <=> b小于-1时,
  • a返回b,并且两个元素保持原始位置。

所以我用一个例子进行了测试:

myArray = [2, 1, 7, 9, 3, 8, 0]
myArray.sort { |a, b| 1 <=> 1 } # making it always return 0
#=> [9, 1, 7, 2, 3, 8, 0]

结果不是我期望的。在我的期望中,当飞船操作员返回0时,每个元素都将停留在原始位置。由于在上面的示例中,宇宙飞船运算符始终返回0,因此该数组应保持不变。但是,结果与原始数组不同。

我有误会吗?

已更新:

以下是我上面的问题来自何处的想法。 最初,我试图按对象的属性对对象进行排序(假设属性为status)。

例如

myObjects = [obj1, obj2,..., objn] # the objects are ordered by create_time already

myObjects.sort{|a,b|
    case a.status
        when 'failed'
            x = 1
        when 'success'
            x = 0
        when 'pending'
            x = -1
     end

     case b.status
        when 'failed'
            y = 1
        when 'success'
            y = 0
        when 'pending'
            y = -1
     end

     x <=> y  # compare two objects (a and b) by the status 
}

myObjects已按created_time排序,但我想再次按每个对象的status对其进行排序。

例如

两个具有相同created time的对象(此处仅考虑小时和分钟,而忽略秒)将根据其状态再次排序,从而将状态为failed的对象放在末尾数组。

以上代码中的xy值将取决于对象的状态, 比较xy以确定顺序。如果两个对象的状态相同(x == y,则元素应保持在同一位置,因为它们已经按created_time进行了排序,无需再次对其进行排序。

当两个对象的状态均为success时,x <=> y将返回0。 但是根据一些评论,太空飞船运营商返回的比较值0似乎输出了不可预测的顺序。 如果myObjects包含所有状态均为相同的元素怎么办?自x == y起,这将导致飞船操作员始终返回0。

在我的期望中,myObjects应该保持相同的顺序,因为状态都是相同的,在我的情况下使用宇宙飞船运算符时应该如何纠正?

非常感谢大家的帮助!

3 个答案:

答案 0 :(得分:9)

您关于排序方式的假设不正确。根据{{​​3}} connectedCallback() { var parent = this.getRootNode().host console.log( parent.localNode ) // my-list } 不稳定:

  

不能保证结果稳定。当两个元素的比较返回0时,元素的顺序是不可预测的。

答案 1 :(得分:7)

Array#sort使用Quicksort算法,该算法不稳定,当元素“相等”时会产生这种行为。

原因在于每步选择和移动 pivot 元素,在这种情况下,ruby实现似乎在中间选择了pivot(但可以选择不同的方式)。

这是您的示例中发生的情况:

    在数组中间的元素9上选择了
  1. 枢轴
  2. now算法可确保枢轴左侧的项目小于它,而右侧的项目则大于或等于,因为所有项目都是“相等”的-这使得所有内容都位于 right 部分
  3. 现在递归地重复 left (在此情况下始终为空)和 right 分区
  4. 结果为sorted_left + [pivot] + sorted_right,左边为空,因此枢轴移动了

Ruby core documentation提到了这一点:

  

当两个元素的比较返回0时,元素的顺序是不可预测的。

此外,太空飞船运营商<=>在这里没有任何作用,您可以致电myArray.sort{0}以获得相同的效果。

更新:

从更新的问题来看,很明显,您要按两个属性进行排序,这可以通过几种方法完成:

方法1:您可以发明一个同时考虑两个值并对其进行排序的指标/键:

 status_order = { 'success' => 1, 'failed' => 2, 'pending' => 3 }
 myObjects.sort_by{|o| "#{status_order[o.status]}_#{o.created_time}" }

就极端性能而言,这不是最佳选择,但更短。

方法2:通过编写如下比较规则来隐式组合键:

status_order = { 'success' => 1, 'failed' => 2, 'pending' => 3 }
status_order.default = 0

myObjects.sort{|a,b|
  if a.status == b.status
    a.created_time <=> b.created_time
  else
    status_order[a.status] <=> status_order[b.status]
  end
}

答案 2 :(得分:4)

工作原理

myArray = [2, 1, 7, 9, 3, 8, 0]

myArray.sort { |a, b| a <=> b }
#=> [0, 1, 2, 3, 7, 8, 9]

myArray.sort { |a, b| b <=> a }
#=> [9, 8, 7, 3, 2, 1, 0]

如果比较结果始终为0,则无法对元素进行排序。这是很合逻辑的。

但是,文档在这种情况下明确指出元素的顺序是不可预测的。这就是为什么结果与旧数组不同的原因。

但是,我在Ruby 2.5.1中复制了您的情况,并返回了旧数组

myArray = [2, 1, 7, 9, 3, 8, 0]
myArray.sort { |a, b| 1 <=> 1 }
#=> [2, 1, 7, 9, 3, 8, 0]

您的代码中还有一个误解。你写了

myArray #=> [9, 1, 7, 2, 3, 8, 0]

但是实际上Array#sort不会更改数组,只有Array#sort!会更改数组。