Domanda

Ho una serie di hash e desidero che ne derivino i valori univoci. Chiamare Array.uniq non mi dà quello che mi aspetto.

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

Dove mi aspettavo:

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

Cercando in rete, non ho trovato una soluzione di cui ero felice. La gente ha raccomandato di ridefinire Hash.eql? e Hash.hash , poiché questo è ciò che Array.uniq sta interrogando.

Modifica: Dove mi sono imbattuto in questo nel mondo reale, gli hash erano leggermente più complessi. Erano il risultato del JSON analizzato che aveva più campi, alcuni dei quali erano anche hash. Avevo una serie di quei risultati che volevo filtrare i valori univoci.

Non mi piace la soluzione di ridefinizione di Hash.eql? e Hash.hash , perché dovrei ridefinire Hash a livello globale o ridefiniscilo per ogni voce nel mio array. La modifica della definizione di Hash per ciascuna voce sarebbe scomoda, soprattutto perché all'interno di ciascuna voce potrebbero essere presenti hash nidificati.

La modifica di Hash a livello globale ha qualche potenziale, soprattutto se è stata eseguita temporaneamente. Vorrei creare un'altra funzione di classe o di supporto che ha concluso salvando le vecchie definizioni e ripristinandole, ma penso che ciò aggiunga più complessità di quanto sia realmente necessario.

L'uso di inject sembra una buona alternativa alla ridefinizione di Hash .

È stato utile?

Soluzione

Posso ottenere quello che voglio chiamando inject

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

Questo restituirà:

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

Altri suggerimenti

Ruby 1.8.7+ restituirà esattamente ciò che ti aspettavi:

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

Ho avuto una situazione simile, ma gli hash avevano le chiavi. Ho usato il metodo di ordinamento.

Cosa intendo:

hai un array:

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

lo ordini ( #sort_by {| t | t [: x]} ) e ottieni questo:

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

ora una versione leggermente modificata della risposta di Aaaron Hinni:

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

Ho anche provato:

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

ma è molto lento. ecco il mio punto di riferimento:

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

Risultati:

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)

Supponendo che i tuoi hash siano sempre coppie chiave-valore singole, funzionerà:

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

Hash.to_a crea una matrice di array di valori-chiave, quindi la prima mappa ti dà:

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

uniq su Arrays fa quello che vuoi, dandoti:

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

e quindi la seconda mappa li rimette insieme come hash.

Puoi usare (testato in 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}]

La risposta che dai è simile a quella discussa qui . Sovrascrive i metodi hash e eql? sugli hash che devono apparire nell'array e quindi fa funzionare correttamente uniq .

Il metodo pipe su array (disponibile dalla 1.8.6) esegue un set union (restituendo un array), quindi il seguente è un altro modo possibile per ottenere elementi univoci di qualsiasi array a :

[] | a

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top