web-dev-qa-db-fra.com

Meilleur moyen de convertir des chaînes en symboles en hachage

Quel est le moyen (le plus rapide/le plus simple/le plus simple) de convertir toutes les clés d'un hachage de chaînes en symboles en Ruby?

Cela serait pratique lors de l'analyse de YAML.

my_hash = YAML.load_file('yml')

J'aimerais pouvoir utiliser:

my_hash[:key] 

Plutôt que:

my_hash['key']
224
Bryan M.

Si vous voulez un one-liner,

my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}

copiera le hachage dans un nouveau avec les clés symbolisées.

205
Sarah Mei

Voici une meilleure méthode si vous utilisez Rails:

params. symbolize_keys

La fin.

Si vous ne l'êtes pas, extrayez leur code (c'est aussi dans le lien):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
290
Sai

Dans le cas spécifique de YAML en Ruby, si les clés commencent par ':', elles seront automatiquement internées sous forme de symboles.

 require 'yaml' 
 require 'pp' 
 yaml_str = "
 connections: 
 - Hôte: Host1.example.com 
 port: 10000 
 - Hôte: Host2. exemple.com 
 port: 20000 
 "
 yaml_sym =" 
: connexions: 
 -: hôte: hôte1.exemple.com 
: port: 10000 
 -: hôte : Host2.example.com 
: Port: 20000 
 "
 Pp yaml_str = YAML.load (yaml_str) 
 Met yaml_str.keys.first.class 
 Pp yaml_sym = YAML.load ( yaml_sym) 
 met yaml_sym.keys.first.class 

Sortie:

 # /opt/Ruby-1.8.6-p287/bin/Ruby ~/test.rb 
 {"connections" => 
 [{"port" => 10000, "Host" => "Host1.example.com"}, 
 {"port" => 20000, "Host" => "Host2.example.com"}]} 
 Chaîne 
 {: connexions => 
 [{: port => 10000,: Host => "Host1.example.com"}, 
 {: port => 20000,: Host => "Host2.example.com"}]} 
 Symbole 
112
jrgm

Encore plus laconique:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
58
Michael Barton

si vous utilisez Rails, c'est beaucoup plus simple: vous pouvez utiliser un HashWithIndifferentAccess et accéder aux clés à la fois sous forme de chaîne et sous forme de symboles:

my_hash.with_indifferent_access 

voir également:

_ { http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html } _


Ou vous pouvez utiliser l’impressionnant "Facets of Ruby" Gem, qui contient de nombreuses extensions des classes Ruby Core et Standard Library.

  require 'facets'
  > {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
    =>  {:some=>"thing", :foo=>"bar}

voir aussi: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api- classe-hachage

54
Tilo

http://api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keys

hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => { name: "Rob", age: "28" }
31
Ery

Depuis Ruby 2.5.0, vous pouvez utiliser Hash#transform_keys ou Hash#transform_keys! .

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
27
Sagar Pandya

Voici un moyen de symboliser profondément un objet

def symbolize(obj)
    return obj.inject({}){|memo,(k,v)| memo[k.to_sym] =  symbolize(v); memo} if obj.is_a? Hash
    return obj.inject([]){|memo,v    | memo           << symbolize(v); memo} if obj.is_a? Array
    return obj
end
26
igorsales

J'aime vraiment le Mash gem.

vous pouvez faire mash['key'] ou mash[:key] ou mash.key

20
ykaganovich

params.symbolize_keys fonctionnera également. Cette méthode transforme les clés de hachage en symboles et renvoie un nouveau hachage.

11
Jae Cho

Une modification à la réponse @igorsales

class Object
  def deep_symbolize_keys
    return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
    return self.inject([]){|memo,v    | memo           << v.deep_symbolize_keys; memo} if self.is_a? Array
    return self
  end
end
10
Tony

Si vous utilisez json et voulez l'utiliser comme un hachage, vous pouvez le faire en Ruby:

json_obj = JSON.parse(json_str, symbolize_names: true)

symbolize_names: si défini sur true, renvoie les symboles pour les noms (clés) dans un objet JSON. Sinon, les chaînes sont retournées. Les chaînes sont les valeurs par défaut.

Doc: Json # parse symbolize_names

10
Mark

Ceci est mon unique doublure pour les hachages imbriqués

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
7
Nick Dobson

Autant de réponses ici, mais la seule méthode de la fonction Rails est hash.symbolize_keys

7
shakirthow
{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

Convertit en:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
7
JayJay

Dans le cas où la raisonvous devez faire cela parce que vos données provenaient de JSON, vous pouvez ignorer cette analyse en passant simplement l'option :symbolize_names lors de l'acquisition de JSON.

Aucun rail requis et fonctionne avec Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)
4
Adam Grant

Vous pourriez être paresseux et l'envelopper dans une lambda:

my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }

my_lamb[:a] == my_hash['a'] #=> true

Mais cela ne fonctionnerait que pour lire à partir du hash, pas pour écrire.

Pour ce faire, vous pouvez utiliser Hash#merge

my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))

Le bloc init convertit les clés une fois à la demande. Toutefois, si vous mettez à jour la valeur de la version de chaîne de la clé après avoir accédé à la version du symbole, celle-ci ne sera pas mise à jour.

irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a]  # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a]  # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}

Vous pouvez également demander au bloc init de ne pas mettre à jour le hachage, ce qui vous protégerait de ce type d'erreur, mais vous seriez tout de même vulnérable au contraire. Mettre à jour la version du symbole ne mettrait pas à jour la version de la chaîne:

irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}

Donc, la chose à laquelle il faut faire attention est de basculer entre les deux formes principales. Bâton avec un.

4
rampion

une fwiw monocouche plus courte: 

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
3
sensadrome

Ceci est destiné aux personnes qui utilisent mruby et pour lesquelles aucune méthode symbolize_keys n'est définie:

class Hash
  def symbolize_keys!
    self.keys.each do |k|
      if self[k].is_a? Hash
        self[k].symbolize_keys!
      end
      if k.is_a? String
        raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
        self[k.to_sym] = self[k]
        self.delete(k)
      end
    end
    return self
  end
end

La méthode:

  • symbolise uniquement les clés qui sont String
  • si symboliser une chaîne signifie perdre certaines informations (écraser une partie du hachage) déclencher une RuntimeError
  • symbolise aussi les hachages récursivement contenus
  • renvoyer le hachage symbolisé
  • travaille sur place!
2
Matteo Ragni

Que dis-tu de ça:

my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
2
Fredrik Boström

Est-ce que quelque chose comme le travail suivant?

new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }

Cela copiera le hasch, mais vous ne vous en soucierez pas la plupart du temps. Il existe probablement un moyen de le faire sans copier toutes les données.

2
ChrisInEdmonton

Le tableau que nous voulons changer.

strings = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]

Créez une nouvelle variable sous forme de tableau vide afin que nous puissions ".Appuyer" sur les symboles.

symboles = []

Voici où nous définissons une méthode avec un bloc.

strings.each {| x | symboles.Push (x.intern)}

Fin du code.

C'est donc probablement le moyen le plus simple de convertir des chaînes en symboles dans vos tableaux dans Ruby. Créez un tableau de chaînes puis créez une nouvelle variable et définissez-la sur un tableau vide. Sélectionnez ensuite chaque élément du premier tableau créé avec la méthode ".each". Utilisez ensuite un code de bloc pour ".Appuyer" sur tous les éléments de votre nouveau tableau et utilisez ".intern ou .to_sym" pour convertir tous les éléments en symboles. 

Les symboles sont plus rapides car ils économisent plus de mémoire dans votre code et vous ne pouvez les utiliser qu'une seule fois. Les symboles sont le plus souvent utilisés pour les clés dans le hachage, ce qui est génial. Je ne suis pas le meilleur programmeur Ruby, mais cette forme de code m'a beaucoup aidé. Si quelqu'un connaît un meilleur moyen de le partager, partagez-le et vous pouvez aussi utiliser cette méthode pour le hachage! 

1
rubyguest123

Si vous souhaitez une solution Vanilla Ruby et que, comme je n'ai pas accès à ActiveSupport, voici une solution symbolique profonde (très similaire aux précédentes)

    def deep_convert(element)
      return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
      return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
      element
    end
1
Haris Krajina

En Ruby, je trouve que c'est le moyen le plus simple et le plus facile à comprendre de transformer des clés de chaîne en symboles de hachage: 

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}

Pour chaque clé du hachage, nous appelons delete, ce qui la supprime du hachage (delete renvoie également la valeur associée à la clé qui a été supprimée) et nous la fixons immédiatement à la clé symbolisée.

0
user4488109

J'aime ce one-line, quand je n'utilise pas Rails, car je n'ai pas besoin de faire un deuxième hachage et de conserver deux ensembles de données pendant que je les traite:

my_hash = { "a" => 1, "b" => "string", "c" => true }

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }

my_hash
=> {:a=>1, :b=>"string", :c=>true}

Hash # delete renvoie la valeur de la clé supprimée

0
nvts8a

Ce n'est pas exactement une ligne, mais cela transforme toutes les clés de chaîne en symboles, également ceux imbriqués:

def recursive_symbolize_keys(my_hash)
  case my_hash
  when Hash
    Hash[
      my_hash.map do |key, value|
        [ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
      end
    ]
  when Enumerable
    my_hash.map { |value| recursive_symbolize_keys(value) }
  else
    my_hash
  end
end
0

Semblable aux solutions précédentes mais écrit un peu différemment.

  • Cela permet un hachage imbriqué et/ou contenant des tableaux.
  • Obtenez la conversion des clés en chaîne en bonus.
  • Le code ne mute pas lorsque le hachage a été transmis.

    module HashUtils
      def symbolize_keys(hash)
        transformer_function = ->(key) { key.to_sym }
        transform_keys(hash, transformer_function)
      end
    
      def stringify_keys(hash)
        transformer_function = ->(key) { key.to_s }
        transform_keys(hash, transformer_function)
      end
    
      def transform_keys(obj, transformer_function)
        case obj
        when Array
          obj.map{|value| transform_keys(value, transformer_function)}
        when Hash
          obj.each_with_object({}) do |(key, value), hash|
            hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
          end
        else
          obj
        end
      end
    end
    
0
jham
Ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
 => {"aaa"=>1, "bbb"=>2} 
Ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
 => {:aaa=>1, :bbb=>2}
0
Vlad Khomich

À partir de Psych 3.0, vous pouvez ajouter le symbolize_names: option

Psych.load("---\n foo: bar") # => {"foo"=>"bar"}

Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}

Remarque: si vous avez une version Psych inférieure à la version 3.0, symbolize_names: sera silencieusement ignoré.

Mon Ubuntu 18.04 l’inclue hors de la boîte avec Ruby 2.5.1p57

0
Rodrigo Estebanez

Facets 'Hash # deep_rekey est également une bonne option, notamment:

  • si vous utilisez d'autres sucres provenant de facettes de votre projet,
  • si vous préférez la lisibilité du code plutôt que les one-liners cryptiques.

Échantillon:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
0
Sergikon