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

时间:2010-12-06 09:27:33

标签: ruby-on-rails ruby

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

我不知道从哪里开始!

6 个答案:

答案 0 :(得分:5)

# {"a.b.c"=>"v", "b.c.d"=>"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

答案 1 :(得分:3)

此代码可能需要重构,但它适用于您提供的输入。

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]}}

答案 2 :(得分:1)

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]}}

但是,至少对我来说,理解正在发生的事情更容易,更快捷。所以这是个人的事情。

答案 3 :(得分:1)

经过一些测试后,我发现如果你有更深层次的结构,你最终会遇到这些算法的问题,因为拆分钥匙只能占一个点('。&#39 ;)在钥匙。如果您有更多a,则算法会失败。

例如,给定:

a.b.c

你会期待:

{
  'a' => 'a',
  'b.a' => 'b.a',
  'b.b' => 'b.b',
  'c.a.b.c.d' => 'c.a.b.c.d',
  'c.a.b.c.e' => 'c.a.b.c.e'
}

如果数据试图用标量覆盖哈希值,反之亦然,也会出现问题:

{
  'a' => 'a',
  'b' => {'a' =>'b.a', 'b' => 'b.b'},
  'c' => {
    'a' => {
      'b' => {
        'c' => {
          'd' => 'c.a.b.c.d',
          'e' => 'c.a.b.c.e'
        }
      }
    }
  }
}

{
  'a3.b.c.d' => 'a3.b.c.d',
  'a3.b' => 'a3.b'
}

这是最终版本。如果发生其中一个不良情况,这个会引发参数错误。显然,如果有意义的话,你可以捕获坏数据版本,只回显原始哈希值。

{
  'a4.b' => 'a4.b',
  'a4.b.c.d' => 'a4.b.c.d'
}

答案 4 :(得分:0)

本着模块化和可重用性的精神,我提出了另一种解决方案。在第一种方法中,我们可以编写一个反向散列构造函数:

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没有这样的方法,但毫无疑问,其他人需要它并编写some solutions。选择你最喜欢的实现,现在只需写下:

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]}

答案 5 :(得分:0)

这是@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