Pergunta

Qual é a (mais rápido / mais limpo / direta) maneira de converter todas as chaves em um hash de strings para símbolos em Ruby?

Este seria útil ao analisar YAML.

my_hash = YAML.load_file('yml')

Eu gostaria de ser capaz de usar:

my_hash[:key] 

Em vez de:

my_hash['key']
Foi útil?

Solução

Se você quiser um one-liner,

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

irá copiar o hash em um novo com as teclas simbolizados.

Outras dicas

Aqui está um método melhor, se você estiver usando Rails:

params. symbolize_keys

O fim.

Se você não está, apenas arrancar o seu código (que é também no link):

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

Para o caso específico de YAML em Ruby, se as chaves começam com ':', eles vão ser internados automaticamente 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

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

Ainda mais concisa:

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

Se você estiver usando Rails, é muito mais simples - você pode usar um HashWithIndifferentAccess e acesso as chaves ambas as String e como símbolos:

my_hash.with_indifferent_access 

ver também:

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


Ou você pode usar os incríveis "Facetas da Ruby" Gem, que contém uma grande quantidade de extensões para classes Ruby Core e biblioteca padrão.

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

ver também: 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 você pode usar Hash#transform_keys ou Hash#transform_keys! .

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

http://api.rubyonrails.org/classes/Hash. html # método-i-symbolize_keys

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

Aqui está uma maneira de simbolizar profunda um 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

Eu realmente gosto do Mash jóia.

você pode fazer mash['key'], ou mash[:key], ou mash.key

Se você estiver usando json, e quiser usá-lo como um hash, no núcleo Ruby que você pode fazê-lo:

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

symbolize_names : Se definido como verdadeiro, retorna símbolos para os nomes (chaves) em um objeto JSON. Caso contrário cordas são devolvidos. Strings são o padrão.

Doc: Json # parse symbolize_names

params.symbolize_keys também irá funcionar. Este método transforma chaves de hash em símbolos e retorna um novo hash.

A modificação a resposta @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 em:

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

Assim, muitas respostas aqui, mas a um método trilhos função é hash.symbolize_keys

Esta é a minha um forro para hashes aninhados

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

No caso de o razão que você precisa para fazer isso é porque seus dados veio originalmente de JSON, você poderia ignorar qualquer desta análise por apenas passando na opção :symbolize_names após a ingestão de JSON.

No Rails necessário e obras com Ruby> 1,9

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

Você poderia ser preguiçoso, e envolvê-la em um lambda:

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

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

Mas isso seria apenas o trabalho para a leitura a partir do hash -. Não escrever

Para fazer isso, você poderia usar Hash#merge

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

O bloco de inicialização irá converter as teclas de um tempo na procura, mas se você atualizar o valor para a versão seqüência da chave após acessar a versão símbolo, a versão símbolo não será atualizado.

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}

Você também pode ter o bloco de inicialização não atualizar o hash, que iria protegê-lo contra esse tipo de erro, mas você ainda estaria vulnerável ao oposto - atualizando a versão símbolo não atualizar a versão string:

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}

Assim, a coisa que ter cuidado com estes é a alternância entre as duas formas principais. Vara com um.

Será que algo como o seguinte trabalho?

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

Vai copiar o hash, mas você não se preocupam com que a maior parte do tempo. Há provavelmente uma maneira de fazer isso sem copiar todos os dados.

a mais curta one-liner fwiw:

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

Como sobre isto:

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

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

Esta é para as pessoas que usa mruby e não têm qualquer método symbolize_keys definido:

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

O método:

  • simboliza apenas as chaves que são String
  • se os meios simbolizar uma corda para perder algumas informações (parte de substituição de haxixe) raise uma RuntimeError
  • simbolizam também hashes recursivamente contidos
  • retornar o hash simbolizado
  • funciona no lugar!

A matriz queremos mudar.

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

Faça uma nova variável como uma matriz vazia para que possamos ".faça pressão sobre" os símbolos.

símbolos = []

Aqui é onde nós definimos um método com um bloco.

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

Fim de código.

Portanto, esta é provavelmente a maneira mais simples de cordas converter para símbolos em sua matriz (s) em Ruby. Faça uma matriz de strings, em seguida, fazer uma nova variável e definir a variável para uma matriz vazia. Em seguida, selecione cada elemento na primeira matriz que você criou com o método ".each". Em seguida, use um código de bloco para ".push" todos os elementos em sua nova matriz e usar ".intern ou .to_sym" para converter todos os elementos para símbolos.

Símbolos são mais rápidos porque eles economizar mais memória dentro do seu código e você só pode usá-los uma vez. Símbolos são mais comumente usados ??para chaves de hash de que é ótimo. Eu sou o não o melhor programador ruby, mas esta forma de código me ajudou lot.If alguém sabe uma maneira melhor por favor, compartilhe e você pode usar este método para de hash também!

Se você gostaria de baunilha solução rubi e como me não têm acesso a ActiveSupport aqui é solução simbolizam profunda (muito semelhante aos 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 você pode adicionar o symbolize_names: opção

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

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

Nota:. Se você tem um menor versão Psych de 3,0 symbolize_names: será ignorado

O meu Ubuntu 18.04 inclui-lo fora da caixa com rubi 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}

Este não é exatamente um one-liner, mas acontece todas as chaves string em símbolos, também os aninhados:

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

Eu gosto deste one-liner, quando eu não estou usando Rails, porque então eu não tenho que fazer uma segunda haxixe e mantenha dois conjuntos de dados, enquanto eu estou processá-lo:

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 # retornos excluir o valor da chave eliminada

Facetas Hash # deep_rekey Também é uma boa opção, especialmente:

  • se você encontrar uso para outro açúcar de facetas em seu projeto,
  • se preferir a legibilidade do código mais de one-liners crípticos.

Amostra:

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

Em Ruby eu achar que este é o mais simples e fácil de entender maneira de transformar as chaves string em hashes de símbolos:

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

Para cada chave no hash que chamamos de exclusão nele que remove-lo a partir do hash (também excluir retorna o valor associado com a chave que foi excluído) e nós imediatamente definir esta igual ao simbolizado chave.

Assim como as soluções anteriores, mas escrito um pouco diferente.

  • Isto permite um hash que é aninhada e / ou tem arrays.
  • Obter conversão de chaves para uma string como um bônus.
  • Código não mutar o hash foi passado.

    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
    
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top