La mejor manera de convertir cadenas a símbolos en hash
Pregunta
¿Cuál es la forma (más rápida / limpia / directa) de convertir todas las claves en un hash de cadenas a símbolos en Ruby?
Esto sería útil al analizar YAML.
my_hash = YAML.load_file('yml')
Me gustaría poder usar:
my_hash[:key]
En lugar de:
my_hash['key']
Solución
Si quieres una sola línea,
my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
copiará el hash en uno nuevo con las teclas simbolizadas.
Otros consejos
Aquí hay un método mejor, si estás usando Rails:
params. symbolize_keys
El final.
Si no lo está, simplemente copie su código (también está en el enlace):
myhash.keys.each do |key|
myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
Para el caso específico de YAML en Ruby, si las teclas comienzan con ':
', se internarán automáticamente como símbolos.
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
Salida:
# /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
Aún más conciso:
Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Si está usando Rails, es mucho más simple: puede usar un HashWithIndifferentAccess y acceder a las teclas tanto como Cadena como como Símbolos:
my_hash.with_indifferent_access
vea también:
http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndif.cc.Access.htc / a>
O puedes usar las impresionantes " Facetas de Ruby " Gem, que contiene muchas extensiones para las clases de Ruby Core y Standard Library.
require 'facets'
> {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
=> {:some=>"thing", :foo=>"bar}
vea también: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash
Desde Ruby 2.5.0
puede usar 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 # method-i-symbolize_keys
hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => { name: "Rob", age: "28" }
Aquí hay una forma de simbolizar en profundidad un objeto
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
Realmente me gusta el Mash gema.
puede hacer mash ['key']
o mash [: key]
, o mash.key
Si estás usando json, y quieres usarlo como un hash, en Ruby Core puedes hacerlo:
json_obj = JSON.parse(json_str, symbolize_names: true)
symbolize_names : si se establece en true, devuelve símbolos para los nombres (claves) en un objeto JSON. De lo contrario se devuelven cadenas. Las cadenas son las predeterminadas.
params.symbolize_keys
también funcionará. Este método convierte las claves hash en símbolos y devuelve un nuevo hash.
Una modificación a @igorsales respuesta
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!
Se convierte en:
{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
Muchas respuestas aquí, pero la función de los rieles de un método es hash.symbolize_keys
Este es mi único forro para hashes anidados
def symbolize_keys(hash)
hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
En caso de que la razón necesite hacer esto porque sus datos provinieron originalmente de JSON, puede omitir cualquiera de estos análisis simplemente pasando la opción : symbolize_names
tras ingerir JSON.
No se requieren rieles y funciona con Ruby > 1.9
JSON.parse(my_json, :symbolize_names => true)
Podría ser perezoso y envolverlo en un lambda
:
my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }
my_lamb[:a] == my_hash['a'] #=> true
Pero esto solo funcionaría para leer desde el hash, no para escribir.
Para hacer eso, puedes usar Hash#merge
my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))
El bloque de inicio convertirá las claves una vez a petición, aunque si actualiza el valor de la versión de cadena de la clave después de acceder a la versión de símbolo, la versión de símbolo no se actualizará.
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}
También puede hacer que el bloque de inicio no actualice el hash, lo que lo protegería de ese tipo de error, pero aún sería vulnerable a lo contrario: actualizar la versión del símbolo no actualizaría la versión de la cadena:
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}
Por lo tanto, hay que tener cuidado con estos cambios entre las dos formas clave. Quédate con uno.
¿Algo como el siguiente trabajo?
new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }
Copiará el hash, pero eso no te importará la mayor parte del tiempo. Probablemente haya una forma de hacerlo sin copiar todos los datos.
un fwiw de una sola línea más corto:
my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
¿Qué tal esto?
my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))
# my_hash['key'] => "val"
# my_hash[:key] => "val"
Esto es para las personas que usan mruby
y no tienen definido ningún symbolize_keys
:
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
El método:
- simboliza solo las claves que son
String
- si simbolizar una cadena significa perder algunas informaciones (sobrescribir parte del hash) genera un
RuntimeError
- simboliza también hashes contenidos recursivamente
- devolver el hash simbolizado
- funciona en su lugar!
La matriz que queremos cambiar.
cadenas = [" HTML " ;, " CSS " ;, " JavaScript " ;, " Python " ;, " Ruby "]
Crea una nueva variable como una matriz vacía para que podamos " .push " los símbolos en.
símbolos = []
Aquí es donde definimos un método con un bloque.
cuerdas. cada {| x | symbols.push (x.intern)}
Fin del código.
Por lo tanto, esta es probablemente la forma más sencilla de convertir cadenas a símbolos en tu (s) matriz (s) en Ruby. Haga una matriz de cadenas, luego cree una nueva variable y establezca la variable en una matriz vacía. Luego, seleccione cada elemento de la primera matriz que creó con " .each " método. Luego usa un código de bloque para " .push " todos los elementos en su nueva matriz y use " .intern o .to_sym " Convertir todos los elementos en símbolos.
Los símbolos son más rápidos porque ahorran más memoria en tu código y solo puedes usarlos una vez. Los símbolos son más comúnmente utilizados para las claves en hash lo que es genial No soy el mejor programador de Ruby, pero esta forma de código me ayudó mucho. Si alguien sabe una mejor manera, por favor, comparta y puede usar este método para el hash también.
Si desea una solución de vainilla rubí y, como yo, no tengo acceso a ActiveSupport
, aquí está la solución de simbolización profunda (muy similar a las anteriores)
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 partir de Psych 3.0, puede agregar symbolize_names: opción
Psych.load (" --- \ n foo: bar ")
# = > {" foo " = > " barra "}
Psych.load (" --- \ n foo: bar " ;, symbolize_names: true)
# = > {: foo = > " barra "}
Nota: si tiene una versión Psych inferior a 3.0 symbolize_names:
se ignorará silenciosamente.
Mi Ubuntu 18.04 lo incluye fuera de la caja 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}
Esto no es exactamente de una sola línea, pero convierte todas las claves de cadena en símbolos, también las anidadas:
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
Me gusta este de una sola línea, cuando no estoy usando Rails, porque entonces no tengo que hacer un segundo hash y mantener dos conjuntos de datos mientras lo estoy procesando:
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 devuelve el valor de la clave eliminada
Hash # deep_rekey de las facetas también es una buena Opción, especialmente:
- si encuentra uso para otro tipo de azúcar de facetas en su proyecto,
- si prefiere la legibilidad del código en lugar de líneas crípticas.
Muestra:
require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
En ruby, considero que esta es la forma más sencilla y fácil de entender para convertir claves de cadena en hashes a símbolos:
my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}
Para cada clave en el hash que llamamos eliminar, que lo elimina del hash (también eliminar devuelve el valor asociado con la clave que se eliminó) e inmediatamente establecemos este valor igual al simbolizado llave.
Similar a las soluciones anteriores pero escrito un poco diferente.
- Esto permite un hash que está anidado y / o tiene matrices.
- Obtenga la conversión de claves a una cadena como un bono.
-
El código no muta el hash que se ha pasado.
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