1. ホーム
  2. ruby

[解決済み] Rubyで配列を降順にソートする方法

2022-02-09 01:18:44

質問

ハッシュの配列があります。

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

しかし、そのマイナス記号は適切ではないようです。

どのように解決するのですか?

様々な回答案についてベンチマークを行うのは、いつも勉強になります。以下は、私が発見したことです。

#!/usr/bin/ruby

require 'ベンチマーク'

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 } }. }
終了

                          ユーザーシステム 合計 実数
ソート 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] しかし、値を否定することは、1回の処理で配列全体を反転させるよりも時間がかかることがわかりました。大した違いではありませんが、ちょっとしたスピードアップが役に立ちます。


<ブロッククオート

この結果は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を使用したものです。新しいマシンや高速なマシンでは値が下がりますが、相対的な差は残ります。


新しいハードウェアと2.1.1バージョンのRubyで、少し更新したバージョンを紹介します。

#!/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)


Mid-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言語の記憶が正しければ、要素へのポインタを逆順にコピーしているので、配列は逆になっています。