dev.marksman.enhancediterables.EnhancedIterable Maven / Gradle / Ivy
Show all versions of enhanced-iterables Show documentation
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 super A, ? extends Boolean> 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 super A, ? extends Boolean> 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 super A, ? extends Boolean> 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 super A, ? extends B> 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, ? extends EnhancedIterable> partition(
Fn1 super A, ? extends CoProduct2> 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 extends NonEmptyFiniteIterable> 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, ? extends EnhancedIterable> span(Fn1 super A, ? extends Boolean> 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 extends EnhancedIterable> 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 super A, ? extends Boolean> 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);
}
}