如何排序一个数组在Ruby降序

我有一个哈希数组:

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

但这个负号似乎不太合适。

289729 次浏览

简单说一下,这表示降序的意图。

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

(同时会想出更好的办法);)


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

是什么:

 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"}]

你可以这样做:

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

对各种建议的答案做一个基准测试总是很有启发性的。以下是我的发现:

#!/usr/bin/ruby


require 'benchmark'


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


n = 500
Benchmark.bm(20) do |x|
x.report("sort")               { n.times { ary.sort{ |a,b| b[:bar] <=> a[:bar] } } }
x.report("sort reverse")       { n.times { ary.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse } }
x.report("sort_by -a[:bar]")   { n.times { ary.sort_by{ |a| -a[:bar] } } }
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 } }
end


user     system      total        real
sort                  3.960000   0.010000   3.970000 (  3.990886)
sort reverse          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 revision 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 > 0);是按倒序复制指向元素的指针(如果我没记错C的话),所以数组是倒序的。

对于上面提到的基准测试套件,这些结果也适用于排序数组。

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)

我看到我们(和其他国家)基本上有两个选择:

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}]

对于那些喜欢在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

从升序到降序的简单解是:

字符串

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]