1

I'd like to stream a map with collection using Java 8 streams.

For example, having the following data:

Map<String, Collection<Integer>> data;

I'd like to go over the elements handling each integer value with the corresponding key strings. For example:

data.keyValueStream((k,v)-> ...)

Any idea how to achieve this? Thanks.

* Regarding the question "Why do you need it?", it could be a bunch of reasons and I'm not sure it's important. Anyhow, I'll "flow" with you... My specific scenario is to batch insert into a DB all the values, under their specific key. Let's keep it a general Java 8 stream question...

AlikElzin-kilaka
  • 34,335
  • 35
  • 194
  • 277
  • 3
    Proper solution may depend on what you really want to achieve. Can you explain what you need that pairs for? – Pshemo Sep 16 '18 at 18:18
  • 1
    `data.forEach((key1, value1) -> value1.forEach(item -> { System.out.println("Key:" + key1 + " value: " + item); }));` – Hadi J Sep 16 '18 at 18:29
  • 3
    Simplest and most efficient solution would just be to use a nested enhanced for loop IMHO. – Ousmane D. Sep 16 '18 at 18:36
  • Added an explanation to the question regarding the "Why you need the pair for?" – AlikElzin-kilaka Sep 17 '18 at 09:41
  • I guess that what I'm looking for doesn't exists. Mapping some collection to tuples and then working with the tuple values directly instead of a single wrapper object - doesn't exists in Java. It's always a single type stream. – AlikElzin-kilaka Sep 21 '18 at 20:56

4 Answers4

5

You can map your Map<String, Collection<Integer>> to List<Map.Entry<String, Integer>>:

data.entrySet().stream()
  .flatMap(e -> e.getValue().stream().map(v -> new HashMap.SimpleEntry<>(e.getKey(), v)))
  .forEach(e -> System.out.printf("key %s val %d%n", e.getKey(), e.getValue()));

or:

data.forEach((k, v) -> v.forEach(n -> System.out.printf("key %s val %d%n", k, n)));
Adam Siemion
  • 15,569
  • 7
  • 58
  • 92
  • Why do `println(String.format("key %s, value %d",` instead of the shorter `printf("key %s, value %d%n",`? – Andreas Sep 16 '18 at 18:37
  • 1
    @Andreas besides that, `flatMap` is eager until java-10, so if OP wants to short-circuit he cant; unlike a plain loop – Eugene Sep 16 '18 at 19:11
  • @Andreas for two nested `forEach`, using `printf` is the simpler choice. For the stream using `flatMap`, it would make more sense to use `String.format`—instead of `new HashMap.SimpleEntry<>(…)` (which is a disguise, by the way, as the nested class is actually inherited from `AbstractMap` and unrelated to `HashMap`). I.e. `data.entrySet().stream() .flatMap(e -> e.getValue().stream().map(v -> String.format("key %s val %d%n", e.getKey(), v))) .forEach(System.out::print);` – Holger Sep 17 '18 at 08:35
3

I realize that you ware asking about stream version, but if you are NOT going to use parallelism simplest and probably more efficient option would be using nested loops. This way you can avoid spending time and space on creating temporary instances for each <Key,CollectionItem> pair.

Instead you can use

for (Map.Entry<String, Collection<Integer>> entry : map.entrySet()){
    String key = entry.getKey();
    for (Integer number : entry.getValye()){
        //here we have access to <key, number> pair, 
        //handle them as you wish;
    }
}
Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • 3
    But, but... Streams are new, so surely they are better than loops! (I sometimes despair that people forget that the old can be better). – Andy Turner Sep 16 '18 at 18:45
  • 1
    @AndyTurner once the code is hot enough, the difference is tiny. Ive answered questions like these with proper JMH tests – Eugene Sep 16 '18 at 19:07
2

While this may be a bit obvious, you can also write:

map.forEach((k, v) -> v.forEach(s -> System.out.println(k + "  " + s)))

Example, in Java 9.

Map< String , Collection< Integer > > map =
    Map.ofEntries(
        Map.entry( "alpha" , List.of( 1 , 2 , 3 ) ) ,
        Map.entry( "beta" , List.of( 4 , 5 , 6 ) ) ,
        Map.entry( "gamma" , List.of( 7 , 8 , 9 ) )
    )
;

map.forEach( ( k , v ) -> {
        v.forEach( s -> System.out.println( k + "  " + s ) );
    }
);
Druckles
  • 3,161
  • 2
  • 41
  • 65
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • You don’t need `ofEntries` for such a short map. You can just use `Map.of("alpha", List.of(1, 2, 3), "beta", List.of(4, 5, 6), "gamma", List.of(7, 8, 9))`. Further, the nested `forEach` doesn’t need the statement syntax. So you can just use `map.forEach((k, v) -> v.forEach(s -> System.out.println(k + " " + s)));` – Holger Sep 17 '18 at 08:39
1

Streaming only processes a single value, so you can't get keyValueStream((k,v)-> ...), but you can get keyValueStream(x -> ...) where x is a tuple/pair.

Since you are starting with a Map, which can stream Entry objects (key/value pairs), and you want a key/value pair in your lambda, a stream of Entry objects seems appropriate.

Which means that you just want to flatten the nested collection, e.g. like this:

import java.util.AbstractMap.SimpleEntry;
data.entrySet()
    .stream()
    .flatMap(e -> e.getValue().stream().map(v -> new SimpleEntry<>(e.getKey(), v)))
    // At this point you have a Stream<Entry<String, Integer>> so you can e.g. do this:
    .forEach(e -> System.out.println(e.getKey() + "=" + e.getValue()))
;
Andreas
  • 154,647
  • 11
  • 152
  • 247