summaryrefslogtreecommitdiff
path: root/base/src/main/java/bjc/utils/funcutils
diff options
context:
space:
mode:
authorBenjamin J. Culkin <bjculkin@mix.wvu.edu>2017-10-08 22:39:59 -0300
committerBenjamin J. Culkin <bjculkin@mix.wvu.edu>2017-10-08 22:39:59 -0300
commitc82e3b3b2de0633317ec8fc85925e91422820597 (patch)
tree96567416ce23c5ce85601f9cedc3a94bb1c55cba /base/src/main/java/bjc/utils/funcutils
parentb3ac1c8690c3e14c879913e5dcc03a5f5e14876e (diff)
Start splitting into maven modules
Diffstat (limited to 'base/src/main/java/bjc/utils/funcutils')
-rw-r--r--base/src/main/java/bjc/utils/funcutils/CollectorUtils.java39
-rw-r--r--base/src/main/java/bjc/utils/funcutils/CompoundCollector.java89
-rw-r--r--base/src/main/java/bjc/utils/funcutils/EnumUtils.java63
-rw-r--r--base/src/main/java/bjc/utils/funcutils/FileUtils.java40
-rw-r--r--base/src/main/java/bjc/utils/funcutils/FuncUtils.java76
-rw-r--r--base/src/main/java/bjc/utils/funcutils/FunctionalFileVisitor.java36
-rw-r--r--base/src/main/java/bjc/utils/funcutils/GroupPartIteration.java62
-rw-r--r--base/src/main/java/bjc/utils/funcutils/IBuilder.java31
-rw-r--r--base/src/main/java/bjc/utils/funcutils/Isomorphism.java60
-rw-r--r--base/src/main/java/bjc/utils/funcutils/LambdaLock.java105
-rw-r--r--base/src/main/java/bjc/utils/funcutils/ListUtils.java294
-rw-r--r--base/src/main/java/bjc/utils/funcutils/NumberUtils.java69
-rw-r--r--base/src/main/java/bjc/utils/funcutils/StringUtils.java196
-rw-r--r--base/src/main/java/bjc/utils/funcutils/TreeUtils.java56
-rw-r--r--base/src/main/java/bjc/utils/funcutils/TriConsumer.java31
15 files changed, 1247 insertions, 0 deletions
diff --git a/base/src/main/java/bjc/utils/funcutils/CollectorUtils.java b/base/src/main/java/bjc/utils/funcutils/CollectorUtils.java
new file mode 100644
index 0000000..a044bfd
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/CollectorUtils.java
@@ -0,0 +1,39 @@
+package bjc.utils.funcutils;
+
+import java.util.stream.Collector;
+
+import bjc.utils.data.IHolder;
+import bjc.utils.data.IPair;
+
+/**
+ * Utilities for producing implementations of {@link Collector}
+ *
+ * @author ben
+ *
+ */
+public class CollectorUtils {
+ /**
+ * Create a collector that applies two collectors at once
+ *
+ * @param <InitialType>
+ * The type of the collection to collect from
+ * @param <AuxType1>
+ * The intermediate type of the first collector
+ * @param <AuxType2>
+ * The intermediate type of the second collector
+ * @param <FinalType1>
+ * The final type of the first collector
+ * @param <FinalType2>
+ * The final type of the second collector
+ * @param first
+ * The first collector to use
+ * @param second
+ * The second collector to use
+ * @return A collector that functions as mentioned above
+ */
+ public static <InitialType, AuxType1, AuxType2, FinalType1, FinalType2> Collector<InitialType, IHolder<IPair<AuxType1, AuxType2>>, IPair<FinalType1, FinalType2>> compoundCollect(
+ final Collector<InitialType, AuxType1, FinalType1> first,
+ final Collector<InitialType, AuxType2, FinalType2> second) {
+ return new CompoundCollector<>(first, second);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/CompoundCollector.java b/base/src/main/java/bjc/utils/funcutils/CompoundCollector.java
new file mode 100644
index 0000000..35695bc
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/CompoundCollector.java
@@ -0,0 +1,89 @@
+package bjc.utils.funcutils;
+
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+import bjc.utils.data.IHolder;
+import bjc.utils.data.IPair;
+import bjc.utils.data.Identity;
+import bjc.utils.data.Pair;
+
+final class CompoundCollector<InitialType, AuxType1, AuxType2, FinalType1, FinalType2>
+ implements Collector<InitialType, IHolder<IPair<AuxType1, AuxType2>>, IPair<FinalType1, FinalType2>> {
+
+ private final Set<java.util.stream.Collector.Characteristics> characteristicSet;
+
+ private final Collector<InitialType, AuxType1, FinalType1> first;
+ private final Collector<InitialType, AuxType2, FinalType2> second;
+
+ public CompoundCollector(final Collector<InitialType, AuxType1, FinalType1> first,
+ final Collector<InitialType, AuxType2, FinalType2> second) {
+ this.first = first;
+ this.second = second;
+
+ characteristicSet = first.characteristics();
+ characteristicSet.addAll(second.characteristics());
+ }
+
+ @Override
+ public BiConsumer<IHolder<IPair<AuxType1, AuxType2>>, InitialType> accumulator() {
+ final BiConsumer<AuxType1, InitialType> firstAccumulator = first.accumulator();
+ final BiConsumer<AuxType2, InitialType> secondAccumulator = second.accumulator();
+
+ return (state, value) -> {
+ state.doWith(statePair -> {
+ statePair.doWith((left, right) -> {
+ firstAccumulator.accept(left, value);
+ secondAccumulator.accept(right, value);
+ });
+ });
+ };
+ }
+
+ @Override
+ public Set<java.util.stream.Collector.Characteristics> characteristics() {
+ return characteristicSet;
+ }
+
+ @Override
+ public BinaryOperator<IHolder<IPair<AuxType1, AuxType2>>> combiner() {
+ final BinaryOperator<AuxType1> firstCombiner = first.combiner();
+ final BinaryOperator<AuxType2> secondCombiner = second.combiner();
+
+ return (leftState, rightState) -> {
+ return leftState.unwrap(leftPair -> {
+ return rightState.transform(rightPair -> {
+ return leftPair.combine(rightPair, firstCombiner, secondCombiner);
+ });
+ });
+ };
+ }
+
+ @Override
+ public Function<IHolder<IPair<AuxType1, AuxType2>>, IPair<FinalType1, FinalType2>> finisher() {
+ return state -> {
+ return state.unwrap(pair -> {
+ return pair.bind((left, right) -> {
+ final FinalType1 finalLeft = first.finisher().apply(left);
+ final FinalType2 finalRight = second.finisher().apply(right);
+
+ return new Pair<>(finalLeft, finalRight);
+ });
+ });
+ };
+ }
+
+ @Override
+ public Supplier<IHolder<IPair<AuxType1, AuxType2>>> supplier() {
+ return () -> {
+ final AuxType1 initialLeft = first.supplier().get();
+ final AuxType2 initialRight = second.supplier().get();
+
+ return new Identity<>(new Pair<>(initialLeft, initialRight));
+ };
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/EnumUtils.java b/base/src/main/java/bjc/utils/funcutils/EnumUtils.java
new file mode 100644
index 0000000..e4c0bda
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/EnumUtils.java
@@ -0,0 +1,63 @@
+package bjc.utils.funcutils;
+
+import java.util.Random;
+import java.util.function.Consumer;
+
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * Utility methods on enums
+ *
+ * @author ben
+ *
+ */
+public class EnumUtils {
+ /**
+ * Do an action for a random number of enum values
+ *
+ * @param <E>
+ * The type of the enum
+ * @param clasz
+ * The enum class
+ * @param nValues
+ * The number of values to execute the action on
+ * @param action
+ * The action to perform on random values
+ * @param rnd
+ * The source of randomness to use
+ */
+ public static <E extends Enum<E>> void doForValues(final Class<E> clasz, final int nValues,
+ final Consumer<E> action, final Random rnd) {
+ final E[] enumValues = clasz.getEnumConstants();
+
+ final IList<E> valueList = new FunctionalList<>(enumValues);
+
+ final int randomValueCount = enumValues.length - nValues;
+
+ for (int i = 0; i <= randomValueCount; i++) {
+ final E rDir = valueList.randItem(rnd::nextInt);
+
+ valueList.removeMatching(rDir);
+ }
+
+ valueList.forEach(action);
+ }
+
+ /**
+ * Get a random value from an enum
+ *
+ * @param <E>
+ * The type of the enum
+ * @param clasz
+ * The class of the enum
+ * @param rnd
+ * The random source to use
+ * @return A random value from the specified enum
+ */
+ public static <E extends Enum<E>> E getRandomValue(final Class<E> clasz, final Random rnd) {
+ final E[] enumValues = clasz.getEnumConstants();
+
+ return new FunctionalList<>(enumValues).randItem(rnd::nextInt);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/FileUtils.java b/base/src/main/java/bjc/utils/funcutils/FileUtils.java
new file mode 100644
index 0000000..87199b1
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/FileUtils.java
@@ -0,0 +1,40 @@
+package bjc.utils.funcutils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.function.BiPredicate;
+
+/**
+ * Utilities for doing things with files
+ *
+ * @author ben
+ *
+ */
+public class FileUtils {
+ /**
+ * Traverse a directory recursively. This is a depth-first traversal
+ *
+ *
+ * @param root
+ * The directory to start the traversal at
+ * @param predicate
+ * The predicate to determine whether or not to traverse
+ * a directory
+ * @param action
+ * The action to invoke upon each file in the directory.
+ * Returning true means to continue the traversal,
+ * returning false stops it
+ * @throws IOException
+ * if the walk throws an exception
+ *
+ * TODO If it becomes necessary, write another overload
+ * for this with all the buttons and knobs from
+ * walkFileTree
+ */
+ public static void traverseDirectory(final Path root, final BiPredicate<Path, BasicFileAttributes> predicate,
+ final BiPredicate<Path, BasicFileAttributes> action) throws IOException {
+ Files.walkFileTree(root, new FunctionalFileVisitor(predicate, action));
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/FuncUtils.java b/base/src/main/java/bjc/utils/funcutils/FuncUtils.java
new file mode 100644
index 0000000..9950add
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/FuncUtils.java
@@ -0,0 +1,76 @@
+package bjc.utils.funcutils;
+
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+
+/**
+ * Utility things for functions
+ *
+ * @author ben
+ *
+ */
+public class FuncUtils {
+ /**
+ * Convert a binary function into a unary function that returns a
+ * function
+ *
+ * @param <A>
+ * The initial type of the function
+ * @param <B>
+ * The intermediate type of the function
+ * @param <C>
+ * The terminal type of the function
+ * @param func
+ * The function to transform
+ * @return The function transformed into a unary function returning a
+ * function
+ */
+ public static <A, B, C> Function<A, Function<B, C>> curry2(final BiFunction<A, B, C> func) {
+ return arg1 -> arg2 -> {
+ return func.apply(arg1, arg2);
+ };
+ }
+
+ /**
+ * Do the specified action the specified number of times
+ *
+ * @param nTimes
+ * The number of times to do the action
+ * @param cons
+ * The action to perform
+ */
+ public static void doTimes(final int nTimes, final Consumer<Integer> cons) {
+ for (int i = 0; i < nTimes; i++) {
+ cons.accept(i);
+ }
+ }
+
+ /**
+ * Return an operator that executes until it converges.
+ *
+ * @param op
+ * The operator to execute.
+ * @param maxTries
+ * The maximum amount of times to apply the function in an
+ * attempt to cause it to converge.
+ */
+ public static <T> UnaryOperator<T> converge(final UnaryOperator<T> op, final int maxTries) {
+ return (val) -> {
+ T newVal = op.apply(val);
+ T oldVal;
+
+ int tries = 0;
+
+ do {
+ oldVal = newVal;
+ newVal = op.apply(newVal);
+
+ tries += 1;
+ } while(!newVal.equals(oldVal) && tries < maxTries);
+
+ return newVal;
+ };
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/FunctionalFileVisitor.java b/base/src/main/java/bjc/utils/funcutils/FunctionalFileVisitor.java
new file mode 100644
index 0000000..db6c43b
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/FunctionalFileVisitor.java
@@ -0,0 +1,36 @@
+package bjc.utils.funcutils;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.function.BiPredicate;
+
+/*
+ * Functional implementation of a file visitor.
+ */
+final class FunctionalFileVisitor extends SimpleFileVisitor<Path> {
+ private final BiPredicate<Path, BasicFileAttributes> predicate;
+ private final BiPredicate<Path, BasicFileAttributes> action;
+
+ public FunctionalFileVisitor(final BiPredicate<Path, BasicFileAttributes> predicate,
+ final BiPredicate<Path, BasicFileAttributes> action) {
+ this.predicate = predicate;
+ this.action = action;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
+ if (predicate.test(dir, attrs)) return FileVisitResult.CONTINUE;
+
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+ if (action.test(file, attrs)) return FileVisitResult.CONTINUE;
+
+ return FileVisitResult.TERMINATE;
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/GroupPartIteration.java b/base/src/main/java/bjc/utils/funcutils/GroupPartIteration.java
new file mode 100644
index 0000000..f3b2254
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/GroupPartIteration.java
@@ -0,0 +1,62 @@
+package bjc.utils.funcutils;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * Implements a single group partitioning pass on a list
+ *
+ * @author ben
+ *
+ * @param <E>
+ * The type of element in the list being partitioned
+ */
+final class GroupPartIteration<E> implements Consumer<E> {
+ private final IList<IList<E>> returnedList;
+
+ public IList<E> currentPartition;
+ private final IList<E> rejectedItems;
+
+ private int numberInCurrentPartition;
+ private final int numberPerPartition;
+
+ private final Function<E, Integer> elementCounter;
+
+ public GroupPartIteration(final IList<IList<E>> returned, final IList<E> rejects, final int nPerPart,
+ final Function<E, Integer> eleCount) {
+ this.returnedList = returned;
+ this.rejectedItems = rejects;
+ this.numberPerPartition = nPerPart;
+ this.elementCounter = eleCount;
+
+ this.currentPartition = new FunctionalList<>();
+ this.numberInCurrentPartition = 0;
+ }
+
+ @Override
+ public void accept(final E value) {
+ final boolean shouldStartPartition = numberInCurrentPartition >= numberPerPartition;
+
+ if (shouldStartPartition) {
+ returnedList.add(currentPartition);
+
+ currentPartition = new FunctionalList<>();
+ numberInCurrentPartition = 0;
+ } else {
+ final int currentElementCount = elementCounter.apply(value);
+
+ final boolean shouldReject = numberInCurrentPartition
+ + currentElementCount >= numberPerPartition;
+
+ if (shouldReject) {
+ rejectedItems.add(value);
+ } else {
+ currentPartition.add(value);
+ numberInCurrentPartition += currentElementCount;
+ }
+ }
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/IBuilder.java b/base/src/main/java/bjc/utils/funcutils/IBuilder.java
new file mode 100644
index 0000000..a96a4d6
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/IBuilder.java
@@ -0,0 +1,31 @@
+package bjc.utils.funcutils;
+
+/**
+ * Generic interface for objects that implement the builder pattern
+ *
+ * @author ben
+ *
+ * @param <E>
+ * The type of object being built
+ */
+public interface IBuilder<E> {
+ /**
+ * Build the object this builder is building
+ *
+ * @return The built object
+ * @throws IllegalStateException
+ * if the data in the builder cannot be built into its
+ * corresponding object at this point in time
+ */
+ public E build();
+
+ /**
+ * Reset the state of this builder to its initial state
+ *
+ * @throws UnsupportedOperationException
+ * if the builder doesn't support resetting its state
+ */
+ public default void reset() {
+ throw new UnsupportedOperationException("Builder doesn't support state resetting");
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/Isomorphism.java b/base/src/main/java/bjc/utils/funcutils/Isomorphism.java
new file mode 100644
index 0000000..2d3655e
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/Isomorphism.java
@@ -0,0 +1,60 @@
+package bjc.utils.funcutils;
+
+import java.util.function.Function;
+
+/**
+ * A pair of functions to transform between a pair of types.
+ *
+ * @author bjculkin
+ *
+ * @param <S>
+ * The source type of the isomorphism.
+ *
+ * @param <D>
+ * The destination type of isomorphism.
+ *
+ */
+public class Isomorphism<S, D> {
+ private Function<S, D> toFunc;
+ private Function<D, S> fromFunc;
+
+ /**
+ * Create a new isomorphism.
+ *
+ * @param to
+ * The 'forward' function, from the source to the
+ * definition.
+ *
+ * @param from
+ * The 'backward' function, from the definition to the
+ * source.
+ */
+ public Isomorphism(Function<S, D> to, Function<D, S> from) {
+ toFunc = to;
+ fromFunc = from;
+ }
+
+ /**
+ * Apply the isomorphism forward.
+ *
+ * @param val
+ * The source value.
+ *
+ * @return The destination value.
+ */
+ public D to(S val) {
+ return toFunc.apply(val);
+ }
+
+ /**
+ * Apply the isomorphism backward.
+ *
+ * @param val
+ * The destination value.
+ *
+ * @return The source value.
+ */
+ public S from(D val) {
+ return fromFunc.apply(val);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/LambdaLock.java b/base/src/main/java/bjc/utils/funcutils/LambdaLock.java
new file mode 100644
index 0000000..62c5d32
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/LambdaLock.java
@@ -0,0 +1,105 @@
+package bjc.utils.funcutils;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Supplier;
+
+/**
+ * A wrapper around a {@link ReadWriteLock} to ensure that the lock is used
+ * properly.
+ *
+ * @author EVE
+ *
+ */
+public class LambdaLock {
+ private final Lock readLock;
+ private final Lock writeLock;
+
+ /**
+ * Create a new lambda-enabled lock around a new lock.
+ */
+ public LambdaLock() {
+ this(new ReentrantReadWriteLock());
+ }
+
+ /**
+ * Create a new lambda-enabled lock.
+ *
+ * @param lck
+ * The lock to wrap.
+ */
+ public LambdaLock(final ReadWriteLock lck) {
+ readLock = lck.readLock();
+ writeLock = lck.writeLock();
+ }
+
+ /**
+ * Execute an action with the read lock taken.
+ *
+ * @param supp
+ * The action to call.
+ *
+ * @return The result of the action.
+ */
+ public <T> T read(final Supplier<T> supp) {
+ readLock.lock();
+
+ try {
+ return supp.get();
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ /**
+ * Execute an action with the write lock taken.
+ *
+ * @param supp
+ * The action to call.
+ *
+ * @return The result of the action.
+ */
+ public <T> T write(final Supplier<T> supp) {
+ writeLock.lock();
+
+ try {
+ return supp.get();
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ /**
+ * Execute an action with the read lock taken.
+ *
+ * @param action
+ * The action to call.
+ *
+ */
+ public void read(final Runnable action) {
+ readLock.lock();
+
+ try {
+ action.run();
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ /**
+ * Execute an action with the write lock taken.
+ *
+ * @param action
+ * The action to call.
+ */
+ public void write(final Runnable action) {
+ writeLock.lock();
+
+ try {
+ action.run();
+ } finally {
+ writeLock.unlock();
+ }
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/funcutils/ListUtils.java b/base/src/main/java/bjc/utils/funcutils/ListUtils.java
new file mode 100644
index 0000000..c0daa1e
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/ListUtils.java
@@ -0,0 +1,294 @@
+package bjc.utils.funcutils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * Utilities for manipulating FunctionalLists that don't belong in the class
+ * itself
+ *
+ * @author ben
+ *
+ */
+public class ListUtils {
+ private static final int MAX_NTRIESPART = 50;
+
+ /**
+ * Collapse a string of tokens into a single string without adding any
+ * spaces
+ *
+ * @param input
+ * The list of tokens to collapse
+ * @return The collapsed string of tokens
+ */
+ public static String collapseTokens(final IList<String> input) {
+ if (input == null) throw new NullPointerException("Input must not be null");
+
+ return collapseTokens(input, "");
+ }
+
+ /**
+ * Collapse a string of tokens into a single string, adding the desired
+ * separator after each token
+ *
+ * @param input
+ * The list of tokens to collapse
+ * @param seperator
+ * The separator to use for separating tokens
+ * @return The collapsed string of tokens
+ */
+ public static String collapseTokens(final IList<String> input, final String seperator) {
+ if (input == null)
+ throw new NullPointerException("Input must not be null");
+ else if (seperator == null) throw new NullPointerException("Seperator must not be null");
+
+ if (input.getSize() < 1)
+ return "";
+ else if (input.getSize() == 1)
+ return input.first();
+ else {
+ final StringBuilder state = new StringBuilder();
+
+ int i = 1;
+ for (final String itm : input.toIterable()) {
+ state.append(itm);
+
+ if (i != input.getSize()) {
+ state.append(seperator);
+ }
+
+ i += 1;
+ }
+
+ return state.toString();
+ }
+ }
+
+ /**
+ * Select a number of random items from the list without replacement
+ *
+ * @param <E>
+ * The type of items to select
+ * @param list
+ * The list to select from
+ * @param number
+ * The number of items to selet
+ * @param rng
+ * A function that creates a random number from 0 to the
+ * desired number
+ * @return A new list containing the desired number of items randomly
+ * selected from the specified list without replacement
+ */
+
+ public static <E> IList<E> drawWithoutReplacement(final IList<E> list, final int number,
+ final Function<Integer, Integer> rng) {
+ final IList<E> selected = new FunctionalList<>(new ArrayList<>(number));
+
+ final int total = list.getSize();
+
+ final Iterator<E> itr = list.toIterable().iterator();
+ E element = null;
+
+ for (final int index = 0; itr.hasNext(); element = itr.next()) {
+ /*
+ * n - m
+ */
+ final int winningChance = number - selected.getSize();
+
+ /*
+ * N - t
+ */
+ final int totalChance = total - (index - 1);
+
+ /*
+ * Probability of selecting the t+1'th element
+ */
+ if (NumberUtils.isProbable(winningChance, totalChance, rng)) {
+ selected.add(element);
+ }
+ }
+
+ return selected;
+ }
+
+ /**
+ * Select a number of random items from the list, with replacement
+ *
+ * @param <E>
+ * The type of items to select
+ * @param list
+ * The list to select from
+ * @param number
+ * The number of items to selet
+ * @param rng
+ * A function that creates a random number from 0 to the
+ * desired number
+ * @return A new list containing the desired number of items randomly
+ * selected from the specified list
+ */
+ public static <E> IList<E> drawWithReplacement(final IList<E> list, final int number,
+ final Function<Integer, Integer> rng) {
+ final IList<E> selected = new FunctionalList<>(new ArrayList<>(number));
+
+ for (int i = 0; i < number; i++) {
+ selected.add(list.randItem(rng));
+ }
+
+ return selected;
+ }
+
+ /**
+ * Partition a list into a list of lists, where each element can count
+ * for more than one element in a partition
+ *
+ * @param <E>
+ * The type of elements in the list to partition
+ *
+ * @param input
+ * The list to partition
+ * @param counter
+ * The function to determine the count for each element
+ * for
+ * @param partitionSize
+ * The number of elements to put in each partition
+ *
+ * @return A list partitioned according to the above rules
+ */
+ public static <E> IList<IList<E>> groupPartition(final IList<E> input, final Function<E, Integer> counter,
+ final int partitionSize) {
+ if (input == null)
+ throw new NullPointerException("Input list must not be null");
+ else if (counter == null)
+ throw new NullPointerException("Counter must not be null");
+ else if (partitionSize < 1 || partitionSize > input.getSize()) {
+ final String fmt = "%d is not a valid partition size. Must be between 1 and %d";
+ final String msg = String.format(fmt, partitionSize, input.getSize());
+
+ throw new IllegalArgumentException(msg);
+ }
+
+ /*
+ * List that holds our results
+ */
+ final IList<IList<E>> returned = new FunctionalList<>();
+
+ /*
+ * List that holds elements rejected during current pass
+ */
+ final IList<E> rejected = new FunctionalList<>();
+
+ final GroupPartIteration<E> it = new GroupPartIteration<>(returned, rejected, partitionSize, counter);
+
+ /*
+ * Run up to a certain number of passes
+ */
+ for (int numberOfIterations = 0; numberOfIterations < MAX_NTRIESPART
+ && !rejected.isEmpty(); numberOfIterations++) {
+ input.forEach(it);
+
+ if (rejected.isEmpty()) {
+ /*
+ * Nothing was rejected, so we're done
+ */
+ return returned;
+ }
+ }
+
+
+ final String fmt = "Heuristic (more than %d iterations of partitioning) detected an unpartitionable list. (%s)\nThe following elements were not partitioned: %s\nCurrent group in formation: %s\nPreviously formed groups: %s\n";
+
+ final String msg = String.format(fmt, MAX_NTRIESPART, input.toString(), rejected.toString(), it.currentPartition.toString(), returned.toString());
+
+ throw new IllegalArgumentException(msg);
+ }
+
+ /**
+ * Merge the contents of a bunch of lists together into a single list
+ *
+ * @param <E>
+ * The type of value in this lists
+ * @param lists
+ * The values in the lists to merge
+ * @return A list containing all the elements of the lists
+ */
+ @SafeVarargs
+ public static <E> IList<E> mergeLists(final IList<E>... lists) {
+ final IList<E> returned = new FunctionalList<>();
+
+ for (final IList<E> list : lists) {
+ for (final E itm : list.toIterable()) {
+ returned.add(itm);
+ }
+ }
+
+ return returned;
+ }
+
+ /**
+ * Pad the provided list out to the desired size
+ *
+ * @param <E>
+ * The type of elements in the list
+ * @param list
+ * The list to pad out
+ * @param counter
+ * The function to count elements with
+ * @param size
+ * The desired size of the list
+ * @param padder
+ * The function to get elements to pad with
+ * @return The list, padded to the desired size
+ * @throws IllegalArgumentException
+ * if the list couldn't be padded to the desired size
+ */
+ public static <E> IList<E> padList(final IList<E> list, final Function<E, Integer> counter, final int size,
+ final Supplier<E> padder) {
+ int count = 0;
+
+ final IList<E> returned = new FunctionalList<>();
+
+ for (final E itm : list.toIterable()) {
+ count += counter.apply(itm);
+
+ returned.add(itm);
+ }
+
+ if (count % size != 0) {
+ /*
+ * We need to pad
+ */
+ int needed = count % size;
+ int threshold = 0;
+
+ while (needed > 0 && threshold <= MAX_NTRIESPART) {
+ final E val = padder.get();
+ final int newCount = counter.apply(val);
+
+ if (newCount <= needed) {
+ returned.add(val);
+
+ threshold = 0;
+
+ needed -= newCount;
+ } else {
+ threshold += 1;
+ }
+ }
+
+ if (threshold > MAX_NTRIESPART) {
+ final String fmt = "Heuristic (more than %d iterations of attempting to pad) detected an unpaddable list. (%s)\nPartially padded list: %S";
+
+ final String msg = String.format(fmt, MAX_NTRIESPART, list.toString(), returned.toString());
+
+ throw new IllegalArgumentException(msg);
+ }
+ }
+
+ return returned;
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/NumberUtils.java b/base/src/main/java/bjc/utils/funcutils/NumberUtils.java
new file mode 100644
index 0000000..770d3a5
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/NumberUtils.java
@@ -0,0 +1,69 @@
+package bjc.utils.funcutils;
+
+import java.util.function.Function;
+
+/**
+ * Utility functions for dealing with numbers
+ *
+ * @author ben
+ *
+ */
+public class NumberUtils {
+ /**
+ * Compute the falling factorial of a number
+ *
+ * @param value
+ * The number to compute
+ * @param power
+ * The power to do the falling factorial for
+ * @return The falling factorial of the number to the power
+ */
+ public static int fallingFactorial(final int value, final int power) {
+ if (power == 0)
+ return 1;
+ else if (power == 1)
+ return value;
+ else {
+ int result = 1;
+
+ for (int currentSub = 0; currentSub < power + 1; currentSub++) {
+ result *= value - currentSub;
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Evaluates a linear probability distribution
+ *
+ * @param winning
+ * The number of winning possibilities
+ * @param total
+ * The number of total possibilities
+ * @param rng
+ * The function to use to generate a random possibility
+ * @return Whether or not a random possibility was a winning one
+ */
+ public static boolean isProbable(final int winning, final int total, final Function<Integer, Integer> rng) {
+ return rng.apply(total) < winning;
+ }
+
+ /**
+ * Check if a number is in an inclusive range.
+ *
+ * @param min
+ * The minimum value of the range.
+ *
+ * @param max
+ * The maximum value of the range.
+ *
+ * @param i
+ * The number to check.
+ *
+ * @return Whether the number is in the range.
+ */
+ public static boolean between(final int min, final int max, final int i) {
+ return i >= min && i <= max;
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/StringUtils.java b/base/src/main/java/bjc/utils/funcutils/StringUtils.java
new file mode 100644
index 0000000..62f78f5
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/StringUtils.java
@@ -0,0 +1,196 @@
+package bjc.utils.funcutils;
+
+import java.util.Deque;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.ibm.icu.text.BreakIterator;
+
+/**
+ * Utility methods for operations on strings
+ *
+ * @author ben
+ *
+ */
+public class StringUtils {
+ /**
+ * Check if a string consists only of one or more matches of a regular
+ * expression
+ *
+ * @param input
+ * The string to check
+ * @param rRegex
+ * The regex to see if the string only contains matches
+ * of
+ * @return Whether or not the string consists only of multiple matches
+ * of the provided regex
+ */
+ public static boolean containsOnly(final String input, final String rRegex) {
+ if (input == null)
+ throw new NullPointerException("Input must not be null");
+ else if (rRegex == null) throw new NullPointerException("Regex must not be null");
+
+ /*
+ * This regular expression is fairly simple.
+ *
+ * First, we match the beginning of the string. Then, we start a
+ * non-capturing group whose contents are the passed in regex.
+ * That group is then matched one or more times and the pattern
+ * matches to the end of the string
+ */
+ return input.matches("\\A(?:" + rRegex + ")+\\Z");
+ }
+
+ /**
+ * Indent the string being built in a StringBuilder n levels
+ *
+ * @param builder
+ * The builder to indent in
+ * @param levels
+ * The number of levels to indent
+ */
+ public static void indentNLevels(final StringBuilder builder, final int levels) {
+ for (int i = 0; i < levels; i++) {
+ builder.append("\t");
+ }
+ }
+
+ /**
+ * Print out a deque with a special case for easily showing a deque is
+ * empty
+ *
+ * @param <ContainedType>
+ * The type in the deque
+ * @param queue
+ * The deque to print
+ * @return A string version of the deque, with allowance for an empty
+ * deque
+ */
+ public static <ContainedType> String printDeque(final Deque<ContainedType> queue) {
+ return queue.isEmpty() ? "(none)" : queue.toString();
+ }
+
+ /**
+ * Converts a sequence to an English list.
+ *
+ * @param objects
+ * The sequence to convert to an English list.
+ * @param join
+ * The string to use for separating the last element from
+ * the rest.
+ * @param comma
+ * The string to use as a comma
+ *
+ * @return The sequence as an English list.
+ */
+ public static String toEnglishList(final Object[] objects, final String join, final String comma) {
+ if (objects == null) throw new NullPointerException("Sequence must not be null");
+
+ final StringBuilder sb = new StringBuilder();
+
+ final String joiner = join;
+ final String coma = comma;
+
+ switch (objects.length) {
+ case 0:
+ /*
+ * Empty list.
+ */
+ break;
+ case 1:
+ /*
+ * One item.
+ */
+ sb.append(objects[0].toString());
+ break;
+ case 2:
+ /*
+ * Two items.
+ */
+ sb.append(objects[0].toString());
+ sb.append(" " + joiner + " ");
+ sb.append(objects[1].toString());
+ break;
+ default:
+ /*
+ * Three or more items.
+ */
+ for (int i = 0; i < objects.length - 1; i++) {
+ sb.append(objects[i].toString());
+ sb.append(coma + " ");
+ }
+ /*
+ * Uncomment this to remove serial commas.
+ *
+ * int lc = sb.length() - 1;
+ *
+ * sb.delete(lc - coma.length(), lc);
+ */
+ sb.append(joiner + " ");
+ sb.append(objects[objects.length - 1].toString());
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Converts a sequence to an English list.
+ *
+ * @param objects
+ * The sequence to convert to an English list.
+ * @param join
+ * The string to use for separating the last element from
+ * the rest.
+ *
+ * @return The sequence as an English list.
+ */
+ public static String toEnglishList(final Object[] objects, final String join) {
+ return toEnglishList(objects, join, ",");
+ }
+
+ /**
+ * Converts a sequence to an English list.
+ *
+ * @param objects
+ * The sequence to convert to an English list.
+ * @param and
+ * Whether to use 'and' or 'or'.
+ *
+ * @return The sequence as an English list.
+ */
+ public static String toEnglishList(final Object[] objects, final boolean and) {
+ if (and)
+ return toEnglishList(objects, "and");
+ else return toEnglishList(objects, "or");
+ }
+
+ /**
+ * Count the number of graphemes in a string.
+ *
+ * @param value
+ * The string to check.
+ *
+ * @return The number of graphemes in the string.
+ */
+ public static int graphemeCount(final String value) {
+ final BreakIterator it = BreakIterator.getCharacterInstance();
+ it.setText(value);
+
+ int count = 0;
+ while (it.next() != BreakIterator.DONE) {
+ count++;
+ }
+
+ return count;
+ }
+
+ public static int countMatches(final String value, final String pattern) {
+ Matcher mat = Pattern.compile(pattern).matcher(value);
+
+ int num = 0;
+ while(mat.find())
+ num += 1;
+
+ return num;
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/TreeUtils.java b/base/src/main/java/bjc/utils/funcutils/TreeUtils.java
new file mode 100644
index 0000000..dcd5738
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/TreeUtils.java
@@ -0,0 +1,56 @@
+package bjc.utils.funcutils;
+
+import java.util.LinkedList;
+import java.util.function.Predicate;
+
+import bjc.utils.data.ITree;
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * Implements various utilities for trees.
+ *
+ * @author Benjamin Culkin
+ */
+public class TreeUtils {
+ /*
+ * Convert a tree into a list of outline nodes that match a certain
+ * path.
+ */
+ public static <T> IList<IList<T>> outlineTree(ITree<T> tre, Predicate<T> leafMarker) {
+ IList<IList<T>> paths = new FunctionalList<>();
+
+ LinkedList<T> path = new LinkedList<>();
+ path.add(tre.getHead());
+
+ tre.doForChildren((child) -> findPath(child, path, leafMarker, paths));
+
+ return paths;
+ }
+
+ private static <T> void findPath(ITree<T> subtree, LinkedList<T> path, Predicate<T> leafMarker, IList<IList<T>> paths) {
+ if(subtree.getChildrenCount() == 0 && leafMarker.test(subtree.getHead())) {
+ /*
+ * We're at a matching leaf node. Add it.
+ */
+ IList<T> finalPath = new FunctionalList<>();
+
+ for(T ePath : path) {
+ finalPath.add(ePath);
+ }
+
+ finalPath.add(subtree.getHead());
+
+ paths.add(finalPath);
+ } else {
+ /*
+ * Check the children of this node.
+ */
+ path.add(subtree.getHead());
+
+ subtree.doForChildren((child) -> findPath(child, path, leafMarker, paths));
+
+ path.removeLast();
+ }
+ }
+}
diff --git a/base/src/main/java/bjc/utils/funcutils/TriConsumer.java b/base/src/main/java/bjc/utils/funcutils/TriConsumer.java
new file mode 100644
index 0000000..f30386c
--- /dev/null
+++ b/base/src/main/java/bjc/utils/funcutils/TriConsumer.java
@@ -0,0 +1,31 @@
+package bjc.utils.funcutils;
+
+/**
+ * Consumer that takes three arguments.
+ *
+ * @author EVE
+ *
+ * @param <A>
+ * Type of the first argument.
+ * @param <B>
+ * Type of the second argument.
+ * @param <C>
+ * Type of the third argument.
+ *
+ */
+@FunctionalInterface
+public interface TriConsumer<A, B, C> {
+ /**
+ * Perform the action.
+ *
+ * @param a
+ * The first parameter.
+ *
+ * @param b
+ * The second parameter.
+ *
+ * @param c
+ * The third parameter.
+ */
+ public void accept(A a, B b, C c);
+}