Rubyで配列を降順でソートする方法


281

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

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

この配列:barを各ハッシュのの値に従って降順で並べ替えようとしています。

sort_by上記の配列の並べ替えに使用しています:

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

ただし、これは配列を昇順でソートします。降順に並べ替えるにはどうすればよいですか?

1つの解決策は、以下を実行することでした。

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

しかし、その負の符号は適切に思えません。


4
他のオプションを考えると、私はまだ-h [:bar]が最もエレガントだと思います。それについてあなたは何が嫌いですか?
マイケルコール

2
私はコードの意図を伝えることにもっと興味を持っていました。
Waseem 2010

1
@Waseem承認された回答を更新するのに苦労しませんか?
colllin 2013

7
@Waseem現在の答えに問題はありません。たまたまはるかに良い答えがあります。Tin Manの回答ははるかに詳細sort_by.reverseであり、現在受け入れられている回答よりも劇的に効率的であることを示しています。また、上記の「コードの意図の伝達」についての懸念への対応も改善されたと思います。その上で、Tin Manはルビーの現在のバージョンに対する彼の答えを更新しました。この質問は15,000回以上閲覧されました。各視聴者の時間を1秒も節約できれば、それだけの価値があると思います。
colllin 2013

3
@collindoありがとうございます。:)
Waseem 2013

回答:


564

さまざまな提案された回答についてベンチマークを行うことは常に賢明です。これが私が見つけたものです:

#!/ usr / bin / ruby

「ベンチマーク」が必要

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

n = 500
Benchmark.bm(20)do | x |
  x.report( "sort"){n.times {ary.sort {| a、b | b [:バー] <=> a [:バー]}}}
  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 | -バー] } } }
  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's sort_by{...}.reverse!が一番速いのは面白いと思います。テストを実行する前は、 " -a[:bar]" よりも遅いと思いましたが、値を無効にすると、1回のパスで配列全体を反転させるよりも時間がかかることがわかりました。それは大した違いではありませんが、あらゆる小さな高速化が役立ちます。


これらの結果は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 > 0); Cを正しく覚えている場合は、要素へのポインターを逆の順序でコピーするので、配列が逆になります。


45
超便利です。余分な努力をありがとう。
Joshua Pinter、2012年

7
人々がこのようなベンチマーク証明を提供するときに私はそれが大好きです!! 驚くばかり!
ktec

25
「このようなベンチマークの証拠を提供してくれる人が大好きです!!」私もそうしなければならないので、そうする必要はありません。
Tin Man、

9
@theTinMan回答にTL; DRを提供してください。このベンチマーク情報はすべて非常に役立ちます。しかし、回答の上にあるTL; DRは、回答を求めているだけの人に役立ちます。私は彼らが説明全体を読まなければならないことを知っています。それでもTL; DRは非常に便利です。がんばってくれてありがとう。
Waseem 2013

8
@Waseemに同意します。この回答と同様に十分に調査されたように、OPは「Rubyで降順ソートを行うための最速の方法は何か」と尋ねませんでした。簡単な使用法を示す上部のTL; DRに続いてベンチマークを使用すると、この回答のIMOが改善されます。

89

簡単に言うと、降順の意図を示しています。

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

(当面はもっと良い方法を考えます);)


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

パブロ、より良い方法を見つけるのにいい仕事を!私がしたベンチマークを見てください。
ブリキの男

最初の方法は1回しかループしないため、より高速です(おそらく醜いかもしれません)。2番目については、!は必要ありません。これはインプレース操作用です。
tokland '19

3
リバース後に強打を使用しない場合は、配列を逆にするのではなく、逆の別の配列を作成します。
Pablo Fernandez

56

あなたがすることができます:

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

4
しかし、使用の全体的なポイントはsort_by、比較機能を何度も実行することを回避したことでした
user102008

3
-1。sort_byより効率的で読みやすくなっています。値を否定するか、最後に逆を行うと、より高速で読みやすくなります。
マルク=アンドレ・Lafortune

1
* -1すべての値(例:時間)では機能せずreverse、等しくソートされた値を並べ替えるので、私はこの回答が好きです
Abe Voelker

8

私たちは(他のものと並んで)基本的に2つのオプションを持っていることがわかります:

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

多くの場合、これを気にする必要はありませんが、気にする場合もあります。このような動作を回避するには、2番目の並べ替えキーを導入します(確かに、少なくとも同じ並べ替えキーを持つすべてのアイテムに対して一意である必要があります)。

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

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

一方でsort即時の値をソートする際の意志の仕事、それが唯一の高速です。あなたがそれらのために掘る必要がある場合sort_byは高速です。ベンチマークをご覧ください。
Tin Man、

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 {}。reverseを実行できるはずです!(強打なしのリバースは新しい配列を作成し、もちろんそれはより遅いと予想します)
bibstha

2

昇順から降順、またはその逆の簡単な解決策は次のとおりです。

ストリング

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.