alturkovic
alturkovic

Reputation: 1140

Java Map with List value to list using streams?

I am trying to rewrite the method below using streams but I am not sure what the best approach is? If I use flatMap on the values of the entrySet(), I lose the reference to the current key.

private List<String> asList(final Map<String, List<String>> map) {
    final List<String> result = new ArrayList<>();
    for (final Entry<String, List<String>> entry : map.entrySet()) {
      final List<String> values = entry.getValue();
      values.forEach(value -> result.add(String.format("%s-%s", entry.getKey(), value)));
    }
    return result;
}

The best I managed to do is the following:

return map.keySet().stream()
          .flatMap(key -> map.get(key).stream()
                             .map(value -> new AbstractMap.SimpleEntry<>(key, value)))
          .map(e -> String.format("%s-%s", e.getKey(), e.getValue()))
          .collect(Collectors.toList());

Is there a simpler way without resorting to creating new Entry objects?

Upvotes: 0

Views: 4186

Answers (2)

Mohamed Anees A
Mohamed Anees A

Reputation: 4621

Adding my two cents to excellent answer by @rzwitserloot. Already flatmap and map is explained in his answer.

List<String> resultLists =  myMap.entrySet().stream()
        .flatMap(mapEntry -> printEntries(mapEntry.getKey(),mapEntry.getValue())).collect(Collectors.toList());
System.out.println(resultLists);

Splitting this to a separate method gives good readability IMO,

private static Stream<String> printEntries(String key, List<String> values) {
    return values.stream().map(val -> String.format("%s-%s",key,val));
  }

Upvotes: 0

rzwitserloot
rzwitserloot

Reputation: 103893

A stream is a sequence of values (possibly unordered / parallel). map() is what you use when you want to map a single value in the sequence to some single other value. Say, map "alturkovic" to "ALTURKOVIC". flatMap() is what you use when you want to map a single value in the sequence to 0, 1, or many other values. Hence why a flatMap lambda needs to turn a value into a stream of values. flatMap can thus be used to take, say, a list of lists of string, and turn that into a stream of just strings.

Here, you want to map a single entry from your map (a single key/value pair) into a single element (a string describing it). 1 value to 1 value. That means flatMap is not appropriate. You're looking for just map.

Furthermore, you need both key and value to perform your mapping op, so, keySet() is also not appropriate. You're looking for entrySet(), which gives you a set of all k/v pairs, juts what we need.

That gets us to:

map.entrySet().stream()
   .map(e -> String.format("%s-%s", e.getKey(), e.getValue()))
   .collect(Collectors.toList());

Your original code makes no effort to treat a single value from a map (which is a List<String>) as separate values; you just call .toString() on the entire ordeal, and be done with it. This means the produced string looks like, say, [Hello, World] given a map value of List.of("Hello", "World"). If you don't want this, you still don't want flatmap, because streams are also homogenous - the values in a stream are all of the same kind, and thus a stream of 'key1 value1 value2 key2 valueA valueB' is not what you'd want:

map.entrySet().stream()
   .map(e -> String.format("%s-%s", e.getKey(), myPrint(e.getValue())))
   .collect(Collectors.toList());

public static String myPrint(List<String> in) {
   // write your own algorithm here
}

Stream API just isn't the right tool to replace that myPrint method.

A third alternative is that you want to smear out the map; you want each string in a mapvalue's List<String> to first be matched with the key (so that's re-stating that key rather a lot), and then do something to that. NOW flatMap IS appropriate - you want a stream of k/v pairs first, and then do something to that, and each element is now of the same kind. You want to turn the map:

key1 = [value1, value2]
key2 = [value3, value4]

first into a stream:

key1:value1
key1:value2
key2:value3
key2:value4

and take it from there. This explodes a single k/v entry in your map into more than one, thus, flatmapping needed:

return map.entrySet().stream()
    .flatMap(e -> e.getValue().stream()
      .map(v -> String.format("%s-%s", e.getKey(), v))
    .collect(Collectors.toList());

Going inside-out, it maps a single entry within a list that belongs to a single k/v pair into the string Key-SingleItemFromItsList.

Upvotes: 2

Related Questions