可变性和总结Ruby哈希值

时间:2018-06-11 23:08:31

标签: ruby hash

我试图从哈希值中对值进行求和,并且我遇到了一些意外(对我而言)的行为。下面是重现行为的代码。

records = [
  { "id" => 5062311, "period" => "May 27, 2018", "items" => 2, "compliant_items" => 2 },
  { "id" => 5062311, "period" => "May 20, 2018", "items" => 3, "compliant_items" => 1 },
  { "id" => 5062311, "period" => "May 13, 2018", "items" => 7, "compliant_items" => 7 },
  { "id" => 5062311, "period" => "May 13, 2018", "items" => 8, "compliant_items" => 7 },
  { "id" => 5062311, "period" => "Jun 03, 2018", "items" => 6, "compliant_items" => 6 }
]

创建输出哈希

items  =  records.flat_map { |item| item["id"] }.uniq
weeks  =  records.flat_map { |item| item["period"] }.uniq
temp   =  items.each_with_object({}) { |item, hash| hash[item] = weeks.product(["total" => 0, "compliant" => 0]).to_h }

输出" temp"如下......

{
  5062311=>{
   "May 27, 2018"=>{"total"=>0, "compliant"=>0},
   "May 20, 2018"=>{"total"=>0, "compliant"=>0}, 
   "May 13, 2018"=>{"total"=>0, "compliant"=>0}, 
   "Jun 03, 2018"=>{"total"=>0, "compliant"=>0}
  }
}

现在,如果我尝试修改特定键的值,则每个其他键也会更新。例如,以下内容:

temp[5062311]["May 20, 2018"]["total"] += 5

...产量

{
  5062311=>{
   "May 27, 2018"=>{"total"=>5, "compliant"=>0},
   "May 20, 2018"=>{"total"=>5, "compliant"=>0}, 
   "May 13, 2018"=>{"total"=>5, "compliant"=>0}, 
   "Jun 03, 2018"=>{"total"=>5, "compliant"=>0}
  }
}

我希望只更新5月20日的记录,并且所有其他值保持为0.我不知道如何重写这个来解决这个问题。有什么建议?感谢。

1 个答案:

答案 0 :(得分:4)

您的问题基本上如下。

a = ["dog", "cat"]
b = [:d1, :d2] 
c = { :f=>1 }

h = a.each_with_object({}) { |pet, h| h[pet] = b.product([c]).to_h }
  #=> {"dog"=>{:d1=>{:f=>1}, :d2=>{:f=>1}},
  #    "cat"=>{:d1=>{:f=>1}, :d2=>{:f=>1}}

现在让我们在其中一个哈希:f

中更改{ :f=>1 }的值
h["cat"][:d2][:f] = 2

然后观察h的新值。

h #=> {"dog"=>{:d1=>{:f=>2}, :d2=>{:f=>2}},
  #    "cat"=>{:d1=>{:f=>2}, :d2=>{:f=>2}}}

您期望h等于:

#=> {"dog"=>{:d1=>{:f=>1}, :d2=>{:f=>1}},
#    "cat"=>{:d1=>{:f=>1}, :d2=>{:f=>2}}}

要了解我们获得此结果的原因,请将每个哈希值{ :f=>1 }替换为object_id

a.each { |aa| d.each { |bb| h[aa][bb] = h[aa][bb].object_id } }
h #=> {"dog"=>{:d1=>36327481, :d2=>36327481},
  #    "cat"=>{:d1=>36327481, :d2=>36327481}}

如你所见,这四个哈希是同一个对象。因此,应该期望如果对象被更改,则更改将出现在哈希出现的任何位置。

以下是解决问题的一种方法。

h = a.each_with_object({}) { |pet, h| h[pet] = b.map { |d| [d, c.dup] }.to_h }
  #=> {"dog"=>{:d1=>{:f=>1}, :d2=>{:f=>1}},
  #    "cat"=>{:d1=>{:f=>1}, :d2=>{:f=>1}}}

h["cat"][:d2][:f] = 2
  #=> 2
h #=> {"dog"=>{:d1=>{:f=>1}, :d2=>{:f=>1}},
  #    "cat"=>{:d1=>{:f=>1}, :d2=>{:f=>2}}}

警告:如果{ :f=>1 }包含嵌套元素,例如{ :f=>{ :g=>1 } },我们就不能简单地复制它(因为更改为{ :g=>1 }会影响所有哈希值);相反,我们需要一个deep copy

相关问题