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

dev.marksman.enhancediterables.EnhancedIterable Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
package dev.marksman.enhancediterables;

import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.coproduct.CoProduct2;
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.lambda.functions.Fn0;
import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.Fn2;
import com.jnape.palatable.lambda.functions.builtin.fn1.Tails;
import com.jnape.palatable.lambda.functions.builtin.fn2.*;
import com.jnape.palatable.lambda.functions.builtin.fn3.ZipWith;
import com.jnape.palatable.lambda.functor.Functor;
import com.jnape.palatable.lambda.monoid.builtin.Concat;

import java.util.Collection;

import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
import static dev.marksman.enhancediterables.EnhancedIterables.finiteIterable;
import static dev.marksman.enhancediterables.EnhancedIterables.nonEmptyIterableOrThrow;
import static dev.marksman.enhancediterables.ProtectedIterator.protectedIterator;
import static dev.marksman.enhancediterables.Validation.*;
import static java.util.Objects.requireNonNull;

/**
 * An {@code Iterable} with some additional methods.
 * 

* May be infinite, finite, or empty. *

* Any {@link Iterable} can be upgraded to an {@code EnhancedIterable} by calling {@link EnhancedIterable#enhance(Iterable)}}. * * @param the element type */ public interface EnhancedIterable extends Iterable, Functor> { /** * Lazily appends an element to the end of this {@code EnhancedIterable}, yielding a new {@code NonEmptyIterable}. * * @param element the element to append * @return a NonEmptyIterable<A> */ default NonEmptyIterable append(A element) { return nonEmptyIterableOrThrow(Snoc.snoc(element, this)); } /** * Lazily concatenates another {@code Iterable} to the end of this {@code EnhancedIterable}, * yielding a new {@code EnhancedIterable}. * * @param other the other {@link Iterable} * @return an EnhancedIterable<A> */ default EnhancedIterable concat(Iterable other) { requireNonNull(other); return enhance(Concat.concat(this, other)); } /** * Lazily concatenates a {@code NonEmptyIterable} to the end of this {@code EnhancedIterable}, * yielding a new {@code NonEmptyIterable}. * * @param other a {@link NonEmptyIterable} * @return a {@code NonEmptyIterable} */ default NonEmptyIterable concat(NonEmptyIterable other) { requireNonNull(other); return nonEmptyIterableOrThrow(Concat.concat(this, other)); } /** * Returns a new {@code EnhancedIterable} that drops the first {@code count} elements of this {@code EnhancedIterable}. * * @param count the number of elements to drop from this {@code EnhancedIterable}. * Must be >= 0. * May exceed size of this {@code EnhancedIterable}, in which case, the result will be an * empty {@code EnhancedIterable}. * @return an {@code EnhancedIterable} */ default EnhancedIterable drop(int count) { validateDrop(count); return enhance(Drop.drop(count, this)); } /** * Returns a new {@code EnhancedIterable} that skips the first contiguous group of elements of this * {@code EnhancedIterable} that satisfy a predicate. *

* Iteration begins at the first element for which the predicate evaluates to false. * * @param predicate a predicate; should be referentially transparent and not have side-effects * @return an EnhancedIterable<A> */ default EnhancedIterable dropWhile(Fn1 predicate) { requireNonNull(predicate); return enhance(DropWhile.dropWhile(predicate, this)); } /** * Returns a new {@code EnhancedIterable} that contains all elements of this {@code EnhancedIterable} * that satisfy a predicate. * * @param predicate a predicate; should be referentially transparent and not have side-effects * @return an EnhancedIterable<A> */ default EnhancedIterable filter(Fn1 predicate) { requireNonNull(predicate); return enhance(Filter.filter(predicate).apply(this)); } /** * Finds the first element of this {@code EnhancedIterable} that satisfies a predicate, if any. * * @param predicate a predicate; not null * @return an element wrapped in a {@link Maybe#just} if a matching element is found; * {@link Maybe#nothing} otherwise. */ default Maybe find(Fn1 predicate) { requireNonNull(predicate); return Find.find(predicate, this); } /** * Returns a new {@code EnhancedIterable} by applying a function to all elements of this {@code EnhancedIterable}. * * @param f a function from {@code A} to {@code B}. * This function should be referentially transparent and not perform side-effects. * It may be called zero or more times for each element. * @param the type returned by {@code f} * @return an EnhancedIterable<B> */ default EnhancedIterable fmap(Fn1 f) { requireNonNull(f); return enhance(Map.map(f, this)); } /** * Returns a new {@code EnhancedIterable} with the provided separator value injected between each value of this * {@code EnhancedIterable}. *

* If this {@code EnhancedIterable} contains fewer than two elements, it is left untouched. * * @param separator the separator value * @return an EnhancedIterable<A> */ default EnhancedIterable intersperse(A separator) { return enhance(Intersperse.intersperse(separator, this)); } /** * Tests whether this {@code EnhancedIterable} is empty. * * @return true if this {@code EnhancedIterable} contains no elements, false otherwise */ default boolean isEmpty() { return !iterator().hasNext(); } /** * Partitions this {@code EnhancedIterable} given a disjoint mapping function. *

* Note that while the returned tuple must be constructed eagerly, the left and right iterables contained therein * are both lazy, so comprehension over infinite iterables is supported. * * @param function the mapping function * @param The output left Iterable element type, as well as the CoProduct2 A type * @param The output right Iterable element type, as well as the CoProduct2 B type * @return a Tuple2<EnhancedIterable<B>, EnhancedIterable<C>> */ default Tuple2, ? extends EnhancedIterable> partition( Fn1> function) { requireNonNull(function); Tuple2, Iterable> partitionResult = Partition.partition(function, this); return tuple(enhance(partitionResult._1()), enhance(partitionResult._2())); } /** * Lazily prepends an element to the front of this {@code EnhancedIterable}, yielding a new {@code NonEmptyIterable}. * * @param element the element to prepend * @return a NonEmptyIterable<A> */ default NonEmptyIterable prepend(A element) { return NonEmptyIterable.nonEmptyIterable(element, this); } /** * Returns a new {@code EnhancedIterable} with the provided separator value injected before each value of this * {@code EnhancedIterable}. *

* If this {@code EnhancedIterable} is empty, it is left untouched. * * @param separator the separator value * @return an EnhancedIterable<A> */ default EnhancedIterable prependAll(A separator) { return enhance(PrependAll.prependAll(separator, this)); } /** * "Slide" a window of {@code k} elements across the {@code EnhancedIterable} by one element at a time. *

* Example: * * EnhancedIterable.of(1, 2, 3, 4, 5).slide(2); // [[1, 2], [2, 3], [3, 4], [4, 5]] * * @param k the number of elements in the sliding window. Must be >= 1. * @return an {@code EnhancedIterable>} */ default EnhancedIterable> slide(int k) { validateSlide(k); return enhance(Map.map(EnhancedIterables::nonEmptyFiniteIterableOrThrow, Slide.slide(k, this))); } /** * Returns a {@code Tuple2} where the first slot is the front contiguous elements of this * {@code EnhancedIterable} matching a predicate and the second slot is all the remaining elements. * * @param predicate a predicate; should be referentially transparent and not have side-effects * @return a Tuple2<EnhancedIterable<B>, EnhancedIterable<C>> */ default Tuple2, ? extends EnhancedIterable> span(Fn1 predicate) { requireNonNull(predicate); Tuple2, Iterable> spanResult = Span.span(predicate).apply(this); return tuple(enhance(spanResult._1()), enhance(spanResult._2())); } /** * Returns a {@code NonEmptyIterable} containing all of the subsequences of tail * elements of this {@code EnhancedIterable}, ordered by size, starting with the full list. * Example: * * EnhancedIterable.of(1, 2, 3).tails(); // [[1, 2, 3], [2, 3], [3], []] * * @return a {@code NonEmptyIterable>} */ default NonEmptyIterable> tails() { return nonEmptyIterableOrThrow(Map.map(EnhancedIterable::enhance, Tails.tails(this))); } /** * Returns a new {@code FiniteIterable} that takes the first {@code count} elements of this {@code EnhancedIterable}. * * @param count the number of elements to take from this {@code EnhancedIterable}. * Must be >= 0. * May exceed size of this {@code EnhancedIterable}, in which case, the result will contain * as many elements available. * @return a {@code FiniteIterable} */ default FiniteIterable take(int count) { validateTake(count); return finiteIterable(Take.take(count, this)); } default EnhancedIterable takeWhile(Fn1 predicate) { requireNonNull(predicate); return enhance(TakeWhile.takeWhile(predicate, this)); } default A[] toArray(Class arrayType) { requireNonNull(arrayType); return ToArray.toArray(arrayType).apply(this); } default > C toCollection(Fn0 cSupplier) { requireNonNull(cSupplier); return ToCollection.toCollection(cSupplier).apply(this); } default EnhancedIterable zipWith(Fn2 fn, Iterable other) { requireNonNull(fn); requireNonNull(other); return enhance(ZipWith.zipWith(fn, this, other)); } default FiniteIterable zipWith(Fn2 fn, FiniteIterable other) { requireNonNull(fn); requireNonNull(other); return EnhancedIterables.finiteIterable(ZipWith.zipWith(fn, this, other)); } default FiniteIterable zipWith(Fn2 fn, Collection other) { requireNonNull(fn); requireNonNull(other); return EnhancedIterables.finiteIterable(ZipWith.zipWith(fn, this, other)); } static EnhancedIterable enhance(Iterable underlying) { requireNonNull(underlying); if (underlying instanceof EnhancedIterable) { return (EnhancedIterable) underlying; } else { return () -> protectedIterator(underlying.iterator()); } } @SafeVarargs static ImmutableNonEmptyFiniteIterable of(A first, A... more) { return EnhancedIterables.of(first, more); } }