So sortieren Sie ein Array in Ruby in absteigender Reihenfolge


281

Ich habe eine Reihe von Hashes:

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

Ich versuche, dieses Array in absteigender Reihenfolge nach dem Wert von :barin jedem Hash zu sortieren .

Ich benutze sort_by, um über Array zu sortieren:

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

Dadurch wird das Array jedoch in aufsteigender Reihenfolge sortiert. Wie sortiere ich es in absteigender Reihenfolge?

Eine Lösung bestand darin, Folgendes zu tun:

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

Dieses negative Vorzeichen erscheint jedoch nicht angemessen.


4
Angesichts der anderen Optionen halte ich -h [: bar] immer noch für die eleganteste. Was magst du daran nicht?
Michael Kohl

2
Ich war viel mehr daran interessiert, die Absicht des Codes zu vermitteln.
Waseem

1
@Waseem könnte ich Sie stören, die akzeptierte Antwort zu aktualisieren?
Colllin

7
@Waseem An der aktuellen Antwort ist nichts auszusetzen. Es gibt einfach eine weitaus bessere Antwort. Die Antwort des Blechmanns ist viel gründlicher und zeigt, dass sie sort_by.reversedramatisch effizienter ist als die derzeit akzeptierte Antwort. Ich glaube, es geht auch besser auf das oben erwähnte Problem ein, "die Absicht des Codes zu vermitteln". Darüber hinaus hat der Blechmann seine Antwort für die aktuelle Version von Ruby aktualisiert. Diese Frage wurde über 15.000 Mal angezeigt. Wenn Sie sogar 1 Sekunde der Zeit jedes Zuschauers sparen können, denke ich, dass es sich lohnt.
Colllin

3
@collindo Danke, ich habe es getan. :)
Waseem

Antworten:


564

Es ist immer aufschlussreich, einen Benchmark für die verschiedenen vorgeschlagenen Antworten durchzuführen. Folgendes habe ich herausgefunden:

#! / usr / bin / ruby

erfordern '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 | -eine 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}}
Ende

                          Benutzersystem total real
sort 3.960000 0.010000 3.970000 (3.990886)
Umkehrung sortieren 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)

Ich finde es interessant, dass @ Pablo's sort_by{...}.reverse!am schnellsten ist. Vor dem Ausführen des Tests dachte ich, es wäre langsamer als " -a[:bar]", aber das Negieren des Werts dauert länger als das Umkehren des gesamten Arrays in einem Durchgang. Es ist kein großer Unterschied, aber jede kleine Beschleunigung hilft.


Bitte beachten Sie, dass diese Ergebnisse in Ruby 1.9 unterschiedlich sind

Hier sind die Ergebnisse für 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)

Diese befinden sich auf einem alten MacBook Pro. Neuere oder schnellere Maschinen haben niedrigere Werte, aber die relativen Unterschiede bleiben bestehen.


Hier ist eine etwas aktualisierte Version auf neuerer Hardware und der 2.1.1-Version von 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)

Neue Ergebnisse beim Ausführen des obigen Codes mit Ruby 2.2.1 auf einem neueren Macbook Pro. Auch hier sind die genauen Zahlen nicht wichtig, es sind ihre Beziehungen:

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)

Aktualisiert für Ruby 2.7.1 auf einem MacBook Pro Mitte 2015:

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)

... die umgekehrte Methode gibt kein umgekehrtes Array zurück - sie gibt einen Enumerator zurück, der erst am Ende beginnt und rückwärts arbeitet.

Die Quelle für Array#reverseist:

               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); kopiert die Zeiger in umgekehrter Reihenfolge auf die Elemente, wenn ich mich richtig an mein C erinnere, sodass das Array umgekehrt ist.


45
Super nützlich. Vielen Dank für den zusätzlichen Aufwand.
Joshua Pinter

7
Ich liebe es, wenn Leute den Benchmark-Beweis wie diesen liefern !! Genial!
ktec

25
"Ich liebe es, wenn Leute den Benchmark-Beweis wie diesen liefern !!" Ich auch, weil ich dann nicht muss.
der Blechmann

9
@theTinMan Können Sie bitte eine TL; DR für Ihre Antwort angeben? All diese Benchmark-Informationen sind sehr nützlich. Ein TL; DR über der Antwort ist jedoch nützlich für Personen, die nur die Antwort wünschen. Ich weiß, dass sie die ganze Erklärung lesen sollten und ich denke, dass sie es tun werden. Immer noch ein TL; DR wird meiner Meinung nach sehr nützlich sein. Danke für deinen Einsatz.
Waseem

8
Ich stimme @Waseem zu. So gut recherchiert diese Antwort auch ist, das OP hat nicht gefragt, "wie man in Ruby am schnellsten absteigend sortiert". Ein TL; DR oben mit einfachen Verwendungszwecken, gefolgt von den Benchmarks, würde diese Antwort IMO verbessern.

89

Nur eine kurze Sache, die die Absicht der absteigenden Reihenfolge anzeigt.

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

(Werde mir in der Zwischenzeit einen besseren Weg überlegen);)


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

Pablo, gute Arbeit bei der Suche nach einem besseren Weg! Siehe den Benchmark, den ich gemacht habe.
der Blechmann

Die erste Methode ist schneller (wenn auch vielleicht hässlicher), da sie nur einmal wiederholt wird. Was das zweite betrifft, brauchen Sie das! Nicht, das ist für Inplace-Operationen.
Tokand

3
Wenn Sie den Knall nach dem Umkehren nicht verwenden, kehren Sie das Array nicht um, sondern erstellen ein anderes, das umgekehrt ist.
Pablo Fernandez

56

Du könntest es tun:

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

4
aber der sort_by
springende

3
-1. sort_byist so viel effizienter und lesbarer. Das Negieren des Werts oder das Umkehren am Ende ist schneller und besser lesbar.
Marc-André Lafortune

1
Ich mag diese Antwort, weil * -1sie nicht mit allen Werten (z. B. Zeit) funktioniert und reverseWerte neu sortiert, die gleich sortiert sind
Abe Voelker

8

Ich sehe, dass wir (neben anderen) grundsätzlich zwei Möglichkeiten haben:

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

und

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

Während beide Möglichkeiten , wie Sie das gleiche Ergebnis , wenn der Sortierschlüssel eindeutig zuzuordnen sind, bedenken Sie, dass die reverseArt und Weise wird die Reihenfolge der Tasten umkehren , die gleich sind .

Beispiel:

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

Während Sie sich oft nicht darum kümmern müssen, tun Sie dies manchmal. Um ein solches Verhalten zu vermeiden, können Sie einen zweiten Sortierschlüssel einführen (der zumindest für alle Elemente mit demselben Sortierschlüssel eindeutig sein muss):

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 für den Hinweis, dass die Semantik von reverseunterschiedlich ist. Ich glaube, es wird auch eine vorherige Sortierung durcheinander bringen, falls versucht wird, mehrere Sortierungen in einer bestimmten Reihenfolge anzuwenden.
Johncip

6

Wie wäre es mit:

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

Es klappt!!

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

Ja, es funktioniert tatsächlich, aber ich denke, der PO möchte mit dem Code seine Absicht zeigen (er hat bereits eine funktionierende Lösung).
Pablo Fernandez

Während sortes funktioniert, ist es nur schneller, wenn unmittelbare Werte sortiert werden. Wenn Sie nach ihnen graben müssen, sort_byist das schneller. Siehe den Benchmark.
der Blechmann

3

In Bezug auf die erwähnte Benchmark-Suite gelten diese Ergebnisse auch für sortierte Arrays.

sort_by/ reversees ist:

# 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

Und die Ergebnisse:

$: 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)

Sie sollten in der Lage sein, sort_by {}. Reverse! (Umkehren ohne den Knall erstellt ein neues Array und ich würde erwarten, dass das natürlich langsamer ist)
Bibstha

2

Einfache Lösung von aufsteigend nach absteigend und umgekehrt ist:

STRINGS

str = ['ravi', 'aravind', 'joker', 'poker']
asc_string = str.sort # => ["aravind", "joker", "poker", "ravi"]
asc_string.reverse # => ["ravi", "poker", "joker", "aravind"]

Ziffern

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

Für diejenigen, die Geschwindigkeit in IPS messen möchten;)

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

Und Ergebnisse:

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.