对于 Ruby 的某些实现,排序是稳定的,但您不应该依赖它。Ruby 排序的稳定性是由实现定义的。
文件说什么
文档说你不应该依赖排序是稳定的:
结果不保证稳定。当两个元素的比较返回 0 时,元素的顺序是不可预测的。
请注意,这并没有说明排序是否稳定。它只是说它不能保证稳定。任何给定的 Ruby 实现都可以具有稳定的排序,并且仍然与文档保持一致。它也可能有一个不稳定的排序,或者随时改变排序是否稳定。
Ruby 实际做了什么
true
如果 Ruby 的排序是稳定的,或者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 机器上安装的所有 Ruby 的结果:
["java", "1.8.7", 357, false]
["java", "1.9.3", 551, false]
["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]
我们可以看到 JRuby 是不稳定的,2.2 之前的 MRI 在 Linux 上是不稳定的。MRI >= 2.2.0 是稳定的(同样,在 Linux 上)。
不过,平台很重要。虽然上面的结果表明,在 Linux 上的 MRI 2.4.1 中排序是稳定的,但在 Windows 上相同的版本是不稳定的:
["x64-mingw32", "2.4.1", 111, false]
为什么 MRI 的排序在 Linux 上是稳定的,而在 Windows 上却不是?
即使在单个版本的 Ruby 实现中,排序算法也可以改变。MRI可以使用至少三种不同的类型。排序例程是在编译时使用util.c中的一系列 #ifdefs 选择的。看起来 MRI 能够使用来自至少两个不同库的排序。它也有自己的实现。
你应该怎么做?
由于排序可能稳定但不能保证稳定,所以不要编写依赖于 Ruby 排序稳定的代码。当在不同的版本、实现或平台上使用时,该代码可能会中断。