Notes



Code


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");
	}

}