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

250

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

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

my_hash = YAML.load_file('yml')

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

my_hash[:key] 

Скорее, чем:

my_hash['key']
Брайан М.
источник
дуп ?
Ян Воан
80
hash.symbolize_keysи hash.deep_symbolize_keysделать работу, если вы используете Rails.
Заз
Джош, если бы ты положил свой комментарий в ответ, я бы проголосовал за тебя. требуют 'rails'; hash.deep_symbolize_keys работает довольно хорошо в irb или pry. : D
Дуглас Г. Аллен

Ответы:

239

В Ruby> = 2.5 ( документы ) вы можете использовать:

my_hash.transform_keys(&:to_sym)

Используете более старую версию Ruby? Вот одна строка, которая скопирует хэш в новый с символизированными ключами:

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

С Rails вы можете использовать:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 
Сара Мей
источник
5
Ах, извините за непонятность - инъекция не меняет вызывающего. Вам нужно сделать my_hash = my_hash.inject ({}) {| memo, (k, v) | памятка [k.to_sym] = v; памятка}
Сара Мэй
4
Это именно то, что я искал. Я немного изменил его и добавил несколько строк, чтобы даже создавать символы в вложенных хэшах. Загляните сюда, если вам интересно: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Мэтт
37
В Ruby 1.9 вы можете использовать each_with_objectтак:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd
8
это не обрабатывает рекурсивные хеши ... Найти для разового, но не для DRY.
baash05
8
@BryanM. Я вступил в эту дискуссию очень поздно :-), но вы также можете использовать .tapметод, чтобы избавиться от необходимости проходить memoв конце. Я создал очищенную версию всех решений (в том числе и рекурсивных) gist.github.com/Integralist/9503099
Integralist
307

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

Титулы.symbolize_keys

Конец.

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

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
Sai
источник
6
to_options - псевдоним для sybolize_keys .
ma11hew28
45
Не будет символизировать вложенные хэши.
Ома
3
Я переключил ссылку symbolize_keysс новым и рабочим (Rails 3) URL. Первоначально я просто исправил URL to_options, но по этой ссылке нет документации. symbolize_keysна самом деле есть описание, поэтому я использовал его вместо.
Крейг Уокер,
23
deep_symbolize_keys! , Работает на рельсах 2+
mastaBlasta
14
Для тех, кому интересно, как сделать обратное, hash.stringify_keysработает.
Ник
112

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

требовать 'yaml'
требует 'pp'
yaml_str = "
соединения:
  - хост: host1.example.com
    порт: 10000
  - хост: host2.example.com
    порт: 20000
"
yaml_sym = "
: соединения:
  -: host: host1.example.com
    : порт: 10000
  -: host: host2.example.com
    : порт: 20000
"
pp yaml_str = YAML.load (yaml_str)
ставит yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
ставит yaml_sym.keys.first.class

Вывод:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{ "соединение" =>
  [{"port" => 10000, "host" => "host1.example.com"},
   {"port" => 20000, "host" => "host2.example.com"}]}
строка
{: Соединения =>
  [{: port => 10000,: host => "host1.example.com"},
   {: port => 20000,: host => "host2.example.com"}]}
Условное обозначение
jrgm
источник
15
Сладкий! Есть ли способ установить YAML#load_fileпо умолчанию все ключи к символам вместо строк без необходимости начинать каждый ключ с двоеточия?
ma11hew28
63

Еще более кратко:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Майкл Бартон
источник
6
Это кажется очевидным выбором.
Кейси Уотсон
12
Это не символизирует вложенные хэши.
rgtk
15
Более читаемая версия:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Деннис
60

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

my_hash.with_indifferent_access 

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

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


Или вы можете использовать удивительный Gem "Facets of Ruby", который содержит множество расширений для классов Ruby Core и Standard Library.

  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

Тило
источник
2
На самом деле это делает наоборот. Преобразует из символа в строку. Для преобразования в символ используйте my_hash.symbolize_keys
Espen
#symbolize_keys работает только в Rails, а не в обычном Ruby / irb. Также обратите внимание, что #symbolize_keys не работает с глубоко вложенными хэшами.
Тило
54

Так как Ruby 2.5.0вы можете использовать Hash#transform_keysили Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
Сагар Пандя
источник
Также работает с transform_values apidock.com/rails/Hash/transform_values . Это действительно хорошо, потому что, кажется, нет эквивалента для изменения значений, как есть с stringify_keysили symbolize_keys.
Майк Валлано,
есть ли способ глубоко символизировать ключи
Абхай Кумар
Это должно быть выбрано решение после 2018 года.
Иван Ван
26

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

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
igorsales
источник
хороший, я пойду с этим, даже если бы я переименовал его deep_symbolize :)
PierrOz
20

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

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

ykaganovich
источник
2
Это очень крутая жемчужина! Делает работу с хешами очень удобной. Спасибо за это!
Асааки
Так красиво просто. Новая разработка этого проекта продолжается в Hashie ( github.com/intridea/hashie ), но он по-прежнему работает примерно так же: github.com/intridea/hashie#keyconversion
jpalmieri
19

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

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

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

Док: Json # parse symbolize_names

отметка
источник
symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)это довольно СУХОЙ (но неэффективный) способ быстро получить хеш с вложенными ключами в качестве символов, если он исходит из файла YAML.
Кэмерон Ганьон
12

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

Чо Чо
источник
26
Этот метод не является ядром Ruby. Это метод Rails.
Рикардо Акрас
10

Модификация ответа @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
Тони
источник
Было бы полезно, если бы вы указали, почему вы изменяете объект для людей, просматривающих ответы.
DBZ
9

В Rails вы можете использовать:

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

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

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
Джей Джей
источник
3
deep_symbolize_keysдобавлено в расширение Hash в Rails , но не является частью ядра Ruby.
Райан Криспин Хениз
7

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

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
Ник Добсон
источник
К вашему сведению, работает только для Rails. В этом случае HashWithIndifferentAccess может быть лучшей альтернативой.
Адам Грант
6

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

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

JSON.parse(my_json, :symbolize_names => true)
Адам Грант
источник
4

Вы могли бы быть ленивыми, и обернуть это в 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}

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

колокольчик-рапунцель
источник
3

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

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

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

ChrisInEdmonton
источник
3

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

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
sensadrome
источник
2

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

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

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
Фредрик Бострем
источник
2

Это для людей, которые используют 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
  • символизируют также рекурсивно содержащиеся хэши
  • вернуть символизированный хеш
  • работает на месте!
Маттео Рагни
источник
1
Там опечатка в вашем методе, вы забыли !ин symbolize_keys. В остальном работает нормально.
Ка Мок
1

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

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

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

символы = []

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

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

Конец кода.

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

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

rubyguest123
источник
2
Вопрос касался хэшей, а не массивов.
Richard_G
1

Если вы хотите ванильное рубиновое решение, и у меня нет доступа к нему, 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
Харис Краина
источник
1

Начиная с Psych 3.0 вы можете добавить symbolize_names: вариант

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

Родриго Эстебанез
источник
0
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}
Влад Хомич
источник
Вы можете aзаключить в скобки, чтобы разложить аргумент блока, чтобы сделать его еще более кратким. Смотрите мой ответ для примера.
Майкл Бартон
0

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

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
ChristofferJoergensen
источник
0

Мне нравится эта строка, когда я не использую 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 возвращает значение удаленного ключа

nevets138
источник
0

Facets 'Hash # deep_rekey также является хорошим вариантом, особенно:

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

Образец:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
Sergikon
источник
0

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

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

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


источник
0

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

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

    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
jham
источник