How to combine Map with Lists value lists using the Java Streams API?

How to reduce grouping Map<X, List<String>>using Xp and at the same time concatenate all list values ​​so that I have Map<Integer, List<String>>at the end?

This is what I have tried so far:

class X {
    int p;
    int q;
    public X(int p, int q) { this.p = p; this.q = q; }
}
Map<X, List<String>> x = new HashMap<>();
x.put(new X(123,5), Arrays.asList("A","B"));
x.put(new X(123,6), Arrays.asList("C","D"));
x.put(new X(124,7), Arrays.asList("E","F"));
Map<Integer, List<String>> z = x.entrySet().stream().collect(Collectors.groupingBy(
    entry -> entry.getKey().p, 
    mapping(Map.Entry::getValue, 
        reducing(new ArrayList<>(), (a, b) -> { a.addAll(b); return a; }))));
System.out.println("z="+z);

But the result: z = {123 = [E, F, A, B, C, D], 124 = [E, F, A, B, C, D]}.

I want to have z = {123 = [A, B, C, D], 124 = [E, F]}

+4
source share
4 answers

You are using the collector incorrectly reducing. The first argument should be the identity value of the reduction operation. But you change it by adding values ​​to it, which perfectly explains the result: all values ​​are added to the same ArrayList, which is expected to be the value of the identifier of the invariant.

, Mutable reduction Collectors.reducing . , Collector.of(…):

Map<Integer, List<String>> z = x.entrySet().stream().collect(groupingBy(
    entry -> entry.getKey().p, Collector.of(
        ArrayList::new, (l,e)->l.addAll(e.getValue()), (a,b)->{a.addAll(b);return a;})));
+3

, :

Map<Integer, List<String>> z = 
// first process the entries of the original Map and produce a 
// Map<Integer,List<List<String>>>
    x.entrySet()
     .stream()
     .collect(Collectors.groupingBy(entry -> entry.getKey().p, 
                                    mapping(Map.Entry::getValue,
                                            toList())))
// then process the entries of the intermediate Map and produce a 
// Map<Integer,List<String>>
     .entrySet()
     .stream()
     .collect (toMap (Map.Entry::getKey,
                      e -> e.getValue()
                            .stream()
                            .flatMap(List::stream)
                            .collect(toList())));

, Java 9 flatMapping Collector, ( Holger).

:

z={123=[A, B, C, D], 124=[E, F]}
+5

, :

Map<Integer, List<String>> z = x.entrySet().stream().collect(
  Collectors.groupingBy(entry -> entry.getKey().p,
    Collectors.mapping(Entry::getValue, 
      Collector.of(ArrayList::new, (a, b) -> a.addAll(b), (a, b) -> {
        a.addAll(b);
        return a;
      })
    )
  )
);
+3

EntryStream StreamEx :

Map<Integer, List<String>> z = EntryStream.of(x)
           .mapKeys(k -> k.p)
           .flatMapValues(List::stream)
           .grouping();

:

Map<Integer, List<String>> z = x.entrySet().stream()
        .map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
        .<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
            .map(s -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), s)))
        .collect(Collectors.groupingBy(e -> e.getKey(), 
            Collectors.mapping(e -> e.getValue(), Collectors.toList())));

, .

, :

Map<Integer, List<String>> z = x.entrySet().stream()
        .<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
                .map(s -> new AbstractMap.SimpleEntry<>(e.getKey().p, s)))
        .collect(Collectors.groupingBy(e -> e.getKey(), 
                Collectors.mapping(e -> e.getValue(), Collectors.toList())));

- .

Finally, note that in JDK9 there is a new standard collector called flatMapping, which can be implemented as follows:

public static <T, U, A, R>
Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
                               Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return Collector.of(downstream.supplier(),
            (r, t) -> {
                try (Stream<? extends U> result = mapper.apply(t)) {
                    if (result != null)
                        result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
                }
            },
            downstream.combiner(), downstream.finisher(),
            downstream.characteristics().toArray(new Collector.Characteristics[0]));
}

Using this collector, your task can be solved more easily without additional libraries:

Map<Integer, List<String>> z = x.entrySet().stream()
        .map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
        .collect(Collectors.groupingBy(e -> e.getKey(), 
                flatMapping(e -> e.getValue().stream(), Collectors.toList())));
+3
source

Source: https://habr.com/ru/post/1599846/


All Articles