Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combine allMatch, noneMatch and anyMatch on a single stream

I would like to have the following logic: (i know it doesn't work because it consumes the stream more than once). but i am not sure how to achieve it.

Stream<ByteBuffer> buffers = super.getBuffers().stream();
if (buffers.allMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (buffers.noneMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}

How can I do that?

like image 839
slashms Avatar asked Dec 08 '16 09:12

slashms


People also ask

What is the difference between allMatch and anyMatch?

anyMatch() returns true if any of the elements in a stream matches the given predicate. If the stream is empty or if there's no matching element, it returns false . allMatch() returns true only if ALL elements in the stream match the given predicate.

How does stream anyMatch work?

Stream anyMatch(Predicate predicate) returns whether any elements of this stream match the provided predicate. It may not evaluate the predicate on all elements if not necessary for determining the result. This is a short-circuiting terminal operation.


1 Answers

Since the result of super.getBuffers() is List<ByteBuffer>, you can iterate over it twice.

List<ByteBuffer> buffers = super.getBuffers();
if (buffers.stream().allMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (buffers.stream().noneMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}

Note that this still doesn’t need iterating over all elements in all cases. allMatch returns as soon as it encounters a non-matching element, noneMatch returns as soon as it encounters a matching element. So in the PARTIALLY_SENT case, it is possible that it got the conclusion without looking at all elements.

An alternative is

List<ByteBuffer> buffers = super.getBuffers();
if(buffers.isEmpty()) return OutgoingMessageStatus.FULLY_SENT;
Predicate<ByteBuffer> p = b -> b.position() > 0;
boolean sent = p.test(buffers.get(0));
if(!sent) p = p.negate();
return buffers.stream().skip(1).allMatch(p)? sent?
    OutgoingMessageStatus.FULLY_SENT:
    OutgoingMessageStatus.WAS_NOT_SENT:
    OutgoingMessageStatus.PARTIALLY_SENT;
}

The status of the first element determines which condition we have to check. As soon as there is a contradicting element, allMatch returns immediately and we have a PARTIALLY_SENT situation. Otherwise, all elements match like the first which implies either “all sent” or “none sent”.

The pre-check for an empty list produces the same behavior as the original code and ensures that get(0) never breaks.


If you really have a Stream instead of a source that you can iterate multiple times, there is no simple short-cutting solutions, as that would require a stateful predicate. There are, however, simple solutions processing all elements.

Map<Boolean,Long> result=getBuffers().stream()
    .collect(Collectors.partitioningBy(b -> b.position() > 0, Collectors.counting()));
return
    result.getOrDefault(false, 0L)==0?
        OutgoingMessageStatus.FULLY_SENT:
    result.getOrDefault(true, 0L)==0?
        OutgoingMessageStatus.WAS_NOT_SENT:
        OutgoingMessageStatus.PARTIALLY_SENT;

or

return super.getBuffers().stream()
    .map(b -> b.position() > 0?
              OutgoingMessageStatus.FULLY_SENT: OutgoingMessageStatus.WAS_NOT_SENT)
    .reduce((a,b) -> a==b? a: OutgoingMessageStatus.PARTIALLY_SENT)
    .orElse(OutgoingMessageStatus.FULLY_SENT);
like image 177
Holger Avatar answered Oct 14 '22 07:10

Holger