Matrice per Hash Ruby


192

Okay, quindi ecco il patto, ho cercato su Google per anni per trovare una soluzione a questo e mentre ce ne sono molti là fuori, sembra che non facciano il lavoro che sto cercando.

Fondamentalmente ho un array strutturato in questo modo

["item 1", "item 2", "item 3", "item 4"] 

Voglio convertirlo in un hash in modo che assomigli a questo

{ "item 1" => "item 2", "item 3" => "item 4" }

cioè gli elementi che si trovano sugli indici "pari" sono le chiavi e gli elementi sugli indici "dispari" sono i valori.

Qualche idea su come farlo in modo pulito? Suppongo che un metodo di forza bruta sarebbe quello di estrarre tutti gli indici pari in un array separato e quindi circondarli per aggiungere i valori.

Risposte:


358
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

Questo è tutto. Si *chiama operatore splat .

Un avvertimento per @Mike Lewis (nei commenti): "Stai molto attento con questo. Ruby espande gli splat nello stack. Se lo fai con un set di dati di grandi dimensioni, aspettati di espellere lo stack."

Quindi, per la maggior parte dei casi d'uso generali questo metodo è ottimo, ma usa un metodo diverso se vuoi fare la conversione su molti dati. Ad esempio, @ Łukasz Niemier (anche nei commenti) offre questo metodo per grandi set di dati:

h = Hash[a.each_slice(2).to_a]

10
@tester, il *si chiama operatore splat . Prende un array e lo converte in un elenco letterale di elementi. Quindi *[1,2,3,4]=> 1, 2, 3, 4. In questo esempio, quanto sopra equivale a fare Hash["item 1", "item 2", "item 3", "item 4"]. E Hashha un []metodo che accetta un elenco di argomenti (rendendo le chiavi degli indici pari e i valori degli indici dispari), ma Hash[]non accetta un array, quindi dividiamo l'array usando *.
Ben Lee,

15
Stai molto attento con questo. Ruby espande gli splat in pila. Se lo fai con un set di dati di grandi dimensioni, prevedi di far esplodere il tuo stack.
Mike Lewis,

9
Su tabelle di grandi quantità di dati è possibile utilizzare Hash[a.each_slice(2).to_a].
Hauleth,

4
Cosa significa "espellere il tuo stack"?
Kevin,

6
@Kevin, lo stack utilizza una piccola area di memoria che il programma alloca e riserva per determinate operazioni specifiche. Più comunemente, viene utilizzato per mantenere una pila di metodi che sono stati chiamati finora. Questa è l'origine del termine stack stack , ed è anche per questo che un metodo infinitamente ricorsivo può causare un overflow dello stack . Il metodo in questa risposta utilizza anche lo stack, ma poiché lo stack è solo una piccola area di memoria, se si prova questo metodo con un array di grandi dimensioni, riempirà lo stack e causerà un errore (un errore lungo le stesse righe di uno stack overflow).
Ben Lee,

103

Ruby 2.1.0 ha introdotto un to_hmetodo sull'array che fa ciò che è necessario se l'array originale è costituito da array di coppie chiave-valore: http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h .

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}

1
Bellissimo! Molto meglio di alcune delle altre soluzioni qui.
Dennis,

3
per le versioni ruby ​​precedenti alla 2.1.0, è possibile utilizzare il metodo Hash :: [] per ottenere risultati simili, purché si disponga di coppie di un array nidificato. quindi a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:

@AfDev, anzi, grazie. Sei corretto (quando ignori gli errori di battitura minori: bardeve essere un simbolo e il simbolo :2dovrebbe essere un numero intero. Quindi, la tua espressione corretta è a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper,

28

Basta usare Hash.[]con i valori nella matrice. Per esempio:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}

1
cosa significa [* arr]?
Alan Coromano,

1
@Marius: *arrconverte arrin un elenco di argomenti, quindi questo chiama il []metodo di Hash con il contenuto di arr come argomenti.
Chuck,

26

Oppure, se disponi di una matrice di [key, value]array, puoi fare:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }

2
La tua risposta non è correlata alla domanda e nel tuo caso è ancora molto più facile usare lo stessoHash[*arr]
Yossi

2
No. Sarebbe tornato { [1, 2] => [3, 4] }. E poiché il titolo della domanda dice "Array to Hash" e il metodo "Hash to Array" integrato fa:, { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]ho pensato che più di uno potesse finire qui cercando di ottenere l'inverso del metodo "Hash to Array" incorporato. In realtà, è così che sono finito qui comunque.
Erik Escobedo,

1
Spiacenti, ho aggiunto un asterisco di riserva. Hash[arr]farà il lavoro per te.
Yossi,

9
Soluzione migliore di IMHO: Hash [* array.flatten (1)]
ospite

2
Yossi: Mi dispiace per aver risuscitato i morti, ma c'è un problema più grande con la sua risposta, e questo è l'uso del #injectmetodo. Con #merge!, #each_with_objectavrebbe dovuto essere usato. Se #injectè insistito, #mergepiuttosto che #merge!avrebbe dovuto essere usato.
Boris Stitnicky,

12

Questo è quello che stavo cercando quando cercavo su Google:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}


Non vuoi usarlo merge, costruisce e scarta una nuova iterazione di hash per loop ed è molto lento. Se invece hai una serie di hash, prova [{a:1},{b:2}].reduce({}, :merge!)invece: unisce tutto nello stesso (nuovo) hash.

Grazie, questo è quello che volevo anche io! :)
Thanasis Petsas il

Puoi anche farlo.reduce(&:merge!)
Ben Lee,

1
[{a: 1}, {b: 2}].reduce(&:merge!)valuta{:a=>1, :b=>2}
Ben Lee il

Ciò funziona perché iniettare / ridurre ha una funzione in cui è possibile omettere l'argomento, nel qual caso utilizza il primo argomento dell'array su cui operare come argomento di input e il resto dell'array come array. Combinalo con symbol-to-proc e otterrai questa costruzione concisa. In altre parole [{a: 1}, {b: 2}].reduce(&:merge!)è lo stesso di quello [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }che è lo stesso di [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee,

10

Enumeratorinclude Enumerable. Da allora 2.1, Enumerableha anche un metodo #to_h. Ecco perché, possiamo scrivere: -

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Perché #each_slicesenza blocco ci dà Enumerator, e secondo la spiegazione sopra, possiamo chiamare il #to_hmetodo Enumeratorsull'oggetto.


7

Potresti provare così, per singolo array

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

per array di array

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}

6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

o, se odi Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

o, se sei un fan pigro della programmazione funzionale non funzionante:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"

Se non odi completamente Hash[ ... ]ma vuoi usarlo come metodo incatenato (come puoi fare con to_h) puoi combinare i suggerimenti di Boris e scrivere:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios

Per rendere più chiara la semantica del codice sopra: Ciò creerà un "hash pigro" h , che inizialmente è vuoto , e estrarrà gli elementi dall'array originale a quando necessario. Solo allora verranno effettivamente memorizzati in h!
Daniel Werner,

1

Tutte le risposte presuppongono che l'array iniziale sia unico. OP non ha specificato come gestire le matrici con voci duplicate, risultando in chiavi duplicate.

Guardiamo:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

Perderai la item 1 => item 2coppia poiché viene ignorata item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Tutti i metodi, incluso il reduce(&:merge!) risultato nella stessa rimozione.

Potrebbe essere che questo è esattamente quello che ti aspetti, però. Ma in altri casi, probabilmente vorrai ottenere un risultato con un Arrayvalore for:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Il modo ingenuo sarebbe quello di creare una variabile helper, un hash che ha un valore predefinito, e quindi riempirlo in un ciclo:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Potrebbe essere possibile usare assoce reducefare sopra in una riga, ma diventa molto più difficile ragionare e leggere.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.