Classificar hash por chave, retornar hash em Ruby

258

Essa seria a melhor maneira de classificar um hash e retornar o objeto Hash (em vez de Array):

h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4}
# => {"a"=>1, "c"=>3, "b"=>2, "d"=>4}

Hash[h.sort]
# => {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
Vincent
fonte
9
Não tenho certeza de que haja muita vantagem em classificar um hash, a menos que você esteja usando eachou each_pairiterar sobre ele. Mesmo assim, eu provavelmente ainda pegaria as chaves, classificá-las e iterar sobre elas, pegando os valores conforme necessário. Isso garante que o código se comporte corretamente nos Rubies mais antigos.
the Tin Man
Também faz sentido no ruby ​​1.9. Eu tinha uma coleção de compromissos agrupados por datas (como chaves) provenientes do db e eu classificava manualmente através do ruby. Por exemplo. {"2012-09-22": [...], "2012-09-30": [...], "12/10/2012": [...]}
Adit Saxena
Sim, acho que seu processo Hash [h.sort] é mais eficaz do que as chaves de classificação e, em seguida, acesso novamente o hash através das chaves classificadas.
Douglas
3
Você teve alguns anos para pensar em sua solução, está pronto para aceitar uma resposta? ;-)
Mark Thomas

Respostas:

219

No Ruby 2.1, é simples:

h.sort.to_h
Mark Thomas
fonte
@zachaysan mas ela não funciona: h.sort{|a,z|a<=>z}.to_h(testado 2.1.10, 2.3.3)
whitehat101
@ whitehat101 Você está certo. Eu tive um bug ( aé uma matriz, não apenas a chave). Eu apaguei meu comentário.
Zachaysan
Apenas no caso de alguém está procurando uma maneira de classificar uma matriz de hashes, isso vai fazer o truque (onde h é a matriz): h.map(&:sort).map(&:to_h).
JM Janzen
82

Nota: Ruby> = 1.9.2 possui um hash de preservação de ordem: as chaves de pedido são inseridas e serão as que estão enumeradas. O abaixo se aplica a versões mais antigas ou a códigos compatíveis com versões anteriores.

Não há conceito de um hash classificado. Então não, o que você está fazendo não está certo.

Se você deseja classificá-lo para exibição, retorne uma string:

"{" + h.sort.map{|k,v| "#{k.inspect}=>#{v.inspect}"}.join(", ") + "}"

ou, se você quiser as chaves em ordem:

h.keys.sort

ou, se você deseja acessar os elementos em ordem:

h.sort.map do |key,value|
  # keys will arrive in order to this block, with their associated value.
end

mas, em resumo, não faz sentido falar sobre um hash classificado. Nos documentos , "A ordem na qual você percorre um hash por chave ou valor pode parecer arbitrária e geralmente não estará na ordem de inserção". Portanto, inserir chaves em uma ordem específica no hash não ajudará.

Peter
fonte
Isto está certo. Eu sugeriria o uso da gema RBtree para obter a funcionalidade do conjunto ordenado em ruby.
Aaron Scruggs
26
A partir do 1.9.2, a ordem de inserção de hash será preservada. Veja redmine.ruby-lang.org/issues/show/994
David
4
"A partir do 1.9.2, a ordem de inserção de hash será preservada.", E é agradável.
the Tin Man
2
Sobre o meu primeiro comentário (sentindo-me engraçado): Por exemplo, confiar na ordenação de hash seria silencioso e imprevisível para versões do Ruby anteriores à 1.9.2.
quer
5
Como essa resposta obteve cerca de 20 + 1s sem responder a nenhuma das duas partes da pergunta do OP? "1) Essa (exemplo do OP) seria a melhor maneira de classificar um hash, 2) e retornar o objeto Hash"? Eu não invejo + 1s :) é só depois dessa leitura que eu ainda fico com as perguntas originais. Além disso, se o ponto é que não existe um hash classificado, observe os comentários para a resposta escolhida para esta pergunta stackoverflow.com/questions/489139/…
jj_
64

Eu sempre usei sort_by. Você precisa agrupar a #sort_bysaída Hash[]para gerar um hash, caso contrário, ela gera uma matriz de matrizes. Como alternativa, para fazer isso, você pode executar o #to_hmétodo na matriz de tuplas para convertê-las em uma k=>vestrutura (hash).

hsh ={"a" => 1000, "b" => 10, "c" => 200000}
Hash[hsh.sort_by{|k,v| v}] #or hsh.sort_by{|k,v| v}.to_h

Existe uma pergunta semelhante em " Como classificar um Ruby Hash pelo valor numérico? ".

boulder_ruby
fonte
8
usar sort_byum hash retornará uma matriz. Você precisaria mapeá-lo como um hash novamente. Hash[hsh.sort_by{|k,v| v}]
stevenspiel
1
sim, os interpreta classe enumerador hashes como matrizes eu acho
boulder_ruby
3
Direita, classificar é em valores: hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}.
Cary Swoveland
1
Observe que as chamadas to_hsão suportadas apenas no Ruby 2.1.0+
Phrogz
1
há um erro de digitação no comentário, correção:sort_by{|k,v| v}.to_h)
jitter
13

Não, não é (Ruby 1.9.x)

require 'benchmark'

h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4}
many = 100_000

Benchmark.bm do |b|
  GC.start

  b.report("hash sort") do
    many.times do
      Hash[h.sort]
    end
  end

  GC.start

  b.report("keys sort") do
    many.times do
      nh = {}
      h.keys.sort.each do |k|
        nh[k] = h[k]
      end
    end
  end
end

       user     system      total        real
hash sort  0.400000   0.000000   0.400000 (  0.405588)
keys sort  0.250000   0.010000   0.260000 (  0.260303)

Para grandes hashes, a diferença crescerá até 10x e mais

fl00r
fonte
12

Você deu a melhor resposta para si mesmo no OP: Hash[h.sort]se você deseja mais possibilidades, aqui está a modificação no local do hash original para classificá-lo:

h.keys.sort.each { |k| h[k] = h.delete k }
Boris Stitnicky
fonte
1
Para os curiosos, isso corre um pouco mais rápido que a "classificação de chaves" em stackoverflow.com/a/17331221/737303 no Ruby 1.9.3.
nitrogênio
9

Classificar hash por chave , retornar hash em Ruby

Com desestruturação e Hash # sort

hash.sort { |(ak, _), (bk, _)| ak <=> bk }.to_h

Enumerável # sort_by

hash.sort_by { |k, v| k }.to_h

Classificação de hash # com comportamento padrão

h = { "b" => 2, "c" => 1, "a" => 3  }
h.sort         # e.g. ["a", 20] <=> ["b", 30]
hash.sort.to_h #=> { "a" => 3, "b" => 2, "c" => 1 }

Nota: <Ruby 2.1

array = [["key", "value"]] 
hash  = Hash[array]
hash #=> {"key"=>"value"}

Nota:> Ruby 2.1

[["key", "value"]].to_h #=> {"key"=>"value"}
Moriarty
fonte
1
se não estiver usando v, você deve prefixar um sublinhadohash.sort_by { |k, _v| k }.to_h
silva96
6

ActiveSupport :: OrderedHash é outra opção se você não quiser usar o ruby ​​1.9.2 ou executar suas próprias soluções alternativas.

eremita
fonte
A ligação é interrompida
Cobra Sanders
3
Corrigi o link para apontar para o Google, caso algum historiador queira pesquisar como isso poderia ter sido feito no passado. Mas qualquer um que venha agora deve usar uma versão mais recente do Ruby.
eremite 16/02
0
@ordered = {}
@unordered.keys.sort.each do |key|
  @ordered[key] = @unordered[key]
end
nascer do sol
fonte
4
Isto é como você faria isso se rubi não tem métodos como Hash # sort_by
boulder_ruby
0

Eu tive o mesmo problema (tive que classificar meus equipamentos pelo nome) e resolvi assim:

<% @equipments.sort.each do |name, quantity| %>
...
<% end %>

@equipments é um hash que eu construo no meu modelo e retorno no meu controlador. Se você chamar .sort, ele classificará o hash com base no seu valor-chave.

Gabriel Mesquita
fonte
-3

Gostei da solução no post anterior.

Eu fiz uma mini turma, chamei class AlphabeticalHash. Ele também tem um método chamado ap, que aceita um argumento, uma Hash, como entrada: ap variable. Semelhante a pp ( pp variable)

Mas (tentará) imprimirá na lista alfabética (suas chaves). Não sei se alguém mais quer usar isso, ele está disponível como uma jóia, você pode instalá-lo da seguinte forma:gem install alphabetical_hash

Para mim, isso é bastante simples. Se outras pessoas precisarem de mais funcionalidades, entre em contato, incluí-las na gema.

Edição: Crédito vai para Peter , que me deu a idéia. :)

shevy
fonte