user4695271
user4695271

Reputation:

Accumulate values and return result in Java stream

I have a class with a collection of Seed elements. One of the method's return type of Seed is Optional<Pair<Boolean, Boolean>>.

I'm trying to loop over all seeds, keeping the return type (Optional<Pair<Boolean, Boolean>>), but I would like to be able to say if there was at least true value (in any of the Pairs) and override the result with it. Basically, if the collection is (skipping the Optional wrapper to make things simpler): [Pair<false, false>, Pair<false, true>, Pair<false, false>] I would like to return and Optional of Pair<false, true> because the second element had true. In the end, I'm interested if there was a true value and that's about it.

  public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
    return seeds.stream()
        .map(Seed::hadExposure)
        ...
  }

I was playing with reduce but couldn't come up with anything useful.

My question is related with Java streams directly. I can easily do this with a for loop, but I aimed initially for streams.

Upvotes: 0

Views: 1357

Answers (4)

Nicolai Parlog
Nicolai Parlog

Reputation: 51060

Straighforward

Since you're Java 11, you can use Optional::stream (introduced in Java 9) to get rid of the Optional wrapper. As a terminal operation, reduce is your friend:

public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
    // wherever the seeds come from
    Stream<Optional<Pair<Boolean, Boolean>>> seeds = seeds();
    return seeds
        .flatMap(Optional::stream)
        .reduce((pair1, pair2) -> new Pair<>(
            pair1.left() || pair2.left(),
            pair1.right() || pair2.right())
    );
}

Extended

If you want to go a step further and give your Pair a general way to be folded with another Pair into a new instance, you can make the code a bit more expressive:

public class Pair<LEFT, RIGHT> {

    private final LEFT left;
    private final RIGHT right;

    // constructor, equals, hashCode, toString, ...

    public Pair<LEFT, RIGHT> fold(
            Pair<LEFT, RIGHT> other,
            BinaryOperator<LEFT> combineLeft,
            BinaryOperator<RIGHT> combineRight) {
        return new Pair<>(
            combineLeft.apply(left, other.left),
            combineRight.apply(right, other.right));
    }

}

// now you can use fold and Boolean::logicalOr
// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Boolean.html#logicalOr(boolean,boolean)

public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
    Stream<Optional<Pair<Boolean, Boolean>>> seeds = seeds();
    return seeds
        .flatMap(Optional::stream)
        .reduce((pair1, pair2) -> pair1
            .fold(pair2, Boolean::logicalOr, Boolean::logicalOr))
    );
}

I probably wouldn't create Pair::fold just for this use case, but I would be tempted. ;)

Upvotes: 2

Michael Mesfin
Michael Mesfin

Reputation: 556

using Collectors.partitioningBy you can get a Map with boolean keys after that you can easily retrieve values indexed with the key true

Optional<Pair<Boolean, Boolean>> collect = Arrays.asList(pair1, pair2, par3).stream()
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.collectingAndThen(Collectors.partitioningBy(p -> p.getFirst() == true || p.getSecond() == true),
                    m -> m.get(true).stream().findAny()));

Upvotes: 0

fps
fps

Reputation: 34460

As you've tagged this question with java-11, you can make use of the Optional.stream method:

public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
    return Optional.of(
        seeds.stream()
             .flatMap(seed -> seed.hadExposure().stream())
             .collect(
                 () -> new Pair<Boolean, Boolean>(false, false),
                 (p, seed) -> { 
                     p.setLeft(p.getLeft() || seed.getLeft());
                     p.setRight(p.getRight() || seed.getRight()); 
                 },
                 (p1, p2) -> {
                     p1.setLeft(p1.getLeft() || p2.getLeft());
                     p1.setRight(p1.getRight() || p2.getRight());
                 }));
}

This first gets rid of the Optional by means of the Optional.stream method (keeping just the pairs) and then uses Stream.collect to mutably reduce the pairs by means of the OR associative operation.

Note: using Stream.reduce would also work, but it would create a lot of unnecessary intermediate pairs. That's why I've used Stream.collect instead.

Upvotes: 0

Joe
Joe

Reputation: 607

Your thoughts on reduce look like the right way to go, using || to reduce both sides of each Pair together. (Not exactly sure what your Optional semantics are, so going to filter out empty ones here and that might get what you want, but you may need to adjust):

Optional<Pair<Boolean, Boolean>> result = seeds.stream().map(Seed::hadExposure)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .reduce((a, b) -> new Pair<>(a.first || b.first, a.second || b.second));

Upvotes: 0

Related Questions