2015-09-06 29 views
1

我试图找到一种有效方法来使用新值更新哈希数组,但只更新与原始值不同的值。Ruby - 使用新值更新哈希值,但仅在与原始值不同时更新

编辑 - 添加的下方CodeBunk:http://codebunk.com/b/83947000/

例如:

h1  = { foo: "a", bar: "b" } 
h2  = { foo: "a", bar: "c" } 
h3  = { foo: "h", bar: "e" } 
h4  = { foo: "s", bar: "b" } 
h5  = { foo: "a"} 
h6  = { foo: "y"} 
h7  = { bar: "b"} 
h8  = { bar: "z"} 

hashes = [h1, h2, h3, h4, h5, h6, h7, h8] 

original = { foo: "a", bar: "b" } 
updated = { foo: "x", bar: "f" } 

hashes.each do |h| 
    # magic? 
end 

# result: 
    # h1.insepct #=> { foo: "x", bar: "f" } 
    # Both "foo" and "bar" change because they both matched their original values; 

    # h2.insepct #=> { foo: "x", bar: "c" } 
    # Only "foo" changes; "bar" does NOT change because it has a value differnt than the original 

    # h3.insepct #=> { foo: "h", bar: "e" } 
    # Neither "foo" nor "bar" change because neither value matched the original 

    # h4.insepct #=> { foo: "s", bar: "f" } 
    # Only "bar" changes; "foo" does not change because it has a value different than the original 

    # h5.inspect #=> { foo: "x" } 
    # "foo" changes as it matched the original value; hash does not include "bar" 

    # h6.inspect #=> { foo: "y" } 
    # "foo" change; hash does not include "bar" 

    # h7.inspect #=> { bar: "f" } 
    # "bar" changes as it matched the original value; hash does not include "foo" 

    # h8.inspect #=> { bar: "z" } 
    # "bar" change; hash does not include "foo" 

到目前为止,我只能够使用多个嵌套的循环来实现这一目标。我真的希望有一个更好的办法...

编辑2 - 这是我目前的解决方案,我想加以改进:

h1  = { foo: "a", bar: "b" } 
    h2  = { foo: "a", bar: "c" } 
    h3  = { foo: "h", bar: "e" } 
    h4  = { foo: "s", bar: "b" } 
    h5  = { foo: "a"} 
    h6  = { foo: "y"} 
    h7  = { bar: "b"} 
    h8  = { bar: "z"} 

    hashes = [h1, h2, h3, h4, h5, h6, h7, h8] 

    legend = {} 

    original_attrs = { foo: "a", bar: "b" } 
    updated_attrs = { foo: "x", bar: "f" } 

    updated_attrs.each do |k, v| 
    legend[k] = { original_value: original_attrs[k], new_value: v } 
    end 

    hashes.each do |hsh| 
    legend.each do |legend_key, legend_value| 
     if hsh.has_key?(legend_key) && hsh[legend_key] == legend_value[:original_value] 
     hsh[legend_key] = legend_value[:new_value] 
     end 
    end 
    end 

    logger.debug "hashes: #{hashes}" 

    # hashes: [ 
    # {:foo=>"x", :bar=>"f"}, 
    # {:foo=>"x", :bar=>"c"}, 
    # {:foo=>"h", :bar=>"e"}, 
    # {:foo=>"s", :bar=>"f"}, 
    # {:foo=>"x"}, 
    # {:foo=>"y"}, 
    # {:bar=>"f"}, 
    # {:bar=>"z"} 
    # ] 
+0

如果你不介意的突变哈希,你的代码很好,因为它很经济易读。一个小点:有些人喜欢写'如果'而不是'如果; ; end'。 –

回答

0

为了说明起见,我用你的例如具有下列的变化:

h8    = { bar: "z", buz: "a"} 
original_attrs = { foo: "a", bar: "b", booz: "pony" } 
updated_attrs = { foo: "x", bar: "f", booz: "pig" } 

的第一步是建立一个从映射到original_attrsupdated_attrs。在这样做时,我假设两个哈希共享相同的密钥。我们获得:

attr_map = original_attrs.merge(updated_attrs) { |_,o,n| { o=>n } } 
    #=> {:foo=>{"a"=>"x"}, :bar=>{"b"=>"f"}} 

事实上,我认为这将是比存储该信息的并行数组更好的数据结构。该计算使用方法Hash#merge的形式,该方法采用一个块(此处为{ |_,o,n| { o=>n } })来计算两个哈希合并中存在的键的值。请注意,这不会改变original_attrsupdated_attrs

如果hashes(散列)的元件具有键:foo与值"a",我们希望该值更改为attr_map[:foo]["a"] #=> "x"在所述阵列中的相应的散列,这是我们正在映射hashes。如果价值是别的,比如说"r",我们想让它保持不变。这样做的一个方法是使用方法Hash#default_proc=为的attr_map每个值(散)指定一个默认值:

attr_map.each { |_,h| h.default_proc = ->(_,v) { v } } 

的结果是,如果attr_map[:foo]没有钥匙kattr_map[:foo][k]将返回值的PROC:

attr_map[:foo]['cat'] 
    #=> "cat" 

这是原始值,这意味着:foo=>'cat'将保持不变。 attr_map的其他键当然也是如此。我们现在可以执行映射如下:

hashes.map { |g| g.each_with_object({}) { |(k,v),h| 
    h[k] = (attr_map.key?(k) ? attr_map[k][v] : v) } } 
    #=> [{:foo=>"x", :bar=>"f"}, 
    # {:foo=>"x", :bar=>"c"}, 
    # {:foo=>"h", :bar=>"e"}, 
    # {:foo=>"s", :bar=>"f"}, 
    # {:foo=>"x"}, 
    # {:foo=>"y"}, 
    # {:bar=>"f"}, 
    # {:bar=>"z", :buz=>"a"}] 

这不会变异hashes

综上所述,代码:

attr_map = original_attrs.merge(updated_attrs) { |_,o,n| { o=>n } } 
attr_map.each { |_,h| h.default_proc = ->(_,v) { v } } 
hashes.map { |g| g.each_with_object({}) { |(k,v),h| 
    h[k] = (attr_map.key?(k) ? attr_map[k][v] : v) } } 

最后,我只想说一下each_with_object的块变量的几句话,这似乎为

|(k,v),h| 

map通过的第一个值hashesh1),并将其变成块变量的值g

g #=> { foo: "a", bar: "b" } 

each_with_object然后将对象初始化为一个空散列,然后将g的每个元素与(更新的)散列对象一起传递到其块。它传递到块的第一个值是:

[[:foo, "a"], {}] 

平行分配消歧采用赋值给三个块变量:

(k,v),h = [[:foo, "a"], {}] 
    #=> [[:foo, "a"], {}] 
k #=> :foo 
v #=> "a" 
h #=> {}