descending - ruby sort array by attribute
Ordenar una matriz en orden descendente en Ruby (7)
Tengo una serie de hashes como siguiente
[
{ :foo => ''foo'', :bar => 2 },
{ :foo => ''foo'', :bar => 3 },
{ :foo => ''foo'', :bar => 5 },
]
Estoy tratando de ordenar la matriz anterior en orden descendente de acuerdo con el valor de :bar
en cada hash.
Estoy usando sort_by
como siguiente para ordenar la matriz anterior.
a.sort_by { |h| h[:bar] }
Sin embargo, lo anterior ordena la matriz en orden ascendente. ¿Cómo lo hago ordenar en orden descendente?
Una solución fue hacer lo siguiente:
a.sort_by { |h| -h[:bar] }
Pero ese signo negativo no parece apropiado. ¿Alguna opinión?
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
Podrías hacerlo:
a.sort{|a,b| b[:bar] <=> a[:bar]}
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"}]
Respecto a la suite de referencia mencionada ... estos resultados también son válidos para arreglos ordenados. sort_by / reverse is :)
P.ej:
# 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)
Siempre es esclarecedor hacer un punto de referencia en las diversas respuestas sugeridas. Esto es lo que descubrí:
#!/usr/bin/ruby require ''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| -a[: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 } } end user system total real sort 3.960000 0.010000 3.970000 ( 3.990886) sort reverse 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 @ la sort_by{...}.reverse!
de Pablo sort_by{...}.reverse!
es el más rápido Antes de ejecutar la prueba, pensé que sería más lento que " -a[:bar]
", pero negar el valor toma más tiempo que en invertir una matriz completa en una sola pasada. No hay mucha diferencia, pero cada pequeña aceleración ayuda.
Tenga en cuenta que estos resultados son diferentes en Ruby 1.9
Aquí están 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 se mantendrán.
Aquí hay una versión actualizada del 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 una 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)
Solo una cosa rápida, que denota la intención del orden descendente.
descending = -1
a.sort_by { |h| h[:bar] * descending }
(Pensará de una manera mejor en el tiempo);)
a.sort_by { |h| h[:bar] }.reverse!
Veo que tenemos (al lado de otras) 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 reverse
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}]
Mientras que a menudo no necesitas preocuparte por esto, a veces lo haces. Para evitar tal 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}]