I have a list of objects and each object has a String property datetime and an int property steps. Some of the objects inside the list have the same value in datetime property. In this case, I want to keep in the list, the one object between all the duplicates, that has the greatest value of steps. For example:

[ Object(a: '1/1/2021', b: 100), Object(a: '1/2/2021', b: 110), Object(a: '1/3/2021', b: 115), Object(a: '1/3/2021', b: 140), Object(a: '1/4/2021', b: 100)]

Should be like:

[ Object(a: '1/1/2021', b: 100), Object(a: '1/2/2021', b: 110), Object(a: '1/3/2021', b: 140), Object(a: '1/4/2021', b: 100)]

Can you help me by suggesting an efficient way of solving this issue?


Solution 1: julemand101

Something like this?

List<Data> compressData(List<Data> list) {
  final map = <String, Data>{};

  for (final data in list) {
    map.update(data.a, (oldData) => oldData.b > data.b ? oldData : data,
        ifAbsent: () => data);
  }

  return map.values.toList();
}

void main() {
  final myList = [
    const Data('1/1/2021', 100),
    const Data('1/2/2021', 110),
    const Data('1/3/2021', 115),
    const Data('1/3/2021', 140),
    const Data('1/4/2021', 100),
  ];
  print(myList);
  // [Object(a: '1/1/2021', b: 100), Object(a: '1/2/2021', b: 110), Object(a: '1/3/2021', b: 115), Object(a: '1/3/2021', b: 140), Object(a: '1/4/2021', b: 100)]

  final myNewList = compressData(myList);
  print(myNewList);
  // [Object(a: '1/1/2021', b: 100), Object(a: '1/2/2021', b: 110), Object(a: '1/3/2021', b: 140), Object(a: '1/4/2021', b: 100)]
}

class Data {
  final String a;
  final int b;

  const Data(this.a, this.b);

  @override
  String toString() => 'Object(a: \'$a\', b: $b)';
}

An alternative solution is to make an generic way to do this operation on Iterable<T> like this:

extension CompressOnIterable<T> on Iterable<T> {
  List<T> compress<Key>(Key Function(T) getKey, T Function(T, T) compare) =>
      fold<Map<Key, T>>(
        {},
        (map, element) => map
          ..update(
            getKey(element),
            (previousElement) => compare(element, previousElement),
            ifAbsent: () => element,
          ),
      ).values.toList();
}

void main() {
  final myList = [
    const Data('1/1/2021', 100),
    const Data('1/2/2021', 110),
    const Data('1/3/2021', 115),
    const Data('1/3/2021', 140),
    const Data('1/4/2021', 100),
  ];

  print(myList);
  // [Object(a: '1/1/2021', b: 100), Object(a: '1/2/2021', b: 110), Object(a: '1/3/2021', b: 115), Object(a: '1/3/2021', b: 140), Object(a: '1/4/2021', b: 100)]

  final compressed = myList.compress(
    (data) => data.a,
    (data1, data2) => data1.b > data2.b ? data1 : data2,
  );

  print(compressed);
  // [Object(a: '1/1/2021', b: 100), Object(a: '1/2/2021', b: 110), Object(a: '1/3/2021', b: 140), Object(a: '1/4/2021', b: 100)]
}

class Data {
  final String a;
  final int b;

  const Data(this.a, this.b);

  @override
  String toString() => 'Object(a: \'$a\', b: $b)';
}

compress does here take two methods as argument:

  1. Is used to get the key we want each element to be unique against. In your case it is the datatime field.
  2. Is used to compare two objects which has the same key, to find out which one we want to keep. In your case we want to compare the b field.