Como classificar uma matriz em ordem decrescente no Ruby

281

Eu tenho uma matriz de hashes:

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

Estou tentando classificar essa matriz em ordem decrescente de acordo com o valor de :barcada hash.

Estou usando sort_bypara classificar acima da matriz:

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

No entanto, isso classifica a matriz em ordem crescente. Como faço para classificar em ordem decrescente?

Uma solução era fazer o seguinte:

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

Mas esse sinal negativo não parece apropriado.

Waseem
fonte
4
Dadas as outras opções, ainda acho que -h [: bar] é a mais elegante. O que você não gosta nisso?
Michael Kohl
2
Eu estava muito mais interessado em transmitir a intenção do código.
Waseem
1
@Waseem, posso incomodar você para atualizar a resposta aceita?
colllin
7
@ Waseem Não há nada de errado com a resposta atual. Acontece que há uma resposta muito melhor. a resposta do homem de lata é muito mais completa e mostra que sort_by.reverseé dramaticamente mais eficiente do que a resposta atualmente aceita. Acredito que ele também lida melhor com a preocupação que você mencionou acima em "transmitir a intenção do código". Além disso, o homem de lata atualizou sua resposta para a versão atual do ruby. Esta pergunta foi vista mais de 15 mil vezes. Se você pode economizar até 1 segundo do tempo de cada espectador, acho que vale a pena.
colllin
3
Obrigado @collindo Eu fiz isso. :)
Waseem

Respostas:

565

É sempre esclarecedor fazer uma referência nas várias respostas sugeridas. Aqui está o que eu descobri:

#! / usr / bin / ruby

requer 'referência'

ary = []
1000.times { 
  ary << {: bar => rand (1000)} 
}

n = 500
Referência.bm (20) do | x |
  x.report ("sort") {n.times {ary.sort {| a, b | b [: bar] <=> a [: bar]}}}
  x.report ("ordem inversa") {n.times {ary.sort {| a, b | a [: bar] <=> b [: bar]} .reverse}}
  x.report ("sort_by -a [: bar]") {n.times {ary.sort_by {| a | -um 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}}
fim

                          total do sistema do usuário real
ordenar 3.960000 0.010000 3.970000 (3.990886)
ordem inversa 4.040000 0.000000 4.040000 (4.038849)
sort_by -a [: bar] 0,690000 0,000000 0,690000 (0,692080)
class_by a [: bar] * - 1 0,700000 0,000000 0,700000 (0,699735)
sort_by.reverse! 0,650000 0,000000 0,650000 (0,654447)

Acho interessante que o @ Pablo's sort_by{...}.reverse!seja o mais rápido. Antes de executar o teste, pensei que seria " -a[:bar]" mais lento que " ", mas negar o valor leva mais tempo do que o necessário para reverter toda a matriz de uma só vez. Não faz muita diferença, mas cada pequena aceleração ajuda.


Observe que esses resultados são diferentes no Ruby 1.9

Aqui estão os resultados para Ruby 1.9.3p194 (revisão em 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)

Estes estão em um MacBook Pro antigo. Máquinas mais novas ou mais rápidas terão valores mais baixos, mas as diferenças relativas permanecerão.


Aqui está uma versão um pouco atualizada no hardware mais recente e na versão 2.1.1 do 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)

Novos resultados executando o código acima usando o Ruby 2.2.1 em um Macbook Pro mais recente. Novamente, os números exatos não são importantes, são os relacionamentos deles:

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)

Atualizado para Ruby 2.7.1 em um MacBook Pro de meados 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)

... o método reverso não retorna um array invertido - ele retorna um enumerador que apenas inicia no final e trabalha para trás.

A fonte para 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); está copiando os ponteiros para os elementos na ordem inversa, se eu me lembrar corretamente do meu C, para que a matriz seja invertida.

o homem de lata
fonte
45
Super útil. Obrigado pelo esforço extra.
Joshua Pinter
7
Eu adoro quando as pessoas fornecem a prova de benchmark como esta !! Impressionante!
Ktc #
25
"Adoro quando as pessoas fornecem a prova de benchmark como esta !!" Eu também, porque então não preciso.
the Tin Man
9
@theTinMan Você pode fornecer um TL; DR para sua resposta. Toda essa informação de benchmark é bastante útil. Mas um TL; DR no topo da resposta será útil para pessoas que apenas querem a resposta. Eu sei que eles deveriam ler toda a explicação e acho que eles lerão. Ainda um TL; DR será IMHO bastante útil. Obrigado pelo seu esforço.
Waseem
8
Eu concordo com @Waseem. Por mais pesquisada que seja essa resposta, o OP não perguntou "qual é a maneira mais rápida de fazer uma classificação descendente em Ruby". Um TL; DR no topo, mostrando usos simples, seguidos pelos benchmarks, melhoraria essa resposta IMO.
89

Apenas uma coisa rápida, que denota a intenção de ordem decrescente.

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

(Pensará em uma maneira melhor nesse meio tempo);)


a.sort_by { |h| h[:bar] }.reverse!
Pablo Fernandez
fonte
Pablo, bom trabalho em encontrar uma maneira melhor! Veja o benchmark que eu fiz.
the Tin Man
o primeiro método é mais rápido (embora talvez seja mais feio) porque apenas faz um loop uma vez. Quanto ao segundo, você não precisa do!, Que é para operações no local.
tokland
3
Se você não usar o estrondo após o reverso, não reverterá a matriz, mas criará outra que é revertida.
Pablo Fernandez
56

Você poderia fazer:

a.sort{|a,b| b[:bar] <=> a[:bar]}
JRL
fonte
4
mas toda a ponto de usar sort_byfoi que ele evitado executando a função de comparação tantas vezes
user102008
3
-1. sort_byé muito mais eficiente e mais legível. Negar o valor ou fazer uma reversão no final será mais rápido e mais legível.
Marc-André Lafortune
1
Eu gosto desta resposta porque * -1não funciona com todos os valores (por exemplo, Tempo) e reversereordenará os valores classificados como iguais
Abe Voelker 07/02
8

Vejo que temos (ao lado de outros) basicamente duas opções:

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

e

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

Embora os dois modos ofereçam o mesmo resultado quando sua chave de classificação é única, lembre-se de que o reversecaminho reverterá a ordem das chaves iguais .

Exemplo:

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

Embora muitas vezes você não precise se preocupar com isso, às vezes precisa. Para evitar esse comportamento, você pode introduzir uma segunda chave de classificação (que com certeza precisa ser exclusiva, pelo menos para todos os itens que possuem a mesma chave de classificação):

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
fonte
+1 por apontar que a semântica de reverseé diferente. Eu acredito que ele também irá atrapalhar uma classificação anterior, no caso em que alguém está tentando aplicar várias classificações em alguma ordem.
johncip
6

A respeito:

 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
fonte
Sim, ele realmente funciona, mas acho que o PO quer mostrar intenção com o código (ele já tem uma solução funcional).
Pablo Fernandez
Embora sortfuncione, é mais rápido ao classificar valores imediatos. Se você tem que cavar para eles sort_byé mais rápido. Veja a referência.
the Tin Man
3

Em relação ao conjunto de benchmarks mencionado, esses resultados também são válidos para matrizes ordenadas.

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 os 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
fonte
Você deve poder fazer sort_by {}. Reverse! (reverso sem a explosão cria uma disposição nova e I seria de esperar que a ser mais lento, é claro)
bibstha
2

A solução simples de ascendente a descendente e vice-versa é:

CORDAS

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

DÍGITOS

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
fonte
1

Para quem gosta de medir a velocidade no 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 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
fonte