Tableau à Hash Ruby

192

D'accord, voici l'affaire, je cherche depuis des lustres sur Google pour trouver une solution à ce problème et bien qu'il y en ait beaucoup, ils ne semblent pas faire le travail que je recherche.

Fondamentalement, j'ai un tableau structuré comme ça

["item 1", "item 2", "item 3", "item 4"] 

Je veux le convertir en hachage pour qu'il ressemble à ceci

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

c'est-à-dire que les éléments qui sont sur les index «pairs» sont les clés et les éléments sur les index «impairs» sont les valeurs.

Des idées comment faire cela proprement? Je suppose qu'une méthode de force brute consisterait simplement à extraire tous les index pairs dans un tableau séparé, puis à les contourner pour ajouter les valeurs.

djhworld
la source

Réponses:

358
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

C'est tout. Le *s'appelle l' opérateur splat .

Une mise en garde par @Mike Lewis (dans les commentaires): "Soyez très prudent avec ceci. Ruby étend les splats sur la pile. Si vous faites cela avec un grand jeu de données, attendez-vous à faire exploser votre pile."

Donc, pour la plupart des cas d'utilisation généraux, cette méthode est excellente, mais utilisez une méthode différente si vous souhaitez effectuer la conversion sur beaucoup de données. Par exemple, @ Łukasz Niemier (également dans les commentaires) propose cette méthode pour les grands ensembles de données:

h = Hash[a.each_slice(2).to_a]
Ben Lee
la source
10
@tester, le *s'appelle l' opérateur splat . Il prend un tableau et le convertit en une liste littérale d'éléments. Donc *[1,2,3,4]=> 1, 2, 3, 4. Dans cet exemple, ce qui précède équivaut à faire Hash["item 1", "item 2", "item 3", "item 4"]. Et Hasha une []méthode qui accepte une liste d'arguments (en créant des clés d'index pairs et des valeurs d'index impaires), mais Hash[]n'accepte pas de tableau, nous splatons donc le tableau en utilisant *.
Ben Lee
15
Soyez très prudent avec cela. Ruby étend les splats sur la pile. Si vous faites cela avec un grand ensemble de données, attendez-vous à faire exploser votre pile.
Mike Lewis
9
Sur les tables Big Data, vous pouvez utiliser Hash[a.each_slice(2).to_a].
Hauleth
4
Que signifie «souffler votre pile»?
Kevin
6
@Kevin, la pile utilise une petite zone de mémoire que le programme alloue et réserve pour certaines opérations spécifiques. Le plus souvent, il est utilisé pour conserver une pile des méthodes qui ont été appelées jusqu'à présent. C'est l'origine du terme trace de pile , et c'est aussi pourquoi une méthode infiniment récursive peut provoquer un débordement de pile . La méthode de cette réponse utilise également la pile, mais comme la pile n'est qu'une petite zone de mémoire, si vous essayez cette méthode avec un grand tableau, elle remplira la pile et provoquera une erreur (une erreur du même ordre que un débordement de pile).
Ben Lee
103

Ruby 2.1.0 a introduit une to_hméthode sur Array qui fait ce dont vous avez besoin si votre tableau d'origine se compose de tableaux de paires clé-valeur: http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-vers_h .

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}
Jochem Schulenklopper
la source
1
Belle! Bien mieux que certaines des autres solutions proposées ici.
Dennis
3
pour les versions ruby ​​antérieures à 2.1.0, vous pouvez utiliser la méthode Hash :: [] pour obtenir des résultats similaires, tant que vous avez des paires d'un tableau imbriqué. donc a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:
@AfDev, en effet, merci. Vous avez raison (en ignorant les fautes de frappe mineures: bardoit être un symbole, et le symbole :2doit être un entier. Donc, votre expression corrigée est a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper
28

Utilisez simplement Hash.[]avec les valeurs du tableau. Par exemple:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}
Mandrin
la source
1
que signifie [* arr]?
Alan Coromano
1
@Marius: *arrconvertit arren une liste d'arguments, donc cela appelle la []méthode de Hash avec le contenu de arr comme arguments.
Chuck
26

Ou si vous avez un tableau de [key, value]tableaux, vous pouvez faire:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }
Erik Escobedo
la source
2
Votre réponse n'est pas liée à la question et dans votre cas, il est toujours beaucoup plus facile d'utiliser la même choseHash[*arr]
Yossi
2
Nan. Il reviendrait { [1, 2] => [3, 4] }. Et puisque le titre de la question dit "Array to Hash" et que la méthode intégrée "Hash to Array" le fait:, { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]j'ai pensé que plus d'un pourrait s'arrêter ici en essayant d'obtenir l'inverse de la méthode intégrée "Hash to Array". En fait, c'est comme ça que j'ai fini ici de toute façon.
Erik Escobedo
1
Désolé, j'ai ajouté un astérisque supplémentaire. Hash[arr]fera le travail pour vous.
Yossi
9
Meilleure solution à mon humble avis: Hash [* array.flatten (1)]
invité
2
Yossi: Désolé d'avoir ressuscité les morts, mais il y a un plus gros problème avec sa réponse, et c'est l'utilisation de la #injectméthode. Avec #merge!, #each_with_objectaurait dû être utilisé. Si #injectest insisté sur, #mergeplutôt que #merge!devrait avoir été utilisé.
Boris Stitnicky
12

C'est ce que je recherchais en recherchant ceci:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}

Karl Glaser
la source
Vous ne voulez pas utiliser merge, il construit et supprime un nouveau hachage par itération de boucle et est très lent. Si vous avez un tableau de hachages, essayez à la [{a:1},{b:2}].reduce({}, :merge!)place - il fusionne tout dans le même (nouveau) hachage.
Merci, c'est ce que je voulais aussi! :)
Thanasis Petsas
Vous pouvez aussi faire.reduce(&:merge!)
Ben Lee
1
[{a: 1}, {b: 2}].reduce(&:merge!)évalue à{:a=>1, :b=>2}
Ben Lee
Cela fonctionne car inject / reduction a une fonction dans laquelle vous pouvez omettre l'argument, auquel cas il utilise le premier argument du tableau pour fonctionner comme argument d'entrée et le reste du tableau comme tableau. Combinez cela avec symbol-to-proc et vous obtenez cette construction concise. En d'autres termes, [{a: 1}, {b: 2}].reduce(&:merge!)c'est le même que [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }ce qui est le même que [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee
10

Enumeratorcomprend Enumerable. Depuis 2.1, a Enumerableégalement une méthode #to_h. C'est pourquoi, on peut écrire: -

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Parce que #each_slicesans bloc nous donne Enumerator, et selon l'explication ci-dessus, nous pouvons appeler la #to_hméthode sur l' Enumeratorobjet.

Arup Rakshit
la source
7

Vous pouvez essayer comme ça, pour un seul tableau

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

pour tableau de tableau

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}
Jenorish
la source
6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

ou, si vous détestez Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

ou, si vous êtes un fan paresseux de la programmation fonctionnelle cassée:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"
Boris Stitnicky
la source
Si vous ne détestez pas complètement Hash[ ... ]mais que vous voulez l'utiliser comme méthode en chaîne (comme vous pouvez le faire avec to_h), vous pouvez combiner les suggestions de Boris et écrire:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios
Pour rendre la sémantique du code ci-dessus plus claire: Cela créera un "hachage paresseux" h , qui est initialement vide , et extraira les éléments du tableau d'origine a lorsque cela sera nécessaire. Ce n'est qu'alors qu'ils seront réellement stockés dans h!
Daniel Werner
1

Toutes les réponses supposent que le tableau de départ est unique. OP n'a pas spécifié comment gérer les tableaux avec des entrées en double, ce qui entraîne des clés en double.

Regardons:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

Vous perdrez la item 1 => item 2paire car elle est remplacée par item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Toutes les méthodes, y compris le reduce(&:merge!)résultat de la même suppression.

Il se peut cependant que ce soit exactement ce à quoi vous vous attendez. Mais dans d'autres cas, vous souhaitez probablement obtenir un résultat avec une Arrayvaleur pour à la place:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

La manière naïve serait de créer une variable d'assistance, un hachage qui a une valeur par défaut, puis de le remplir dans une boucle:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Il pourrait être possible d'utiliser assoc et reducede faire ci-dessus en une seule ligne, mais cela devient beaucoup plus difficile à raisonner et à lire.

berkes
la source