如何将哈希中的“点符号”字符串键转换为嵌套哈希?

如何转换看起来像这样的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