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

StackOverflow https://stackoverflow.com/questions/800122

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

my_hash = YAML.load_file('yml')

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

my_hash[:key] 

Скорее, чем:

my_hash['key']
Это было полезно?

Решение

Если вам нужен однострочник,

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

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

Другие советы

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

параметры.символизировать_ключи

Конец.

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

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, это намного проще — вы можете использовать HashWithIndependentAccess и получать доступ к ключам как в виде строки, так и в виде символов:

my_hash.with_indifferent_access 

смотрите также:

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


Или вы можете использовать замечательный Gem «Facets of Ruby», который содержит множество расширений для классов 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

С Ruby 2.5.0 вы можете использовать Hash#transform_keys или 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" }

Вот способ глубокого обозначения объекта

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

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

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

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

Док: Json#parsesymbolize_names

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

Модификация ответа @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!

Преобразуется в:

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

Здесь так много ответов, но функция рельсов с одним методом: 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, вы можете пропустить любой из этих анализов, просто передав :symbolize_names опция при приеме JSON.

Rails не требуется, работает с Ruby> 1.9.

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

Вы могли бы полениться и обернуть это в 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'))

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

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}

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

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}

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

Будет ли работать что-то вроде следующего?

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

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

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

my_hash.inject({}){|h,(k,v)| h.merge({ 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
  • символизировать также рекурсивно содержащиеся хеши
  • вернуть символизированный хэш
  • работает на месте!

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

строки = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]

Создайте новую переменную как пустой массив, чтобы мы могли «.вставить» символы.

символы = [ ]

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

Строки. Symbols.push (x.intern)}

Конец кода.

Так что это, вероятно, самый простой способ конвертировать строки в символы в ваших массивах в Ruby.Создайте массив строк, затем создайте новую переменную и установите для нее пустой массив.Затем выберите каждый элемент в первом массиве, который вы создали, с помощью метода «.each».Затем используйте блочный код для «.push» всех элементов в новом массиве и используйте «.intern или .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

Начиная с Психика 3.0 вы можете добавить символизировать_имена: вариант

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

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

Примечание:если у вас версия Psych ниже 3.0 symbolize_names: будут молча игнорироваться.

Моя Ubuntu 18.04 включает его из коробки с 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}

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

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

Хэш Facets#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, который удаляет его из хеша (также удаляем возвращает значение, связанное с ключом который был удален), и мы немедленно установили его равным символьному ключу.

Аналогично предыдущим решениям, но написано немного иначе.

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

    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
    
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top