Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reduce a Java stream to a boolean value based upon comparing consecutive list elements

As a refactoring exercise, I'm trying to take something like this:

    for (int i = 1; i < orderedList.size(); i++) {
        Object object1 = ordered.get(i - 1);
        Object object2 = ordered.get(i);
        if (isDifferent(object1, object2)) return true;
    }

    return false;

into a simple Java functional statement. At first glance, I'm processing elements of a list sequentially, so reduce() sounded promising, but

  1. It would require carrying along a boolean value, which loses me the previous element; or
  2. carrying along the previous element of the list, which means I need to put it in some kind of wrapper object, or kludgy use of Optional or another List as a wrapper, in order to keep track of whether I have a difference or not.

So, I see ways I can use a reduce operation, but it would be more complicated and hard to understand than the for-loop original.

I looked at creating a custom collector, but ran into the same issues.

Is this a proper use of reduce, or am I just tempted because I know I can use it to process sequential values against a previous iteration? If it's a proper use, how can I compose the functions to reduce myself to a boolean?

Thanks for help with this thought exercise.

like image 720
orbfish Avatar asked Jan 26 '26 22:01

orbfish


2 Answers

Because you're indexing two Lists with a for-loop, you can replace it with an IntStream and reduce it with IntStream#anyMatch:

return IntStream.range(1, orderedList.size())
                .anyMatch(i -> isDifferent(ordered.get(i - 1), ordered.get(i)));

Although, I don't really see this providing much benefit, so it may be more readable to just keep it as a for-loop.

like image 173
Jacob G. Avatar answered Jan 29 '26 11:01

Jacob G.


The fundamental operation here is called "zipping": given two streams of As and Bs and a combining operator (A, B) -> C, you can create a stream of Cs (truncating to the shorter input stream). Assuming you have such a function

<A, B, C> Stream<C> zip
 (Stream<? extends A> as, Stream<? extends B> bs,
  BiFunction<? super A, ? super B, ? extends C> combine);

You can implement your operation as

zip(ordered.stream(), ordered.stream().skip(1), this::isDifferent).anyMatch(x -> x);
// ordered: a b c d ... y z
// skipped: b c d ... y z
// zipped : (a, b) (b, c) (c, d) ... (y, z)

There is no zip in the standard library. If you're using something like Guava, you may check whether it has this operation, e.g. here's one. Or, you can implement it yourself and stick it in some utility class, at which point you may want to check out this answer. I will reproduce the code listed there (which appears to be cribbed from a beta version of the actual Streams API):

public static<A, B, C> Stream<C> zip(Stream<? extends A> a,
                                     Stream<? extends B> b,
                                     BiFunction<? super A, ? super B, ? extends C> zipper) {
    Objects.requireNonNull(zipper);
    Spliterator<? extends A> aSpliterator = Objects.requireNonNull(a).spliterator();
    Spliterator<? extends B> bSpliterator = Objects.requireNonNull(b).spliterator();

    // Zipping looses DISTINCT and SORTED characteristics
    int characteristics = aSpliterator.characteristics() & bSpliterator.characteristics() &
            ~(Spliterator.DISTINCT | Spliterator.SORTED);

    long zipSize = ((characteristics & Spliterator.SIZED) != 0)
            ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown())
            : -1;

    Iterator<A> aIterator = Spliterators.iterator(aSpliterator);
    Iterator<B> bIterator = Spliterators.iterator(bSpliterator);
    Iterator<C> cIterator = new Iterator<C>() {
        @Override
        public boolean hasNext() {
            return aIterator.hasNext() && bIterator.hasNext();
        }

        @Override
        public C next() {
            return zipper.apply(aIterator.next(), bIterator.next());
        }
    };

    Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics);
    return (a.isParallel() || b.isParallel())
           ? StreamSupport.stream(split, true)
           : StreamSupport.stream(split, false);
}
like image 44
HTNW Avatar answered Jan 29 '26 12:01

HTNW



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!