如何将哈希中的“点符号”字符串键转换为嵌套哈希?
如何转换看起来像这样的Ruby Hash:
{ :axis => [1,2], :"coord.x" => [12,13], :"coord.y" => [14,15], }
进入这个:
{ :axis => [1,2], #unchaged from input (ok) :coord => #this has become a hash from coord.x and coord.y keys above { :x => [12,13] :y => [14,15] } }
我不知道从哪里开始!
此代码可能需要重构,但它适用于您给出的输入。
hash = { :axis => [1,2], "coord.x" => [12,13], "coord.y" => [14,15], } new_hash = {} hash.each do |key, val| new_key, new_sub_key = key.to_s.split('.') new_key = new_key.to_sym unless new_sub_key.nil? new_sub_key = new_sub_key.to_sym new_hash[new_key] = {} if new_hash[new_key].nil? new_hash[new_key].merge!({new_sub_key => val}) else new_hash.store(key, val) end end new_hash # => {:axis=>[1, 2], :coord=>{:x=>[12, 13], :y=>[14, 15]}}
# {"abc"=>"v", "bcd"=>"c"} ---> {:a=>{:b=>{:c=>"v"}}, :b=>{:c=>{:d=>"c"}}} def flat_keys_to_nested(hash) hash.each_with_object({}) do |(key,value), all| key_parts = key.split('.').map!(&:to_sym) leaf = key_parts[0...-1].inject(all) { |h, k| h[k] ||= {} } leaf[key_parts.last] = value end end
关于Ruby的好处是你可以用不同的方式做事。 这是另一个(但我测量 – 稍慢,虽然这取决于散列大小)方法:
hash = { :axis => [1,2], "coord.x" => [12,13], "coord.y" => [14,15], } new_hash = Hash.new { |hash, key| hash[key] = {} } hash.each do |key, value| if key.respond_to? :split key.split('.').each_slice(2) do |new_key, sub_key| new_hash[new_key.to_sym].store(sub_key.to_sym, value) end next end new_hash[key] = value end puts new_hash # => {:axis=>[1, 2], :coord=>{:x=>[12, 13], :y=>[14, 15]}}
但是,至少对我而言,理解正在发生的事情会更容易,更快捷。 所以这是个人的事情。
在做了一些测试之后,我发现如果你有一个更深层次的结构,你最终会遇到这些算法的问题,因为拆分密钥只会占用密钥中的一个点(’。’)。 如果你有更多的abc
,那么算法就会失败。
例如,给定:
{ 'a' => 'a', 'ba' => 'b.a', 'bb' => 'b.b', 'cabcd' => 'cabcd', 'cabce' => 'cabce' }
你会期望:
{ 'a' => 'a', 'b' => {'a' =>'b.a', 'b' => 'b.b'}, 'c' => { 'a' => { 'b' => { 'c' => { 'd' => 'cabcd', 'e' => 'cabce' } } } } }
如果数据试图用标量覆盖散列值,反之亦然,也会出现问题:
{ 'a3.bcd' => 'a3.bcd', 'a3.b' => 'a3.b' }
要么
{ 'a4.b' => 'a4.b', 'a4.bcd' => 'a4.bcd' }
这是最终版本。 如果出现其中一个不良情况,则会引发参数错误。 显然,如果有意义的话,你可以捕获坏数据版本并回显原始哈希值。
def convert_from_dotted_keys(hash) new_hash = {} hash.each do |key, value| h = new_hash parts = key.to_s.split('.') while parts.length > 0 new_key = parts[0] rest = parts[1..-1] if not h.instance_of? Hash raise ArgumentError, "Trying to set key #{new_key} to value #{value} on a non hash #{h}\n" end if rest.length == 0 if h[new_key].instance_of? Hash raise ArgumentError, "Replacing a hash with a scalar. key #{new_key}, value #{value}, current value #{h[new_key]}\n" end h.store(new_key, value) break end if h[new_key].nil? h[new_key] = {} end h = h[new_key] parts = rest end end new_hash end
本着模块化和可重用性的精神,我提出了另一种解决方案。 在第一种方法中,我们可以编写一个反向散列构造函数:
input_hash.map do |main_key, main_value| main_key.to_s.split(".").reverse.inject(main_value) do |value, key| {key.to_sym => value} end end # [{:coord=>{:x=>[12, 13]}}, {:coord=>{:y=>[14, 15]}}, {:axis=>[1, 2]}]
不是你想要的,但非常接近。 只有当Ruby有一个哈希的递归合并时,我们才能完成。 Ruby没有这样的方法,但毫无疑问其他人需要它并编写一些解决方案 。 选择你最喜欢的实现,现在简单地写:
input_hash.map do |main_key, main_value| main_key.to_s.split(".").reverse.inject(main_value) do |value, key| {key.to_sym => value} end end.inject(&:deep_merge) # {:coord=>{:y=>[14, 15], :x=>[12, 13]}, :axis=>[1, 2]}
这是@grosser答案的略微重构版本:
def flatten(hash) hash.each_with_object({}) do |(path,value), all| *path, key = key.split('.').map!(&:to_sym) leaf = path.inject(all) { |h, k| h[k] ||= {} } leaf[key] = value end end
- 当我们需要使用bundle时:在rails3上锁定和解锁
- “require File.dirname(__ FILE__)” – 如何安全地撤消文件系统依赖?
- 通过CentOS 64位在Ruby 1.9.2中安装rubygem’mysql2’时出现编译器错误
- Delayed_job:作业无法加载:未初始化的常量Syck :: Syck
- Rails3如何在命名范围内使用:params?
- Ruby:“if!object.nil?”或“if object”
- Ruby google_drive gem oAuth2节省
- Rails 4.1 Mailer预览和设计自定义电子邮件
- 什么是在ruby中格式化xml字符串的最佳方法?