In Ruby, dato un array in una delle seguenti forme ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... qual è il modo migliore per convertirlo in un hash sotto forma di ...
{apple => 1, banana => 2}
In Ruby, dato un array in una delle seguenti forme ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... qual è il modo migliore per convertirlo in un hash sotto forma di ...
{apple => 1, banana => 2}
Risposte:
NOTA : per una soluzione concisa ed efficiente, vedere la risposta di Marc-André Lafortune seguito.
Questa risposta è stata originariamente offerta come alternativa agli approcci che utilizzano l'appiattimento, che erano i più votati al momento della scrittura. Avrei dovuto chiarire che non intendevo presentare questo esempio come una best practice o un approccio efficiente. Segue la risposta originale.
Avvertimento! Le soluzioni che utilizzano flatten non manterranno le chiavi oi valori di matrice!
Basandosi sulla popolare risposta di @John Topley, proviamo:
a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]
Questo genera un errore:
ArgumentError: odd number of arguments for Hash
from (irb):10:in `[]'
from (irb):10
Il costruttore si aspettava un array di lunghezza pari (ad esempio ['k1', 'v1,' k2 ',' v2 ']). Quel che è peggio è che un array diverso che si appiattisce a una lunghezza pari ci darebbe silenziosamente un hash con valori errati.
Se vuoi usare chiavi o valori array, puoi usare map :
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
Ciò preserva la chiave Array:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
h3 = Hash[*a3.flatten(1)]
invece di h3 = Hash[*a3.flatten]
che genererebbe un errore.
to_h
sia migliore.
Basta usare Hash[*array_variable.flatten]
Per esempio:
a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"
a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"
L'utilizzo Array#flatten(1)
limita la ricorsione in modo che Array
chiavi e valori funzionino come previsto.
Hash[*ary.flatten(1)]
, che preserverà chiavi e valori dell'array. È il ricorsivo flatten
che sta distruggendo quelli, che è abbastanza facile da evitare.
Il modo migliore è usare Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
Nota che to_h
accetta anche un blocco:
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
Nota : to_h
accetta un blocco in Ruby 2.6.0+; per i primi rubini puoi usare la mia backports
gemma erequire 'backports/2.6.0/enumerable/to_h'
to_h
senza un blocco è stato introdotto in Ruby 2.1.0.
Prima di Ruby 2.1, si poteva usare il meno leggibile Hash[]
:
array = [ [:apple,1],[:banana,2] ]
Hash[ array ] #= > {:apple => 1, :banana => 2}
Infine, diffidare di qualsiasi soluzione che utilizzi flatten
, ciò potrebbe creare problemi con i valori che sono gli array stessi.
to_h
metodo mi piace di più rispetto alle risposte precedenti perché esprime l'intento di convertire dopo aver operato sull'array.
Array#to_h
né Enumerable#to_h
è nel core ruby 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
e voglio l'output come {"apple" =>[1,3], "banana"=>[2,4]}
?
Aggiornare
Ruby 2.1.0 è stato rilasciato oggi . E viene fornito con Array#to_h
( note di rilascio e ruby-doc ), che risolve il problema della conversione Array
di un file Hash
.
Esempio di documentazione Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
Modifica: ho visto le risposte pubblicate mentre stavo scrivendo, Hash [a.flatten] sembra la strada da percorrere. Devo aver perso quel pezzo nella documentazione quando stavo pensando alla risposta. Ho pensato che le soluzioni che ho scritto possano essere utilizzate come alternative, se necessario.
La seconda forma è più semplice:
a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }
a = array, h = hash, r = return-value hash (quello in cui accumuliamo), i = item nell'array
Il modo più pulito in cui posso pensare di fare il primo modulo è qualcosa del genere:
a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
a.inject({})
riga che consente assegnazioni di valore più flessibili.
h = {}
dal secondo esempio tramite l'uso di inject, finendo cona.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
Questa risposta spera di essere un riepilogo completo delle informazioni da altre risposte.
La versione brevissima, visti i dati della domanda più un paio di extra:
flat_array = [ apple, 1, banana, 2 ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [ apple, 1, banana ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana ] ] # count=2 of either k or k,v arrays
# there's one option for flat_array:
h1 = Hash[*flat_array] # => {apple=>1, banana=>2}
# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0 => {apple=>1, banana=>2}
h2b = Hash[nested_array] # => {apple=>1, banana=>2}
# ok if *only* the last value is missing:
h3 = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4 = Hash[incomplete_n] # or .to_h => {apple=>1, banana=>nil}
# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3 # => false
h3 == h4 # => true
Seguono discussioni e dettagli.
Per mostrare i dati che utilizzeremo in anticipo, creerò alcune variabili per rappresentare varie possibilità per i dati. Rientrano nelle seguenti categorie:
a1
e a2
:(Nota: presumo che apple
e banana
dovevano rappresentare variabili. Come altri hanno fatto, userò le stringhe da qui in poi in modo che input e risultati possano corrispondere.)
a1 = [ 'apple', 1 , 'banana', 2 ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input
a3
:In alcune altre risposte, è stata presentata un'altra possibilità (che espanderò qui): chiavi e / o valori possono essere array da soli:
a3 = [ [ 'apple', 1 ],
[ 'banana', 2 ],
[ ['orange','seedless'], 3 ],
[ 'pear', [4, 5] ],
]
a4
:Per buona misura, ho pensato di aggiungerne uno per un caso in cui potremmo avere un input incompleto:
a4 = [ [ 'apple', 1],
[ 'banana', 2],
[ ['orange','seedless'], 3],
[ 'durian' ], # a spiky fruit pricks us: no value!
]
a1
:Alcuni hanno suggerito di usare #to_h
(che è apparso in Ruby 2.1.0 e può essere sottoposto a backport alle versioni precedenti). Per un array inizialmente piatto, questo non funziona:
a1.to_h # => TypeError: wrong element type String at 0 (expected array)
L'uso Hash::[]
combinato con l' operatore splat fa:
Hash[*a1] # => {"apple"=>1, "banana"=>2}
Quindi questa è la soluzione per il caso semplice rappresentato da a1
.
a2
:Con un array di array di [key,value]
tipi, ci sono due modi per andare.
Innanzitutto, Hash::[]
funziona ancora (come ha fatto con *a1
):
Hash[a2] # => {"apple"=>1, "banana"=>2}
E poi #to_h
funziona anche ora:
a2.to_h # => {"apple"=>1, "banana"=>2}
Quindi, due semplici risposte per il semplice caso di array nidificato.
a3
:Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}
a3.to_h # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}
Se abbiamo ottenuto dati di input non bilanciati, incontreremo problemi con #to_h
:
a4.to_h # => ArgumentError: wrong array length at 3 (expected 2, was 1)
Ma Hash::[]
funziona ancora, basta impostare nil
come valore per durian
(e qualsiasi altro elemento dell'array in a4 che è solo un array a 1 valore):
Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
a5
ea6
Alcune altre risposte menzionate flatten
, con o senza 1
argomento, quindi creiamo alcune nuove variabili:
a5 = a4.flatten
# => ["apple", 1, "banana", 2, "orange", "seedless" , 3, "durian"]
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"]
Ho scelto di utilizzare a4
come dati di base a causa del problema di equilibrio che abbiamo avuto, che si è presentato con a4.to_h
. Immagino che chiamare flatten
potrebbe essere un approccio che qualcuno potrebbe usare per provare a risolverlo, che potrebbe assomigliare al seguente.
flatten
senza argomenti ( a5
):Hash[*a5] # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)
A prima vista, sembra funzionare, ma con le arance senza semi siamo partiti con il piede sbagliato, creando così anche 3
una chiave e durian
un valore .
E questo, come con a1
, semplicemente non funziona:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Quindi a4.flatten
non è utile per noi, vorremmo solo usareHash[a4]
flatten(1)
caso ( a6
):Ma che dire dell'appiattimento solo parziale? Vale la pena notare che chiamare Hash::[]
using splat
sull'array ( a6
) parzialmente appiattito non è la stessa cosa che chiamare Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6
):Ma se fosse così che abbiamo ottenuto l'array in primo luogo? (Cioè, paragonabilmente a a1
, erano i nostri dati di input - solo che questa volta alcuni dei dati possono essere array o altri oggetti.) Abbiamo visto che Hash[*a6]
non funziona, ma se volessimo ancora ottenere il comportamento in cui il ultimo elemento (importante! vedi sotto) ha agito come chiave per un nil
valore?
In una situazione del genere, c'è ancora un modo per farlo, usando Enumerable#each_slice
per tornare alle coppie chiave / valore come elementi nell'array esterno:
a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]]
Nota che questo finisce per fornirci un nuovo array che non è " identico " a a4
, ma ha gli stessi valori :
a4.equal?(a7) # => false
a4 == a7 # => true
E così possiamo ancora usare Hash::[]
:
Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]
È importante notare che la each_slice(2)
soluzione riporta le cose alla sanità mentale solo se l' ultima chiave era quella a cui mancava un valore. Se in seguito abbiamo aggiunto una coppia chiave / valore aggiuntiva:
a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple", 1],
# ["banana", 2],
# [["orange", "seedless"], 3], # multi-value key
# ["durian"], # missing value
# ["lychee", 4]] # new well-formed item
a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]
a7_plus = a6_plus.each_slice(2).to_a
# => [["apple", 1],
# ["banana", 2],
# [["orange", "seedless"], 3], # so far so good
# ["durian", "lychee"], # oops! key became value!
# [4]] # and we still have a key without a value
a4_plus == a7_plus # => false, unlike a4 == a7
E i due hash che otterremmo da questo sono diversi in modi importanti:
ap Hash[a4_plus] # prints:
{
"apple" => 1,
"banana" => 2,
[ "orange", "seedless" ] => 3,
"durian" => nil, # correct
"lychee" => 4 # correct
}
ap Hash[a7_plus] # prints:
{
"apple" => 1,
"banana" => 2,
[ "orange", "seedless" ] => 3,
"durian" => "lychee", # incorrect
4 => nil # incorrect
}
(Nota: sto usando awesome_print
's ap
solo per rendere più facile mostrare la struttura qui; non ci sono requisiti concettuali per questo.)
Quindi la each_slice
soluzione a un ingresso piatto sbilanciato funziona solo se il bit sbilanciato è proprio alla fine.
[key, value]
coppie (un sotto-array per ogni elemento nell'array esterno).#to_h
o Hash::[]
funzioneranno entrambi.Hash::[]
combinato con splat ( *
) funzionerà, a patto che gli input siano bilanciati .value
elemento è l'unico che manca.Nota a margine: sto postando questa risposta perché sento che c'è un valore da aggiungere: alcune delle risposte esistenti contengono informazioni errate e nessuna (quella che ho letto) ha dato una risposta completa come sto cercando di fare qui. Spero che sia utile Rendo comunque grazie a coloro che sono venuti prima di me, molti dei quali hanno fornito ispirazione per parti di questa risposta.
Aggiunta alla risposta ma utilizzando array anonimi e annotazioni:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
Prendendo quella risposta a parte, partendo dall'interno:
"a,b,c,d"
è in realtà una stringa.split
tra virgole in un array.zip
quello insieme al seguente array.[1,2,3,4]
è un array effettivo.Il risultato intermedio è:
[[a,1],[b,2],[c,3],[d,4]]
flatten quindi lo trasforma in:
["a",1,"b",2,"c",3,"d",4]
e poi:
*["a",1,"b",2,"c",3,"d",4]
lo srotola in
"a",1,"b",2,"c",3,"d",4
che possiamo usare come argomenti del Hash[]
metodo:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
che produce:
{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
*
) e flatten: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]
=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
. Più dettagli in una risposta che ho aggiunto.
se hai un array che assomiglia a questo -
data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]
e vuoi che i primi elementi di ogni array diventino le chiavi per l'hash e il resto degli elementi diventino array di valori, allora puoi fare qualcosa del genere -
data_hash = Hash[data.map { |key| [key.shift, key] }]
#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
Non sono sicuro che sia il modo migliore, ma funziona:
a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
m1[a[x*2]] = a[x*2 + 1]
end
b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
m2[x] = y
end
Se i valori numerici sono indici seq, allora potremmo avere modi più semplici ... Ecco il mio invio di codice, My Ruby è un po 'arrugginito
input = ["cat", 1, "dog", 2, "wombat", 3]
hash = Hash.new
input.each_with_index {|item, index|
if (index%2 == 0) hash[item] = input[index+1]
}
hash #=> {"cat"=>1, "wombat"=>3, "dog"=>2}