Question

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

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

...quelle est la meilleure façon de convertir ceci en un hachage sous la forme de...

{apple => 1, banana => 2}
Était-ce utile?

La solution

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

Cette réponse a été initialement proposée comme alternative aux approches utilisant l'aplatissement, qui ont été 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 bonne pratique ou une approche efficace.La réponse originale suit.


Avertissement! Solutions utilisant aplatir ne conservera 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 s'attendait à un tableau de longueur égale (par ex.['k1','v1,'k2','v2']).Ce qui est pire, c'est qu'un tableau différent, aplati à une longueur égale, nous donnerait silencieusement un hachage avec des valeurs incorrectes.

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

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

Cela préserve la clé du tableau :

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

Autres conseils

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

En utilisant Array#flatten(1) limite la récursion donc Array les clés et les valeurs fonctionnent comme prévu.

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

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

Noter 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"}

Note: to_h accepte un bloc dans Ruby 2.6.0+ ;pour les premiers rubis, vous pouvez utiliser mon backports gemme et require '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.

Mise à jour

Ruby 2.1.0 est publié aujourd'hui.Et je viens avec Array#to_h (notes de version et rubis-doc), ce qui résout le problème de la conversion d'un Array à un Hash.

Exemple de documentation Ruby :

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

Modifier:Vu les réponses publiées pendant que j'écrivais, Hash[a.flatten] semble la voie à suivre.J'ai dû manquer ce passage dans la documentation lorsque je réfléchissais à la réponse.Je pensais que les solutions que j'ai écrites pouvaient ê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 façon la plus intéressante à laquelle je puisse penser de créer le premier formulaire ressemble à ceci :

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

Vous pouvez aussi 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} 

Ajouter à 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 démontant cette réponse, 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 véritable tableau.

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]

et puis:

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

que nous pouvons utiliser comme arguments pour 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}

Résumé & TL;DR :

Cette réponse espère être un résumé 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

La discussion et les détails suivent.


Installation:variables

Afin de montrer les données que nous utiliserons dès le départ, je vais créer des variables pour représenter diverses possibilités pour les données.Ils entrent dans les catégories suivantes :

Sur la base de ce qui était directement dans la question, comme a1 et a2:

(Note:je présume que apple et banana étaient censés représenter des variables.Comme d'autres l'ont fait, j'utiliserai désormais des chaînes afin que les entrées 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, pour travailler :

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

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

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

En utilisant Hash::[] combiné avec le opérateur d'éclaboussures 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 paires clé/valeur, a2:

Avec un éventail de [key,value] tapez des tableaux, il y a deux façons de procéder.

D'abord, Hash::[] fonctionne toujours (comme avec *a1):

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

Et puis aussi #to_h fonctionne maintenant :

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

Donc, deux réponses simples pour le cas simple des tableaux imbriqués.

Cela reste vrai même avec des sous-tableaux comme 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 problème) :

Si nous avons obtenu 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 régler 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}

Aplatissement - utilisation de nouvelles variables a5 et a6

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 avons eu, qui est apparu avec a4.to_h.Je pense appeler flatten pourrait être une approche que quelqu'un pourrait utiliser pour essayer de résoudre ce problème, 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]`.)

À première vue, cela semble fonctionner – mais cela nous a mis du mauvais pied avec les oranges sans pépins, ce qui a également 3 un clé et durian un valeur.

Et ceci, comme pour a1, ça ne marche tout simplement pas :

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

Donc a4.flatten ne nous est pas utile, nous voudrions simplement utiliser Hash[a4]

Le flatten(1) cas (a6):

Mais qu’en est-il d’un aplatissement partiel seulement ?Il convient de noter que l'appel Hash::[] en utilisant splat sur le tableau partiellement aplati (a6) est pas la même chose que d'appeler Hash[a4]:

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

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

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

Dans une telle situation, il existe toujours un moyen de le faire, en utilisant Enumerable#each_slice pour revenir à la clé/valeur paires comme é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 donner un nouveau tableau qui ne l'est pas "identique" à a4, mais a le 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 le each_slice(2) solution ne ramène les choses à la raison que si le dernier la clé était celle qui manquait une valeur.Si nous ajoutions 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 obtiendrons sont différents sur des points importants :

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_printc'est ap juste pour qu'il soit plus facile de montrer la structure ici ;il n'y a aucune exigence conceptuelle pour cela.)

Alors le each_slice La solution à une entrée plate déséquilibrée ne fonctionne que si le bit déséquilibré est à la toute fin.


À retenir :

  1. Dans la mesure du possible, configurez la saisie de ces éléments comme [key, value] paires (un sous-tableau pour chaque élément du tableau externe).
  2. Quand vous pouvez effectivement faire cela, soit #to_h ou Hash::[] fonctionneront tous les deux.
  3. Si vous n'en êtes pas capable, Hash::[] combiné avec le splat (*) marchera, tant que les entrées sont équilibrées.
  4. Avec un déséquilibré et plat tableau en entrée, la seule façon pour que cela fonctionne raisonnablement est si le dernier value l'article est le seul qui manque.

Note latérale :Je publie cette réponse parce que je pense qu'il y a de la valeur à ajouter – certaines des réponses existantes contiennent des informations incorrectes, et aucune (que j'ai lue) 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 remercie néanmoins ceux qui m'ont précédé, dont plusieurs ont inspiré des parties de cette réponse.

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 ceci -

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 la meilleure solution, 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 séquentiels, 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}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top