Изменчивость и суммирование рубиновых хеш-значений - PullRequest
0 голосов
/ 12 июня 2018

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

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. IЯ не уверен, как переписать это, чтобы обойти это поведение.Какие-либо предложения?Спасибо.

1 Ответ

0 голосов
/ 12 июня 2018

Ваша проблема в основном заключается в следующем.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...