Domanda

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}
È stato utile?

Soluzione

NOTA:Per una soluzione concisa ed efficiente, vedere La risposta di Marc-André Lafortune sotto.

Questa risposta è stata originariamente offerta come alternativa agli approcci che utilizzavano l'appiattimento, che erano i più votati al momento della stesura di questo articolo.Avrei dovuto chiarire che non intendevo presentare questo esempio come una migliore pratica o un approccio efficiente.Segue la risposta originale.


Avvertimento! Soluzioni utilizzando appiattire non conserverà le chiavi o i valori dell'array!

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 es.['k1','v1,'k2','v2']).Quel che è peggio è che un diverso Array appiattito su una lunghezza uniforme ci fornirebbe semplicemente silenziosamente un Hash con valori errati.

Se desideri utilizzare chiavi o valori dell'array, puoi utilizzare carta geografica:

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}

Altri suggerimenti

Usalo semplicemente 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}"

Utilizzando Array#flatten(1) limita la ricorsione così Array chiavi e valori funzionano come previsto.

Il modo migliore è usare Array#to_h:

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

Notare 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 il mio backports gemma e require 'backports/2.6.0/enumerable/to_h'

to_h senza 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 delle soluzioni che utilizzano flatten, ciò potrebbe creare problemi con i valori che sono essi stessi array.

Aggiornamento

Ruby 2.1.0 viene rilasciato oggi.E io vengo con Array#to_h (note di rilascio E rubino-doc), che risolve il problema della conversione di an Array ad a Hash.

Esempio di documenti Ruby:

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

Modificare:Ho visto le risposte pubblicate mentre stavo scrivendo, Hash[a.flatten] sembra la strada da percorrere.Devo essermi perso quella parte della documentazione mentre riflettevo sulla 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 = hash del valore restituito (quello in cui accumuliamo), i = elemento nell'array

Il modo più carino che mi viene in mente di realizzare il primo modulo è qualcosa del genere:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }

Puoi anche semplicemente convertire 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} 

Aggiungendo alla risposta ma utilizzando array anonimi e annotando:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

Analizzando la risposta, partendo dall'interno:

  • "a,b,c,d" è in realtà una stringa.
  • split sulle virgole in un array.
  • zip che insieme al seguente array.
  • [1,2,3,4] è un vero e proprio array.

Il risultato intermedio è:

[[a,1],[b,2],[c,3],[d,4]]

appiattire quindi lo trasforma in:

["a",1,"b",2,"c",3,"d",4]

poi:

*["a",1,"b",2,"c",3,"d",4] lo srotola dentro"a",1,"b",2,"c",3,"d",4

che possiamo usare come argomenti per il Hash[] metodo:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

che produce:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}

Riepilogo e TL;DR:

Questa risposta spera di essere un riepilogo completo delle informazioni provenienti da altre risposte.

La versione molto breve, dati 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 discussione e dettagli.


Impostare:variabili

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

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

(Nota:Lo presumo apple E banana dovevano rappresentare le variabili.Come hanno fatto altri, da qui in poi utilizzerò le stringhe 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 espando qui): le chiavi e/o i valori possono essere array di per sé:

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:

Partendo da un array inizialmente piatto, a1:

Alcuni hanno suggerito di utilizzare #to_h (che è apparso in Ruby 2.1.0 e può esserlo backport alle versioni precedenti).Per un array inizialmente piatto, questo non funziona:

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

Utilizzando Hash::[] combinato con il operatore splat fa:

Hash[*a1] # => {"apple"=>1, "banana"=>2}

Quindi questa è la soluzione per il semplice caso rappresentato da a1.

Con un array di array di coppie chiave/valore, a2:

Con una serie di [key,value] array di tipi, ci sono due modi per procedere.

Primo, Hash::[] funziona ancora (come ha fatto con *a1):

Hash[a2] # => {"apple"=>1, "banana"=>2}

E poi anche #to_h funziona ora:

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

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

Ciò rimane vero anche con sottoarray 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 (le strutture anomale danno problemi):

Se abbiamo ricevuto 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 sia solo un array a 1 valore):

Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}

Appiattimento: utilizzo di nuove variabili a5 E a6

Alcune altre risposte menzionate flatten, con o senza a 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 usare a4 come dati di base a causa del problema di equilibrio che abbiamo avuto, che si è presentato a4.to_h.Immagino di chiamare flatten potrebbe essere un approccio che qualcuno potrebbe utilizzare 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 che funzioni, ma con le arance senza semi ci ha fatto partire con il piede sbagliato, rendendoci così anche 3 UN 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)

COSÌ a4.flatten non ci è utile, vorremmo semplicemente usarlo Hash[a4]

IL flatten(1) caso (a6):

Ma che dire dell'appiattimento solo parziale?Vale la pena notare quella chiamata Hash::[] utilizzando splat sull'array parzialmente appiattito (a6) È non è lo stesso che chiamare Hash[a4]:

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

Array pre-appiattito, ancora nidificato (modo alternativo di ottenere a6):

E se fosse stato proprio così che avessimo ottenuto l'array?(Cioè, paragonabile a a1, erano i nostri dati di input - solo che questa volta alcuni dati possono essere array o altri oggetti.) Lo abbiamo visto Hash[*a6] non funziona, ma cosa succederebbe se volessimo comunque ottenere il comportamento dove il ultimo elemento (importante!vedere sotto) fungeva da chiave per a nil valore?

In una situazione del genere, c'è ancora un modo per farlo, utilizzando Enumerable#each_slice per tornare alla chiave/valore coppie 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 procurarci un nuovo array che non è "identico" A a4, ma ha il stessi valori:

a4.equal?(a7) # => false
a4 == a7      # => true

E quindi possiamo usare di nuovo 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 il each_slice(2) la soluzione riporta le cose alla normalità solo se il scorso la chiave era quella a cui mancava un valore.Se in seguito aggiungiamo una coppia chiave/valore extra:

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ù semplice mostrare la struttura qui;non esiste alcun requisito concettuale per questo.)

Così il each_slice la soluzione per un ingresso piatto sbilanciato funziona solo se il bit sbilanciato è alla fine.


Asporto:

  1. Quando possibile, imposta l'input per queste cose come [key, value] coppie (un sottoarray per ogni elemento nell'array esterno).
  2. Quando puoi davvero farlo, neanche tu #to_h O Hash::[] funzioneranno entrambi.
  3. Se non sei in grado di farlo, Hash::[] combinato con lo splat (*) funzionerà, purché gli input siano bilanciati.
  4. Con un sbilanciato E Piatto array come input, l'unico modo in cui funzionerà ragionevolmente è se il file scorso value l'articolo è l'unico che manca.

Nota a margine:Sto pubblicando questa risposta perché ritengo che ci sia valore da aggiungere: alcune delle risposte esistenti contengono informazioni errate e nessuna (quella che ho letto) ha fornito una risposta completa come sto cercando di fare qui.Spero che sia utile.Ringrazio tuttavia coloro che sono venuti prima di me, molti dei quali hanno fornito ispirazione per parti di questa risposta.

se hai un array simile 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 codice inviato, 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}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top