【问题标题】:Custom sort using spaceship operator in ruby [duplicate]在ruby中使用宇宙飞船运算符进行自定义排序[重复]
【发布时间】:2019-08-10 22:43:20
【问题描述】:

我正在实现自定义排序。有宇宙飞船运算符<=> 处理数组排序:

myArray.sort { |a, b| a <=> b }
  • b 大于a 时,a &lt;=&gt; b 返回1,并且交换了两个元素。
  • a 等于b 时,a &lt;=&gt; b 返回0,并且两个元素保持在原来的位置。
  • a 小于b 时,a &lt;=&gt; b 返回-1,并且两个元素保持在原来的位置。

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

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 时,每个元素都将保持在原始位置。由于在上面的示例中,spaceship 运算符始终返回 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 值将取决于对象的状态, 和x y 比较来决定顺序。如果两个对象的状态相等(x == y),则元素应该保持在相同的位置,因为它们已经按created_time排序,不需要再次排序。

当两个对象的状态都是success时,x &lt;=&gt; y会返回0。 但是根据一些cmets的说法,spaceship算子返回的比较值0似乎输出了不可预测的顺序。 如果myObjects 包含所有具有相同状态的元素怎么办?这将导致宇宙飞船运算符自 x == y 以来一直返回 0。

在我的预期中,myObjects 应该保持相同的顺序,因为状态都是一样的,在我的情况下使用 spaceship 运算符时应该如何更正?

非常感谢大家的帮助!

【问题讨论】:

  • 不是空间操作员,是宇宙飞船操作员。空间看起来不像&lt;=&gt;
  • "[W]当空间运算符返回0时,每个元素都将保持在原来的位置"--这是从何而来的?
  • 你可以用0而不是1 &lt;=&gt; 1,所以这不是因为宇宙飞船运算符。
  • @sawa 因为我认为当宇宙飞船运算符返回 1 时,Ruby 会交换元素。这就是为什么我认为 Ruby 在看到 0 或 -1 时会不理会元素。我的概念正确吗?

标签: arrays ruby sorting


【解决方案1】:

您对排序方式的假设不正确。根据the documentation #sort 不稳定:

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

【讨论】:

  • 这让我再次感到困惑。如果比较为0,当两个对象被视为相同时,如何使数组相同? (当两个对象被某个属性视为同一个对象时)
  • @Wilson.Wang 已经有另一个问题询问如何使排序稳定。我将此问题作为重复问题关闭。
  • 知道了。感谢您的更新,这个问题也对我有帮助。非常感谢。
【解决方案2】:

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

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

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

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

Ruby core documentation 提到这一点:

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

另外,宇宙飞船操作员&lt;=&gt; 在这里没有任何作用,你可以直接调用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
}

【讨论】:

  • 感谢您的回答。我已经更新了我的问题,期待任何建议。谢谢。
  • @Wilson.Wang 查看更新的答案
  • 感谢您的详细解释,这让我了解了更多想法。但是我发现了一些我仍然不清楚的东西:
  • 在Method2中,如果a.status == b.status,它将比较created_time,这就是我现在卡住的地方。因为我认为 myObjects 已经按 created_time 排序,所以我将 xy (x 和 y 取决于对象的状态,所以 x 等于 y )在我的代码中,表明两个状态是相同的,这似乎是问题导致意想不到的结果?当遇到相同的状态时,是否有必要比较另一件事(例如您的示例中的 created_time )而不是仅使用两个相同的整数返回 0 作为结果?
  • @Wilson.Wang 是的,在排序时 - 您可能会认为先前的数组状态是随机的并描述您想要的最终状态
【解决方案3】:

它是如何工作的

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! 会。

【讨论】:

  • 感谢您的回答,现在我知道进行比较总是返回 0 值会导致不可预测的结果,并且应该总是有一些东西可以比较。谢谢。
猜你喜欢
  • 2011-03-04
  • 2010-10-24
  • 2010-11-25
  • 2019-07-24
  • 2023-03-30
  • 2021-08-07
  • 1970-01-01
  • 2021-07-27
相关资源
最近更新 更多