SplayTreeMap is removing duplicate after ordering a Map in Flutter?

Viewed 20

I have the following Map:

Map<String, dynamic> data = {'two': {'date' : 2}, 'zero': {'date' : 1}, 'three': {'date' : 3}, 'one': {'date' : 2},};

I'd like to order it by date. I'm trying this code:

final sorted = SplayTreeMap<String,dynamic>.from(data, (a, b) => data[a]["date"].compareTo(data[b]["date"]));

with output:

{'two': {'date' : 2}, 'zero': {'date' : 1}, 'three': {'date' : 3}}

How could I avoid deletion of duplicate value ('date': 2 appeared two times but now in sorted appears only once)?

1 Answers

From the SplayTreeMap documentation:

Keys of the map are compared using the compare function passed in the constructor, both for ordering and for equality.

Your comparison function considers the entries 'two': {'date' : 2} and 'one': {'date' : 2} as equal, so SplayTreeMap assumes that they have equivalent keys and overwrites one with the other.

You could (sort of) fix this by changing your comparison function to have a strict ordering; that is, sort by the key if the 'dates are equal:

  final sorted = SplayTreeMap<String, dynamic>.from(data, (a, b) {
    var valueA = data[a]?['date'];
    var valueB = data[b]?['date'];
    if (valueA != null && valueB != null) {
      var result = valueA.compareTo(valueB);
      if (result != 0) {
        return result;
      }
    } 
    return a.compareTo(b);
  });

Note that the comparison function is used when doing lookups, so I had to add some extra null-checks to handle lookups of non-existent keys.

That said, I caution against the above code. A SplayTreeMap is meant to sort a Map by keys. However, you want your Map sorted by values. This is inherently problematic because the keys of a Map should be immutable (if one of them changed, lookups would be broken since the value would still be stored in the original location), but values of a Map can change. Making the order dependent on values also means that lookups by keys won't be efficient.

Instead, you'd probably be better off converting your Map to a List, sorting that, and creating a new Map:

  var sorted = Map.fromEntries(data.entries.toList()
    ..sort((a, b) => a.value['date'].compareTo(b.value['date'])));
Related