0

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.

x80486
  • 6,627
  • 5
  • 52
  • 111
  • What is the type of `seeds`? also can you share what `Seed::hadExposure` do? – Youcef LAIDANI Sep 21 '20 at 17:18
  • It's of type `Seed`, a custom type. But the important bit is that I'm working with a return type (inside that class) of `Optional>`. I don't do anything with `seeds` except looping over them. – x80486 Sep 21 '20 at 17:20
  • https://stackoverflow.com/questions/22725537/using-java-8s-optional-with-streamflatmap. I think this will help – Dhyey Shah Sep 21 '20 at 17:20
  • what `Seed::hadExposure` return? – Youcef LAIDANI Sep 21 '20 at 17:22
  • It returns `Optional>`. I'm using `Pair` from `Apache Commons Lang3`, but in the end, it can be any tuple of two elements. – x80486 Sep 21 '20 at 17:24
  • Not quite. I'm looking to return `Optional>`, but first I do need to know if any of the `Pair` values had a `true` (`left` and/or `right`), and convert the return value accordingly. If the collection has 20 `Pair`s, and one of them is `true` on the `left`, and another one is `true` on the `right`, then the result is `Pair`. Anything `true` on that side will turn the final value to `true` on the same side. – x80486 Sep 21 '20 at 17:36

4 Answers4

3

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. ;)

Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
1

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));
Joe
  • 607
  • 6
  • 13
  • I was doing something like this based on @DHS comment: `seeds.stream().map(Seed::hadExposure).flatMap(Optional::stream).reduce((p, n) -> new ImmutablePair<>(n.getLeft() || p.getLeft(), n.getRight() || p.getRight()))`. I think your solution might work also. I'll test everything first. – x80486 Sep 21 '20 at 18:22
  • Ok yeah, looks like that should be equivalent to what I have from what I can tell. – Joe Sep 21 '20 at 20:18
1

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.

fps
  • 33,623
  • 8
  • 55
  • 110
  • Probably I didn't make myself clear, but I need to know, from all the `Pair`s, if there are `true` values in both sides (`left` and `right`) and account for those in the final result. – x80486 Sep 21 '20 at 18:21
  • @x80486 Now I understood what you were after. Please check if it works for you – fps Sep 21 '20 at 20:56
0

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()));
Michael Mesfin
  • 546
  • 4
  • 11