Domanda

Qual è il modo (più veloce / più pulito / più semplice) per convertire tutte le chiavi in ??un hash da stringhe a simboli in Ruby?

Questo sarebbe utile quando si analizza YAML.

my_hash = YAML.load_file('yml')

Mi piacerebbe poter usare:

my_hash[:key] 

Invece di:

my_hash['key']
È stato utile?

Soluzione

Se vuoi un one-liner,

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

copierà l'hash in una nuova con i tasti simbolizzati.

Altri suggerimenti

Ecco un metodo migliore, se stai usando Rails:

params. symbolize_keys

La fine.

In caso contrario, basta strappare il loro codice (è anche nel collegamento):

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

Per il caso specifico di YAML in Ruby, se le chiavi iniziano con ': ', verranno automaticamente internate come simboli.

require 'yaml'
require 'pp'
yaml_str = "
connections:
  - host: host1.example.com
    port: 10000
  - host: host2.example.com
    port: 20000
"
yaml_sym = "
:connections:
  - :host: host1.example.com
    :port: 10000
  - :host: host2.example.com
    :port: 20000
"
pp yaml_str = YAML.load(yaml_str)
puts yaml_str.keys.first.class
pp yaml_sym = YAML.load(yaml_sym)
puts yaml_sym.keys.first.class

Output:

#  /opt/ruby-1.8.6-p287/bin/ruby ~/test.rb
{"connections"=>
  [{"port"=>10000, "host"=>"host1.example.com"},
   {"port"=>20000, "host"=>"host2.example.com"}]}
String
{:connections=>
  [{:port=>10000, :host=>"host1.example.com"},
   {:port=>20000, :host=>"host2.example.com"}]}
Symbol

Ancora più conciso:

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

se stai usando Rails, è molto più semplice: puoi usare un HashWithIndifferentAccess e accedere alle chiavi sia come String che come Simboli:

my_hash.with_indifferent_access 

vedi anche:

http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html


Oppure puoi usare le fantastiche " Facets of Ruby " Gem, che contiene molte estensioni alle classi Ruby Core e Standard Library.

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

vedi anche: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash

Da Ruby 2.5.0 puoi usare Hash # transform_keys o Hash # transform_keys! .

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

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

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

Ecco un modo per simboleggiare in profondità un oggetto

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

Mi piace molto il Mash gemma.

puoi fare mash ['key'] , o mash [: key] , oppure mash.key

Se stai usando json e vuoi usarlo come hash, nel core Ruby puoi farlo:

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

symbolize_names : se impostato su true, restituisce i simboli per i nomi (chiavi) in un oggetto JSON. Altrimenti vengono restituite le stringhe. Le stringhe sono quelle predefinite.

Doc: Json # analizza symbolize_names

Anche

params.symbolize_keys funzionerà. Questo metodo trasforma le chiavi hash in simboli e restituisce un nuovo hash.

Una modifica alla risposta di @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
{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

Converte in:

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

Quante risposte qui, ma la funzione di rails di un metodo è hash.symbolize_keys

Questa è la mia unica fodera per gli hash nidificati

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

Nel caso in cui il motivo devi farlo perché i tuoi dati provengono originariamente da JSON, puoi saltare una qualsiasi di queste analisi semplicemente passando l'opzione : symbolize_names dopo aver ingerito JSON.

Nessun binario richiesto e funziona con Ruby > 1.9

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

Potresti essere pigro e avvolgerlo in un lambda :

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

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

Ma questo funzionerebbe solo per leggere dall'hash, non per scrivere.

Per farlo, puoi usare Hash#merge

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

Il blocco init convertirà le chiavi una volta su richiesta, sebbene se si aggiorna il valore per la versione stringa della chiave dopo aver effettuato l'accesso alla versione simbolo, la versione simbolo non verrà aggiornata.

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}

Potresti anche fare in modo che il blocco init non aggiorni l'hash, il che ti proteggerebbe da quel tipo di errore, ma saresti comunque vulnerabile al contrario - l'aggiornamento della versione del simbolo non aggiornerebbe la versione della stringa:

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}

Quindi la cosa di cui fare attenzione con questi è il passaggio tra le due forme chiave. Stick con uno.

Qualcosa come il seguente funziona?

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

Copierà l'hash, ma non te ne importerai per la maggior parte del tempo. C'è probabilmente un modo per farlo senza copiare tutti i dati.

una prima riga più corta:

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

Che ne dici di questo:

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

# my_hash['key'] => "val"
# my_hash[:key]  => "val"

Questo è per le persone che usano mruby e non hanno alcun metodo symbolize_keys definito:

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

Il metodo:

  • simboleggia solo le chiavi che sono String
  • se simboleggiare una stringa significa perdere alcune informazioni (sovrascrivere parte dell'hash) generare un RuntimeError
  • simboleggiano anche hash ricorsivamente contenuti
  • restituisce l'hash simbolizzato
  • funziona sul posto!

La matrice che vogliamo cambiare.

stringhe = [" HTML " ;, " CSS " ;, " JavaScript " ;, " Python " ;, " Ruby "]

Crea una nuova variabile come un array vuoto in modo che possiamo " .push " i simboli in.

simboli = []

Ecco dove definiamo un metodo con un blocco.

strings.each {| x | symbols.push (x.intern)}

Fine del codice.

Quindi questo è probabilmente il modo più semplice per convertire stringhe in simboli nei tuoi array in Ruby. Crea una matrice di stringhe, quindi crea una nuova variabile e imposta la variabile su una matrice vuota. Quindi seleziona ciascun elemento nel primo array che hai creato con " .each " metodo. Quindi utilizza un codice di blocco per " .push " tutti gli elementi nel tuo nuovo array e usa " .intern o .to_sym " per convertire tutti gli elementi in simboli.

I simboli sono più veloci perché risparmiano più memoria nel tuo codice e puoi usarli solo una volta. I simboli sono più comunemente usati per le chiavi nell'hash, il che è fantastico. Non sono il miglior programmatore di ruby, ma questa forma di codice mi ha aiutato molto. Se qualcuno conosce un modo migliore, per favore, condividi e puoi usare questo metodo anche per l'hash!

Se desideri una soluzione rubino alla vaniglia e poiché non ho accesso a ActiveSupport ecco una soluzione simbolica profonda (molto simile alle precedenti)

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

Questo non è esattamente un liner, ma trasforma tutte le chiavi di stringa in simboli, anche quelli nidificati:

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

Mi piace questo one-liner, quando non utilizzo Rails, perché non devo creare un secondo hash e conservare due set di dati mentre lo sto elaborando:

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 restituisce il valore della chiave eliminata

Hash # deep_rekey di Facet è anche un buon opzione, in particolare:

  • se trovi uso di altri zuccheri dalle sfaccettature nel tuo progetto,
  • se preferisci la leggibilità del codice rispetto a una riga criptica.

Esempio:

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

In ruby ??trovo che questo sia il modo più semplice e facile da capire per trasformare le chiavi di stringa in hash in simboli:

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

Per ogni chiave dell'hash che chiamiamo delete su di essa che la rimuove dall'hash (elimina anche restituisce il valore associato alla chiave che è stata eliminata) e lo impostiamo immediatamente uguale al simbolizzato chiave.

Simile alle soluzioni precedenti ma scritto in modo leggermente diverso.

  • Ciò consente un hash nidificato e / o con array.
  • Ottieni la conversione di chiavi in ??una stringa come bonus.
  • Il codice non muta l'hash passato.

    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
    
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top