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

com.shapesecurity.functional.data.ImmutableList Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright 2014 Shape Security, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.shapesecurity.functional.data;

import com.shapesecurity.functional.Effect;
import com.shapesecurity.functional.F;
import com.shapesecurity.functional.F2;
import com.shapesecurity.functional.Pair;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * An immutable singly linked list implementation. None of the operations in {@link ImmutableList}
 * changes the list itself. Therefore you can freely share the list in your system. 

This is a * "classical" list implementation that the list is only allowed to prepend and to remove the first * element efficiently. Therefore it is essentially equivalent to a stack.

It is either an empty * list, or a record that contains the first element (called "head") and a list that follows(called * "tail"). With the assumption that all the elements in the list are also immutable, the sharing of * the tails is possible.

For a data structure that allows O(1) concatenation, try {@link * ConcatList}. A BinaryTree can be converted into a List in O(n) time. * * @param The super type of all the elements. */ @CheckReturnValue public abstract class ImmutableList implements Iterable { @SuppressWarnings("StaticInitializerReferencesSubClass") private static final ImmutableList EMPTY = new Nil<>(); @Nullable private volatile Integer hashCode = null; /** * The length of the list. */ public final int length; // package local ImmutableList(int length) { super(); this.length = length; } /** * Creating List from. * * @param arrayList The {@link java.util.ArrayList} to construct the {@link ImmutableList} * from. * @param The type of the elements of the list. * @return a new {@link ImmutableList} that is comprised of all the elements in the {@link * java.util.ArrayList}. */ @Nonnull public static ImmutableList from(@Nonnull List arrayList) { // Manual expansion of tail recursion. ImmutableList l = empty(); int size = arrayList.size(); for (int i = size - 1; i >= 0; i--) { l = cons(arrayList.get(i), l); } return l; /* TODO: use Collection here if it doesn't incur performance penalties ImmutableList list = empty(); int size = collection.size(); for (A element : collection) { list = cons(element, list); } return list; */ } @Nonnull @Deprecated public static ImmutableList list(@Nonnull List arrayList) { return ImmutableList.from(arrayList); } /** * Prepends "cons" an head element to a {@link ImmutableList}. * * @param head The head element to be prepended to the {@link ImmutableList}. * @param tail The {@link ImmutableList} to be prepended to. * @param The super type of both the element and the {@link ImmutableList} * @return A {@link ImmutableList} that is comprised of the head then the tail. */ public static NonEmptyImmutableList cons(@Nonnull T head, @Nonnull ImmutableList tail) { return new NonEmptyImmutableList<>(head, tail); } // Construction @SuppressWarnings("unchecked") public static ImmutableList empty() { return (ImmutableList) EMPTY; } @SuppressWarnings("unchecked") @Deprecated public static ImmutableList nil() { return ImmutableList.empty(); } @SuppressWarnings("unchecked") @Deprecated public static ImmutableList list() { return ImmutableList.empty(); } /** * A helper constructor to create a {@link NonEmptyImmutableList}. * * @param head The first element * @param el rest of the elements * @param The type of the element * @return a NonEmptyImmutableList of type T. */ @Nonnull @SafeVarargs public static NonEmptyImmutableList of(@Nonnull T head, @Nonnull T... el) { if (el.length == 0) { return cons(head, ImmutableList.empty()); } NonEmptyImmutableList l = cons(el[el.length - 1], ImmutableList.empty()); for (int i = el.length - 2; i >= 0; i--) { l = cons(el[i], l); } return cons(head, l); } /** * A helper constructor to create a potentially empty {@link ImmutableList}. * * @param el Elements of the list * @param The type of elements * @return a ImmutableList of type A. */ @Nonnull @SafeVarargs public static ImmutableList from(@Nonnull A... el) { return fromBounded(el, 0, el.length); } @Nonnull @SafeVarargs @Deprecated public static NonEmptyImmutableList list(@Nonnull T head, @Nonnull T... el) { return ImmutableList.of(head, el); } /** * A helper constructor to create a potentially empty {@link ImmutableList} from part of an array. * * @param el Elements of the list * @param start The index to start conversion * @param end The index before which the conversion stops. end will not be used as * an index to access el * @param The type of elements * @return a ImmutableList of type A. */ @Nonnull public static ImmutableList fromBounded(@Nonnull A[] el, int start, int end) { if (end == start) { return empty(); } NonEmptyImmutableList l = cons(el[end - 1], ImmutableList.empty()); for (int i = end - 2; i >= start; i--) { l = cons(el[i], l); } return l; } protected abstract int calcHashCode(); @Override public final int hashCode() { // Manually expanded thunk Integer hashCodeCached = this.hashCode; if (hashCodeCached == null) { // This is safe because calcHashCode has no side-effects. int hc = this.calcHashCode(); this.hashCode = hc; return hc; } else { return hashCodeCached; } } /** * This function is provided by Iterable that can be used to avoid a creation * of an Iterator instance. * * @param action The action to be performed for each element */ @Override public void forEach(@Nonnull Consumer action) { // Manually expanded recursion. ImmutableList list = this; while (list instanceof NonEmptyImmutableList) { A head = ((NonEmptyImmutableList) list).head; action.accept(head); list = ((NonEmptyImmutableList) list).tail; } } @Override public Iterator iterator() { return new Iterator() { private ImmutableList curr = ImmutableList.this; @Override public boolean hasNext() { return !this.curr.isEmpty(); } @Override public A next() { if (this.curr instanceof NonEmptyImmutableList) { NonEmptyImmutableList nel = (NonEmptyImmutableList) this.curr; this.curr = nel.tail; return nel.head; } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } // Methods /** * Prepend an element to the list. * * @param left The element to prepend. * @return A list with left as the first element followed by this. */ @Nonnull public final NonEmptyImmutableList cons(@Nonnull A left) { return cons(left, this); } /** * Classic "foldl" operation on the {@link ImmutableList}. * * @param f The function. * @param init The initial value. * @param The type of the result of the folding. * @return The result of the folder. * @see http://en.wikipedia.org/wiki/Fold_ * (higher-order_function) */ @Nonnull public abstract B foldLeft(@Nonnull F2 f, @Nonnull B init); /** * Classic "foldr" operation on the {@link ImmutableList}. * * @param f The function. * @param init The initial value. * @param The type of the result of the folding. * @return The result of the folder. * @see http://en.wikipedia.org/wiki/Fold_ * (higher-order_function) */ @Nonnull public abstract B foldRight(@Nonnull F2 f, @Nonnull B init); /** * Returns the head of the {@link ImmutableList}. * * @return Maybe.of(head of the list), or Maybe.empty() if the list is empty. */ @Nonnull public abstract Maybe maybeHead(); /** * Returns the last of the {@link ImmutableList}. * * @return Maybe.of(last of the list), or Maybe.empty() if the list is empty. */ @Nonnull public abstract Maybe maybeLast(); /** * Returns the tail of the {@link ImmutableList}. * * @return Maybe.of(tail of the list), or Maybe.empty() if the list is empty. */ @Nonnull public abstract Maybe> maybeTail(); /** * Returns the init of the {@link ImmutableList}. The init of a List is defined as the rest of * List removing the last element. * * @return Maybe.of(init of the list), or Maybe.empty() if the list is empty. */ @Nonnull public abstract Maybe> maybeInit(); /** * Returns a new list of elements when applying f to an element returns true. * * @param f The "predicate" function. * @return A new list of elements that satisfies the predicate. */ @Nonnull public abstract ImmutableList filter(@Nonnull F f); /** * Applies the f function to each of the elements of the list and collect the * result. It will be a new list with the same length of the original one. * * @param f The function to apply. * @param The type of the new {@link ImmutableList}. * @return The new {@link ImmutableList} containing the result. */ @Nonnull public abstract ImmutableList map(@Nonnull F f); /** * Applies the f function to each of the elements of the list and collect the * result. This method also provides an extra index parameter to f function as the * first parameter. * * @param f The function to apply. * @param The type of the new {@link ImmutableList}. * @return The new {@link ImmutableList} containing the result. */ @Nonnull public abstract ImmutableList mapWithIndex(@Nonnull F2 f); /** * The the first n elements of the list and create a new List of them. If the * original list contains less than n elements, returns a copy of the original * List. * * @param n The number of elements to take. * @return A new list containing at most n elements that are the first elements of * the original list. */ @Nonnull public abstract ImmutableList take(int n); /** * Removes the first n elements of the list and return the rest of the List by * reference. If the original list contains less than n elements, returns an empty * list as if it is returned by {@link #empty}. * * @param n The number of elements to skip. * @return A shared list containing at most n elements removed from the original * list. */ @Nonnull public abstract ImmutableList drop(int n); /** * Specialize this type to be a {@link NonEmptyImmutableList} if possible. * * @return Returns is Maybe.of(this) if this is indeed non-empty. Otherwise * returns Maybe.empty(). */ @Nonnull public abstract Maybe> toNonEmptyList(); /** * Deconstruct the list in to its head and tail, and feed them into another function * f. * * @param f The function to receive the head and tail if they exist. * @param The return type of f * @return If the list is an non-empty list, returns Maybe.of(f(head, tail)); * otherwise returns Maybe.empty(). */ @Nonnull public abstract Maybe decons(@Nonnull F2, B> f); /** * Takes another list and feeds the elements of both lists to a function at the same pace, then * collects the result and forms another list. Stops once either of the two lists came to an * end.

Another way to visualize this operation is to imagine this operation as if it's * zipping a zipper. taking two lists of things, merge them one by one, and collect the * results. * * @param f The function to apply * @param list the other list to zip with this. * @param The type of the element of the other list. * @param The type of the result of the merging function. * @return The return type of the merging function. */ @Nonnull public abstract ImmutableList zipWith(@Nonnull F2 f, @Nonnull ImmutableList list); /** * Converts this list into an array.

Due to type erasure, the type of the resulting array * has to be determined at runtime. Fortunately, you can create a zero length array and this * method can create an large enough array to contain all the elements. If the given array is * large enough, this method will put elements in it. * * @param target The target array. * @return The array that contains the elements. It may or may not be the same reference of * target. */ @SuppressWarnings("unchecked") @Nonnull public final A[] toArray(@Nonnull A[] target) { int length = this.length; if (target.length < length) { // noinspection unchecked target = (A[]) Array.newInstance(target.getClass().getComponentType(), length); } ImmutableList l = this; for (int i = 0; i < length; i++) { target[i] = ((NonEmptyImmutableList) l).head; l = ((NonEmptyImmutableList) l).tail; } return target; } /** * Converts this list into an array.

Due to type erasure, the type of the resulting array * has to be determined at runtime. Fortunately, you can create a zero length array and this * method can create an large enough array to contain all the elements. If the given array is * large enough, this method will put elements in it. * * @param constructor The constructor of the target array * @return The array that contains the elements. It may or may not be the same reference of * target. */ @SuppressWarnings("unchecked") @Nonnull public final A[] toArray(@Nonnull F constructor) { int length = this.length; A[] target = constructor.apply(length); return this.toArray(target); } /** * Runs an effect function across all the elements. * * @param f The Effect function. * @deprecated Use {@link #forEach(Consumer)} instead. */ @Deprecated public final void foreach(@Nonnull Effect f) { this.forEach(f::e); } public abstract boolean isEmpty(); /** * Creates a list with the content of the current list followed by another list. If the current * list is empty, simply return the second one. * * @param defaultClause The list to concatenate with. It will be reused as part of the returned * list. * @param The type of the resulting list. * @return The concatenation of the two lists. */ @Nonnull public abstract ImmutableList append(@Nonnull ImmutableList defaultClause); /** * Tests all the elements in the {@link ImmutableList} with predicate f until it * finds the element or reaches the end, then returns whether an element has been found or not. * * @param f The predicate. * @return Whether an elements satisfies the predicate f. */ public abstract boolean exists(@Nonnull F f); /** * Tests using object identity whether this list contains the element a. *

* WARNING: object identity is tests using the == operator. * To test if object exists by equality, use {@link #exists(F)} * * @param a An element. * @return Whether this list contains the element a. */ public abstract boolean contains(@Nonnull A a); /** * Separates the list into a pair of lists such that 1. the concatenation of the lists is equal * to this; 2. The first list is the longest list that every element of the list * fails the predicate f. * * @param f The predicate. * @return The pair. */ @Nonnull public abstract Pair, ImmutableList> span(@Nonnull F f); /** * A synonym of {@link #flatMap}. * * @param f The function. * @param The type of result function. * @return The result of bind. */ @Nonnull public final ImmutableList bind(@Nonnull F> f) { return this.flatMap(f); } /** * Apply f to each element of this list to get a list of lists (of not necessarily * the same type), then concatenate all these lists to get a single list.

This operation can * be thought of as a generalization of {@link #map} and {@link #filter}, which, instead of * keeping the number of elements in the list, changes the number and type of the elements in an * customizable way but keeps the original order.

This operation can also be thought of as * an assembly line that takes one stream of input and returns another stream of output, but not * necessarily of the same size, type or number.

This operation is often called "bind" or * ">>=" of a monad in pure functional programming context. * * @param f The function to expand the list element. * @param The type of the result list. * @return The result list. */ @Nonnull public abstract ImmutableList flatMap(@Nonnull F> f); public final boolean isNotEmpty() { return !this.isEmpty(); } /** * Tests the elements of the list with a predicate f and returns the first one that * satisfies the predicate without testing the rest of the list. * * @param f The predicate. * @return Maybe.of(the found element) if an element is found or * Maybe.empty() if none is found. */ @Nonnull public final Maybe find(@Nonnull F f) { ImmutableList self = this; while (self instanceof NonEmptyImmutableList) { NonEmptyImmutableList selfNel = (NonEmptyImmutableList) self; boolean result = f.apply(selfNel.head); if (result) { return Maybe.of(selfNel.head); } self = selfNel.tail(); } return Maybe.empty(); } /** * Tests the elements of the list with a predicate f and returns the index of the first one that * satisfies the predicate without testing the rest of the list. * * @param f The predicate. * @return Maybe.of(the found element index) if an element is found or * Maybe.empty() if none is found. */ @Nonnull public final Maybe findIndex(@Nonnull F f) { ImmutableList self = this; int i = 0; while (self instanceof NonEmptyImmutableList) { NonEmptyImmutableList selfNel = (NonEmptyImmutableList) self; if (f.apply(selfNel.head)) { return Maybe.of(i); } self = selfNel.tail(); ++i; } return Maybe.empty(); } /** * Run f on each element of the list and return the result immediately if it is a * Maybe.of. Other wise return Maybe.empty() * * @param f The predicate. * @param The type of the result of the mapping function. * @return Maybe.of(the found element) if an element is found or * Maybe.empty() if none is found. */ @Nonnull public final Maybe findMap(@Nonnull F> f) { ImmutableList self = this; while (self instanceof NonEmptyImmutableList) { NonEmptyImmutableList selfNel = (NonEmptyImmutableList) self; Maybe result = f.apply(selfNel.head); if (result.isJust()) { return result; } self = selfNel.tail(); } return Maybe.empty(); } /** * Creats a new list with all the elements but those satisfying the predicate. * * @param f The predicate. * @return A new list of filtered elements. */ @Nonnull public abstract ImmutableList removeAll(@Nonnull F f); /** * Reverses the list in linear time. * * @return Reversed list. */ @Nonnull public abstract ImmutableList reverse(); /** * Patches the current list. Patching a list first takes the first index elements * then concatenates it with replacements and then concatenates it with the * original list dropping index + patchLength elements.

A visualization of this * operation is to replace the patchLength elements in the list starting from * index with a list of new elements given by replacements. * * @param index The index to start patching. * @param patchLength The length to patch. * @param replacements The replacements of the patch. * @param The type of the replacements. It must be A or a subtype of A. * @return The patched list. */ @Nonnull public ImmutableList patch(int index, int patchLength, @Nonnull ImmutableList replacements) { return this.take(index).append(replacements).append(this.drop(index + patchLength)); } /** * mapAccumL performs {@link #map} and {@link #foldLeft} method at the same time. * It is similar to {@link #foldLeft}, but instead of returning the new accumulation value, it * also allows the user to return an extra value which will be collected and returned. * * @param f The accumulation function. * @param acc The initial value of the fold part. * @param The type of the initial value. * @param The type of the result of map. * @return A pair of the accumulation value and a mapped list. */ @Nonnull public abstract Pair> mapAccumL(@Nonnull F2> f, @Nonnull B acc); /** * Get the indexth element of the list. It is comparable to the [] * operator for array but instead of returning the element, it returns an Maybe to * indicate whether the element can be found or not. * * @param index The index. * @return Maybe.of(found element)if the element can be retrieved; or * Maybe.empty() if index out of range(). */ @Nonnull public final Maybe index(int index) { ImmutableList l = this; if (index < 0) { return Maybe.empty(); } while (index > 0) { if (l.isEmpty()) { return Maybe.empty(); } index--; l = ((NonEmptyImmutableList) l).tail; } return l.maybeHead(); } @Nonnull public abstract ImmutableSet uniqByEquality(); @Nonnull public abstract ImmutableSet uniqByIdentity(); @Nonnull public abstract ImmutableSet uniqByEqualityOn(@Nonnull F f); @Override public abstract boolean equals(Object o); @Nonnull public Pair, ImmutableList> partition(@Nonnull Predicate predicate) { @SuppressWarnings("unchecked") A[] result = (A[]) new Object[this.length]; int[] l = new int[]{0}; int[] r = new int[]{this.length}; this.forEach(a -> { if (predicate.test(a)) { result[l[0]++] = a; } else { result[--r[0]] = a; } }); ImmutableList right = empty(); for (int i = l[0]; i < this.length; i++) { right = right.cons(result[i]); } return Pair.of(fromBounded(result, 0, l[0]), right); } }