diff options
| author | Benjamin J. Culkin <bjculkin@mix.wvu.edu> | 2017-10-08 22:39:59 -0300 |
|---|---|---|
| committer | Benjamin J. Culkin <bjculkin@mix.wvu.edu> | 2017-10-08 22:39:59 -0300 |
| commit | c82e3b3b2de0633317ec8fc85925e91422820597 (patch) | |
| tree | 96567416ce23c5ce85601f9cedc3a94bb1c55cba /base/src/main/java/bjc/utils/funcutils | |
| parent | b3ac1c8690c3e14c879913e5dcc03a5f5e14876e (diff) | |
Start splitting into maven modules
Diffstat (limited to 'base/src/main/java/bjc/utils/funcutils')
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); +} |
