Adding and removing values in a list inside a nested Map affects all nested Maps

995

You actually did not specify the important part in your question, but by understanding how Maps and Lists work, I can tell what the problem is: you are initializing them incorrectly.

The thing is, Map and List point to places in memory and when you do something like the following:

final map1 = {};
final map2 = map1;

You will now have two maps that point to the same places in memory, so editing map1 will also affect map2.

Having said that, there a few important tips I can give you:

  • When constructing your (probably not ideal coupon horse race data structure), make sure that you assign every entry to a map with an actually different value, i.e. do not do create your 6 races once and then "copy" them to your other coupon, but create it for every coupon.

  • You can use List.of and Map.of to copy maps or lists, but be aware that this is not a deep copy, which means that copying a Map<int, List<int>> will not copy the lists for you, which means that they are still the same.

  • Include everything you do that is relevant to your problem in your question - here you did not include the only relevant part: the creation of your Map<int, Map<int, List<int>>.

Read this Wikipedia article to learn more about copying.


Problem with a shallow copy

I just want to quickly illustrate this to be sure that you get this:

final map1 = {
  1: [1, 2], 
  2: [3, 4],
};

final map2 = Map.of(map1);

You might think: "Oh, I followed the advice to copy my data using Map.of. Now I should be fine." However, this is a fallacy because you still have your lists in the map, which are not copied, so the following will occur:

map1[1].remove(2);

print(map1[1]); // [1]
print(map2[1]); // [1]

As you can see, the lists in both maps have been altered. If you want to also copy only the contents of the list, you need to do it like this:

final map2 = {};

for (final entry in map1.entries) {
  map2[entry.key] = List.of(entry.value);
}

This is a deep copy and the full code would look like this:

void main() {
  final map1 = {
    1: [1, 2],
    2: [3, 4],
  };

  final map2 = {};

  for (final entry in map1.entries) {
    map2[entry.key] = List.of(entry.value);
  }

  map1[1].remove(2);

  print(map1[1]); // [1]
  print(map2[1]); // [1, 2]
}
Share:
995
Admin
Author by

Admin

Updated on December 16, 2022

Comments

  • Admin
    Admin over 1 year

    I've been struggling with this issues for days now and im usually not the one to ask questions here, but this time im really curious to how i would solve this issue..

    The thing is, i have a Map that is a "unique identifier" for a hypothetical horse racing coupon (<int,) and a Map which represents (Key: for columns) and (value: for rows) for hypothetical races and horses in each race, So together this variable looks like this: (Map<int, Map<int, List<int>>> couponData = new Map();).

    Printing something like this results in for example:

    {1: {0: [1,6], 1: [], 2: [2,6], 3: [], 4: [3,6,7,8], 5: [], 6: []}}
    

    Or in a more literal sense:

    {Coupon1: {Race1: [Horse1,Horse6], Race2: [], Race3: [Horse2,Horse6], Race4: [], Race5: [Horse3,Horse6,Horse7,Horse8], Race6: [], Race7: []}}
    

    Now everything works so far. My issue is however, in when i add new horses into each race (or list) or when i remove them.

    If i have multiple coupons and just want to change a horse in my other coupon it affects all of them and not just the one i currently have selected. So the code looks like this:

    int activeCoupon = 1;
    
    if (!couponData[activeCoupon][raceNumber].contains(horseNumber + 1)) {
      couponData[activeCoupon][raceNumber].add(horseNumber + 1);
    } else {
      couponData[activeCoupon][raceNumber].remove(horseNumber + 1);
    }
    

    Selecting Horse 1 and Horse 2 in the first race on my first coupon (out of 2) and printing couponData in the console now looks like this:

    {1: {0: [1, 2], 1: [], 2: [], 3: [], 4: [], 5: [], 6: []}, 2: {0: [1, 2], 1: [], 2: [], 3: [], 4: [], 5: [], 6: []}}
    

    Which means that it has added Horse 1 and Horse 2 into both coupons even if i specified that i wanted to add or remove inside the first one hence the (activeCoupon = 1). Is there something wrong im doing with adding and removing lists inside a nested map?

    I've tried alot of different methods such as going into a .forEach and manually adding and removing values associated with respective races and horses but with same results..

    Any clues to how i would do this differently? Cheers

  • Admin
    Admin over 4 years
    Had a read of you link, very informative. Thanks for the answer, im still learning so its a bit hard to formulate a piece of code out of context, or at least trying to give some context. My explanation and structure of my coupon may not be ideal, what would you suggest instead now that you've seen at least what results i want? :- ) I solved it after i read you answer about copying maps and lists. I'll post an answer in my thread! Thanks, really appriciated! I'll have a read also on your more in-depth explanation now that i see that you edited. Really helpful that you explain this :)
  • creativecreatorormaybenot
    creativecreatorormaybenot over 4 years
    @spacecoder I am glad that I was able to help you :) Looking back at my answer, it seems that I was a bit harsh, but this is mostly reflecting my own struggles I had with copying back when I got into programming. Seeing indexed integers (I mean incrementing from 0) as Map keys is a bad sign for me. I think you should store your data in nested lists instead. It will be a bit more cumbersome to construct because you will have to use List.add. If there is a possibility that you have gaps in your indices, e.g. only coupon 50 and 90, you could create classes to hold the data with indexes.
  • Admin
    Admin over 4 years
    Nothing to worry about, i'm glad that i can learn what others have struggled with as i'm just learning by myself, it can be hard sometimes to grasp how things should work. Getting such a quick answer is really worth gold!