Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 stream grouping a List<Map<>> by the same <Key, Value> to a new List<Map<>>

I have a List<Map<String,String>> such as:

Map<String, String> m1 = new HashMap<>();
m1.put("date", "2020.1.5");
m1.put("B", "10");

Map<String, String> m2 = new HashMap<>();
m2.put("date", "2020.1.5");
m2.put("A", "20");

Map<String, String> m3 = new HashMap<>();
m3.put("date", "2020.1.6");
m3.put("A", "30");

Map<String, String> m4 = new HashMap<>();
m4.put("date", "2020.1.7");
m4.put("C", "30");

List<Map<String, String>> before = new ArrayList<>();
before.add(m1);
before.add(m2);
before.add(m3);
before.add(m4);

My expect result is to generate a new List map, which is grouped by date , and all the entry set in the same date would be put together, like:

[{"A":"20","B":"10","date":"2020.1.5"},{"A":"30","date":"2020.1.6"},{"C":"30","date":"2020.1.7"}]

I tried with the following method, but always not my expect result.

stream().flatmap().collect(Collectors.groupingBy())

Some Additional Comments for this problem:

I worked this out with for LOOP, but the application hangs when the list size is about 50000, so I seek a better performant way to do this. Java 8 stream flat map is a perhaps way as far as I know. So the key point is not only to remap this but also with the most performant way to do this.

like image 676
Better Man Avatar asked Jun 15 '20 14:06

Better Man


People also ask

How to use Java 8 stream collectors to group by list?

In this article, we will show you how to use Java 8 Stream Collectors to group by, count, sum and sort a List. 1. Group By, Count and Sort 1.1 Group by a List and display the total count of it. 1.2 Add sorting. 2. List Objects Examples to ‘group by’ a list of user defined Objects.

What is groupingby in Java 8 stream?

GroupingBy Collectors. The Java 8 Stream API lets us process collections of data in a declarative way. The static factory methods Collectors.groupingBy() and Collectors.groupingByConcurrent() provide us with functionality similar to the ‘GROUP BY' clause in the SQL language.

How to create a map from a list in Java?

A Map is created, when you collect a stream of elements using either Collectors.toMap () or Collectors.groupingBy (). Let’s stream the List and collect it to a Map using Collectors.toMap (keyMapper, valueMapper) where key is unique id of user and value is name of the user which may duplicate:-

How to perform group by operation in Java 8?

In the previous article, we have seen how to perform Group by operation in java 8 with count, sum actions. After calling the Collectors.groupingby () method it returns the Map<K, V> key and value . In some of the cases you may need to return the key type as List or Set. 2. Modify Map Value Type to List For GroupingBy Result


3 Answers

before
  .stream()
  .collect(Collectors.toMap((m) -> m.get("date"), m -> m, (a,b) -> {
      Map<String, String> res = new HashMap<>();
      res.putAll(a);
      res.putAll(b);
      return res;
  }))
  .values();

This is the solution you're looking for.

The toMap function receives 3 parameters:

  • the key mapper, which in your case is the date
  • the value mapper, which is the map itself that's being processed
  • the merge function, which takes 2 maps with the same date and puts all the keys together

Output:

[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]
like image 68
Silviu Burcea Avatar answered Oct 19 '22 09:10

Silviu Burcea


You can do this way using groupingBy and Collector.of

List<Map<String, String>> list = new ArrayList<>(before.stream()
        .collect(Collectors.groupingBy(
                k -> k.get("date"),
                Collector.of( HashMap<String,String>::new,
                        (m,e)-> m.putAll(e),
                        (map1,map2)->{ map1.putAll(map2); return map1;}
                ))).values());

Here, first use Collectors.groupingBy to group by date. Then define custom collector using Collector.of to collect List<Map<String, String>> into Map<String, String>. After create list using map values.

And using Collectors.flatMapping from Java 9

List<Map<String, String>> list = new ArrayList<>(before.stream()
        .collect(Collectors.groupingBy(
                k -> k.get("date"),
                Collectors.flatMapping(m -> m.entrySet().stream(), 
                    Collectors.toMap(k -> k.getKey(), v -> v.getValue(), (a,b) -> a))))
               .values());
like image 4
Eklavya Avatar answered Oct 19 '22 08:10

Eklavya


You can achieve the very same result using a certain number of Collectors, orderly:

  • Collectors.groupingBy to group by the date
  • Collectors.reducing to merge the Map<String, String> items
  • Collectors.collectingAndThen to transform the values from Map<String, Optional<Map<String, String>>>, as a result of the previous reducing to the final output List<Map<String, String>>.
List<Map<String, String>> list = before.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.groupingBy(
            m -> m.get("date"),
            Collectors.reducing((l, r) -> {
                l.putAll(r);
                return l; })
        ),
        o -> o.values().stream()
                       .flatMap(Optional::stream)
                       .collect(Collectors.toList())));

The list contains what are you looking for:

[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]

Important: This solution has two he disadvantages:

  • It looks clumsy and might not be clear for an independent viewer
  • It mutates (modifies) the original maps included in the List<Map<String, String>> before.
like image 1
Nikolas Charalambidis Avatar answered Oct 19 '22 07:10

Nikolas Charalambidis