Ruby - 使用新值更新哈希值,但仅在与原始值不同时更新哈希值

时间:2015-09-06 00:02:40

标签: ruby hashmap

我正在尝试找到一种有效的方法来更新具有新值的哈希数组,但只更新与原始值不同的值。

修改添加了以下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"}
  # ]

1 个答案:

答案 0 :(得分: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|

maphashesh1)的第一个值传递到其块中,它变为块变量g的值:

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

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

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

并行分配消歧用于为三个块变量赋值:

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