Il modo migliore per convertire stringhe in simboli in hash
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']
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}
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.
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
A partire da Psych 3.0 puoi aggiungere symbolize_names:
Psych.load (" --- \ n foo: bar ")
# = > {& Quot; foo " = > " bar "}
Psych.load (" --- \ n foo: bar " ;, symbolize_names: true)
# = > {: Foo = > " bar "}
Nota: se hai una versione di Psych inferiore a 3.0 symbolize_names:
verrà silenziosamente ignorato.
Il mio Ubuntu 18.04 lo include immediatamente con ruby ??2.5.1p57
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