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


281

我有一系列哈希:

[
  { :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] }

但是,这种负号似乎不合适。


4
考虑到其他选项,我仍然认为-h [:bar]是最优雅的。你不喜欢它什么?
迈克尔·科尔

2
我对传达代码的意图更感兴趣。
Waseem

1
@Waseem我可以麻烦您更新接受的答案吗?
colllin

7
@Waseem当前答案没有任何问题。碰巧会有一个更好的答案。Tin Man的答案要彻底得多,sort_by.reverse并且比目前接受的答案要有效得多。我相信它也可以更好地解决您上面提到的“传达代码意图”的问题。最重要的是,“锡人”更新了他对当前版本红宝石的回答。这个问题已被浏览超过15,000次。如果您甚至可以节省每个观看者1秒的时间,那是值得的。
colllin

3
@collindo谢谢,我做到了。:)
Waseem

Answers:


564

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

#!/ usr / bin / ruby

需要“基准”

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

n = 500
Benchmark.bm(20)做| x |
  x.report(“ sort”){n。次{ary.sort {| a,b | b [:bar] <=> a [:bar]}}}
  x.report(“ sort reverse”){n。次{ary.sort {| a,b | a [:bar] <=> b [:bar]} .reverse}}
  x.report(“ sort_by -a [:bar]”){n。次{ary.sort_by {| a | -一间酒吧] } } }
  x.report(“ sort_by a [:bar] *-1”){n。次{ary.sort_by {| a | a [:bar] *-1}}}
  x.report(“ sort_by.reverse!”){n。次{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)

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

来源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 > 0); 如果我没记错我的C语言,则会以相反的顺序将指针复制到元素,因此数组是反向的。


45
超级有用。感谢您的付出。
2012年

7
当人们提供这样的基准证明时,我会喜欢它!太棒了!
ktec 2012年

25
“当人们提供这样的基准证明时,我喜欢它!” 我也这样做,因为那样就不必了。
Tin Man

9
@theTinMan能否请您提供TL; DR。所有这些基准信息都是非常有用的。但是在答案之上的TL; DR对于只想要答案的人很有用。我知道他们应该阅读整个说明,我认为他们会的。仍然,TL; DR将是非常有用的恕我直言。谢谢你的努力。
Waseem

8
我同意@Waseem。对此答案进行了充分研究,OP并未询问“在Ruby中进行降序排序的最快方法是什么”。顶部显示一个简单用法的TL; DR及其后的基准将改善IMO的答案。

89

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

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

(与此同时,会想到一种更好的方法);)


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

Pablo,在找到更好的方法方面做得很好!请参阅我所做的基准测试。
Tin Man 2010年

第一种方法更快(尽管可能更难看),因为它只循环一次。至于第二个,则不需要!,它用于就地操作。
tokland 2010年

3
如果您不使用反转后的爆炸,则不会反转阵列,而是创建另一个反转的阵列。
Pablo Fernandez 2010年

56

您可以这样做:

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

4
但使用的全部要点sort_by是,它避免了多次运行比较功能
user102008

3
-1。sort_by效率更高,可读性更高。取反值或最后取反将更快捷,更易读。
马克-安德烈·Lafortune

1
我喜欢这个答案,因为* -1它不适用于所有值(例如“时间”),并且reverse会重新排序排序为相等的值
Abe Voelker

8

我看到我们(除了其他人之外)基本上有两个选择:

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不同。我相信,如果有人试图以某种顺序应用多种排序,它也会弄乱前一种排序。
johncip '18年

6

关于什么:

 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希望显示代码意图(他已经有一个可行的解决方案)。
Pablo Fernandez 2010年

虽然sort可以使用,但仅在对立即值进行排序时更快。如果您必须为它们挖掘,sort_by则速度更快。请参阅基准。
锡人

3

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

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 {}。反向!(没有爆炸的反向会创建一个新数组,我当然希望它会变慢)
bibstha

2

从升序到降序,反之亦然的简单解决方案是:

STRINGS

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]

1

对于那些喜欢在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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.