2010-12-06 132 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