Cómo ordenar una matriz en orden descendente en Ruby

282

Tengo una variedad de hashes:

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

Estoy tratando de ordenar esta matriz en orden descendente de acuerdo con el valor de :baren cada hash.

Estoy usando sort_bypara ordenar la matriz anterior:

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

Sin embargo, esto ordena la matriz en orden ascendente. ¿Cómo hago que se ordene en orden descendente?

Una solución fue hacer lo siguiente:

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

Pero ese signo negativo no parece apropiado.

Waseem
fuente
44
Dadas las otras opciones, sigo pensando que -h [: bar] es la más elegante. ¿Qué no te gusta de eso?
Michael Kohl el
2
Estaba mucho más interesado en transmitir la intención del código.
Waseem
1
@Waseem, ¿podría molestarlo para que actualice la respuesta aceptada?
colllin
77
@Waseem No hay nada malo con la respuesta actual. Simplemente hay una respuesta mucho mejor. La respuesta del Hombre de hojalata es mucho más exhaustiva y muestra que sort_by.reversees dramáticamente más eficiente que la respuesta actualmente aceptada. Creo que también aborda mejor la preocupación que mencionó anteriormente para "transmitir la intención del código". Además de eso, Tin Man ha actualizado su respuesta para la versión actual de ruby. Esta pregunta ha sido vista más de 15k veces. Si puede ahorrar incluso 1 segundo del tiempo de cada espectador, creo que vale la pena.
Colllin
3
@collindo Gracias lo hice. :)
Waseem

Respuestas:

566

Siempre es esclarecedor hacer un punto de referencia en las diferentes respuestas sugeridas. Esto es lo que descubrí:

#! / usr / bin / ruby

requiere 'punto de referencia'

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 ("ordenar al revés") {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 [: barra] * - 1}}}
  x.report ("sort_by.reverse!") {n.times {ary.sort_by {| a | a [: bar]} .reverse}}
final

                          sistema de usuario total real
ordenar 3.960000 0.010000 3.970000 (3.990886)
ordenar inverso 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)

Creo que es interesante que @ Pablo's sort_by{...}.reverse!sea ​​el más rápido. Antes de ejecutar la prueba, pensé que sería más lento que " -a[:bar]", pero negar el valor lleva más tiempo que invertir la matriz completa en una sola pasada. No es una gran diferencia, pero cada pequeña aceleración ayuda.


Tenga en cuenta que estos resultados son diferentes en Ruby 1.9

Estos son los resultados para Ruby 1.9.3p194 (2012-04-20 revisión 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)

Estos están en un viejo MacBook Pro. Las máquinas más nuevas o más rápidas tendrán valores más bajos, pero las diferencias relativas permanecerán.


Aquí hay una versión un poco actualizada en el hardware más nuevo y la versión 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)

Nuevos resultados ejecutando el código anterior usando Ruby 2.2.1 en un Macbook Pro más reciente. Una vez más, los números exactos no son importantes, son sus relaciones:

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)

Actualizado para Ruby 2.7.1 en una MacBook Pro de mediados de 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)

... el método inverso en realidad no devuelve una matriz inversa: devuelve un enumerador que solo comienza al final y funciona al revés.

La fuente Array#reversees:

               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); está copiando los punteros a los elementos en orden inverso si recuerdo mi C correctamente, por lo que la matriz se invierte.

el hombre de hojalata
fuente
45
Súper útil Gracias por el esfuerzo extra.
Joshua Pinter el
77
¡Me encanta cuando la gente proporciona la prueba de referencia como esta! ¡Increíble!
ktec
25
"¡Me encanta cuando la gente proporciona la prueba de referencia como esta!" Yo también, porque entonces no tengo que hacerlo.
El Hombre de hojalata
9
@ theTinMan ¿Puede proporcionar un TL; DR para su respuesta? Toda esta información de referencia es bastante útil. Pero un TL; DR encima de la respuesta será útil para las personas que solo quieren la respuesta. Sé que deberían leer toda la explicación y creo que lo harán. Sigue siendo un TL; DR será bastante útil en mi humilde opinión. Gracias por tu esfuerzo.
Waseem
8
Estoy de acuerdo con @Waseem. Por bien investigada que sea esta respuesta, el OP no preguntó "cuál es la forma más rápida de hacer un descenso en Ruby". Un TL; DR en la parte superior que muestra usos simples, seguido de los puntos de referencia, mejoraría esta respuesta de la OMI.
89

Solo una cosa rápida, que denota la intención de descender el orden.

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

(Pensará en una mejor manera mientras tanto);)


a.sort_by { |h| h[:bar] }.reverse!
Pablo Fernández
fuente
Pablo, ¡buen trabajo para encontrar una mejor manera! Ver el punto de referencia que hice.
The Tin Man
El primer método es más rápido (aunque quizás más feo) porque solo se repite una vez. En cuanto al segundo, no necesita el!, Eso es para operaciones in situ.
tokland el
3
Si no usa la explosión después de invertir, no invertirá la matriz sino que creará otra que esté invertida.
Pablo Fernández
56

Podrías hacerlo:

a.sort{|a,b| b[:bar] <=> a[:bar]}
JRL
fuente
44
pero el punto de utilización sort_byfue que evitó correr la función de comparación tantas veces
user102008
3
-1. sort_byes mucho más eficiente y más legible. Negar el valor o hacer un reverso al final será más rápido y más legible.
Marc-André Lafortune
1
Me gusta esta respuesta porque * -1no funciona con todos los valores (por ejemplo, Tiempo), y reversereordenará los valores que se ordenaron como iguales
Abe Voelker
8

Veo que tenemos (junto a otros) básicamente dos opciones:

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

y

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

Si bien ambas formas le dan el mismo resultado cuando su clave de clasificación es única, tenga en cuenta que la reverseforma invertirá el orden de las claves que son iguales .

Ejemplo:

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

Si bien a menudo no necesita preocuparse por esto, a veces sí. Para evitar este comportamiento, puede introducir una segunda clave de clasificación (que seguramente debe ser única al menos para todos los elementos que tienen la misma clave de clasificación):

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}]
Niklas Hoesl
fuente
+1 por señalar que la semántica de reversees diferente. Creo que también estropeará un tipo anterior, en el caso de que uno esté tratando de aplicar varios tipos en algún orden.
johncip
6

Qué pasa:

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

¡¡Funciona!!

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"}]
OscarRyz
fuente
Sí, en realidad funciona, pero creo que el PO quiere mostrar la intención con el código (ya tiene una solución que funciona).
Pablo Fernández
Si bien sortfuncionará, solo es más rápido al ordenar valores inmediatos. Si tienes que cavar por ellos sort_byes más rápido. Ver el punto de referencia.
The Tin Man
3

Con respecto al conjunto de referencia mencionado, estos resultados también son válidos para las matrices ordenadas.

sort_by/ reversees:

# 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

Y los resultados:

$: 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)
Christopher Kuttruff
fuente
Debería poder hacer sort_by {}. Reverse! (revertir sin la explosión crea una nueva matriz y esperaría que sea más lento, por supuesto)
bibstha
2

La solución simple de ascendente a descendente y viceversa es:

INSTRUMENTOS DE CUERDA

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

DIGITOS

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]
Ravistm
fuente
1

Para aquellas personas que les gusta medir la velocidad 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

Y resultados:

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
mpospelov
fuente