Ruby中的排序是否稳定?

6rqinv9w  于 2023-03-17  发布在  Ruby
关注(0)|答案(4)|浏览(117)

Ruby中的sort稳定吗?也就是说,对于与sort并列的元素,它们之间的相对顺序是否保留了原始顺序?例如,给定:

a = [
  {id: :a, int: 3},
  {id: :b, int: 1},
  {id: :c, int: 2},
  {id: :d, int: 0},
  {id: :e, int: 1},
  {id: :f, int: 0},
  {id: :g, int: 1},
  {id: :h, int: 2},
]

是否能保证我们总能得到

a.sort_by{|h| h[:int]}

以下

[
  {id: :d, int: 0},
  {id: :f, int: 0},
  {id: :b, int: 1},
  {id: :e, int: 1},
  {id: :g, int: 1},
  {id: :c, int: 2},
  {id: :h, int: 2},
  {id: :a, int: 3},
]

具有:id值的元素:d:f之间,:b:e:g之间,以及:c:h之间的相对顺序没有任何变化?如果是这种情况,文档中的哪个位置对此进行了描述?
这个问题可能与this question有关,也可能无关。

5f0d552i

5f0d552i1#

MRI的sort和sort_by都是不稳定的,前段时间有个request让它们稳定,但是被拒绝了,原因是:Ruby使用了in-place quicksort algorithm,如果不需要稳定性的话,in-place quicksort algorithm会表现得更好,注意你仍然可以从不稳定的方法实现稳定的方法:

module Enumerable
  def stable_sort
    sort_by.with_index { |x, idx| [x, idx] }
  end

  def stable_sort_by
    sort_by.with_index { |x, idx| [yield(x), idx] }
  end
end
zysjyyx4

zysjyyx42#

不,ruby的内置排序不稳定。
如果你想要稳定排序,这应该可以。如果你经常使用它,你可能需要为它创建一个方法。

a.each_with_index.sort_by {|h, idx| [h[:int], idx] }.map(&:first)

基本上,它跟踪每个项的原始数组索引,并在h[:int]相同时将其用作平局决胜。

更多信息,为好奇:

据我所知,在使用不稳定排序时,使用原始数组索引作为决胜局是保证稳定性的唯一方法,项目的实际属性(或其他数据)不会告诉您它们的原始顺序。
您的示例有些做作,因为:id键在原始数组中是升序排序的。你会希望结果中的:id在决胜局时是递减的,如下所示:

[
 {:id=>:f, :int=>0},
 {:id=>:d, :int=>0},
 {:id=>:g, :int=>1},
 {:id=>:e, :int=>1},
 {:id=>:b, :int=>1},
 {:id=>:h, :int=>2},
 {:id=>:c, :int=>2},
 {:id=>:a, :int=>3}
]

使用原始索引也可以处理这个问题。

更新日期:

Matz自己的建议(参见this page)也是类似的,并且可能比上面的建议稍微更有效:

n = 0
ary.sort_by {|x| n+= 1; [x, n]}
rsaldnfx

rsaldnfx3#

对于Ruby的一些实现来说,sort是稳定的,但是你不应该依赖它,Ruby的sort的稳定性是由实现定义的。

文档内容

该文档指出,您不应该依赖于sort是否稳定:
不保证结果是稳定的。当两个元素的比较返回0时,元素的顺序是不可预测的。
注意,这并不是说排序是否稳定。它只是说排序不一定是稳定的。任何给定的Ruby实现都可能有一个稳定的排序,但仍然与文档一致。它也可能有一个不稳定的排序,或者随时改变排序是否稳定。

露比真正做的是

如果Ruby的排序是稳定的,那么这个测试代码输出true,否则输出false

Foo = Struct.new(:value, :original_order) do
  def <=>(foo)
    value <=> foo.value
  end
end

size = 1000
unsorted = size.times.map do |original_order|
  value = rand(size / 10)
  Foo.new(value, original_order)
end
sorted = unsorted.sort
stably_sorted = unsorted.sort_by do |foo|
  [foo.value, foo.original_order]
end
p [RUBY_PLATFORM, RUBY_VERSION, RUBY_PATCHLEVEL, sorted == stably_sorted]

下面是我在Linux系统上安装的所有Rubies的结果:

["java", "1.8.7", 357, false]
["java", "1.9.3", 551, false]
["java", "2.3.3", 0, true]
["java", "2.5.7", 0, true]
["x86_64-linux", "1.8.7", 374, false]
["x86_64-linux", "1.8.7", 374, false]
["x86_64-linux", "1.8.7", 376, false]
["x86_64-linux", "1.9.3", 392, false]
["x86_64-linux", "1.9.3", 484, false]
["x86_64-linux", "1.9.3", 551, false]
["x86_64-linux", "2.0.0", 643, false]
["x86_64-linux", "2.0.0", 648, false]
["x86_64-linux", "2.1.0", 0, false]
["x86_64-linux", "2.1.10", 492, false]
["x86_64-linux", "2.1.1", 76, false]
["x86_64-linux", "2.1.2", 95, false]
["x86_64-linux", "2.1.3", 242, false]
["x86_64-linux", "2.1.4", 265, false]
["x86_64-linux", "2.1.5", 273, false]
["x86_64-linux", "2.1.6", 336, false]
["x86_64-linux", "2.1.7", 400, false]
["x86_64-linux", "2.1.8", 440, false]
["x86_64-linux", "2.1.9", 490, false]
["x86_64-linux", "2.2.0", 0, true]
["x86_64-linux", "2.2.1", 85, true]
["x86_64-linux", "2.2.2", 95, true]
["x86_64-linux", "2.2.3", 173, true]
["x86_64-linux", "2.2.4", 230, true]
["x86_64-linux", "2.2.5", 319, true]
["x86_64-linux", "2.2.6", 396, true]
["x86_64-linux", "2.3.0", 0, true]
["x86_64-linux", "2.3.1", 112, true]
["x86_64-linux", "2.3.2", 217, true]
["x86_64-linux", "2.3.3", 222, true]
["x86_64-linux", "2.4.0", 0, true]
["x86_64-linux", "2.4.0", -1, true]
["x86_64-linux", "2.4.0", -1, true]
["x86_64-linux", "2.4.0", -1, true]
["x86_64-linux", "2.4.0", -1, true]
["x86_64-linux", "2.4.1", 111, true]
["x86_64-linux", "2.4.2", 198, true]
["x86_64-linux", "2.4.5", 335, true]
["x86_64-linux", "2.4.9", 362, true]
["x86_64-linux", "2.5.0", 0, true]
["x86_64-linux", "2.5.3", 105, true]
["x86_64-linux", "2.5.7", 206, true]
["x86_64-linux", "2.6.0", 0, true]
["x86_64-linux", "2.6.2", 47, true]
["x86_64-linux", "2.6.3", 62, true]
["x86_64-linux", "2.6.4", 104, true]
["x86_64-linux", "2.6.5", 114, true]
["x86_64-linux", "2.6.6", 146, true]
["x86_64-linux", "2.7.0", 0, true]
["x86_64-linux", "2.7.1", 83, true]
["x86_64-linux", "2.7.2", 137, true]
["x86_64-linux", "3.0.0", 0, true]
["x86_64-linux", "3.0.0", -1, true]
["x86_64-linux", "3.0.1", 64, true]
["x86_64-linux", "3.0.2", 107, true]
["x86_64-linux", "3.0.3", 157, true]
["x86_64-linux", "3.1.0", 0, true]
["x86_64-linux", "3.1.1", 18, true]
["x86_64-linux", "3.1.2", 20, true]
["x86_64-linux", "3.1.3", 185, true]
["x86_64-linux", "3.2.0", 0, true]
["x86_64-linux", "3.2.1", 31, true]

我们可以看到JRuby是不稳定的,MRI 2.2之前在Linux上是不稳定的,MRI〉= 2.2.0是稳定的(同样在Linux上)。
尽管上面的结果表明sort在Linux上的MRI 2.4.1中是稳定的,但在Windows上同样的版本是不稳定的:

["x64-mingw32", "2.4.1", 111, false]

为什么MRI的排序在Linux上稳定,而在Windows上不稳定?

即使在一个Ruby实现的单一版本中,排序算法也可以改变。MRI可以使用至少三种不同的排序。在util.c中,排序例程是在编译时使用一系列#ifdef选择的。看起来MRI可以使用至少两个不同库中的排序。它也有自己的实现。

你应该怎么做?

因为排序可能是稳定的,但不能保证是稳定的,所以不要编写依赖于Ruby排序稳定的代码,因为在不同的版本、实现或平台上使用时,代码可能会崩溃。

7kjnsjlb

7kjnsjlb4#

就我个人而言,我不会指望这个。不如这样做:

a.sort {|a, b| s1 = a[:int] <=> b[:int]; s1 != 0 ? s1 : a[:id] <=> b[:id] }

相关问题