【问题标题】:Is sort in Ruby stable?Ruby 中的排序稳定吗?
【发布时间】:2013-03-15 21:22:19
【问题描述】:

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有关,也可能无关。

【问题讨论】:

  • 不,至少不是你是怎么做到的。 sort 在给定的比较器相同的情况下如何保证两个元素的顺序?
  • @Linuxios:一些排序算法是stable
  • @mu:谢谢。有趣的。不过我不认为 Ruby 是这样的。
  • 我得到了这个答案 => [{:id=>:f, :int=>0}, {:id=>:d, :int=>0}, {:id=> :e, :int=>1}, {:id=>:b, :int=>1}, {:id=>:g, :int=>1}, {:id=>:c, :int =>2}, {:id=>:h, :int=>2}, {:id=>:a, :int=>3}]
  • @beck03076 您会将第二条评论更改为答案吗?我认为它消极地回答了我的问题。我不明白你的第一条评论。

标签: ruby sorting


【解决方案1】:

MRIsortsort_by 都是 unstable。前段时间有一个request让他们稳定,但是被拒绝了。原因:Ruby 使用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

【讨论】:

  • 引用Wikipedia“快速排序是一种比较排序,在有效的实现中,它不是一种稳定的排序。”
  • 此外,如果任何排序实现在其合同中没有说它是稳定的,那么依赖它是稳定的总是错误的。
  • 您对stable_sort 的实现不尊重接受比较器的sort 的签名。
  • Ruby 的排序有时稳定的(但你不应该依赖它)。我认为创建此答案时,这种有时稳定性并不存在。详情请见my answer
【解决方案2】:

不,ruby 的内置排序不稳定。

如果您想要稳定的排序,这应该可以。如果您要经常使用它,您可能想为其创建一个方法。

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

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

更多信息,好奇者:

据我所知,使用原始数组索引作为决胜局是在使用不稳定排序时保证稳定性的唯一方法。商品的实际属性(或其他数据)不会告诉您它们的原始顺序。

您的示例有些做作,因为 :id 键在原始数组中按升序排序。假设原始数组按: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]}

【讨论】:

    【解决方案3】:

    对于 Ruby 的某些实现,排序是稳定的,但您不应该依赖它。 Ruby 排序的稳定性由实现定义。

    文档内容

    The documentation 说你不应该依赖排序是稳定的:

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

    请注意,这并没有说明排序是否稳定。它只是说它不能保证稳定。任何给定的 Ruby 实现都可以具有稳定的排序,并且仍然与文档保持一致。它也可能有一个不稳定的排序,或者随时改变排序是否稳定。

    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]
    ["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 中的一系列#ifdef 选择排序例程。看起来 MRI 能够使用来自至少两个不同库的排序。它也有自己的实现。

    你应该怎么做?

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

    【讨论】:

    • “恰好”稳定与“保证”稳定有很大不同。除非您希望在更改 Ruby 版本或平台时出现没有警告的错误,否则我的建议是忽略恰好稳定的实现。
    • @DaveSlutzkin 我同意。我说过这个(“但你不应该依赖它”),但如果你没有抓住它,可能还不够清楚。我会添加更多的单词。
    【解决方案4】:

    我个人不会指望这一点。做这样的事情怎么样:

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

    【讨论】:

    • 这既不回答我的问题,也不给出与我的问题的答案是肯定的结果相同的结果。
    猜你喜欢
    • 2016-05-23
    • 2012-05-16
    • 2017-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 2011-06-01
    相关资源
    最近更新 更多