From c82e3b3b2de0633317ec8fc85925e91422820597 Mon Sep 17 00:00:00 2001 From: "Benjamin J. Culkin" Date: Sun, 8 Oct 2017 22:39:59 -0300 Subject: Start splitting into maven modules --- .../java/bjc/utils/funcutils/CollectorUtils.java | 39 +++ .../bjc/utils/funcutils/CompoundCollector.java | 89 +++++++ .../main/java/bjc/utils/funcutils/EnumUtils.java | 63 +++++ .../main/java/bjc/utils/funcutils/FileUtils.java | 40 +++ .../main/java/bjc/utils/funcutils/FuncUtils.java | 76 ++++++ .../bjc/utils/funcutils/FunctionalFileVisitor.java | 36 +++ .../bjc/utils/funcutils/GroupPartIteration.java | 62 +++++ .../main/java/bjc/utils/funcutils/IBuilder.java | 31 +++ .../main/java/bjc/utils/funcutils/Isomorphism.java | 60 +++++ .../main/java/bjc/utils/funcutils/LambdaLock.java | 105 ++++++++ .../main/java/bjc/utils/funcutils/ListUtils.java | 294 +++++++++++++++++++++ .../main/java/bjc/utils/funcutils/NumberUtils.java | 69 +++++ .../main/java/bjc/utils/funcutils/StringUtils.java | 196 ++++++++++++++ .../main/java/bjc/utils/funcutils/TreeUtils.java | 56 ++++ .../main/java/bjc/utils/funcutils/TriConsumer.java | 31 +++ 15 files changed, 1247 insertions(+) create mode 100644 base/src/main/java/bjc/utils/funcutils/CollectorUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/CompoundCollector.java create mode 100644 base/src/main/java/bjc/utils/funcutils/EnumUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/FileUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/FuncUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/FunctionalFileVisitor.java create mode 100644 base/src/main/java/bjc/utils/funcutils/GroupPartIteration.java create mode 100644 base/src/main/java/bjc/utils/funcutils/IBuilder.java create mode 100644 base/src/main/java/bjc/utils/funcutils/Isomorphism.java create mode 100644 base/src/main/java/bjc/utils/funcutils/LambdaLock.java create mode 100644 base/src/main/java/bjc/utils/funcutils/ListUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/NumberUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/StringUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/TreeUtils.java create mode 100644 base/src/main/java/bjc/utils/funcutils/TriConsumer.java (limited to 'base/src/main/java/bjc/utils/funcutils') 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 + * The type of the collection to collect from + * @param + * The intermediate type of the first collector + * @param + * The intermediate type of the second collector + * @param + * The final type of the first collector + * @param + * 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 Collector>, IPair> compoundCollect( + final Collector first, + final Collector 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 + implements Collector>, IPair> { + + private final Set characteristicSet; + + private final Collector first; + private final Collector second; + + public CompoundCollector(final Collector first, + final Collector second) { + this.first = first; + this.second = second; + + characteristicSet = first.characteristics(); + characteristicSet.addAll(second.characteristics()); + } + + @Override + public BiConsumer>, InitialType> accumulator() { + final BiConsumer firstAccumulator = first.accumulator(); + final BiConsumer secondAccumulator = second.accumulator(); + + return (state, value) -> { + state.doWith(statePair -> { + statePair.doWith((left, right) -> { + firstAccumulator.accept(left, value); + secondAccumulator.accept(right, value); + }); + }); + }; + } + + @Override + public Set characteristics() { + return characteristicSet; + } + + @Override + public BinaryOperator>> combiner() { + final BinaryOperator firstCombiner = first.combiner(); + final BinaryOperator secondCombiner = second.combiner(); + + return (leftState, rightState) -> { + return leftState.unwrap(leftPair -> { + return rightState.transform(rightPair -> { + return leftPair.combine(rightPair, firstCombiner, secondCombiner); + }); + }); + }; + } + + @Override + public Function>, IPair> 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>> 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 + * 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 > void doForValues(final Class clasz, final int nValues, + final Consumer action, final Random rnd) { + final E[] enumValues = clasz.getEnumConstants(); + + final IList 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 + * 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 getRandomValue(final Class 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 predicate, + final BiPredicate 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 + * The initial type of the function + * @param + * The intermediate type of the function + * @param + * 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 Function> curry2(final BiFunction 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 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 UnaryOperator converge(final UnaryOperator 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 { + private final BiPredicate predicate; + private final BiPredicate action; + + public FunctionalFileVisitor(final BiPredicate predicate, + final BiPredicate 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 + * The type of element in the list being partitioned + */ +final class GroupPartIteration implements Consumer { + private final IList> returnedList; + + public IList currentPartition; + private final IList rejectedItems; + + private int numberInCurrentPartition; + private final int numberPerPartition; + + private final Function elementCounter; + + public GroupPartIteration(final IList> returned, final IList rejects, final int nPerPart, + final Function 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 + * The type of object being built + */ +public interface IBuilder { + /** + * 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 + * The source type of the isomorphism. + * + * @param + * The destination type of isomorphism. + * + */ +public class Isomorphism { + private Function toFunc; + private Function 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 to, Function 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 read(final Supplier 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 write(final Supplier 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 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 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 + * 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 IList drawWithoutReplacement(final IList list, final int number, + final Function rng) { + final IList selected = new FunctionalList<>(new ArrayList<>(number)); + + final int total = list.getSize(); + + final Iterator 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 + * 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 IList drawWithReplacement(final IList list, final int number, + final Function rng) { + final IList 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 + * 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 IList> groupPartition(final IList input, final Function 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> returned = new FunctionalList<>(); + + /* + * List that holds elements rejected during current pass + */ + final IList rejected = new FunctionalList<>(); + + final GroupPartIteration 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 + * 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 IList mergeLists(final IList... lists) { + final IList returned = new FunctionalList<>(); + + for (final IList list : lists) { + for (final E itm : list.toIterable()) { + returned.add(itm); + } + } + + return returned; + } + + /** + * Pad the provided list out to the desired size + * + * @param + * 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 IList padList(final IList list, final Function counter, final int size, + final Supplier padder) { + int count = 0; + + final IList 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 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 + * 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 String printDeque(final Deque 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 IList> outlineTree(ITree tre, Predicate leafMarker) { + IList> paths = new FunctionalList<>(); + + LinkedList path = new LinkedList<>(); + path.add(tre.getHead()); + + tre.doForChildren((child) -> findPath(child, path, leafMarker, paths)); + + return paths; + } + + private static void findPath(ITree subtree, LinkedList path, Predicate leafMarker, IList> paths) { + if(subtree.getChildrenCount() == 0 && leafMarker.test(subtree.getHead())) { + /* + * We're at a matching leaf node. Add it. + */ + IList 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 + * Type of the first argument. + * @param + * Type of the second argument. + * @param + * Type of the third argument. + * + */ +@FunctionalInterface +public interface TriConsumer { + /** + * 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); +} -- cgit v1.2.3