package bjc.utils.funcdata; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import bjc.utils.data.GenHolder; /** * A wrapper over another list that provides eager functional operations over it. * Differs from a stream in every way except for the fact that they both provide functional * operations. * @author ben * * @param The type in this list */ public class FunctionalList { private List wrap; /** * Create a new empty functional list. */ public FunctionalList() { wrap = new ArrayList<>(); } /** * Create a new functional list containing the specified items. * @param items The items to put into this functional list. */ @SafeVarargs public FunctionalList(E... items) { wrap = new ArrayList<>(items.length); for (E item : items) { wrap.add(item); } } /** * Create a functional list using the same backing list as the provided list. * * @param fl The source for a backing list */ public FunctionalList(FunctionalList fl) { // TODO Find out if this should make a copy of fl.wrap instead. wrap = fl.wrap; } /** * Create a new functional list with the specified size. * @param sz The size of the backing list . */ private FunctionalList(int sz) { wrap = new ArrayList<>(sz); } /** * Create a new functional list as a wrapper of a existing list. * @param l The list to use as a backing list. */ public FunctionalList(List l) { wrap = l; } /** * Add an item to this list * @param item The item to add to this list. * @return Whether the item was added to the list succesfully. */ public boolean add(E item) { return wrap.add(item); } /** * Check if all of the elements of this list match the specified predicate. * @param p The predicate to use for checking. * @return Whether all of the elements of the list match the specified predicate. */ public boolean allMatch(Predicate p) { for (E item : wrap) { if (!p.test(item)) { return false; } } return true; } /** * Check if any of the elements in this list match the specified list. * @param p The predicate to use for checking. * @return Whether any element in the list matches the provided predicate. */ public boolean anyMatch(Predicate p) { for (E item : wrap) { if (p.test(item)) { return true; } } return false; } /** * Combine this list with another one into a new list and merge the results. * Works sort of like a combined zip/map over resulting pairs. * Does not change the underlying list. * * NOTE: The returned list will have the length of the shorter of this list and the combined one. * @param l The list to combine with * @param bf The function to use for combining element pairs. * @return A new list containing the merged pairs of lists. */ public FunctionalList combineWith(FunctionalList l, BiFunction bf) { FunctionalList r = new FunctionalList<>(); Iterator i2 = l.wrap.iterator(); for (Iterator i1 = wrap.iterator(); i1.hasNext() && i2.hasNext();) { r.add(bf.apply(i1.next(), i2.next())); } return r; } /** * Get the first element in the list * @return The first element in this list. */ public E first() { return wrap.get(0); } /** * Apply a function to each member of the list, then flatten the results. * Does not change the underlying list. * @param f The function to apply to each member of the list. * @return A new list containing the flattened results of applying the provided function. */ public FunctionalList flatMap(Function> f) { FunctionalList fl = new FunctionalList<>(this.wrap.size()); forEach(e -> { f.apply(e).forEach(e2 -> { fl.add(e2); }); }); return fl; } /** * Apply a given action for each member of the list * @param c The action to apply to each member of the list. */ public void forEach(Consumer c) { wrap.forEach(c); } /** * Apply a given function to each element in the list and its index. * @param c The function to apply to each element in the list and its index. */ public void forEachIndexed(BiConsumer c) { int i = 0; for (E e : wrap) { c.accept(i++, e); } } /** * Retrieve a value in the list by its index. * @param i The index to retrieve a value from. * @return The value at the specified index in the list. */ public E getByIndex(int i) { return wrap.get(i); } /** * Get the internal backing list. * @return The backing list this list is based off of. */ public List getInternal() { return wrap; } /** * Check if this list is empty. * @return Whether or not this list is empty. */ public boolean isEmpty() { return wrap.isEmpty(); } /** * Create a new list by applying the given function to each element in the list. * Does not change the underlying list. * @param f The function to apply to each element in the list * @return A new list containing the mapped elements of this list. */ public FunctionalList map(Function f) { FunctionalList fl = new FunctionalList<>(this.wrap.size()); forEach(e -> fl.add(f.apply(e))); return fl; } /** * Select a random item from this list, using the provided random number generator. * @param rnd The random number generator to use. * @return A random element from this list. */ public E randItem(Random rnd) { return wrap.get(rnd.nextInt(wrap.size())); } /** * Reduce this list to a single value, using a accumulative approach. * @param val The initial value of the accumulative state. * @param bf The function to use to combine a list element with the accumulative state. * @param finl The function to use to convert the accumulative state into a final result. * @return A single value condensed from this list and transformed into its final state. */ public F reduceAux(T val, BiFunction bf, Function finl) { GenHolder acum = new GenHolder<>(val); wrap.forEach(e -> { acum.held = bf.apply(e, acum.held); }); return finl.apply(acum.held); } /** * Perform a binary search for the specified key using the provided means of * comparing elements. * Since this IS a binary search, the list must have been sorted before hand. * @param key The key to search for. * @param cmp The way to compare elements for searching * @return The element if it is in this list, or null if it is not. */ public E search(E key, Comparator cmp) { int res = Collections.binarySearch(wrap, key, cmp); return (res >= 0) ? wrap.get(res) : null; } /** * Sort the elements of this list using the provided way of comparing elements. * Does change the underlying list. * @param cmp The way to compare elements for sorting. */ public void sort(Comparator cmp) { Collections.sort(wrap, cmp); } /* * Reduce this item to a form useful for looking at in the debugger. * (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { StringBuilder sb = new StringBuilder("("); forEach(s -> sb.append(s + ", ")); sb.deleteCharAt(sb.length()); sb.append(")"); return sb.toString(); } }