Come ordinare un array in ordine decrescente in Ruby


282

Ho una serie di hash:

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

Sto cercando di ordinare questo array in ordine decrescente in base al valore di :barin ogni hash.

Sto usando sort_byper ordinare sopra l'array:

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

Tuttavia, questo ordina la matrice in ordine crescente. Come faccio a ordinarlo in ordine decrescente?

Una soluzione era fare quanto segue:

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

Ma quel segno negativo non sembra appropriato.


4
Date le altre opzioni, penso ancora -h [: bar] sia il più elegante. Cosa non ti piace a riguardo?
Michael Kohl,

2
Ero molto più interessato a comunicare l'intento del codice.
Waseem,

1
@Waseem potrei disturbarti ad aggiornare la risposta accettata?
Colllin,

7
@Waseem Non c'è niente di sbagliato nella risposta attuale. C'è solo una risposta di gran lunga migliore. la risposta di Tin Man è molto più approfondita e dimostra che sort_by.reverseè drammaticamente più efficiente della risposta attualmente accettata. Credo che sia anche meglio rispondere alla preoccupazione che hai menzionato sopra per "comunicare l'intento del codice". Inoltre, Tin Man ha aggiornato la sua risposta per l'attuale versione di ruby. Questa domanda è stata visualizzata più di 15.000 volte. Se riesci a risparmiare anche 1 secondo del tempo di ogni spettatore, penso che ne valga la pena.
Colllin,

3
@collindo Grazie ce l'ho fatta. :)
Waseem,

Risposte:


566

È sempre illuminante fare un punto di riferimento sulle varie risposte suggerite. Ecco cosa ho scoperto:

#! / Usr / bin / ruby

richiede '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 | -un 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}}
fine

                          sistema utente totale reale
ordina 3.960000 0.010000 3.970000 (3.990886)
ordina al contrario 4.040000 0.000000 4.040000 (4.038849)
sort_by -a [: bar] 0.690000 0.000000 0.690000 (0.692080)
ordina_di un [: bar] * - 1 0.700000 0.000000 0.700000 (0.699735)
sort_by.reverse! 0.650000 0.000000 0.650000 (0.654447)

Penso che sia interessante che @ Pablo's sort_by{...}.reverse!sia il più veloce. Prima di eseguire il test ho pensato che sarebbe stato più lento di " -a[:bar]" ma negare il valore risulta impiegare più tempo di quanto non faccia per invertire l'intero array in un passaggio. Non fa molta differenza, ma ogni piccola accelerazione aiuta.


Si noti che questi risultati sono diversi in Ruby 1.9

Ecco i risultati per Ruby 1.9.3p194 (revisione 20/04/2012 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)

Questi sono su un vecchio MacBook Pro. Le macchine più recenti o più veloci avranno valori più bassi, ma le differenze relative rimarranno.


Ecco una versione un po 'aggiornata dell'hardware più recente e la versione 2.1.1 di 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)

Nuovi risultati che eseguono il codice sopra usando Ruby 2.2.1 su un Macbook Pro più recente. Ancora una volta, i numeri esatti non sono importanti, sono le loro relazioni:

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)

Aggiornato per Ruby 2.7.1 su un MacBook Pro di metà 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)

... il metodo inverso in realtà non restituisce un array invertito, ma restituisce un enumeratore che inizia alla fine e funziona all'indietro.

La fonte per 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); sta copiando i puntatori sugli elementi in ordine inverso se ricordo correttamente la mia C, quindi l'array è invertito.


45
Super utile. Grazie per lo sforzo extra.
Joshua Pinter,

7
mi piace quando le persone forniscono la prova di riferimento come questa !! Eccezionale!
ktec,

25
"Adoro quando le persone forniscono la prova di riferimento come questa !!" Anch'io lo faccio, perché non devo farlo.
Tin Man,

9
@theTinMan Puoi per favore fornire un TL; DR per la tua risposta. Tutte queste informazioni di riferimento sono abbastanza utili. Ma un TL; DR in cima alla risposta sarà utile per le persone che vogliono solo la risposta. So che dovrebbero leggere l'intera spiegazione e penso che lo faranno. Ancora un TL; DR sarà abbastanza utile IMHO. Grazie per i tuoi sforzi.
Waseem,

8
Sono d'accordo con @Waseem. Così ben studiato come questa risposta, l'OP non ha chiesto "qual è il modo più veloce per fare una specie discendente in Ruby". Un TL; DR nella parte superiore che mostra usi semplici, seguito dai parametri di riferimento, migliorerebbe questa risposta IMO.

89

Solo una cosa veloce, che indica l'intento di ordine decrescente.

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

(Penserà a un modo migliore nel frattempo);)


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

Pablo, bel lavoro nel trovare un modo migliore! Vedi il benchmark che ho fatto.
Tin Man,

il primo metodo è più veloce (anche se forse più brutto) perché gira solo una volta. Per quanto riguarda il secondo, non è necessario il!, Che è per le operazioni sul posto.
tokland

3
Se non si utilizza il botto dopo l'inversione, non si inverte l'array ma si crea un altro invertito.
Pablo Fernandez,

56

Potresti fare:

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

4
ma il punto di utilizzo sort_byera che evitava di eseguire la funzione di confronto così tante volte
user102008

3
-1. sort_byè molto più efficiente e più leggibile. Negare il valore o fare un contrario alla fine sarà più veloce e più leggibile.
Marc-André Lafortune,

1
Mi piace questa risposta perché * -1non funziona con tutti i valori (ad es. Tempo) e reverseriordinerà i valori ordinati come uguali
Abe Voelker

8

Vedo che abbiamo (oltre agli altri) sostanzialmente due opzioni:

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

e

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

Mentre in entrambi i modi si ottiene lo stesso risultato quando la chiave di ordinamento è unica, tenere presente che il reversemodo invertirà l'ordine delle chiavi uguali .

Esempio:

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

Mentre spesso non ti devi preoccupare di questo, a volte lo fai. Per evitare tale comportamento, è possibile introdurre una seconda chiave di ordinamento (che sicuramente deve essere unica almeno per tutti gli articoli che hanno la stessa chiave di ordinamento):

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 per indicare che la semantica di reverseè diversa. Credo che rovinerà anche un ordinamento precedente, nel caso in cui si stia cercando di applicare più tipi in un certo ordine.
johncip,

6

Che dire:

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

Funziona!!

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

Sì, funziona davvero, ma penso che l'OP voglia mostrare l'intento con il codice (ha già una soluzione funzionante).
Pablo Fernandez,

Mentre sortfunzionerà, è solo più veloce quando si ordinano i valori immediati. Se devi scavare per loro sort_byè più veloce. Vedi il benchmark.
Tin Man,

3

Per quanto riguarda la suite di benchmark citata, questi risultati valgono anche per array ordinati.

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

E i risultati:

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

Dovresti essere in grado di fare sort_by {}. Reverse! (il contrario senza il botto crea un nuovo array e mi aspetto che sia più lento ovviamente)
bibstha,

2

La soluzione semplice da crescente a decrescente e viceversa è:

STRINGHE

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

CIFRE

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

Per quelle persone a cui piace misurare la velocità in 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

E risultati:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.