Я пытаюсь суммировать значения из хэша и сталкиваюсь с неожиданным (для меня) поведением. Ниже приведен код, воспроизводящий поведение.

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

Я ожидал, что будет обновлена ​​только запись от 20 мая, а все остальные значения останутся равными нулю. Я не уверен, как переписать это, чтобы обойти такое поведение. Какие-либо предложения? Спасибо.

0
martinmsf 12 Июн 2018 в 02:08

1 ответ

Лучший ответ

Ваша проблема в основном в следующем.

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 } повлияет на все хэши); вместо этого нам потребуется глубокая копия.

4
Cary Swoveland 12 Июн 2018 в 17:07