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 java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

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

import org.jetbrains.annotations.NotNull;

/**
 * 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. */ public abstract class ImmutableList implements Iterable { private static final ImmutableList NIL = new Nil<>(); @NotNull private final Thunk hashCodeThunk = Thunk.from(this::calcHashCode); /** * 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}. */ @NotNull public static ImmutableList from(@NotNull List arrayList) { // Manual expansion of tail recursion. ImmutableList l = nil(); 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 = nil(); int size = collection.size(); for (A element : collection) { list = cons(element, list); } return list; */ } /** * 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(@NotNull T head, @NotNull ImmutableList tail) { return new NonEmptyImmutableList<>(head, tail); } // Construction @SuppressWarnings("unchecked") public static ImmutableList nil() { return (ImmutableList) NIL; } @SuppressWarnings("unchecked") public static ImmutableList list() { return (ImmutableList) NIL; } /** * 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. */ @NotNull @SafeVarargs public static NonEmptyImmutableList list(@NotNull T head, @NotNull T... el) { if (el.length == 0) { return cons(head, ImmutableList.nil()); } NonEmptyImmutableList l = cons(el[el.length - 1], ImmutableList.nil()); 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. */ @NotNull @SafeVarargs public static ImmutableList from(@NotNull A... el) { return fromBounded(el, 0, el.length); } /** * 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. */ @NotNull public static ImmutableList fromBounded(@NotNull A[] el, int start, int end) { if (end == start) { return nil(); } NonEmptyImmutableList l = cons(el[end - 1], ImmutableList.nil()); for (int i = end - 2; i >= start; i--) { l = cons(el[i], l); } return l; } protected abstract int calcHashCode(); @Override public final int hashCode() { return this.hashCodeThunk.get(); } @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.isEmpty()) { throw new NoSuchElementException(); } A head = this.curr.maybeHead().just(); this.curr = this.curr.maybeTail().just(); return head; } @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. */ @NotNull public final NonEmptyImmutableList cons(@NotNull 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) */ @NotNull public abstract B foldLeft(@NotNull F2 f, @NotNull 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) */ @NotNull public abstract B foldRight(@NotNull F2 f, @NotNull B init); /** * Returns the head of the {@link ImmutableList}. * * @return Maybe.just(head of the list), or Maybe.nothing() if the list is empty. */ @NotNull public abstract Maybe maybeHead(); /** * Returns the last of the {@link ImmutableList}. * * @return Maybe.just(last of the list), or Maybe.nothing() if the list is empty. */ @NotNull public abstract Maybe maybeLast(); /** * Returns the tail of the {@link ImmutableList}. * * @return Maybe.just(tail of the list), or Maybe.nothing() if the list is empty. */ @NotNull 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.just(init of the list), or Maybe.nothing() if the list is empty. */ @NotNull 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. */ @NotNull public abstract ImmutableList filter(@NotNull 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. */ @NotNull public abstract ImmutableList map(@NotNull 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. */ @NotNull public abstract ImmutableList mapWithIndex(@NotNull 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. */ @NotNull 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 #nil}. * * @param n The number of elements to skip. * @return A shared list containing at most n elements removed from the original * list. */ @NotNull public abstract ImmutableList drop(int n); /** * Specialize this type to be a {@link NonEmptyImmutableList} if possible. * * @return Returns is Maybe.just(this) if this is indeed non-empty. Otherwise * returns Maybe.nothing(). */ @NotNull 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.just(f(head, tail)); * otherwise returns Maybe.nothing(). */ @NotNull public abstract Maybe decons(@NotNull 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. */ @NotNull public abstract ImmutableList zipWith(@NotNull F2 f, @NotNull 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") @NotNull public final A[] toArray(@NotNull 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] = l.maybeHead().just(); l = l.maybeTail().just(); } 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") @NotNull public final A[] toArray(@NotNull F constructor) { int length = this.length; A[] target = constructor.apply(length); return toArray(target); } /** * Runs an effect function across all the elements. * * @param f The Effect function. */ public final void foreach(@NotNull Effect f) { // Hand expanded recursion. ImmutableList list = this; Maybe head; while ((head = list.maybeHead()).isJust()) { f.e(head.just()); list = list.maybeTail().just(); } } 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. */ @NotNull public abstract ImmutableList append(@NotNull 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(@NotNull 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(@NotNull 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. */ @NotNull public abstract Pair, ImmutableList> span(@NotNull F f); /** * A synonym of {@link #flatMap}. * * @param f The function. * @param The type of result function. * @return The result of bind. */ @NotNull public final ImmutableList bind(@NotNull 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. */ @NotNull public abstract ImmutableList flatMap(@NotNull F> f); public final boolean isNotEmpty() { return !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.just(the found element) if an element is found or * Maybe.nothing() if none is found. */ @NotNull public final Maybe find(@NotNull F f) { ImmutableList self = this; while (self instanceof NonEmptyImmutableList) { NonEmptyImmutableList selfNel = (NonEmptyImmutableList) self; boolean result = f.apply(selfNel.head); if (result) { return Maybe.just(selfNel.head); } self = selfNel.tail(); } return Maybe.nothing(); } /** * Run f on each element of the list and return the result immediately if it is a * Maybe.just. Other wise return Maybe.nothing() * * @param f The predicate. * @param The type of the result of the mapping function. * @return Maybe.just(the found element) if an element is found or * Maybe.nothing() if none is found. */ @NotNull public final Maybe findMap(@NotNull 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.nothing(); } /** * Creats a new list with all the elements but those satisfying the predicate. * * @param f The predicate. * @return A new list of filtered elements. */ @NotNull public abstract ImmutableList removeAll(@NotNull F f); /** * Reverses the list in linear time. * * @return Reversed list. */ @NotNull 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. */ @NotNull public ImmutableList patch(int index, int patchLength, @NotNull 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. */ @NotNull public abstract Pair> mapAccumL(@NotNull F2> f, @NotNull 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.just(found element)if the element can be retrieved; or * Maybe.nothing() if index out of range(). */ @NotNull public final Maybe index(int index) { ImmutableList l = this; if (index < 0) { return Maybe.nothing(); } while (index > 0) { if (l.isEmpty()) { return Maybe.nothing(); } index--; l = l.maybeTail().just(); } return l.maybeHead(); } @Override public abstract boolean equals(Object o); }