2010-12-06 105 views

回答

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

如果你想掌握红宝石,我要说的集中在红宝石的对象模型。 “红宝石之道”的书很好而且彻底。恐怕较短的书籍不会给你对Ruby的深刻见解。就代码而言,这基本上是迭代通过散列中的每个键 - 值对,将键以2(以点(。)为键)分开,并将分离的字符串分别构建为键和子键。其实很简单,如果你知道[Ruby Hash] [http://ruby-doc.org/core/classes/Hash.html] – Chirantan 2010-12-06 10:07:43

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

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

+0

谢谢!我会尝试并理解你的答案:) – Zabba 2010-12-06 11:40:50

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

试图理解,几乎融化了我的大脑:)虽然非常好...(我尝试在实际变量重命名之前做到这一点:P) – Ernest 2010-12-06 12:47:35

+0

@ c64ification:我希望变量重命名更清楚:-)我重新使用“钥匙”,并为“价值”添加了一个奇怪的短名称,我认为这样做更好。如果您不熟悉Enumerable#inject(函数式编程的旧“foldl”),它可能看起来难以理解,但本质上它非常简单。 – tokland 2010-12-06 13:06:55

1

做一些测试后,我发现,如果你曾经有一个更深层次的架构,你最终运行到与这些算法的问题,因为分裂的关键只在密钥中占一个点('。')。如果你有更多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' 
} 

这是最终版本。如果发生了其中一种不良情况,则这将引发参数错误。显然你可以捕捉到不好的数据版本,只需回显原始哈希值就行了。

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 
4
# {"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 
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