如何在Ruby中按降序对数组进行排序

Posted

技术标签:

【中文标题】如何在Ruby中按降序对数组进行排序【英文标题】:How to sort an array in descending order in 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] 【参考方案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]

【讨论】:

以上是关于如何在Ruby中按降序对数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

如何使用extjs4.1在网格中按降序对带有连字符的浮点值进行排序

在django模板中按降序对相关项目进行排序

如何按值按降序对哈希进行排序并在 ruby​​ 中输出哈希?

如何按降序对列表视图项进行排序

如何按降序对 JSON 数组元素进行排序? [复制]

如何根据特定值按降序对数组进行排序[重复]