11

I have map with key as String and value as List. List can have 10 unique values. I need to convert this map with key as Integer and value as List. Example as below :

Input :

"Key-1" : 1,2,3,4

"Key-2" : 2,3,4,5

"Key-3" : 3,4,5,1

Expected output :

1 : "Key-1","Key-3"

2 : "Key-1","Key-2"

3 : "Key-1", "Key-2", "Key-3"

4 : "Key-1", "Key-2", "Key-3"

5 : "Key-2", "Key-3"

I am aware that using for loops i can achieve this but i needed to know can this be done via streams/lamda in java8.

-Thanks.

4castle
  • 32,613
  • 11
  • 69
  • 106
KhajalId
  • 119
  • 1
  • 4

5 Answers5

17

An idea could be to generate all value-key pairs from the original map and then group the keys by these values:

import java.util.AbstractMap.SimpleEntry;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

...

Map<Integer, List<String>> transposeMap =
    map.entrySet()
       .stream()
       .flatMap(e -> e.getValue().stream().map(i -> new SimpleEntry<>(i, e.getKey())))
       .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
4

Alexis’ answer contains the general solution for this kind of task, using flatMap and a temporary holder for the combination of key and flattened value. The only alternative avoiding the creation of the temporary holder objects, is to re-implement the logic of the groupingBy collector and inserting the loop over the value list logic into the accumulator function:

Map<Integer, List<String>> mapT = map.entrySet().stream().collect(
    HashMap::new,
    (m,e) -> e.getValue().forEach(
                 i -> m.computeIfAbsent(i,x -> new ArrayList<>()).add(e.getKey())),
    (m1,m2) -> m2.forEach((k,v) -> m1.merge(k, v, (l1,l2)->{l1.addAll(l2); return l1;})));
Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
2

It's a bit scary (I generally try to break it down to make it more readable) but you could do it this way:

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

map.forEach((key, list) -> list.stream().forEach(
    elm -> transposeMap.put(elm,
        transposeMap.get(elm) == null ? Arrays.asList(key) : (Stream.concat(transposeMap.get(elm).stream(),
            Arrays.asList(key).stream()).collect(Collectors.toList())))));

Assuming Map<String, List<Integer>> map is your original Map that you want to transpose. transposeMap will have transposed map that you need.

justAbit
  • 4,226
  • 2
  • 19
  • 34
  • 1
    Streams are better suited for stateless operations. While this answer is correct, it would be better suited to an imperative style. It's basically the OP's loop strategy, but relabeled with a stream's `forEach` instead. – 4castle Jul 20 '16 at 13:18
  • 1
    `forEach` does not belong to `stream`s. It is declared in `Iterable`, `list.stream().forEach` should be `list.forEach`. – Jean-François Savard Jul 20 '16 at 16:41
  • 1
    @Jean-François Savard: Streams also have a `forEach` method, though with a slightly different semantic. – Holger Jul 20 '16 at 17:45
  • @Holger I know that, but I've always tought the point of having forEach in a `stream` is to gain some *potential* performance(parralelism) after a long pipeline where you are already streamed. Is there actually a point of *streaming* a `Collection` if all you want to do is loop over it ? (I don't think so) – Jean-François Savard Jul 20 '16 at 17:50
  • 2
    @Jean-François Savard: there’s no point in using this more complicated variant (besides testing), but that’s a different statement than “should be [the simpler form]”. – Holger Jul 20 '16 at 17:54
2

You can Achieve in this way

Let suppose I have Person class with Gender and Age . I want to get it in this form

Map<SEX,List<Person>> 

I would write simply

Map<SEX,List<Person>> map =  personList.stream()

                           .collect(Collectors.groupingBy(Person::getGender));

it will get me something like below (one key against multiple values )

key:MALE
age31sexMALE
age28sexMALE

key:FEMALE
age40sexFEMALE
age44sexFEMALE
naila naseem
  • 577
  • 3
  • 12
  • 21
  • This does not answer the question since you are assuming you have one big List as an input with elements having gender field. Translating OP's question into your example, OP has `Map>` . – Tomáš Záluský Apr 21 '22 at 16:12
0

with teeing You can work on keys and values in 2 streams separately
since Java 12

Map<Integer, List<String>> to = from.entrySet().stream()
  .collect(teeing(flatMapping(e -> e.getValue().stream(), toList()),
      flatMapping(e -> (Stream<String>)e.getValue().stream().map(i -> e.getKey()), toList()),
      (k, v) -> {
        return IntStream.range(0, k.size()).boxed().collect(
            groupingBy(i -> k.get(i), mapping(i -> v.get(i), toList())));
      }));
Kaplan
  • 2,572
  • 13
  • 14