Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flatten a Map<Integer, List<String>> to Map<String, Integer> with stream and lambda

Tags:

I would like to flatten a Map which associates an Integer key to a list of String, without losing the key mapping. I am curious as though it is possible and useful to do so with stream and lambda.

We start with something like this:

Map<Integer, List<String>> mapFrom = new HashMap<>(); 

Let's assume that mapFrom is populated somewhere, and looks like:

1: a,b,c 2: d,e,f etc. 

Let's also assume that the values in the lists are unique.

Now, I want to "unfold" it to get a second map like:

a: 1 b: 1 c: 1 d: 2 e: 2 f: 2 etc. 

I could do it like this (or very similarly, using foreach):

Map<String, Integer> mapTo = new HashMap<>(); for (Map.Entry<Integer, List<String>> entry: mapFrom.entrySet()) {     for (String s: entry.getValue()) {         mapTo.put(s, entry.getKey());     } } 

Now let's assume that I want to use lambda instead of nested for loops. I would probably do something like this:

Map<String, Integer> mapTo = mapFrom.entrySet().stream().map(e -> {     e.getValue().stream().?     // Here I can iterate on each List,      // but my best try would only give me a flat map for each key,      // that I wouldn't know how to flatten. }).collect(Collectors.toMap(/*A String value*/,/*An Integer key*/)) 

I also gave a try to flatMap, but I don't think that it is the right way to go, because although it helps me get rid of the dimensionality issue, I lose the key in the process.

In a nutshell, my two questions are :

  • Is it possible to use streams and lambda to achieve this?
  • Is is useful (performance, readability) to do so?
like image 854
Vongo Avatar asked Nov 08 '16 10:11

Vongo


People also ask

What is flat map () method does in stream?

In Java 8 Streams, the flatMap() method applies operation as a mapper function and provides a stream of element values. It means that in each iteration of each element the map() method creates a separate new stream. By using the flattening mechanism, it merges all streams into a single resultant stream.

How do you flatten a stream List?

The standard solution is to use the Stream. flatMap() method to flatten a List of Lists. The flatMap() method applies the specified mapping function to each element of the stream and flattens it.

How do you flatten a string in Java?

Create an empty list to collect the flattened elements. With the help of forEach loop, convert each elements of the list into stream and add it to the list. Now convert this list into stream using stream() method. Now flatten the stream by converting it into list using collect() method.


1 Answers

You need to use flatMap to flatten the values into a new stream, but since you still need the original keys for collecting into a Map, you have to map to a temporary object holding key and value, e.g.

Map<String, Integer> mapTo = mapFrom.entrySet().stream()        .flatMap(e->e.getValue().stream()                     .map(v->new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))        .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); 

The Map.Entry is a stand-in for the nonexistent tuple type, any other type capable of holding two objects of different type is sufficient.

An alternative not requiring these temporary objects, is a custom collector:

Map<String, Integer> mapTo = mapFrom.entrySet().stream().collect(     HashMap::new, (m,e)->e.getValue().forEach(v->m.put(v, e.getKey())), Map::putAll); 

This differs from toMap in overwriting duplicate keys silently, whereas toMap without a merger function will throw an exception, if there is a duplicate key. Basically, this custom collector is a parallel capable variant of

Map<String, Integer> mapTo = new HashMap<>(); mapFrom.forEach((k, l) -> l.forEach(v -> mapTo.put(v, k))); 

But note that this task wouldn’t benefit from parallel processing, even with a very large input map. Only if there were additional computational intense task within the stream pipeline that could benefit from SMP, there was a chance of getting a benefit from parallel streams. So perhaps, the concise, sequential Collection API solution is preferable.

like image 97
Holger Avatar answered Sep 20 '22 17:09

Holger