package com.codepoetics.protonpack; import com.codepoetics.protonpack.functions.TriFunction; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * 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 <T> The type over which the source stream streams. * @return A stream of indexed values. */ public static <T> Stream<Indexed<T>> zipWithIndex(Stream<T> 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 <L> The type over which the "left" stream streams. * @param <R> The type over which the "right" stream streams. * @param <O> 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 <L, R, O> Stream<O> zip(Stream<L> lefts, Stream<R> rights, BiFunction<L, R, O> 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 <L> The type over which the "left" stream streams. * @param <M> The type over which the "middle" stream streams. * @param <R> The type over which the "right" stream streams. * @param <O> 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 <L, M, R, O> Stream<O> zip(Stream<L> lefts, Stream<M> middles, Stream<R> rights, TriFunction<L, M, R, O> combiner) { return StreamSupport.stream(TriZippingSpliterator.zipping( lefts.spliterator(), middles.spliterator(), rights.spliterator(), combiner), false); } /** * Zip together a list of streams until one of them runs out of values. * Each tuple of values is combined into a single value using the supplied combiner function. * @param streams The streams to zip. * @param combiner The function to combine the values. * @param <T> The type over which the streams stream. * @param <O> The type created by the combiner out of groups of values, over * which the resulting stream streams. * @return A stream of zipped values. */ public static <T, O> Stream<O> zip(List<Stream<T>> streams, Function<List<T>, O> combiner) { List<Spliterator<T>> spliterators = streams.stream().map(Stream::spliterator).collect(Collectors.toList()); return StreamSupport.stream(ListZippingSpliterator.zipping(spliterators, 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 <T> The type over which the stream streams. * @return A condition-bounded stream. */ public static <T> Stream<T> takeWhile(Stream<T> source, Predicate<T> condition) { return StreamSupport.stream(TakeWhileSpliterator.over(source.spliterator(), condition), false); } /** * Construct a stream which takes values from the source stream until but including the first value that 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 <T> The type over which the stream streams. * @return A condition-bounded stream. */ public static <T> Stream<T> takeWhileInclusive(Stream<T> source, Predicate<T> condition) { return StreamSupport.stream(TakeWhileSpliterator.overInclusive(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 <T> The type over which the stream streams. * @return A condition-bounded stream. */ public static <T> Stream<T> takeUntil(Stream<T> source, Predicate<T> condition) { return takeWhile(source, condition.negate()); } /** * Construct a stream which takes values from the source stream until but including the first value that is * encountered which meets the supplied condition. * @param source The source stream. * @param condition The condition to apply to elements of the source stream. * @param <T> The type over which the stream streams. * @return A condition-bounded stream. */ public static <T> Stream<T> takeUntilInclusive(Stream<T> source, Predicate<T> condition) { return takeWhileInclusive(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 <T> The type over which the stream streams. * @return An element-skipping stream. */ public static <T> Stream<T> skipWhile(Stream<T> source, Predicate<T> condition) { return StreamSupport.stream(SkipUntilSpliterator.over(source.spliterator(), condition.negate()), false); } /** * Construct a stream which skips values from the source stream up until but including the first value is found that * does not meet the given condition. * @param source The source stream. * @param condition The condition to apply to elements of the source stream. * @param <T> The type over which the stream streams. * @return An element-skipping stream. */ public static <T> Stream<T> skipWhileInclusive(Stream<T> source, Predicate<T> condition) { return StreamSupport.stream(SkipUntilSpliterator.overInclusive(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 <T> The type over which the stream streams. * @return An element-skipping stream. */ public static <T> Stream<T> skipUntil(Stream<T> source, Predicate<T> condition) { return StreamSupport.stream(SkipUntilSpliterator.over(source.spliterator(), condition), false); } /** * Construct a stream which skips values from the source stream up until but including the first value is found that * satisfies the given condition. * @param source The source stream. * @param condition The condition to apply to elements of the source stream. * @param <T> The type over which the stream streams. * @return An element-skipping stream. */ public static <T> Stream<T> skipUntilInclusive(Stream<T> source, Predicate<T> condition) { return StreamSupport.stream(SkipUntilSpliterator.overInclusive(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 <T> The type over which the stream streams. * @return An unfolding stream. */ public static <T> Stream<T> unfold(T seed, Function<T, Optional<T>> 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 <T> The type over which to stream * @return A stream of lists representing the window */ public static <T> Stream<List<T>> windowed(Stream<T> 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 <T> The type over which to stream * @return A stream of lists representing the windows */ public static <T> Stream<List<T>> windowed(Stream<T> 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 <T> The type over which to stream * @return A stream of lists representing the windows */ public static <T> Stream<List<T>> windowed(Stream<T> 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 <T> The type over which to stream * @return A stream of lists of grouped runs */ public static <T extends Comparable<T>> Stream<List<T>> groupRuns(Stream<T> source){ return groupRuns(source, Comparable::compareTo); } /** * Constructs a stream that represents grouped run using the custom 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 <T> The type over which to stream * @return A stream of lists of grouped runs */ public static <T> Stream<List<T>> groupRuns(Stream<T> source, Comparator<T> comparator){ return StreamSupport.stream(new GroupRunsSpliterator<T>(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 <T> The type over which the interleaved streams stream. * @return An interleaved stream. */ public static <T> Stream<T> interleave(Function<T[], Integer> selector, Stream<T>... streams) { Spliterator<T>[] spliterators = (Spliterator<T>[]) 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 <T> The type over which the interleaved streams stream. * @return An interleaved stream. */ public static <T> Stream<T> interleave(Function<T[], Integer> selector, List<Stream<T>> streams) { Spliterator<T>[] spliterators = (Spliterator<T>[]) 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 <T> The type over which the merged streams stream. * @param <O> The type of the accumulator, over which the constructed stream streams. * @return A merging stream. */ public static <T, O> Stream<O> merge(Supplier<O> unitSupplier, BiFunction<O, T, O> merger, Stream<T>...streams) { Spliterator<T>[] spliterators = (Spliterator<T>[]) 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 <T> The type over which the merged streams stream. * @return A merging stream of lists of T. */ public static <T> Stream<List<T>> mergeToList(Stream<T>...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 <T> The type over which the stream streams. * @return A rejecting stream. */ public static <T> Stream<T> reject(Stream<T> source, Predicate<? super T> 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 <T> The type over which the stream streams. * @return Stream of List<T> aggregated according to predicate */ public static <T> Stream<List<T>> aggregate(Stream<T> source, BiPredicate<T, T> predicate) { return StreamSupport.stream(new AggregatingSpliterator<T>(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 <T> 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 <T> Stream<List<T>> aggregate(Stream<T> source, int size) { if (size <= 0) throw new IllegalArgumentException("Positive size expected, was: "+size); return StreamSupport.stream(new AggregatingSpliterator<T>(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 <T> The type over which the stream streams. * @return Stream of List<T> aggregated according to predicate */ public static <T> Stream<List<T>> aggregateOnListCondition(Stream<T> source, BiPredicate<List<T>, T> predicate) { return StreamSupport.stream(new AggregatingSpliterator<T>(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 <T> The type of the value. * @return A stream of zero or one values. * @deprecated use {@link StreamUtils#ofSingleNullable(Object)} */ public static <T> Stream<T> streamNullable(T nullable) { return ofSingleNullable(nullable); } // can't be named ofNullable() due to overloading difficulty with erasure of generic type /** * Converts nulls into an empty stream, and non-null values into a stream with one element. * @param nullable The nullable value to convert. * @param <T> The type of the value. * @return A stream of zero or one values. */ public static <T> Stream<T> ofSingleNullable(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 <T> The type of the optional value * @return Stream of a single item of type T or an empty stream */ public static <T> Stream<T> stream(Optional<T> optional) { return optional.map(Stream::of).orElseGet(Stream::empty); } /** * Converts an Iterable into a Stream. * @param iterable The iterable to stream. * @param <T> The type of the iterable * @return Stream of the values returned by the iterable */ public static <T> Stream<T> stream(Iterable<T> iterable) { return StreamSupport.stream(iterable.spliterator(), false); } /** * Converts a nullable Iterable into a Stream. * @param iterable The iterable to stream. * @param <T> The type of the iterable * @return Stream of the values returned by the iterable, or an empty stream if the iterable is null */ public static <T> Stream<T> ofNullable(Iterable<T> iterable) { return null == iterable ? Stream.empty() : stream(iterable); } /** * Converts nullable int array into an empty stream, and non-null array into a stream. * @param nullable The nullable array to convert. * @return A stream of zero or more values. */ public static IntStream ofNullable(int[] nullable) { return null == nullable ? IntStream.empty() : Arrays.stream(nullable); } /** * Converts nullable long array into an empty stream, and non-null array into a stream. * @param nullable The nullable array to convert. * @return A stream of zero or more values. */ public static LongStream ofNullable(long[] nullable) { return null == nullable ? LongStream.empty() : Arrays.stream(nullable); } /** * Converts nullable float array into an empty stream, and non-null array into a stream. * @param nullable The nullable array to convert. * @return A stream of zero or more values. */ public static DoubleStream ofNullable(double[] nullable) { return null == nullable ? DoubleStream.empty() : Arrays.stream(nullable); } /** * Converts nullable array into an empty stream, and non-null array into a stream. * @param nullable The nullable array to convert. * @param <T> The type of the value. * @return A stream of zero or more values. */ public static <T> Stream<T> ofNullable(T[] nullable) { return null == nullable ? Stream.empty() : Stream.of(nullable); } }