Quelle est la meilleure façon de convertir un tableau en hachage dans Ruby

123

Dans Ruby, étant donné un tableau sous l'une des formes suivantes ...

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

... quel est le meilleur moyen de le convertir en hachage sous la forme de ...

{apple => 1, banana => 2}
Nathan Fritz
la source

Réponses:

91

REMARQUE : Pour une solution concise et efficace, veuillez consulter la réponse de Marc-André Lafortune ci-dessous.

Cette réponse a été proposée à l'origine comme une alternative aux approches utilisant flatten, qui étaient les plus votées au moment de la rédaction. J'aurais dû préciser que je n'avais pas l'intention de présenter cet exemple comme une meilleure pratique ou une approche efficace. La réponse originale suit.


Avertissement! Les solutions utilisant flatten ne conserveront pas les clés ou les valeurs du tableau!

En nous appuyant sur la réponse populaire de @John Topley, essayons:

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

Cela génère une erreur:

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

Le constructeur attendait un tableau de longueur paire (par exemple ['k1', 'v1,' k2 ',' v2 ']). Ce qui est pire, c'est qu'un tableau différent qui s'aplatit à une longueur égale nous donnerait simplement un hachage avec des valeurs incorrectes.

Si vous souhaitez utiliser des clés ou des valeurs de tableau, vous pouvez utiliser map :

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

Cela préserve la clé Array:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
Ragoût
la source
15
C'est la même chose que Hash [a3], puisque a3 == a3.map {| k, v | [k, v]} est vrai, c'est en fait l'équivalent de a3.dup.
Cluster
2
Au lieu d'utiliser la carte, pourquoi ne pas simplement spécifier la profondeur d'aplatissement? Par exemple: h3 = Hash[*a3.flatten(1)]au lieu de h3 = Hash[*a3.flatten]cela, une erreur serait générée.
Jeff McCune
3
Cette réponse n'est pas efficace. Il est également obsolète. Voyez ma réponse.
Marc-André Lafortune
1
Oui, je pense que celle de Marc-André to_hest meilleure.
B Seven
1
@ Marc-André Lafortune merci, j'ai mis à jour ma réponse pour diriger les internautes vers la vôtre.
Ragoût
145

Utilisez simplement Hash[*array_variable.flatten]

Par exemple:

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'utilisation Array#flatten(1)limite la récursivité afin que les Arrayclés et les valeurs fonctionnent comme prévu.

John Topley
la source
4
Oh, l'éloquence! C'est pourquoi j'aime Ruby
iGbanam
11
AVERTISSEMENT: les réponses utilisant flatten poseront des problèmes si vous voulez des clés ou des valeurs de tableau.
Ragoût du
J'ai publié une solution alternative ci-dessous qui évitera les problèmes avec les clés ou les valeurs de tableau.
Ragoût du
5
Il vaut mieux ne pas essayer de faire une solution fourre-tout pour cela. Si vos clés et valeurs sont jumelées comme dans [[key1, value1], [key2, value2]], passez-le simplement à Hash [] sans grossir. Hash [a2] == Hash [* a2.flatten]. Si le tableau est déjà aplati comme dans, [clé1, valeur1, clé2, valeur2] alors préfixez simplement la variable avec *, Hash [* a1]
Cluster
8
FWIW, si vous voulez vraiment (davantage) une version unique, vous pouvez également utiliser Hash[*ary.flatten(1)], qui conservera les clés et les valeurs du tableau. C'est le récursif flattenqui les détruit, ce qui est assez facile à éviter.
brymck
81

La meilleure façon est d'utiliser Array#to_h:

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

Notez que to_haccepte également un bloc:

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

Remarque : to_haccepte un bloc dans Ruby 2.6.0+; pour les premiers rubis, vous pouvez utiliser ma backportsgemme etrequire 'backports/2.6.0/enumerable/to_h'

to_h sans bloc a été introduit dans Ruby 2.1.0.

Avant Ruby 2.1, on pouvait utiliser le moins lisible Hash[]:

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

Enfin, méfiez-vous de toute solution utilisant flatten, cela pourrait créer des problèmes avec les valeurs qui sont elles-mêmes des tableaux.

Marc-André Lafortune
la source
4
Merci pour la simplicité de la nouvelle méthode .to_h!
coding addicted le
3
J'aime la to_hméthode mieux que les réponses ci-dessus car elle exprime l'intention de convertir après avoir fonctionné sur le tableau.
B Seven
1
@BSeven Ni l'un Array#to_hni l' autre Enumerable#to_hn'est dans le noyau ruby ​​1.9.
Iron Savior
Que faire si j'ai un tableau en tant que [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]et que je veux la sortie en tant que {"apple" =>[1,3], "banana"=>[2,4]}?
nishant le
@NishantKumar c'est une autre question.
Marc-André Lafortune
9

Edit: Vu les réponses postées pendant que j'écrivais, Hash [a.flatten] semble la voie à suivre. Doit avoir manqué ce morceau dans la documentation lorsque je réfléchissais à la réponse. Je pensais que les solutions que j'ai écrites peuvent être utilisées comme alternatives si nécessaire.

La deuxième forme est plus simple:

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

a = tableau, h = hachage, r = hachage de la valeur de retour (celui dans lequel nous accumulons), i = élément du tableau

La meilleure façon dont je puisse penser de faire le premier formulaire est quelque chose comme ceci:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
Daemin
la source
2
+1 pour le a.inject({})one-liner qui permet des attributions de valeur plus flexibles.
Chris Bloom
Il est également possible de supprimer le h = {}du deuxième exemple via l'utilisation de inject, se terminant para.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes
Vous pourriez fairea.each_slice(2).to_h
Conor O'Brien
6

Vous pouvez également simplement convertir un tableau 2D en hachage en utilisant:

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

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

1.9.3p362 :006 > h = Hash[a]

 => {1=>2, 3=>4} 
Priyanka
la source
4

Résumé & TL; DR:

Cette réponse espère être un récapitulatif complet des informations provenant d'autres réponses.

La version très courte, compte tenu des données de la question plus quelques extras:

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

Une discussion et des détails suivent.


Configuration: variables

Afin de montrer les données que nous allons utiliser à l'avance, je vais créer des variables pour représenter diverses possibilités pour les données. Ils entrent dans les catégories suivantes:

Basé sur ce qui était directement dans la question, comme a1et a2:

(Remarque: je présume que appleet bananaétaient censés représenter des variables. Comme d'autres l'ont fait, j'utiliserai des chaînes à partir de maintenant afin que l'entrée et les résultats puissent correspondre.)

a1 = [  'apple', 1 ,  'banana', 2  ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input

Clés et / ou valeurs à valeurs multiples, comme a3:

Dans d'autres réponses, une autre possibilité a été présentée (que je développe ici) - les clés et / ou les valeurs peuvent être des tableaux à elles seules:

a3 = [ [ 'apple',                   1   ],
       [ 'banana',                  2   ],
       [ ['orange','seedless'],     3   ],
       [ 'pear',                 [4, 5] ],
     ]

Tableau déséquilibré, comme a4:

Pour faire bonne mesure, j'ai pensé en ajouter un pour un cas où nous pourrions avoir une entrée incomplète:

a4 = [ [ 'apple',                   1],
       [ 'banana',                  2],
       [ ['orange','seedless'],     3],
       [ 'durian'                    ], # a spiky fruit pricks us: no value!
     ]

Maintenant, au travail:

En commençant par un tableau initialement plat, a1:

Certains ont suggéré d'utiliser #to_h(qui apparaissait dans Ruby 2.1.0 et peut être rétroporté vers des versions antérieures). Pour un tableau initialement plat, cela ne fonctionne pas:

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

L'utilisation de Hash::[]combiné avec l' opérateur splat fait:

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

Voilà donc la solution pour le cas simple représenté par a1.

Avec un tableau de tableaux de paire de clés / valeur, a2:

Avec un tableau de tableaux de [key,value]types, il existe deux façons de procéder.

Tout d'abord, Hash::[]fonctionne toujours (comme il l'a fait avec *a1):

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

Et puis #to_hfonctionne aussi maintenant:

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

Donc, deux réponses faciles pour le cas de tableau imbriqué simple.

Cela reste vrai même avec des sous-tableaux en tant que clés ou valeurs, comme avec 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]}

Mais les durians ont des pointes (les structures anormales posent des problèmes):

Si nous avons des données d'entrée qui ne sont pas équilibrées, nous rencontrerons des problèmes avec #to_h:

a4.to_h  # => ArgumentError: wrong array length at 3 (expected 2, was 1)

Mais Hash::[]fonctionne toujours, il suffit de définir nilcomme valeur pour durian(et tout autre élément de tableau dans a4 qui n'est qu'un tableau à 1 valeur):

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

Aplatissement - utilisation de nouvelles variables a5eta6

Quelques autres réponses mentionnées flatten, avec ou sans 1argument, créons donc de nouvelles variables:

a5 = a4.flatten
# => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 

J'ai choisi d'utiliser a4comme données de base en raison du problème d'équilibre que nous avions, qui s'est manifesté a4.to_h. Je me figure appelerflatten pourrait être une approche que quelqu'un pourrait utiliser pour essayer de résoudre ce problème, ce qui pourrait ressembler à ce qui suit.

flattensans arguments ( a5):

Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)

D'un coup d'œil naïf, cela semble fonctionner - mais cela nous a mis du mauvais pied avec les oranges sans pépins, créant ainsi également 3une clé et durianune valeur .

Et cela, comme avec a1, ne fonctionne tout simplement pas:

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

Cela a4.flattenne nous est donc pas utile, nous voulons juste utiliserHash[a4]

Le flatten(1)cas ( a6):

Mais qu'en est-il de l'aplatissement partiel? Il convient de noter que l'appel à l' Hash::[]aide splatdu tableau partiellement aplati ( a6) n'est pas la même chose que l'appel Hash[a4]:

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

Tableau pré-aplati, toujours imbriqué (autre façon d'obtenir a6):

Mais que se passerait-il si c'était ainsi que nous avions obtenu le tableau en premier lieu? (C'est-à-dire, comparativement à a1, c'était nos données d'entrée - juste cette fois, certaines des données peuvent être des tableaux ou d'autres objets.) Nous avons vu que Hash[*a6]cela ne fonctionne pas, mais que se passerait-il si nous voulions toujours obtenir le comportement où le dernier élément (important! voir ci-dessous) a agi comme une clé pour une nilvaleur?

Dans une telle situation, il existe toujours un moyen de le faire, en utilisant Enumerable#each_slicepour revenir aux paires clé / valeur en tant qu'éléments du tableau externe:

a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]] 

Notez que cela finit par nous obtenir un nouveau tableau qui n'est pas " identique " à a4, mais qui a les mêmes valeurs :

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

Et ainsi nous pouvons à nouveau utiliser Hash::[]:

Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]

Mais il y a un problème!

Il est important de noter que la each_slice(2)solution ne ramène les choses à la bonne santé que si la dernière clé était celle qui manquait une valeur. Si nous avons ajouté plus tard une paire clé / valeur supplémentaire:

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

Et les deux hachages que nous en tirerions sont différents de manière importante:

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
}

(Note: J'utilise awesome_printl »ap . Juste pour le rendre plus facile de montrer la structure ici, il n'y a pas d' exigence conceptuelle pour cela)

Donc, la each_slicesolution à une entrée plate asymétrique ne fonctionne que si le bit asymétrique est à la toute fin.


À emporter:

  1. Dans la mesure du possible, configurez l'entrée de ces éléments sous forme de [key, value]paires (un sous-tableau pour chaque élément du tableau externe).
  2. Lorsque vous pouvez effectivement faire cela, l'un #to_hou l' autre fonctionnera ou Hash::[]les deux fonctionneront.
  3. Si vous ne pouvez pas, Hash::[]combiné avec le splat ( *) fonctionnera, tant que les entrées sont équilibrées .
  4. Avec un tableau asymétrique et plat en entrée, la seule façon dont cela fonctionnera raisonnablement est si le dernier value élément est le seul qui manque.

Remarque: je poste cette réponse parce que j'estime qu'il y a une valeur à ajouter - certaines des réponses existantes ont des informations incorrectes, et aucune (que j'ai lu) n'a donné une réponse aussi complète que je m'efforce de le faire ici. J'espère que c'est utile. Je tiens néanmoins à remercier ceux qui sont venus avant moi, dont plusieurs ont inspiré des parties de cette réponse.

tilleuls
la source
3

Ajout à la réponse mais en utilisant des tableaux anonymes et en annotant:

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

En prenant cette réponse à part, en commençant par l'intérieur:

  • "a,b,c,d" est en fait une chaîne.
  • split sur des virgules dans un tableau.
  • zip cela avec le tableau suivant.
  • [1,2,3,4] est un tableau réel.

Le résultat intermédiaire est:

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

aplatir puis transforme cela en:

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

puis:

*["a",1,"b",2,"c",3,"d",4] déroule ça dans "a",1,"b",2,"c",3,"d",4

que nous pouvons utiliser comme arguments de la Hash[]méthode:

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

ce qui donne:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
StevenJenkins
la source
Cela fonctionne également sans splat ( *) et aplatir: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. Plus de détails dans une réponse que j'ai ajoutée.
lindes
0

si vous avez un tableau qui ressemble à ceci -

data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]

et vous voulez que les premiers éléments de chaque tableau deviennent les clés du hachage et que le reste des éléments devienne des tableaux de valeurs, alors vous pouvez faire quelque chose comme ça -

data_hash = Hash[data.map { |key| [key.shift, key] }]

#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
user3588841
la source
0

Je ne sais pas si c'est le meilleur moyen, mais cela fonctionne:

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
Anders Sandvig
la source
-1

Si les valeurs numériques sont des index seq, alors nous pourrions avoir des moyens plus simples ... Voici ma soumission de code, Mon Ruby est un peu rouillé

   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}
Gishu
la source