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}
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}
Réponses:
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}
h3 = Hash[*a3.flatten(1)]
au lieu de h3 = Hash[*a3.flatten]
cela, une erreur serait générée.
to_h
est meilleure.
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 Array
clés et les valeurs fonctionnent comme prévu.
Hash[*ary.flatten(1)]
, qui conservera les clés et les valeurs du tableau. C'est le récursif flatten
qui les détruit, ce qui est assez facile à éviter.
La meilleure façon est d'utiliser Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
Notez que to_h
accepte également un bloc:
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
Remarque : to_h
accepte un bloc dans Ruby 2.6.0+; pour les premiers rubis, vous pouvez utiliser ma backports
gemme 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.
to_h
méthode mieux que les réponses ci-dessus car elle exprime l'intention de convertir après avoir fonctionné sur le tableau.
Array#to_h
ni l' autre Enumerable#to_h
n'est dans le noyau ruby 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
et que je veux la sortie en tant que {"apple" =>[1,3], "banana"=>[2,4]}
?
Mettre à jour
Ruby 2.1.0 est publié aujourd'hui . Et je suis livré avec Array#to_h
( notes de publication et ruby-doc ), qui résout le problème de la conversion Array
d'unHash
.
Exemple de documentation Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
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 }
a.inject({})
one-liner qui permet des attributions de valeur plus flexibles.
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 }
a.each_slice(2).to_h
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.
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:
a1
et a2
:(Remarque: je présume que apple
et 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
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] ],
]
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!
]
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
.
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_h
fonctionne aussi maintenant:
a2.to_h # => {"apple"=>1, "banana"=>2}
Donc, deux réponses faciles pour le cas de tableau imbriqué simple.
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]}
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 nil
comme 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}
a5
eta6
Quelques autres réponses mentionnées flatten
, avec ou sans 1
argument, 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 a4
comme 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.
flatten
sans 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 3
une clé et durian
une 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.flatten
ne nous est donc pas utile, nous voulons juste utiliserHash[a4]
flatten(1)
cas ( a6
):Mais qu'en est-il de l'aplatissement partiel? Il convient de noter que l'appel à l' Hash::[]
aide splat
du 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
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 nil
valeur?
Dans une telle situation, il existe toujours un moyen de le faire, en utilisant Enumerable#each_slice
pour 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]
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_print
l »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_slice
solution à une entrée plate asymétrique ne fonctionne que si le bit asymétrique est à la toute fin.
[key, value]
paires (un sous-tableau pour chaque élément du tableau externe).#to_h
ou l' autre fonctionnera ou Hash::[]
les deux fonctionneront.Hash::[]
combiné avec le splat ( *
) fonctionnera, tant que les entrées sont équilibrées .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.
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}
*
) 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.
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]}
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
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}