Search code examples
flutterdarthashmapdeep-copy

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


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


Solution

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