Найкращий спосіб конвертувати рядки в символи в хеш


250

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

Це було б зручно при розборі YAML.

my_hash = YAML.load_file('yml')

Я хотів би мати можливість використовувати:

my_hash[:key] 

Замість:

my_hash['key']


80
hash.symbolize_keysі hash.deep_symbolize_keysвиконайте роботу, якщо ви використовуєте 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 ({}) {| пам'ятка, (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

6
to_options - псевдонім для sybolize_keys .
ma11hew28

45
Не буде символізувати вкладені хеши.
ома

3
Я перейшов на посилання symbolize_keysза допомогою нової та робочої URL-адреси (Rails 3). Я спочатку виправляв URL-адресу для to_options, але на цьому посиланні є нульова документація. symbolize_keysнасправді є опис, тому я використовував це замість цього.
Крейг Уокер

23
deep_symbolize_keys! . Працює на рейках 2+
masBlasta

14
Для тих, хто цікавиться, як зробити реверс, hash.stringify_keysпрацює.
Нік

112

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

вимагають "ямл"
вимагають 'pp'
yaml_str = "
з'єднання:
  - хост: host1.example.com
    порт: 10000
  - хост: host2.example.com
    порт: 20000
"
yaml_sym = "
: з'єднання:
  -: хост: host1.example.com
    : порт: 10000
  -: хост: 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"}]}
Символ

15
Солодке! Чи є спосіб встановити YAML#load_fileза замовчуванням всі клавіші символів замість рядків, без яких потрібно починати кожну клавішу двокрапкою?
ma11hew28


60

якщо ви використовуєте Rails, це набагато простіше - ви можете використовувати HashWithIndifferentAccess і отримувати доступ до клавіш як String, так і як Symbols:

my_hash.with_indifferent_access 

дивитися також:

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


Або ви можете скористатися дивовижною дорогоцінною коштовністю «Фасетки Рубі», яка містить безліч розширень до класів 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

приємно, я піду з цим, навіть якщо я перейменую його deep_symbolize :)
PierrOz

20

Мені дуже подобається дорогоцінний камінь Mash .

ви можете зробити mash['key'], або mash[:key], абоmash.key


2
Це дуже крутий дорогоцінний камінь! Робить роботу з хешами дуже зручною. Дякую за це!
asaaki

Так красиво просто. Нова розробка цього проекту продовжується в 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 # розбір символізує імена


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)це досить сухий (але неефективний) спосіб швидко отримати хеш із вкладеними ключами як символами, якщо вони надходять із файлу YAML.
Камерон Ганьон

12

params.symbolize_keysтакож буде працювати. Цей метод перетворює хеш-ключі в символи та повертає новий хеш.


26
Цей метод не є основним Рубі. Це метод 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

Було б корисно, якщо ви включили, чому ви модифікували об'єкт для людей, які сканують відповіді.
Дбз

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

FYI, працює лише для Rails. У цьому випадку HashWithIndifferentAccess може бути кращою альтернативою.
Адам Грант

6

У разі , якщо причина , що вам потрібно зробити це , тому що ваші дані спочатку прийшли з JSON, ви можете пропустити яке - або з цього розбору, просто передавши :symbolize_namesопції при ковтанні JSON.

Не потрібні рейки та працює з 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 }

Він скопіює хеш, але вам це буде байдуже. Напевно, існує спосіб зробити це без копіювання всіх даних.




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
  • якщо символізує рядок, означає втратити деяку інформацію (перезаписати частину хешу) підняти a RuntimeError
  • символізують також рекурсивно містяться хеші
  • повернути символізований хеш
  • працює на місці!

1
Там помилка в вашому методі, ви забули !ін symbolize_keys. Інакше добре працює.
Ka Mok

1

Масив, який ми хочемо змінити.

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

Зробіть нову змінну як порожній масив, щоб ми могли ".push" символи в.

символи = []

Ось де ми визначаємо метод з блоком.

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

Кінець коду.

Тож це, мабуть, найпростіший спосіб перетворення рядків у символи у вашому масиві (Ruby) у Ruby. Створіть масив рядків, потім зробіть нову змінну і встановіть змінну в порожній масив. Потім виберіть кожен елемент у першому масиві, створеному методом ".each". Потім використовуйте код блоку для ".push" всіх елементів у вашому новому масиві та використовуйте ".intern або .to_sym" для перетворення всіх елементів у символи.

Символи швидше, оскільки вони зберігають більше пам'яті у вашому коді, і ви можете використовувати їх лише один раз. Символи найчастіше використовуються для клавіш у хеші, що чудово. Я не найкращий рубіновий програміст, але ця форма коду мені дуже допомогла. Якщо хтось знає кращий спосіб, будь ласка, поділіться, і ви можете використовувати цей метод і для хешу!


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 включає його з коробки з рубіном 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

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


0

Фасетний хеш # deep_rekey - також хороший варіант, особливо:

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

Зразок:

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

0

У рубіні я вважаю, що це найпростіший і простий для розуміння спосіб перетворення рядкових клавіш у хешах до символів:

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

Для кожного ключа в хеші ми називаємо 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.