Pergunta

Eu tenho uma matriz de hashes, e eu quero que os valores exclusivos de fora. Chamando Array.uniq não me dá o que eu espero.

a = [{:a => 1},{:a => 2}, {:a => 1}]
a.uniq # => [{:a => 1}, {:a => 2}, {:a => 1}]

Onde eu esperava:

[{:a => 1}, {:a => 2}]

Na pesquisa em torno na net, eu não chegar a uma solução que eu estava feliz com. Pessoas recomendado redefinindo Hash.eql? e Hash.hash, que é o que Array.uniq está consultando.

Edit: Onde eu corri para este no mundo real, os hashes foram ligeiramente mais complexa. Eles foram o resultado de JSON analisado que tinha vários campos, alguns dos quais os valores foram hashes também. Eu tive uma série dos resultados que eu queria para filtrar os valores exclusivos.

Eu não gosto da solução Hash.eql? redefinir e Hash.hash, porque eu teria que redefinir Hash globalmente, ou redefini-lo para cada entrada na minha matriz. Alterar a definição de Hash para cada entrada seria complicado, especialmente porque pode haver hashes aninhados dentro de cada entrada.

Alterar Hash globalmente tem algum potencial, especialmente se fosse feito temporariamente. Eu quero construir uma outra função classe ou auxiliar que envolveu poupança fora as velhas definições e restaurá-los, mas acho que isso acrescenta mais complexidade do que é realmente necessário.

Usando inject parece ser uma boa alternativa para redefinir Hash.

Foi útil?

Solução

Eu posso conseguir o que quero chamando inject

a = [{:a => 1},{:a => 2}, {:a => 1}]
a.inject([]) { |result,h| result << h unless result.include?(h); result }

Isso irá retornar:

[{:a=>1}, {:a=>2}]

Outras dicas

Rubi 1.8.7+ retornará apenas o que você espera:

[{:a=>1}, {:a=>2}, {:a=>1}].uniq
#=> [{:a=>1}, {:a=>2}] 

Eu tive uma situação semelhante, mas hashes tinha as chaves. I utilizado o método de classificação.

O que quero dizer:

você tem um array:

[{:x=>1},{:x=>2},{:x=>3},{:x=>2},{:x=>1}]

você classificá-lo (#sort_by {|t| t[:x]}) e obter o seguinte:

[{:x=>1}, {:x=>1}, {:x=>2}, {:x=>2}, {:x=>3}]

Agora um pouco versão de resposta modificado por Aaaron Hinni:

your_array.inject([]) do |result,item| 
  result << item if !result.last||result.last[:x]!=item[:x]
  result
end

Eu também tentei:

test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]}

mas é muito lento. aqui está o meu ponto de referência:

test=[]
1000.times {test<<{:x=>rand}}

Benchmark.bmbm do |bm|
  bm.report("sorting: ") do
    test.sort_by {|t| t[:x]}.inject([]) {|r,h| r<<h if !r.last||r.last[:x]!=h[:x]; r}
  end
  bm.report("inject: ") {test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]} }
end

resultados:

Rehearsal ---------------------------------------------
sorting:    0.010000   0.000000   0.010000 (  0.005633)
inject:     0.470000   0.140000   0.610000 (  0.621973)
------------------------------------ total: 0.620000sec

                user     system      total        real
sorting:    0.010000   0.000000   0.010000 (  0.003839)
inject:     0.480000   0.130000   0.610000 (  0.612438)

Assumindo seus hashes são sempre pares chave-valor único, isso vai funcionar:

a.map {|h| h.to_a[0]}.uniq.map {|k,v| {k => v}}

Hash.to_a cria um array de arrays de valor-chave, de modo que o primeiro mapa você fica:

[[:a, 1], [:a, 2], [:a, 1]]

uniq em Arrays faz o que quiser, dando-lhe:

[[:a, 1], [:a, 2]]

e depois a segunda mapa coloca-los juntos novamente como hashes novamente.

Você pode usar (testado em Ruby 1.9.3),

[{a: 1},{a: 2},{a:1}].uniq => [{a:1},{a: 2}]
[{a: 1,b: 2},{a: 2, b: 2},{a: 1, b: 3}].uniq_by {|v| v[:a]} => [{a: 1,b: 2},{a: 2, b: 2}]

A resposta que você dá é semelhante ao discutido aqui . Ela substitui os métodos hash e eql? sobre os hashes que estão a aparecer na matriz que faz então se comportam uniq corretamente.

O método de tubos em matrizes (disponíveis desde 1.8.6) executa união set (retornando um array), então o seguinte é outra forma possível para obter elementos únicos de qualquer a array:

[] | a

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top