Comment trier un tableau par ordre décroissant dans Ruby


282

J'ai un tableau de hachages:

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

J'essaie de trier ce tableau par ordre décroissant en fonction de la valeur de :bardans chaque hachage.

J'utilise sort_bypour trier le tableau ci-dessus:

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

Cependant, cela trie le tableau par ordre croissant. Comment puis-je le trier par ordre décroissant?

Une solution consistait à faire ce qui suit:

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

Mais ce signe négatif ne semble pas approprié.


4
Étant donné les autres options, je pense que -h [: bar] est la plus élégante. Qu'est-ce qui ne vous plaît pas?
Michael Kohl

2
J'étais beaucoup plus intéressé à transmettre l'intention du code.
Waseem

1
@Waseem pourrais-je vous déranger pour mettre à jour la réponse acceptée?
colllin

7
@Waseem Il n'y a rien de mal avec la réponse actuelle. Il se trouve que c'est une bien meilleure réponse. la réponse de Tin Man est beaucoup plus approfondie et montre qu'elle sort_by.reverseest considérablement plus efficace que la réponse actuellement acceptée. Je crois que cela répond aussi mieux à la préoccupation que vous avez mentionnée ci-dessus pour «transmettre l'intention du code». En plus de cela, le Tin Man a mis à jour sa réponse pour la version actuelle de rubis. Cette question a été vue plus de 15k fois. Si vous pouvez gagner même 1 seconde du temps de chaque spectateur, je pense que ça vaut le coup.
colllin

3
@collindo Merci je l'ai fait. :)
Waseem

Réponses:


566

Il est toujours instructif de faire un point de repère sur les différentes réponses suggérées. Voici ce que j'ai découvert:

#! / usr / bin / ruby

exiger une «référence»

ary = []
1000 fois { 
  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 | un [: bar] * - 1}}}
  x.report ("sort_by.reverse!") {n.times {ary.sort_by {| a | un [: bar]} .reverse}}
fin

                          système utilisateur total réel
trier 3,960000 0,010000 3,970000 (3,990886)
tri inversé 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)

Je pense que c'est intéressant que @ Pablo sort_by{...}.reverse!soit le plus rapide. Avant d'exécuter le test, je pensais que ce serait plus lent que " -a[:bar]" mais annuler la valeur s'avère plus long que pour inverser l'ensemble du tableau en une seule passe. Ce n'est pas vraiment une différence, mais chaque petite accélération aide.


Veuillez noter que ces résultats sont différents dans Ruby 1.9

Voici les résultats pour Ruby 1.9.3p194 (2012-04-20 révision 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)

Ce sont sur un vieux MacBook Pro. Les machines plus récentes ou plus rapides auront des valeurs plus faibles, mais les différences relatives resteront.


Voici une version un peu mise à jour sur du matériel plus récent et la version 2.1.1 de 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)

Nouveaux résultats exécutant le code ci-dessus en utilisant Ruby 2.2.1 sur un Macbook Pro plus récent. Encore une fois, les chiffres exacts ne sont pas importants, ce sont leurs relations:

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)

Mis à jour pour Ruby 2.7.1 sur un MacBook Pro mi-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)

... la méthode inverse ne retourne pas réellement un tableau inversé - elle retourne un énumérateur qui commence juste à la fin et fonctionne à l'envers.

La source de Array#reverseest:

               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); copie les pointeurs vers les éléments dans l'ordre inverse si je me souviens bien de mon C, donc le tableau est inversé.


45
Super utile. Merci pour l'effort supplémentaire.
Joshua Pinter

7
j'adore quand les gens fournissent la preuve de référence comme ça !! Impressionnant!
ktec

25
"J'adore quand les gens fournissent la preuve de référence comme ça !!" Moi aussi, car je n'ai pas à le faire.
The Tin Man

9
@theTinMan Pouvez-vous s'il vous plaît fournir un TL; DR pour votre réponse. Toutes ces informations de référence sont très utiles. Mais un TL; DR au-dessus de la réponse sera utile pour les personnes qui veulent juste la réponse. Je sais qu'ils devraient lire toute l'explication et je pense qu'ils le feront. Encore un TL; DR sera très utile à mon humble avis. Merci pour votre effort.
Waseem

8
Je suis d'accord avec @Waseem. Aussi bien documentée que cette réponse est, le PO n'a pas demandé "quel est le moyen le plus rapide de faire un tri descendant en Ruby". Un TL; DR en haut montrant des utilisations simples, suivi des repères, améliorerait cette réponse OMI.

89

Juste une chose rapide, qui dénote l'intention de l'ordre décroissant.

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

(Pensera à une meilleure façon dans l'intervalle);)


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

Pablo, beau travail pour trouver une meilleure façon! Voir le repère que j'ai fait.
The Tin Man

la première méthode est plus rapide (mais peut-être plus laide) car elle ne boucle qu'une seule fois. Quant au second, vous n'avez pas besoin du!, C'est pour les opérations sur place.
Tokland

3
Si vous n'utilisez pas le bang après l'inversion, vous n'inverserez pas le tableau mais en créerez un autre inversé.
Pablo Fernandez

56

Vous pourriez faire:

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

4
mais le but de l'utilisation sort_byétait qu'il évitait d'exécuter la fonction de comparaison tant de fois
user102008

3
-1. sort_byest tellement plus efficace et plus lisible. Nier la valeur ou faire un revers à la fin sera plus rapide et plus lisible.
Marc-André Lafortune

1
J'aime cette réponse, car * -1elle ne fonctionne pas avec toutes les valeurs (par exemple, l'heure) et reverseréorganisera les valeurs triées comme égales
Abe Voelker

8

Je vois que nous avons (à côté des autres) essentiellement deux options:

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

et

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

Bien que les deux méthodes vous donnent le même résultat lorsque votre clé de tri est unique, gardez à l'esprit que la reverseméthode inversera l'ordre des clés qui sont égales .

Exemple:

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

Bien que vous n'ayez souvent pas besoin de vous en soucier, vous le faites parfois. Pour éviter un tel comportement, vous pouvez introduire une deuxième clé de tri (qui doit à coup sûr être unique au moins pour tous les éléments qui ont la même clé de tri):

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 pour avoir souligné que la sémantique de reverseest différente. Je crois que cela gâchera également un type précédent, dans le cas où l'on essaie d'appliquer plusieurs types dans un certain ordre.
johncip

6

Qu'en est-il de:

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

Ça marche!!

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

Oui, cela fonctionne réellement, mais je pense que le PO veut montrer son intention avec le code (il a déjà une solution de travail).
Pablo Fernandez

Alors que sortfonctionne, il est seulement plus rapide lors du tri des valeurs immédiates. Si vous devez creuser pour eux, sort_byc'est plus rapide. Voir le benchmark.
The Tin Man

3

En ce qui concerne la suite de référence mentionnée, ces résultats valent également pour les tableaux triés.

sort_by/ reversec'est:

# 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

Et les résultats:

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

Vous devriez pouvoir faire sort_by {}. Reverse! (inverser sans bang crée un nouveau tableau et je m'attends à ce que ce soit plus lent bien sûr)
bibstha

2

La solution simple de l'ascendant au descendant et vice versa est:

STRINGS

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

CHIFFRES

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

Pour ceux qui aiment mesurer la vitesse en 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

Et les résultats:

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.