2011-09-22 10 views
6

Ho un hash del formato:Hash invert in Ruby?

{key1 => [a, b, c], key2 => [d, e, f]} 

e voglio finire con:

{ a => key1, b => key1, c => key1, d => key2 ... } 

Qual è il modo più semplice per ottenere questo risultato?

Sto usando Ruby on Rails.

UPDATE

OK sono riuscito a estrarre l'oggetto reale dal log del server, esso viene spinta tramite la tecnologia AJAX.

Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}} 
+0

Non sono sicuro di come farlo stampare un array in modo da poterlo leggere per sperimentare. – cjm2671

+0

Ancora, che cosa hai provato? 'p array' stampa le cose. – Mat

+1

Sei sicuro che sia un array e non un hash? Il modo in cui lo descrivi è ambiguo. –

risposta

7
hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]} 

prima variante

hash.map{|k, v| v.map{|f| {f => k}}}.flatten 
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}] 

o

hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h} 
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2} 

UPD

ok, il vostro hash è:

hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}} 
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h} 
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"} 
+0

Questo sembra giusto ma non riesco a farlo funzionare; Ho aggiornato il post ora per mostrare l'oggetto reale. – cjm2671

+0

vedi il mio aggiornamento .. – fl00r

+0

come abbiamo discusso nella tua domanda precedente, fl00r, usando inject quando non ce n'è bisogno è IMHO non una buona opzione. Inoltre, stai usando la mappa ma in realtà stai facendo effetti collaterali, il che è fonte di confusione, ognuno è migliore (beh, "meglio", ognuno di solito è cattivo). Sì, lo so, sono un bigotto funzionale :-) – tokland

1

Se stai cercando di invertire un hash formattato come questo, possono aiutare a:

a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]} 
a.inject({}) do |memo, (key, values)| 
    values.each {|value| memo[value] = key } 
    memo 
end 

restituisce:

{"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2} 
1
new_hash={} 
hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']} 
hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }} 

Questo dà

new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"} 
2

Ok, supponiamo. Dici di avere un array ma sono d'accordo con Benoit che quello che probabilmente hai è un hash. Un approccio funzionale:

h = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]} 
h.map { |k, vs| Hash[vs.map { |v| [v, k] }] }.inject(:merge) 
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2} 

anche:

h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge) 
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2} 
+0

Questo funziona solo con Ruby (> =) 1.9. –

+0

@undur_gongor: il primo snippet dovrebbe funzionare in 1.8 – tokland

+0

Continuo a ricevere "il numero dispari di argomenti per l'hash". 'Hash [vs ...]' dovrebbe essere 'Hash [* vs.map {| v | [v, k]} .flatten] 'e' inject (: merge) 'è solo 1.9. Comunque, questo dimostra che dovrei passare a 1.9 :-) –

0

Un modo per ottenere quello che stai cercando:

arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}] 

results_arr = [] 
arr.each do |hsh| 
    hsh.values.flatten.each do |val| 
    results_arr << { [val] => hsh.keys.first }··· 
    end 
end 


Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}] 
1

Se si vuole affrontare in modo corretto con i valori duplicati, quindi dovresti usare l'hash # inverso da Facets of Ruby

Hash#inverse conserva i valori duplicati, ad es.assicura che hash.inverse.inverse == hash

sia:

come questo :

require 'facets' 

h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]} 
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]} 

h.inverse 
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2} 

Il codice è simile al seguente:

# this doesn't looks quite as elegant as the other solutions here, 
# but if you call inverse twice, it will preserve the elements of the original hash 

# true inversion of Ruby Hash/preserves all elements in original hash 
# e.g. hash.inverse.inverse ~ h 

class Hash 

    def inverse 
    i = Hash.new 
    self.each_pair{ |k,v| 
     if (v.class == Array) 
     v.each{ |x| 
      i[x] = i.has_key?(x) ? [k,i[x]].flatten : k 
     } 
     else 
     i[v] = i.has_key?(v) ? [k,i[v]].flatten : k 
     end 
    } 
    return i 
    end 

end 


h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]} 
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]} 

h.inverse 
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2} 
2

Nel caso in cui un valore corrisponde a più di una chiave, come "c" in questo esempio ...

{ :key1 => ["a", "b", "c"], :key2 => ["c", "d", "e"]} 

... un po ' delle altre risposte non darà il risultato atteso. Avremo bisogno l'hash invertito per memorizzare le chiavi in ​​array, in questo modo:

{ "a" => [:key1], "b" => [:key1], "c" => [:key1, :key2], "d" => [:key2], "e" => [:key2] } 

Questo dovrebbe fare il trucco:

reverse = {} 
hash.each{ |k,vs| 
    vs.each{ |v| 
     reverse[v] ||= [] 
     reverse[v] << k 
    } 
} 

Questo era il mio caso d'uso, ed io avremmo definito il mio problema molto allo stesso modo dell'OP (in effetti, una ricerca di una frase simile mi ha portato qui), quindi sospetto che questa risposta possa aiutare gli altri utenti.

3

Molte altre buone risposte. Volevo solo gettare questo uno troppo per Ruby 2.0 e 1.9.3:

hash = {apple: [1, 14], orange: [7, 12, 8, 13]} 

Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }] 
# => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange} 

Questo sta sfruttando: Hash::[] e Enumerable#flat_map

Anche in queste nuove versioni non c'è Enumerable::each_with_object che è molto simile a Enumerable::inject/Enumerable::reduce :

hash.each_with_object(Hash.new){ |(k, v), inverse| 
    v.each{ |e| inverse[e] = k } 
} 

Esecuzione di un rapido benchmark (rubino 2.0.0p0; 2012 Macbook Air) usando un hash originale con 100 tasti, ciascuno con 100 valori distinti:

Hash::[] w/ Enumerable#flat_map 
      155.7 (±9.0%) i/s -  780 in 5.066286s 
Enumerable#each_with_object w/ Enumerable#each 
      199.7 (±21.0%) i/s -  940 in 5.068926s 

dimostra che la variante di each_with_object è più veloce per questo insieme di dati.

+0

grazie, Aaron, per avermi insegnato #flat_map –