59

I have this list of maps.

[
    {title: 'Avengers', release_date: '10/01/2019'},
    {title: 'Creed', release_date: '10/01/2019'}
    {title: 'Jumanji', release_date: '30/10/2019'},
]

I would like to write a code that would group the list of movies by release_date like that.

[
   {
      "10/01/2019": [
         {
            "title": "Avengers"
         },
         {
            "title": "Creed"
         }
      ]
   },
   {
      "30/10/2019": [
         {
            "title": "Jumanji"
         }
      ]
   }
]
Boken
  • 4,825
  • 10
  • 32
  • 42
Guillaume
  • 1,437
  • 2
  • 15
  • 17
  • Can you please let me know how did you get data like 'var data' as mentioned above, when fetching data from API, because when I tried to get data from API it gives me data in (key,value) pair. – Darshana Nov 25 '19 at 04:32

8 Answers8

119

The package collection implements the groupBy function.

For grouping by date:

import "package:collection/collection.dart";

main(List<String> args) {
  var data = [
    {"title": 'Avengers', "release_date": '10/01/2019'},
    {"title": 'Creed', "release_date": '10/01/2019'},
    {"title": 'Jumanji', "release_date": '30/10/2019'},
  ];


  var newMap = groupBy(data, (Map obj) => obj['release_date']);

  print(newMap);
}

For removing the release_date key from each map entry:

var newMap = groupBy(data, (Map obj) => obj['release_date']).map(
    (k, v) => MapEntry(k, v.map((item) { item.remove('release_date'); return item;}).toList()));

For changing a key:

var newMap = groupBy(data, (Map obj) => obj['release_date']).map(
    (k, v) => MapEntry(k, v.map((item) => {'name': item['title']}).toList()));
attdona
  • 17,196
  • 7
  • 49
  • 60
  • 18
    Note that this `groupBy` is not part of the [`dart:collection`](https://api.dart.dev/stable/2.7.1/dart-collection/dart-collection-library.html) -- this references a [third-party collection](https://pub.dartlang.org/packages/collection) package. – AWhitford Mar 17 '20 at 07:00
  • How do I groupby on year in the same above example? release_date is still in Date format – Sampat Apr 13 '20 at 07:55
  • 7
    Since `collection 1.15.0-nullsafety.4`, you can replace it with `data.groupListsBy((obj) => ...)`. https://github.com/dart-lang/collection/blob/1.15.0/lib/src/iterable_extensions.dart#L389 – Manabu Nakazawa Jun 07 '21 at 10:23
  • 1
    The operator '[]' isn't defined for the type 'Object'. Try defining the operator '[]'. This is error I am getting on var newMap = groupBy(data, (obj) => obj['release_date']); – HIMANSHU MISHRA Jun 17 '21 at 06:06
  • 1
    Add Map to (obj) -> (Map obj) to solve this – Kisakyamukama Oct 22 '21 at 08:43
  • info: Only use double quotes for strings containing single quotes. – Anton Duzenko Dec 28 '21 at 08:37
67

If you have Dart 2.7, you can extend Iterable to add a useful groupBy method:

extension Iterables<E> on Iterable<E> {
  Map<K, List<E>> groupBy<K>(K Function(E) keyFunction) => fold(
      <K, List<E>>{},
      (Map<K, List<E>> map, E element) =>
          map..putIfAbsent(keyFunction(element), () => <E>[]).add(element));
}

Now, you're List of Maps, could be grouped using something like:

final releaseDateMap = listOfMaps.groupBy((m) => m['release_date'])

Data like this:

[
    {title: 'Avengers', release_date: '10/01/2019'},
    {title: 'Creed', release_date: '10/01/2019'}
    {title: 'Jumanji', release_date: '30/10/2019'},
]

would turn into:

{
  '10/01/2019': [
    {title: 'Avengers', release_date: '10/01/2019'},
    {title: 'Creed', release_date: '10/01/2019'}
  ],
  '30/10/2019': [
    {title: 'Jumanji', release_date: '30/10/2019'},
  ]
}
AWhitford
  • 3,708
  • 3
  • 28
  • 38
6

I don't know why no one has mentioned that with how basic built-in functions and methods you can achieve it, like:

main() {
  List<Map> data = [
    {'title': 'Avengers', 'release_date': '10/01/2019'},
    {'title': 'Creed', 'release_date': '10/01/2019'},
    {'title': 'Jumanji', 'release_date': '30/10/2019'},
  ];

  // Loop through empty {} map and check if the release date exists, if not
  // add as the key and empty list as the value, then fill the list with element
  // itself, removing 'release_date' before adding. Then you can map the map
  // to a list of maps.
  List result = data
      .fold({}, (previousValue, element) {
        Map val = previousValue as Map;
        String date = element['release_date'];
        if (!val.containsKey(date)) {
          val[date] = [];
        }
        element.remove('release_date');
        val[date]?.add(element);
        return val;
      })
      .entries
      .map((e) => {e.key: e.value})
      .toList();

  print(result);
}

Output:

C:\Users\duoqu\Desktop>dart run test.dart
[{10/01/2019: [{title: Avengers}, {title: Creed}]}, {30/10/2019: [{title: Jumanji}]}]
Guven Degirmenci
  • 684
  • 7
  • 16
4
extension UtilListExtension on List{
  groupBy(String key) {
    try {
      List<Map<String, dynamic>> result = [];
      List<String> keys = [];

      this.forEach((f) => keys.add(f[key]));

      [...keys.toSet()].forEach((k) {
        List data = [...this.where((e) => e[key] == k)];
        result.add({k: data});
      });

      return result;
    } catch (e, s) {
      printCatchNReport(e, s);
      return this;
    }
  }
}

then use it like this

var data = [
    {"title": 'Avengers', "release_date": '10/01/2019'},
    {"title": 'Creed', "release_date": '10/01/2019'},
    {"title": 'Jumanji', "release_date": '30/10/2019'},
];

var result = data.groupBy('title');
print(result);

then the result is

[{10/01/2019: [{title: Avengers, release_date: 10/01/2019}, {title: Creed, release_date: 10/01/2019}]}, {30/10/2019: [{title: Jumanji, release_date: 30/10/2019}]}]
Ashtav
  • 2,586
  • 7
  • 28
  • 44
3

This is a method naively implemented (in case you don't want to use the groupBy function from the collections package):

List<Map<String, List<Map<String, String>>>> MapByKey(String keyName, String newKeyName, String keyForNewName, List<Map<String,String>> input) {
  Map<String, Map<String, List<Map<String, String>>>> returnValue = Map<String, Map<String, List<Map<String, String>>>>();
  for (var currMap in input) {
    if (currMap.containsKey(keyName)) {
      var currKeyValue = currMap[keyName];
      var currKeyValueForNewName = currMap[keyForNewName];
      if (!returnValue.containsKey(currKeyValue)){
        returnValue[currKeyValue] = {currKeyValue : List<Map<String, String>>()};  
      }
      returnValue[currKeyValue][currKeyValue].add({newKeyName : currKeyValueForNewName});
    }
  }
  return returnValue.values.toList();
}

void main() {
    var test = [
    {"title": 'Avengers', "release_date": '10/01/2019'},
    {"title": 'Creed', "release_date": '10/01/2019'},
    {"title": 'Jumanji', "release_date": '30/10/2019'},
  ];

  var testMapped = MapByKey("release_date", "name", "title", test);

  print("$testMapped");
}

The output is:

[
    {
        10/01/2019: [
            {name: Avengers
            },
            {name: Creed
            }
        ]
    },
    {
        30/10/2019: [
            {name: Jumanji
            }
        ]
    }
]
Oswin Noetzelmann
  • 9,166
  • 1
  • 33
  • 46
  • I don't know why I wrote `name` instead of `title` in the result but you solved my problem thanks! – Guillaume Jan 04 '19 at 08:45
  • Group is a subset of Fold. I think that an implementation based on Fold would perform better and be more readable. – Fredrik Andersson Aug 10 '19 at 23:33
  • @Oswin Noetzelmann Can you please let me know how did you get data like 'var test' as mentioned above, when fetching data from API, because when I tried to get data from API it gives me data in (key,value) pair. – Darshana Nov 24 '19 at 15:26
  • @Darshana the var test example is the exact structure that the OP had asked for in his question above. – Oswin Noetzelmann Nov 24 '19 at 15:56
3

To add to the accepted answer if you come arcross this, In flutter 2, you will Get an error, as i got.

The operator '[]' isn't defined for the type 'dynamic Function(dynamic)'

Use

var data = [
     {"title": 'Avengers', "release_date": '10/01/2019'},
     {"title": 'Creed', "release_date": '10/01/2019'},
     {"title": 'Jumanji', "release_date": '30/10/2019'},
   ];


   var newMap = groupBy(data, (Map oj) => oj['release_date']);

   print(newMap);

This might be of help to someone.

lawrence Da
  • 101
  • 2
  • 3
2

Using the supercharged package, you'd write it like this:

List list = [
  { title: 'Avengers', release_date: '10/01/2019' },
  { title: 'Creed', release_date: '10/01/2019' }
  { title: 'Jumanji', release_date: '30/10/2019' },
];

final map = list.groupBy<String, Map>((item) => 
  item['release_date'],
  valueTransform: (item) => item..remove('release_date'),
);
vovahost
  • 34,185
  • 17
  • 113
  • 116
-1

It may not be the best solution. But it can give you an idea

List arrayData = [
  {"name": 'John', "gender": 'male'},
  {"name": 'James', "gender": 'male'},
  {"name": 'Mary', "gender": 'female'}
];

Retrieve list ​​by gender:

List males = arrayData.where((o) => o['gender'] == "male").toList();
List females = arrayData.where((o) => o['gender'] == "female").toList();

Make new map with desired format:

List result = [
  {
    "male": males.map((f) => {"name": f['name']}).toList()
  },
  {
    "female": females.map((f) => {"name": f['name']}).toList()
  }
];

print:

debugPrint('${result}');

result:

[{male: [{name: John}, {name: James}]}, {female: [{name: Mary}]}]
EdHuamani
  • 1,857
  • 13
  • 17
  • Scan the list extracting the group-by value into a `HashSet` then, for each member of that set, use @EdHuamani's `where` syntax. – Richard Heap Jan 03 '19 at 21:36
  • I just edited my problem. As you see, the key I need to `groupBy` is dynamic so I can't use this method. – Guillaume Jan 03 '19 at 21:41