All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codepoetics.protonpack.StreamUtils Maven / Gradle / Ivy

There is a newer version: 1.16
Show newest version
package com.codepoetics.protonpack;

import com.codepoetics.protonpack.functions.TriFunction;

import java.util.*;
import java.util.function.*;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Utility class providing static methods for performing various operations on Streams.
 */
public final class StreamUtils {

    private StreamUtils() {

    }

    /**
     * Constructs an infinite (although in practice bounded by Long.MAX_VALUE) stream of longs 0, 1, 2, 3...
     * for use as indices.
     * @return A stream of longs.
     */
    public static LongStream indices() {
        return LongStream.iterate(0L, l -> l + 1);
    }

    /**
     * Zip the source stream together with the stream of indices() to provide a stream of indexed values.
     * @param source  The source stream.
     * @param  The type over which the source stream streams.
     * @return A stream of indexed values.
     */
    public static  Stream> zipWithIndex(Stream source) {
        return zip(indices().mapToObj(Long::valueOf), source, Indexed::index);
    }

    /**
     * Zip together the "left" and "right" streams until either runs out of values.
     * Each pair of values is combined into a single value using the supplied combiner function.
     * @param lefts The "left" stream to zip.
     * @param rights The "right" stream to zip.
     * @param combiner The function to combine "left" and "right" values.
     * @param  The type over which the "left" stream streams.
     * @param  The type over which the "right" stream streams.
     * @param  The type created by the combiner out of pairs of "left" and "right" values, over which the resulting
     *           stream streams.
     * @return A stream of zipped values.
     */
    public static  Stream zip(Stream lefts, Stream rights, BiFunction combiner) {
        return StreamSupport.stream(ZippingSpliterator.zipping(lefts.spliterator(), rights.spliterator(), combiner), false);
    }

    /**
     * Zip together the "left", "middle" and "right" streams until any stream runs out of values.
     * Each triple of values is combined into a single value using the supplied combiner function.
     * @param lefts The "left" stream to zip.
     * @param middles The "middle" stream to zip.
     * @param rights The "right" stream to zip.
     * @param combiner The function to combine "left", "middle" and "right" values.
     * @param  The type over which the "left" stream streams.
     * @param  The type over which the "middle" stream streams.
     * @param  The type over which the "right" stream streams.
     * @param  The type created by the combiner out of triples of "left", "middle" and "right" values, over which the resulting
     *           stream streams.
     * @return A stream of zipped values.
     */
    public static  Stream zip(Stream lefts, Stream middles, Stream rights, TriFunction combiner) {
        return StreamSupport.stream(TriZippingSpliterator.zipping(
                lefts.spliterator(),
                middles.spliterator(),
                rights.spliterator(),
                combiner), false);
    }

    private static boolean isSized(int characteristics) {
        return (characteristics & Spliterator.SIZED) != 0;
    }

    /**
     * Construct a stream which takes values from the source stream for as long as they meet the supplied condition, and stops
     * as soon as a value is encountered which does not meet the condition.
     * @param source The source stream.
     * @param condition The condition to apply to elements of the source stream.
     * @param  The type over which the stream streams.
     * @return A condition-bounded stream.
     */
    public static  Stream takeWhile(Stream source, Predicate condition) {
        return StreamSupport.stream(TakeWhileSpliterator.over(source.spliterator(), condition), false);
    }

    /**
     * Construct a stream which takes values from the source stream until one of them meets the supplied condition,
     * and then stops.
     * @param source The source stream.
     * @param condition The condition to apply to elements of the source stream.
     * @param  The type over which the stream streams.
     * @return A condition-bounded stream.
     */
    public static  Stream takeUntil(Stream source, Predicate condition) {
        return takeWhile(source, condition.negate());
    }

    /**
     * Construct a stream which skips values from the source stream for as long as they meet the supplied condition,
     * then streams every remaining value as soon as the first value is found which does not meet the condition.
     * @param source The source stream.
     * @param condition The condition to apply to elements of the source stream.
     * @param  The type over which the stream streams.
     * @return An element-skipping stream.
     */
    public static  Stream skipWhile(Stream source, Predicate condition) {
        return StreamSupport.stream(SkipUntilSpliterator.over(source.spliterator(), condition.negate()), false);
    }

    /**
     * Construct a stream which skips values from the source stream for as long as they do not meet the supplied condition,
     * then streams every remaining value as soon as the first value is found which does meet the condition.
     * @param source The source stream.
     * @param condition The condition to apply to elements of the source stream.
     * @param  The type over which the stream streams.
     * @return An element-skipping stream.
     */
    public static  Stream skipUntil(Stream source, Predicate condition) {
        return StreamSupport.stream(SkipUntilSpliterator.over(source.spliterator(), condition), false);
    }

    /**
     * Construct a stream which takes the seed value and applies the generator to create the next value, feeding each
     * new value back into the generator to create subsequent values. If the generator returns Optional.empty(), then
     * the stream has no more values.
     * @param seed The seed value.
     * @param generator The generator to use to create new values.
     * @param  The type over which the stream streams.
     * @return An unfolding stream.
     */
    public static  Stream unfold(T seed, Function> generator) {
        return StreamSupport.stream(UnfoldSpliterator.over(seed, generator), false);
    }

    /**
     * Constructs a stream that is a windowed view of the source stream of the size window size
     * with a default overlap of one item
     *
     * @param source The source stream
     * @param windowSize The window size
     * @param  The type over which to stream
     * @return A stream of lists representing the window
     */
    public static  Stream> windowed(Stream source, int windowSize){
        return windowed(source, windowSize, 1);
    }

    /**
     * Constructs a windowed stream where each element is a list of the window size
     * and the skip is the offset from the start of each window.
     *
     * For example, a skip of size 1 is a traditional window a la ([1, 2, 3], [2, 3, 4] ...).
     *
     * A skip of size 2 for a window of size 3 would look like
     * ([1, 2, 3], [3, 4, 5], ...)
     *
     * If the stream finishes, the last window is guaranteed to be of the desired size (possible data loss).
     *
     * A stream [1, 2, 3] with a size 2 and skip 2 will result in ([1,2])
     *
     * @param source The input stream
     * @param windowSize The window size
     * @param skip The skip amount between windows
     * @param  The type over which to stream
     * @return A stream of lists representing the windows
     */
    public static  Stream> windowed(Stream source, int windowSize, int skip){
        return windowed(source, windowSize, skip, false);
    }

    /**
     * Constructs a windowed stream where each element is a list of the window size
     * and the skip is the offset from the start of each window.
     *
     * For example, a skip of size 1 is a traditional window a la ([1, 2, 3], [2, 3, 4] ...).
     *
     * A skip of size 2 for a window of size 3 would look like
     * ([1, 2, 3], [3, 4, 5], ...)
     *
     * If the stream finishes, the last windows may have a window size lesser than the desired size. This is allowed
     * via the allowLesserSize parameter.
     *
     * @param source The input stream
     * @param windowSize The window size
     * @param skip The skip amount between windows
     * @param allowLesserSize Allow end of stream windows to have a lower size for completion
     * @param  The type over which to stream
     * @return A stream of lists representing the windows
     */
    public static  Stream> windowed(Stream source, int windowSize, int skip, boolean allowLesserSize){
        return StreamSupport.stream(WindowedSpliterator.over(source.spliterator(), windowSize, skip, allowLesserSize), false);
    }

    /**
     * Constructs a stream that represents grouped run using the default comparator. This means
     * that similar elements will get grouped into a list. I.e. given a list of [1,1,2,3,4,4]
     * you will get a stream of ([1,1], [2], [3], [4, 4])
     *
     * @param source The input stream
     * @param  The type over which to stream
     * @return A stream of lists of grouped runs
     */
    public static > Stream> groupRuns(Stream source){
        return groupRuns(source, Comparable::compareTo);
    }

    /**
     * Constructs a stream that represents grouped run using the default comparator. This means
     * that similar elements will get grouped into a list. I.e. given a list of [1,1,2,3,4,4]
     * you will get a stream of ([1,1], [2], [3], [4, 4])
     *
     * @param source The input stream
     * @param comparator The comparator to determine if neighbor elements are the same
     * @param  The type over which to stream
     * @return A stream of lists of grouped runs
     */
    public static  Stream> groupRuns(Stream source, Comparator comparator){
        return StreamSupport.stream(new GroupRunsSpliterator(source.spliterator(), comparator), false);
    }

    /**
     * Construct a stream which interleaves the supplied streams, picking items using the supplied selector function.
     *
     * The selector function will be passed an array containing one value from each stream, or null if that stream
     * has no more values, and must return the integer index of the value to accept. That value will become part of the
     * interleaved stream, and the source stream at that index will advance to the next value.
     *
     * See the {@link com.codepoetics.protonpack.selectors.Selectors} class for ready-made selectors for round-robin and sorted
     * item selection.
     * @param selector The selector function to use.
     * @param streams The streams to interleave.
     * @param  The type over which the interleaved streams stream.
     * @return An interleaved stream.
     */
    public static  Stream interleave(Function selector, Stream... streams) {
        Spliterator[] spliterators = (Spliterator[]) Stream.of(streams).map(s -> s.spliterator()).toArray(Spliterator[]::new);
        return StreamSupport.stream(InterleavingSpliterator.interleaving(spliterators, selector), false);
    }

    /**
     * Construct a stream which interleaves the supplied streams, picking items using the supplied selector function.
     *
     * The selector function will be passed an array containing one value from each stream, or null if that stream
     * has no more values, and must return the integer index of the value to accept. That value will become part of the
     * interleaved stream, and the source stream at that index will advance to the next value.
     *
     * See the {@link com.codepoetics.protonpack.selectors.Selectors} class for ready-made selectors for round-robin and sorted
     * item selection.
     * @param selector The selector function to use.
     * @param streams The streams to interleave.
     * @param  The type over which the interleaved streams stream.
     * @return An interleaved stream.
     */
    public static  Stream interleave(Function selector, List> streams) {
        Spliterator[] spliterators = (Spliterator[]) streams.stream().map(s -> s.spliterator()).toArray(Spliterator[]::new);
        return StreamSupport.stream(InterleavingSpliterator.interleaving(spliterators, selector), false);
    }

    /**
     * Construct a stream which merges together values from the supplied streams, somewhat in the manner of the
     * stream constructed by {@link com.codepoetics.protonpack.StreamUtils#zip(java.util.stream.Stream, java.util.stream.Stream, java.util.function.BiFunction)},
     * but for an arbitrary number of streams and using a merger to merge the values from multiple streams
     * into an accumulator.
     *
     * @param unitSupplier Supplies the initial "zero" or "unit" value for the accumulator.
     * @param merger Merges each item from the collection of values taken from the source streams into the accumulator value.
     * @param streams The streams to merge.
     * @param  The type over which the merged streams stream.
     * @param  The type of the accumulator, over which the constructed stream streams.
     * @return A merging stream.
     */
    public static  Stream merge(Supplier unitSupplier, BiFunction merger, Stream...streams) {
        Spliterator[] spliterators = (Spliterator[]) Stream.of(streams).map(s -> s.spliterator()).toArray(Spliterator[]::new);
        return StreamSupport.stream(MergingSpliterator.merging(spliterators, unitSupplier, merger), false);
    }

    /**
     * Construct a stream which merges together values from the supplied streams into lists of values, somewhat in the manner of the
     * stream constructed by {@link com.codepoetics.protonpack.StreamUtils#zip(java.util.stream.Stream, java.util.stream.Stream, java.util.function.BiFunction)},
     * but for an arbitrary number of streams.
     *
     * @param streams The streams to merge.
     * @param  The type over which the merged streams stream.
     * @return A merging stream of lists of T.
     */
    public static  Stream> mergeToList(Stream...streams) {
        return merge(ArrayList::new, (l, x) -> {
            l.add(x);
            return l;
        }, streams);
    }

    /**
     * Filter with the condition negated. Will throw away any members of the source stream that match the condition.
     *
     * @param source The source stream.
     * @param predicate The filter condition.
     * @param  The type over which the stream streams.
     * @return A rejecting stream.
     */
    public static  Stream reject(Stream source, Predicate predicate) {
        return source.filter(predicate.negate());
    }
    
    /**
     * Aggregates items from source stream into list of items while supplied predicate is true when evaluated on previous and current item.
     * Can by seen as streaming alternative to Collectors.groupingBy when source stream is sorted by key. 
     * @param source - source stream
     * @param predicate - predicate specifying boundary between groups of items
     * @param  The type over which the stream streams.
     * @return Stream of List<T> aggregated according to predicate
     */
    public static  Stream> aggregate(Stream source, BiPredicate predicate) {
        return StreamSupport.stream(new AggregatingSpliterator(source.spliterator(), 
                (a, e) -> a.isEmpty() || predicate.test(a.get(a.size() - 1), e)), false);
    }

    /**
     * Aggregates items from source stream into list of items with fixed size
     * @param source - source stream
     * @param size - size of the aggregated list
     * @param  The type over which the stream streams.
     * @return Stream of List<T> with all list of size @size with possible exception of last List<T>
     */
    public static  Stream> aggregate(Stream source, int size) {
        if (size <= 0) throw new IllegalArgumentException("Positive size expected, was: "+size);
        return StreamSupport.stream(new AggregatingSpliterator(source.spliterator(), (a, e) -> a.size() < size), false);
    }
    
    /**
     * Aggregates items from source stream. Similar to @aggregate, but uses different predicate, evaluated on all items aggregated so far
     * and next item from source stream.
     * @param source - source stream
     * @param predicate - predicate specifying boundary between groups of items
     * @param  The type over which the stream streams.
     * @return Stream of List<T> aggregated according to predicate
     */
    public static  Stream> aggregateOnListCondition(Stream source, BiPredicate, T> predicate) {
        return StreamSupport.stream(new AggregatingSpliterator(source.spliterator(), predicate), false);
    }

    /**
     * Converts nulls into an empty stream, and non-null values into a stream with one element.
     * @param nullable The nullable value to convert.
     * @param  The type of the value.
     * @return A stream of zero or one values.
     */
    public static  Stream streamNullable(T nullable) {
        return null == nullable ? Stream.empty() : Stream.of(nullable);
    }

    /**
     * Converts an Optional value to a stream of 0..1 values
     * @param optional source optional value
     * @param  The type of the optional value
     * @return Stream of a single item of type T or an empty stream
     */
    public static  Stream stream(Optional optional) {
        return optional.map(Stream::of).orElseGet(Stream::empty);
    }

    /**
     * Converts an Iterable into a Stream.
     * @param iterable The iterable to stream.
     * @param  The type of the iterable
     * @return Stream of the values returned by the iterable
     */
    public static  Stream stream(Iterable iterable) {
        return StreamSupport.stream(iterable.spliterator(), false);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy