Qual è il modo migliore per convertire un array in un hash in Ruby


123

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:


91

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}

15
È lo stesso di Hash [a3], poiché a3 == a3.map {| k, v | [k, v]} è vero, in realtà è l'equivalente di a3.dup.
Cluster

2
Invece di usare map, perché non specificare semplicemente la profondità di appiattimento? Ad esempio: h3 = Hash[*a3.flatten(1)]invece di h3 = Hash[*a3.flatten]che genererebbe un errore.
Jeff McCune

3
Questa risposta non è efficiente. Inoltre è obsoleto. Vedi la mia risposta.
Marc-André Lafortune

1
Sì, penso che quello di Marc-André to_hsia migliore.
B Seven

1
@ Marc-André Lafortune grazie, ho aggiornato la mia risposta per indirizzare gli utenti alla tua.
Spezzatino

145

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 Arraychiavi e valori funzionino come previsto.


4
Oh, l'eloquenza! Questo è il motivo per cui amo Ruby
iGbanam

11
ATTENZIONE: le risposte che utilizzano l'appiattimento causeranno problemi se si desiderano chiavi o valori di matrice.
Stufato il

Ho pubblicato una soluzione alternativa di seguito che eviterà problemi con chiavi o valori di matrice.
Stufato

5
È meglio non provare a fare una soluzione universale per questo. Se le chiavi e i valori sono accoppiati come in [[key1, value1], [key2, value2]], passalo semplicemente a Hash [] senza ingrassare. Hash [a2] == Hash [* a2.flatten]. Se l'array è già appiattito come in [key1, value1, key2, value2], anteponi alla var *, Hash [* a1]
Cluster

8
FWIW, se vuoi davvero (più di una) versione valida per tutti, puoi anche usare Hash[*ary.flatten(1)], che preserverà chiavi e valori dell'array. È il ricorsivo flattenche sta distruggendo quelli, che è abbastanza facile da evitare.
brymck

81

Il modo migliore è usare Array#to_h:

[ [:apple,1],[:banana,2] ].to_h  #=> {apple: 1, banana: 2}

Nota che to_haccetta anche un blocco:

[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
  # => {apple: "I like apples", banana: "I like bananas"}

Nota : to_haccetta un blocco in Ruby 2.6.0+; per i primi rubini puoi usare la mia backportsgemma 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.


4
Grazie per la semplicità del nuovo metodo .to_h!
coding addicted

3
Il to_hmetodo mi piace di più rispetto alle risposte precedenti perché esprime l'intento di convertire dopo aver operato sull'array.
B Seven

1
@BSeven Né Array#to_hEnumerable#to_hè nel core ruby ​​1.9.
Iron Savior

Cosa succede se ho un array come [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]e voglio l'output come {"apple" =>[1,3], "banana"=>[2,4]}?
nishant

@NishantKumar questa è una domanda diversa.
Marc-André Lafortune


9

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 }

2
+1 per la a.inject({})riga che consente assegnazioni di valore più flessibili.
Chris Bloom

È anche possibile eliminare il h = {}dal secondo esempio tramite l'uso di inject, finendo cona.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes

Si potrebbe farea.each_slice(2).to_h
Conor O'Brien

6

Puoi anche convertire semplicemente un array 2D in hash usando:

1.9.3p362 :005 > a= [[1,2],[3,4]]

 => [[1, 2], [3, 4]]

1.9.3p362 :006 > h = Hash[a]

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

4

Riepilogo & TL; DR:

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.


Setup: variabili

Per mostrare i dati che utilizzeremo in anticipo, creerò alcune variabili per rappresentare varie possibilità per i dati. Rientrano nelle seguenti categorie:

Sulla base di ciò che era direttamente nella domanda, come a1e a2:

(Nota: presumo che applee bananadovevano 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

Chiavi e / o valori multivalore, come 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] ],
     ]

Array sbilanciato, come 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!
     ]

Ora, per lavorare:

Iniziando con un array inizialmente piatta, 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.

Con una serie di matrici coppia chiave / valore, 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_hfunziona anche ora:

a2.to_h  # => {"apple"=>1, "banana"=>2}

Quindi, due semplici risposte per il semplice caso di array nidificato.

Questo rimane vero anche con sotto-array come chiavi o valori, come con 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]}

Ma i durian hanno picchi (strutture anomale danno problemi):

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 nilcome 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}

Appiattimento: utilizzo di nuove variabili a5ea6

Alcune altre risposte menzionate flatten, con o senza 1argomento, 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 a4come dati di base a causa del problema di equilibrio che abbiamo avuto, che si è presentato con a4.to_h. Immagino che chiamare flattenpotrebbe essere un approccio che qualcuno potrebbe usare per provare a risolverlo, che potrebbe assomigliare al seguente.

flattensenza 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 3una chiave e durianun valore .

E questo, come con a1, semplicemente non funziona:

a5.to_h # => TypeError: wrong element type String at 0 (expected array)

Quindi a4.flattennon è utile per noi, vorremmo solo usareHash[a4]

Il flatten(1)caso ( a6):

Ma che dire dell'appiattimento solo parziale? Vale la pena notare che chiamare Hash::[]using splatsull'array ( a6) parzialmente appiattito non è la stessa cosa che chiamare Hash[a4]:

Hash[*a6] # => ArgumentError: odd number of arguments for Hash

Matrice pre-appiattita, ancora nidificata (metodo alternativo per ottenere 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 nilvalore?

In una situazione del genere, c'è ancora un modo per farlo, usando Enumerable#each_sliceper 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]

Ma c'è un problema!

È 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 apsolo per rendere più facile mostrare la struttura qui; non ci sono requisiti concettuali per questo.)

Quindi la each_slicesoluzione a un ingresso piatto sbilanciato funziona solo se il bit sbilanciato è proprio alla fine.


Asporto:

  1. Quando possibile, imposta l'input per queste cose come [key, value]coppie (un sotto-array per ogni elemento nell'array esterno).
  2. Quando puoi davvero farlo, #to_ho Hash::[]funzioneranno entrambi.
  3. Se non puoi farlo, Hash::[]combinato con splat ( *) funzionerà, a patto che gli input siano bilanciati .
  4. Con un array sbilanciato e piatto come input, l'unico modo in cui funzionerà ragionevolmente è se l' ultimo 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.


3

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}

Funziona anche senza splat ( *) 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.
Lindes

0

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]}

0

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

-1

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}
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.