【问题标题】:How to sort an array in descending order in Ruby如何在Ruby中按降序对数组进行排序
【发布时间】:2011-02-08 04:32:16
【问题描述】:

我有一个哈希数组:

[
  { :foo => 'foo', :bar => 2 },
  { :foo => 'foo', :bar => 3 },
  { :foo => 'foo', :bar => 5 },
]

我正在尝试根据每个哈希中:bar 的值对这个数组进行降序排序。

我正在使用sort_by 对上面的数组进行排序:

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

但是,这会按升序对数组进行排序。如何让它按降序排序?

一种解决方案是执行以下操作:

a.sort_by { |h| -h[:bar] }

但那个负号似乎不合适。

【问题讨论】:

  • 考虑到其他选项,我仍然认为 -h[:bar] 是最优雅的。你不喜欢它的什么地方?
  • 我对传达代码的意图更感兴趣。
  • @Waseem 当前答案没有问题。恰好有一个更好的答案。铁皮人的回答要彻底得多,并表明sort_by.reverse 比目前接受的答案要高效得多。我相信它也更好地解决了您上面提到的“传达代码意图”的问题。最重要的是,铁皮人已经更新了他对当前版本 ruby​​ 的答案。这个问题已被查看超过 15k 次。如果你能节省每位观众 1 秒的时间,我认为这是值得的。
  • @collindo 谢谢我做到了。 :)
  • 这是 2016 年,我们有另一种方式(自 v2.2 起):arr.max_by(arr.size) { |h| h[:bar] } #=> [{:foo=>"foo", :bar=>5}, {:foo=>"foo", :bar=>3}, {:foo=>"foo", :bar=>2}]

标签: ruby sorting


【解决方案1】:

对各种建议的答案进行基准测试总是很有启发性。这是我发现的:

#!/usr/bin/ruby 需要“基准” ary = [] 1000.次{ ary rand(1000)} } n = 500 Benchmark.bm(20) 做 |x| x.report("sort") { n.times { ary.sort{ |a,b| b[:bar] a[:bar] } } } x.report("反向排序") { n.times { ary.sort{ |a,b| a[:bar] b[:bar] }.reverse } } x.report("sort_by -a[:bar]") { n.times { ary.sort_by{ |a| -一间酒吧] } } } x.report("sort_by a[:bar]*-1") { n.times { ary.sort_by{ |a| a[:bar]*-1 } } } x.report("sort_by.reverse!") { n.times { ary.sort_by{ |a| a[:bar] }.reverse } } 结尾 用户系统总真实 排序 3.960000 0.010000 3.970000 ( 3.990886) 反向排序 4.040000 0.000000 4.040000 (4.038849) sort_by -a[:bar] 0.690000 0.000000 0.690000 (0.692080) sort_by a[:bar]*-1 0.700000 0.000000 0.700000 (0.699735) sort_by.reverse! 0.650000 0.000000 0.650000 ( 0.654447)

我认为有趣的是@Pablo 的sort_by{...}.reverse! 最快。在运行测试之前,我认为它会比“-a[:bar]”慢,但否定该值比一次反转整个数组所需的时间更长。差别不大,但每一个小小的加速都会有所帮助。


请注意,这些结果在 Ruby 1.9 中有所不同

以下是 Ruby 1.9.3p194(2012-04-20 修订版 35410)[x86_64-darwin10.8.0] 的结果:

                           user     system      total        real
sort                   1.340000   0.010000   1.350000 (  1.346331)
sort reverse           1.300000   0.000000   1.300000 (  1.310446)
sort_by -a[:bar]       0.430000   0.000000   0.430000 (  0.429606)
sort_by a[:bar]*-1     0.420000   0.000000   0.420000 (  0.414383)
sort_by.reverse!       0.400000   0.000000   0.400000 (  0.401275)

这些在旧 MacBook Pro 上。较新或较快的机器将具有较低的值,但相对差异将保持不变。


以下是较新硬件和 Ruby 2.1.1 版本的更新版本:

#!/usr/bin/ruby

require 'benchmark'

puts "Running Ruby #{RUBY_VERSION}"

ary = []
1000.times {
  ary << {:bar => rand(1000)}
}

n = 500

puts "n=#{n}"
Benchmark.bm(20) do |x|
  x.report("sort")               { n.times { ary.dup.sort{ |a,b| b[:bar] <=> a[:bar] } } }
  x.report("sort reverse")       { n.times { ary.dup.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse } }
  x.report("sort_by -a[:bar]")   { n.times { ary.dup.sort_by{ |a| -a[:bar] } } }
  x.report("sort_by a[:bar]*-1") { n.times { ary.dup.sort_by{ |a| a[:bar]*-1 } } }
  x.report("sort_by.reverse")    { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse } }
  x.report("sort_by.reverse!")   { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse! } }
end

# >> Running Ruby 2.1.1
# >> n=500
# >>                            user     system      total        real
# >> sort                   0.670000   0.000000   0.670000 (  0.667754)
# >> sort reverse           0.650000   0.000000   0.650000 (  0.655582)
# >> sort_by -a[:bar]       0.260000   0.010000   0.270000 (  0.255919)
# >> sort_by a[:bar]*-1     0.250000   0.000000   0.250000 (  0.258924)
# >> sort_by.reverse        0.250000   0.000000   0.250000 (  0.245179)
# >> sort_by.reverse!       0.240000   0.000000   0.240000 (  0.242340)

在更新的 Macbook Pro 上使用 Ruby 2.2.1 运行上述代码的新结果。同样,确切的数字并不重要,重要的是它们的关系:

Running Ruby 2.2.1
n=500
                           user     system      total        real
sort                   0.650000   0.000000   0.650000 (  0.653191)
sort reverse           0.650000   0.000000   0.650000 (  0.648761)
sort_by -a[:bar]       0.240000   0.010000   0.250000 (  0.245193)
sort_by a[:bar]*-1     0.240000   0.000000   0.240000 (  0.240541)
sort_by.reverse        0.230000   0.000000   0.230000 (  0.228571)
sort_by.reverse!       0.230000   0.000000   0.230000 (  0.230040)

在 2015 年中的 MacBook Pro 上针对 Ruby 2.7.1 进行了更新:

Running Ruby 2.7.1
n=500     
                           user     system      total        real
sort                   0.494707   0.003662   0.498369 (  0.501064)
sort reverse           0.480181   0.005186   0.485367 (  0.487972)
sort_by -a[:bar]       0.121521   0.003781   0.125302 (  0.126557)
sort_by a[:bar]*-1     0.115097   0.003931   0.119028 (  0.122991)
sort_by.reverse        0.110459   0.003414   0.113873 (  0.114443)
sort_by.reverse!       0.108997   0.001631   0.110628 (  0.111532)

... reverse 方法实际上并不返回一个反转数组 - 它返回一个仅从末尾开始并向后工作的枚举数。

Array#reverse的来源是:

               static VALUE
rb_ary_reverse_m(VALUE ary)
{
    long len = RARRAY_LEN(ary);
    VALUE dup = rb_ary_new2(len);

    if (len > 0) {
        const VALUE *p1 = RARRAY_CONST_PTR_TRANSIENT(ary);
        VALUE *p2 = (VALUE *)RARRAY_CONST_PTR_TRANSIENT(dup) + len - 1;
        do *p2-- = *p1++; while (--len > 0);
    }
    ARY_SET_LEN(dup, RARRAY_LEN(ary));
    return dup;
}

do *p2-- = *p1++; while (--len &gt; 0); 如果我没记错我的 C,则以相反的顺序复制指向元素的指针,因此数组是相反的。

【讨论】:

  • 超级有用。感谢您的额外努力。
  • 我喜欢人们提供这样的基准证明!太棒了!
  • “我喜欢人们提供这样的基准证明!!”我也这样做,因为那样我就不必了。
  • @theTinMan 您能否提供一个 TL;DR 来回答您的问题。所有这些基准信息都非常有用。但是,答案之上的 TL;DR 对于只想要答案的人来说会很有用。我知道他们应该阅读整个解释,我认为他们会的。仍然是 TL;DR 将非常有用恕我直言。感谢您的努力。
  • 我同意@Waseem。尽管这个答案经过充分研究,OP 并没有问“在 Ruby 中进行降序排序的最快方法是什么”。顶部的 TL;DR 显示简单用途,然后是基准,将改进 IMO 的这个答案。
【解决方案2】:

只是一个快速的事情,表示降序的意图。

descending = -1
a.sort_by { |h| h[:bar] * descending }

(同时会想到更好的方法);)


a.sort_by { |h| h[:bar] }.reverse!

【讨论】:

  • Pablo,找到更好的方法做得很好!查看我所做的基准测试。
  • 第一种方法更快(虽然可能更丑),因为它只循环一次。至于第二个,您不需要!,这是用于就地操作的。
  • 如果您在 reverse 后不使用 bang,您将不会反转数组,而是创建另一个反转的数组。
【解决方案3】:

你可以这样做:

a.sort{|a,b| b[:bar] <=> a[:bar]}

【讨论】:

  • 但是使用sort_by的重点是它避免了多次运行比较功能
  • -1。 sort_by 效率更高,可读性更强。最后取反或取反会更快,更易读。
  • 我喜欢这个答案,因为* -1 不适用于所有值(例如时间),并且reverse 将重新排序排序为相等的值
【解决方案4】:

我看到我们(除其他外)基本上有两种选择:

a.sort_by { |h| -h[:bar] }

a.sort_by { |h| h[:bar] }.reverse

虽然当您的排序键是唯一的时,这两种方式都会为您提供相同的结果,但请记住,reverse 方式将反转相等键的顺序

例子:

a = [{foo: 1, bar: 1},{foo: 2,bar: 1}]
a.sort_by {|h| -h[:bar]}
 => [{:foo=>1, :bar=>1}, {:foo=>2, :bar=>1}]
a.sort_by {|h| h[:bar]}.reverse
 => [{:foo=>2, :bar=>1}, {:foo=>1, :bar=>1}]

虽然您通常不需要关心这一点,但有时您确实需要。为了避免这种行为,您可以引入第二个排序键(至少对于具有相同排序键的所有项目,它肯定需要是唯一的):

a.sort_by {|h| [-h[:bar],-h[:foo]]}
 => [{:foo=>2, :bar=>1}, {:foo=>1, :bar=>1}]
a.sort_by {|h| [h[:bar],h[:foo]]}.reverse
 => [{:foo=>2, :bar=>1}, {:foo=>1, :bar=>1}]

【讨论】:

  • +1 指出reverse 的语义不同。我相信在尝试以某种顺序应用多种排序的情况下,它也会弄乱先前的排序。
【解决方案5】:

怎么样:

 a.sort {|x,y| y[:bar]<=>x[:bar]}

有效!!

irb
>> a = [
?>   { :foo => 'foo', :bar => 2 },
?>   { :foo => 'foo', :bar => 3 },
?>   { :foo => 'foo', :bar => 5 },
?> ]
=> [{:bar=>2, :foo=>"foo"}, {:bar=>3, :foo=>"foo"}, {:bar=>5, :foo=>"foo"}]

>>  a.sort {|x,y| y[:bar]<=>x[:bar]}
=> [{:bar=>5, :foo=>"foo"}, {:bar=>3, :foo=>"foo"}, {:bar=>2, :foo=>"foo"}]

【讨论】:

  • 是的,它确实有效,但我认为 PO 想通过代码显示意图(他已经有了一个可行的解决方案)。
  • 虽然sort 可以工作,但只有在对立即数进行排序时才会更快。如果你必须为他们挖掘sort_by 更快。查看基准。
【解决方案6】:

关于提到的基准套件,这些结果也适用于排序数组。

sort_by/reverse是:

# foo.rb
require 'benchmark'

NUM_RUNS = 1000

# arr = []
arr1 = 3000.times.map { { num: rand(1000) } }
arr2 = 3000.times.map { |n| { num: n } }.reverse

Benchmark.bm(20) do |x|
  { 'randomized'     => arr1,
    'sorted'         => arr2 }.each do |label, arr|
    puts '---------------------------------------------------'
    puts label

    x.report('sort_by / reverse') {
      NUM_RUNS.times { arr.sort_by { |h| h[:num] }.reverse }
    }
    x.report('sort_by -') {
      NUM_RUNS.times { arr.sort_by { |h| -h[:num] } }
    }
  end
end

结果:

$: ruby foo.rb
                           user     system      total        real
---------------------------------------------------
randomized
sort_by / reverse      1.680000   0.010000   1.690000 (  1.682051)
sort_by -              1.830000   0.000000   1.830000 (  1.830359)
---------------------------------------------------
sorted
sort_by / reverse      0.400000   0.000000   0.400000 (  0.402990)
sort_by -              0.500000   0.000000   0.500000 (  0.499350)

【讨论】:

  • 你应该可以做 sort_by{}.reverse! (没有爆炸的反向创建一个新数组,我希望它当然会更慢)
【解决方案7】:

对于那些喜欢在 IPS 中测量速度的人 ;)

require 'benchmark/ips'

ary = []
1000.times { 
  ary << {:bar => rand(1000)} 
}

Benchmark.ips do |x|
  x.report("sort")               { ary.sort{ |a,b| b[:bar] <=> a[:bar] } }
  x.report("sort reverse")       { ary.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse }
  x.report("sort_by -a[:bar]")   { ary.sort_by{ |a| -a[:bar] } }
  x.report("sort_by a[:bar]*-1") { ary.sort_by{ |a| a[:bar]*-1 } }
  x.report("sort_by.reverse!")   { ary.sort_by{ |a| a[:bar] }.reverse }
  x.compare!
end

结果:

Warming up --------------------------------------
                sort    93.000  i/100ms
        sort reverse    91.000  i/100ms
    sort_by -a[:bar]   382.000  i/100ms
  sort_by a[:bar]*-1   398.000  i/100ms
    sort_by.reverse!   397.000  i/100ms
Calculating -------------------------------------
                sort    938.530  (± 1.8%) i/s -      4.743k in   5.055290s
        sort reverse    901.157  (± 6.1%) i/s -      4.550k in   5.075351s
    sort_by -a[:bar]      3.814k (± 4.4%) i/s -     19.100k in   5.019260s
  sort_by a[:bar]*-1      3.732k (± 4.3%) i/s -     18.706k in   5.021720s
    sort_by.reverse!      3.928k (± 3.6%) i/s -     19.850k in   5.060202s

Comparison:
    sort_by.reverse!:     3927.8 i/s
    sort_by -a[:bar]:     3813.9 i/s - same-ish: difference falls within error
  sort_by a[:bar]*-1:     3732.3 i/s - same-ish: difference falls within error
                sort:      938.5 i/s - 4.19x  slower
        sort reverse:      901.2 i/s - 4.36x  slower

【讨论】:

    【解决方案8】:

    从升序到降序的简单解决方案是:

    字符串

    str = ['ravi', 'aravind', 'joker', 'poker']
    asc_string = str.sort # => ["aravind", "joker", "poker", "ravi"]
    asc_string.reverse # => ["ravi", "poker", "joker", "aravind"]
    

    数字

    digit = [234,45,1,5,78,45,34,9]
    asc_digit = digit.sort # => [1, 5, 9, 34, 45, 45, 78, 234]
    asc_digit.reverse # => [234, 78, 45, 45, 34, 9, 5, 1]
    

    【讨论】:

      猜你喜欢
      • 2016-11-16
      • 2014-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-20
      • 1970-01-01
      • 2020-03-06
      • 2019-01-01
      相关资源
      最近更新 更多