import java.util.Arrays; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.BiFunction; /** QueryArray - read-only queryable array */ public class QueryArray<E> { //------------------------------------------ // Object Structure (Instance Variables) private final E[] elements; //------------------------------------------ // Constructing /** Construct new QueryArray from passed elements */ public static <T> QueryArray<T> from(T[] elements) { return new QueryArray<>(elements); } /** Construct new QueryArray from passed elements */ private QueryArray(E[] elements) { this.elements = elements; } //------------------------------------------ // Accessing (Core) /** Returns nice display string */ @Override public String toString() { if (this.size() <= 4) return Arrays.toString(this.elements); return "Array Size -- " + this.size(); } /** Iterates over elements in "this" object. For each element, * performs actionFct (passing element being iterated on) */ public void forEach(Consumer<E> actionFct) { for (E nextElem: this.elements) actionFct.accept(nextElem); } /** Prints all elements to console, with newline after each */ public void printAll(String label) { println(label); if (this.size() == 0) println("empty array"); else this.forEach(nextElem -> println(nextElem.toString())); } // ----------------------------------------------------- //Specified Methods /** Returns a new collection by selecting elements * from "this.elements" where the selectFct outputs * true when invoked */ public QueryArray<E> select(Function<E, Boolean> selectFct) { int sz = this.size(); E[] array = arrayOfSize(sz); int selectSz = 0; for (E next: this.elements) if (selectFct.apply(next)) array[selectSz++] = next; return QueryArray.from(truncateArray(array, selectSz)); } /** Returns a new collection containing all elements in * the passed collection by rejecting those that yield * "true" using the passed reject function. */ public QueryArray<E> reject(Function<E, Boolean> rejectFct) { Function<E, Boolean> inverseFct = (elem) -> !rejectFct.apply(elem); return this.select(inverseFct); } /** Returns the index of the first match, or -1 if no match */ public int findFirst(Function<E, Boolean> matchFct) { for (int i = 0; i < this.size(); i++) if (matchFct.apply(this.elements[i])) return i; return -1; } /** Returns the index of the last match, or -1 if no match */ public int findLast(Function<E, Boolean> matchFct) { for (int i = this.size() - 1; i >= 0; i--) if (matchFct.apply(this.elements[i])) return i; return -1; } /** Returns a new QueryArray containing the elements of this list * between the given index start (inclusive) and the given * index stop (exclusive). */ public QueryArray<E> subList(int start, int stop) { Function<Integer, String> msgFct = (i) -> String.format("(subList) index %d is out of bounds", i); this.validateIndex(start, msgFct); this.validateIndex(stop - 1, msgFct); E[] array = arrayOfSize(stop - start); for (int i = start, j = 0; i < stop; i++, j++) array[j] = this.get(i); return QueryArray.from(array); } /** Returns the element at the passed zero-based index */ public E get(int index) { this.validateIndex(index, i -> String.format("(get) index %d is out of bounds", i)); return this.elements[index]; } /** Returns new QueryArray where mapFct is used to generate * new elements from existing elements in "this" object */ public <T> QueryArray<T> map(Function<E, T> mapFct) { //noinspection unchecked int sz = this.size(); T[] array = arrayOfSize(sz); for (int i = 0; i < sz; i++) array[i] = mapFct.apply(this.get(i)); return QueryArray.from(array); } /** Accumulate a value by iterating over the collection * and accumulating the next value using the fct */ public <T> T accumulate(BiFunction<T, E, T> fct, T initialValue) { T accumulation = initialValue; for (E each: this.elements) accumulation = fct.apply(accumulation, each); return accumulation; } // ----------------------------------------------------- //Helper Methods /** Returns array of type "T" and of specified size */ @SuppressWarnings("unchecked") public static <T> T[] arrayOfSize(int sz) { return (T[]) new Object[sz]; } /** Returns array that is passed array truncated to * specified size */ public static <T> T[] truncateArray(T[] array, int size) { T[] truncated = arrayOfSize(size); for (int i = 0; i < size; i++) truncated[i] = array[i]; return truncated; } public int size() { return this.elements.length; } /** Validate index, throw exception if invalid */ private void validateIndex(int index, Function<Integer, String> exceptionMessageFct) { if (!(index >= 0 && index < this.size())) throw new RuntimeException(exceptionMessageFct.apply(index)); } public static void println(Object o) { System.out.println(o != null ? o.toString() : "null"); } }