Лучший способ конвертировать строки в символы в 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
Если вы используете 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
также будет работать. Этот метод превращает хэш-ключи в символы и возвращает новый хэш.
Это моя одна строка для вложенных хэшей
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. В противном случае возвращаются строки. Строки по умолчанию.
{'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)
что-то вроде следующей работы?
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