Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perform flatMap operation with Collectors

I would like to understand a way to perform a flatMap while using Collectors. Here is an example.

Scenario:

I have the following interfaces:

interface Ic {
    //empty
}

interface Ib {
    Stream<Ic> getCs();
}

interface Ia {
    String getName();
    Stream<Ib> getBs();
}

And I'm trying to implement the following method:

Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
   return streamOfA.collect(groupBy(Ia::getName, ???));
}

The classification function is pretty straitforward, my problem is with the downstream collector. I need to count the number of "C" associated with "A".

What I tried to to:

If I wanted to simply return the count, without creating a map, I would do:

streamOfA
  .flatMap(Ia::getBs)
  .flatMap(Ib::getCs)
  .count();

But the Collectors class only allows me to do mapping operations. What else can I try to do?

Thanks.

like image 882
Some_user_qwerty Avatar asked Jun 25 '17 18:06

Some_user_qwerty


People also ask

How do you use a flatMap?

We can use a flatMap() method on a stream with the mapper function List::stream. On executing the stream terminal operation, each element of flatMap() provides a separate stream. In the final phase, the flatMap() method transforms all the streams into a new stream.

What is difference between flatMap () and map () functions?

The difference is that the map operation produces one output value for each input value, whereas the flatMap operation produces an arbitrary number (zero or more) values for each input value.

Does flatMap preserve order?

Does flatmap() method preserve the order of the streams? Yes, It does and map() also.


2 Answers

This answer points you already into the right direction, but there is no need to nest multiple mapping collectors, as you can just write these functions into a single lambda expression. Considering that the summingLong collector expects a function which evaluates to long, you can simply pass that function to the collector without any mapping collector at all:

Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            summingLong(ia -> ia.getBs().flatMap(Ib::getCs).count())));
}

This also has the advantage that the long values are not boxed to Long instances.

There’s also an alternative to flatMap:

Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            summingLong(ia -> ia.getBs().mapToLong(ib -> ib.getCs().count()).sum())));
}
like image 82
Holger Avatar answered Oct 09 '22 14:10

Holger


the documentation described the Collectors#mapping as:

Adapts a Collector accepting elements of type U to one accepting elements of type T by applying a mapping function to each input element before accumulation.

The mapping() collectors are most useful when used in a multi-level reduction, such as downstream of a groupingBy or partitioningBy.

which means you can composing any possible Collectors as you can.

import static java.util.stream.Collectors.*;

Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            mapping(
                    Ia::getBs,
                    mapping(
                            it -> it.flatMap(Ib::getCs),
            //    reduce() does boxing & unboxing ---v
                            mapping(Stream::count, reducing(0L,Long::sum))
                    )
            )
    ));
}

OR using Collectors#summingLong instead.

Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            mapping(
                    Ia::getBs,
                    mapping(
                            it -> it.flatMap(Ib::getCs),
            //    summingLong() does boxing      ---v
                            mapping(Stream::count, summingLong(Long::longValue))
            //      Long::longValue does unboxing operation ---^
                    )
            )
    ));
}

thanks for @Holger point out the potential problem of the code above, that you can simply using summingLong(Stream::count) instead. in this approach is no need to boxing Stream#count which return a long to a Long. and Long::longValue unboxing a Long to long.

Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
        Ia::getName,
        mapping(
            Ia::getBs,
        //    summingLong() doesn't any boxing ---v
            mapping(it -> it.flatMap(Ib::getCs), summingLong(Stream::count))
        )
    ));
}
like image 38
holi-java Avatar answered Oct 09 '22 15:10

holi-java