Лучший способ конвертировать строки в символы в hash

каков (самый быстрый / чистый / простой) способ преобразования всех ключей в хэше из строк в символы в Ruby?

Это было бы удобно при разборе YAML.

my_hash = YAML.load_file('yml')

Я хотел бы иметь возможность использовать:

my_hash[:key] 

вместо:

my_hash['key']

29 ответов


Если вы хотите однострочный,

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

скопирует хэш в новый с символами клавиш.


вот лучший метод, если вы используете Rails:

params.symbolize_keys

конец.

если нет, просто сорвите их код (он также находится в ссылке):

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

для конкретного случая YAML в Ruby, если ключи начинаются с':', они будут автоматически интернированы как символы.

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

выход:

#  /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

еще более лаконична:

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

Если вы используете Rails, это намного проще - вы можете использовать HashWithIndifferentAccess и получить доступ к ключам как в виде строки, так и в виде символов:

my_hash.with_indifferent_access 

Читайте также:

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


или вы можете использовать удивительный камень" грани Рубина", который содержит множество расширений для Ruby Core и стандартных классов библиотек.

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

посмотреть также: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash


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

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

вот способ глубоко символизировать объект

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

мне очень нравится Маш камень.

можно сделать mash['key'] или mash[:key] или mash.key


С Ruby 2.5.0 можно использовать Hash#transform_keys или Hash#transform_keys!.

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

модификация @igorsales answer

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

params.symbolize_keys также будет работать. Этот метод превращает хэш-ключи в символы и возвращает новый хэш.


так много ответов здесь, но один метод rails функция hash.symbolize_keys


Это моя одна строка для вложенных хэшей

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

Если вы используете json и хотите использовать его в качестве хэша, в core Ruby вы можете сделать это:

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

symbolize_names: если установлено значение true, возвращает символы для имен (ключей) в объекте JSON. В противном случае возвращаются строки. Строки по умолчанию.

Doc:в формате JSON#разбираем symbolize_names


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

превращается в:

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

вы можете быть ленивым, и обернуть его в lambda:

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

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

но это будет работать только для чтения из хэша - не пишу.

для этого вы можете использовать Hash#merge

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

блок init преобразует ключи один раз по требованию, хотя если вы обновите значение для Строковой версии ключа после доступа к версии символа, версия символа не будет обновлена.

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}

у вас также может быть блок init, который не обновляется хэш, который защитит вас от такого рода ошибок, но вы все равно будете уязвимы для противоположного-обновление версии символа не обновит версию строки:

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}

таким образом, нужно быть осторожным с этим переключением между двумя ключевыми формами. Палка с одним.


в случае причина вам нужно сделать это, потому что ваши данные изначально пришли из JSON, вы можете пропустить любой из этого разбора, просто передав :symbolize_names опция при приеме JSON.

не требуется рельсов и работает с Ruby > 1.9

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

более короткий однострочный fwiw:

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

что-то вроде следующей работы?

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

он скопирует хэш, но вы не будете заботиться об этом большую часть времени. Возможно, есть способ сделать это без копирования всех данных.


Как насчет этого:

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

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

Это для людей, которые используют mruby и не имеют каких-либо 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

методика:

  • символизирует только ключи String
  • если символ строки означает потерять некоторую информацию (перезаписать часть хэша) поднять RuntimeError
  • символизировать также рекурсивно содержащиеся хэши
  • вернуть обозначенный хэш
  • работы на месте!

массив, который мы хотим изменить.

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

сделайте новую переменную как пустой массив, чтобы мы могли ".нажмите "символы".

символы = [ ]

здесь мы определяем метод с блоком.

строки.каждый символ {|x/.push(x.интерн)}

конец кода.

таким образом, это, вероятно, самый простой способ преобразования строк в символы в вашем массиве(массивах) в Ruby. Создайте массив строк, затем создайте новую переменную и установите переменную в пустой массив. Выберите каждый элемент в первом массиве, который вы создали с ".каждый метод. Затем используйте блок-код для ".нажмите "все элементы в Вашем новом массиве и используйте".интерн или .to_sym " для преобразования всех элементов в символы.

символы быстрее, потому что они сохраняют больше памяти в вашем коде, и вы можете использовать их только один раз. Символы чаще всего используются для ключей в хэше, который отличный. Я не лучший программист ruby, но эта форма кода мне очень помогла.Если кто-то знает лучший способ, пожалуйста, поделитесь, и вы можете использовать этот метод для хэша тоже!


Если вы хотите ванильное рубиновое решение, и у меня нет доступа к ActiveSupport вот глубокий символ решения (очень похож на предыдущие)

    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}

Это не совсем однострочный, но он превращает все строковые ключи в символы, а также вложенные:

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

Мне нравится этот однострочный, когда я не использую Rails, потому что тогда мне не нужно делать второй хэш и удерживать два набора данных во время его обработки:

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 возвращает значение удаленного ключа


хэш фасетов#deep_rekey также хороший вариант, особенно:

  • если вы найдете использование для другого сахара из граней в вашем проекте,
  • если вы предпочитаете читаемость кода над загадочными однострочными.

пример:

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

в ruby я считаю, что это самый простой и понятный способ превратить строковые ключи в хэши в символы :

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

для каждого ключа в хэше мы вызываем delete на нем, который удаляет его из хэша (также delete возвращает значение, связанное с ключом Это было удалено), и мы сразу же установили это равным символу ключа.


symbolize_keys рекурсивно для любого хэша:

class Hash
  def symbolize_keys
    self.is_a?(Hash) ? Hash[ self.map { |k,v| [k.respond_to?(:to_sym) ? k.to_sym : k, v.is_a?(Hash) ? v.symbolize_keys : v] } ] : self
  end
end