- Home /
Nested Dictionary Change Value Problem
Hi Unity Community, thank you for looking at my post. I will get str8 to the point. I have having an issue with changing, the values in a nested Dictionary, There are no errors and most of it works as expected but it does not respect a particular key and changes the value for all entries.
I have set up a test that replicates my problem, if you run this class and then hit "A" you will see a debug output of the dictionary as I set it up (all as expected). Then you hit "B" and it will change the values of the dictionary in a very specific way. BUT ONLY for a specific day!, then you hit "C" and what you will find is that it changed the value for every day, ignoring the key for the day.
Here is the class. Sorry to include your name @aldonaletto but this seams right up your ally. Any idea?
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TestScript : MonoBehaviour
{
//The Master Dictionary to hold <location < day index < hour index < price>>>>
Dictionary <string,Dictionary<int,List<int>>> priceByHour;
//The defualt price, value to change.
private int defaultPrice = 3;
void Start ()
{
// Initilazing master Dictionary
priceByHour = new Dictionary<string, Dictionary<int, List<int>>> ();
// The locations
List<string> locations = new List<string> {"NY"};//, "LA", "Chenarous", "Azaroth", "York", "Lancaster"};
foreach (string loc in locations) {
//Create Hours List with the prices
List<int> hours = new List<int> ();
for (int i = 0; i < 24; i ++) {
hours.Add (defaultPrice);
}
// Create Days list with the hours and prices
Dictionary<int,List<int>> days = new Dictionary<int, List<int>> ();
for (int i = 0; i < 7; i ++) {
days.Add (i, hours);
}
// add it all to the master dictionary.
priceByHour.Add (loc, days);
}
}
// To Test and review;
void Update(){
//Print out the content of the master dictionary
if (Input.GetKeyDown (KeyCode.A)) {
PrintOutMasterDictionary();
}
// change the values
if (Input.GetKeyDown (KeyCode.B)) {
ChangeValuesOfDayTwo();
}
// print againg to compare result
if (Input.GetKey (KeyCode.C)) {
PrintOutMasterDictionary();
}
// Notice that the dayToChange was set to index 4 but it changed the value of every over after the 6th hour to the new price!
}
// Print out the result to compare after the change.
void PrintOutMasterDictionary ()
{
foreach (KeyValuePair<string,Dictionary<int,List<int>>> masterEntry in priceByHour) {
Debug.Log ("Location = " + masterEntry.Key);
foreach (KeyValuePair<int,List<int>> day in masterEntry.Value) {
Debug.Log ("Day = " + day.Key.ToString ());
for (int i = 0; i < day.Value.Count; i ++) {
Debug.Log ("Hour = " + i.ToString () + " has a value of " + day.Value [i].ToString ());
}
}
}
}
void ChangeValuesOfDayTwo(){
// set change perameters
int dayToChange = 4;
int newPrice = 5;
string locationToChange = "NY";
int hourToChangeBehind = 6;
//Iterate through all possible hours
for (int i = 0; i < 24; i++) {
// if the hour is greater than hour to change, change the value
if(i > hourToChangeBehind){
priceByHour[locationToChange][dayToChange][i] = newPrice;
}
}
}
}
Thank you.
Answer by steakpinball · Feb 23, 2015 at 08:51 PM
This behavior occurs because List
is a reference type. Reference types are simply pointers to memory. When you pass them to a method only the reference is copied. It still points to the same object. This means the loop on line 31 is making a dictionary where all values are the exact same list. Not copies. You need to either copy the list or make a new one for each day.
Value Types and Reference Types
https://msdn.microsoft.com/en-us/library/t63sy5hs.aspx
Value vs. Reference Types in C#
wow, this is getting very confusing. According to these articles, all Arrays are reference types? So I could not solve this issue by simple avoiding the LIst and replacing with with another Dictionary? like so?
// Before
//The $$anonymous$$aster Dictionary to hold <location < day index < hour index < price>>>>
Dictionary <string,Dictionary<int,List<int>>> priceByHour;
// After
Dictionary <string,Dictionary<int,Dictionary<int,int>>> priceByHour;
I assume its not going to be that easy?
Its wired that there would be a problem around line 31 as when I run the test and hit A the first time, the master dictionary populated properly, but you are saying its not actually storing it but merely holding it in memory and to solve it I would have to copy the List out before I apply it like so?
// Before...
// Create Days list with the hours and prices
Dictionary<int,List<int>> days = new Dictionary<int, List<int>> ();
for (int i = 0; i < 7; i ++) {
days.Add (i, hours);
}
// After
// Create Days list with the hours and prices
Dictionary<int,List<int>> days = new Dictionary<int, List<int>> ();
for (int i = 0; i < 7; i ++) {
days.Add (new $$anonymous$$eyValuePair<int,List<int>>(i, hours));//<-- or did you mean I need to move it out of that for loop because it always considers the i as the same int?
}
When passing the list to a method, the reference is sent. The only time it copies is if the implementation of the method copies it. The List constructor copies a list sent to it.
Line 32 should be
days.Add(i, new List<int>(hours));
This copies the hours list to a new list and sends the new reference to the method.
O$$anonymous$$G YES! You did it! Thank you for reading this really long question. This was driving my crazy b/c could not find the flaw.
Awesome $$anonymous$$, Thank you for your help.
Answer by Radetic · Feb 24, 2015 at 03:45 AM
Isn't List a reference type? Therefore isn't the line
days.Add (i, hours);
relating the same list to every day instead of giving each day it's own copy of the list?
Can't try right now in this computer, but my first test from your script would be changing that line to something like
days.Add (i, new List<int>(hours));
just to be sure each day gets a separated copy
Answer by Alchemy_Fire · Feb 24, 2015 at 03:44 AM
C# object types are reference types. This means that you need to 'new up' an hours list each time you add it to a days dictionary - otherwise a reference will be shared between each element of the days dictionary and any change to one element will apply to all elements.
Something similar to the following is probably what you are after.
foreach (string loc in locations)
{
var days = new Dictionary<int, List<int>>();
for (int i = 0; i < 7; i++)
{
var hours = new List<int>();
for (int j = 0; j < 24; j++)
hours.Add(defaultPrice);
days.Add(i, hours);
}
}