diff options
Diffstat (limited to 'src/main/java')
76 files changed, 4435 insertions, 2094 deletions
diff --git a/src/main/java/bjc/data/ArrayIterator.java b/src/main/java/bjc/data/ArrayIterator.java index 6d11a1d..c5f68cc 100644 --- a/src/main/java/bjc/data/ArrayIterator.java +++ b/src/main/java/bjc/data/ArrayIterator.java @@ -34,8 +34,7 @@ public class ArrayIterator<T> implements Iterator<T> { @SuppressWarnings("unchecked") @Override public T next() { - if (idx >= arr.length) - return null; + if (idx >= arr.length) return null; return (T) (arr[idx++]); } diff --git a/src/main/java/bjc/data/BooleanToggle.java b/src/main/java/bjc/data/BooleanToggle.java index af083f5..e0286d4 100644 --- a/src/main/java/bjc/data/BooleanToggle.java +++ b/src/main/java/bjc/data/BooleanToggle.java @@ -59,23 +59,18 @@ public class BooleanToggle implements Toggle<Boolean> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof BooleanToggle)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof BooleanToggle)) return false; final BooleanToggle other = (BooleanToggle) obj; - if (val != other.val) - return false; - - return true; + if (val != other.val) return false; + else return true; } @Override public String toString() { - return String.format("BooleanToggle [val=%s]", val); + return String.format("%s", val); } } diff --git a/src/main/java/bjc/data/Context.java b/src/main/java/bjc/data/Context.java new file mode 100644 index 0000000..7efdc4d --- /dev/null +++ b/src/main/java/bjc/data/Context.java @@ -0,0 +1,62 @@ +package bjc.data; + +/** + * Represents a 'context' which is a hierarchical set of objects. + * @author Ben Culkin + * + */ +public interface Context { + /** + * Register an object with this context. + * + * @param name The name of the object. + * @param o The object to register. + */ + void register(String name, Object o); + + /** + * Get the parent of this context. + * + * @return The parent of this context. + */ + Context getParent(); + + /** + * Get an object from this context. + * + * @param name The name of the object. + * + * @return The object bound to that name. + */ + Object get(String name); + + /** + * Get an object which is an instance of the provided class or a subclass + * thereof. + * + * @param <T> The type of the object. + * + * @param contract The class of the object. + * + * @return An instance of the provided class. + */ + <T> T get(Class<T> contract); + + /** + * Get a named object which is an instance of the provided class or a subclass + * thereof. + * + * @param <T> The type of the object. + * + * @param name The name of the object + * @param contract The class of the object. + * + * @return An instance of the provided class, with the given name.. + */ + default <T> T get(String name, Class<T> contract) { + Object obj = get(name); + return obj == null + ? getParent().get(name, contract) + : contract.cast(obj); + }; +} diff --git a/src/main/java/bjc/data/Contexts.java b/src/main/java/bjc/data/Contexts.java new file mode 100644 index 0000000..b028ad1 --- /dev/null +++ b/src/main/java/bjc/data/Contexts.java @@ -0,0 +1,103 @@ +package bjc.data; + +import java.util.*; + +/** + * Utility methods for dealing with contexts. + * + * @author Ben Culkin + * + */ +public class Contexts { + /** + * The null context, which always throws an exception. + */ + public static final Context NULL = new NullContextImpl(); + + private Contexts() { + throw new UnsupportedOperationException(); + } + + /** + * Create a new context with no parent. + * + * @return A context with no parent. + */ + public static Context create() { + return new ContextImpl(NULL); + } + + /** + * Create a context with the specified parent. + * + * @param parent The parent of this context. + * + * @return A context with the given context as its parent. + */ + public static Context create(Context parent) { + return new ContextImpl(parent); + } + + private static class NullContextImpl implements Context { + @Override + public Context getParent() { + return this; + } + + @Override + public void register(String name, Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public Object get(String name) { + throw new NoSuchElementException(); + } + + @Override + public <T> T get(Class<T> contract) { + throw new NoSuchElementException(); + } + } + + private static class ContextImpl implements Context { + + private final Context parent; + + private final Map<String, Object> objects; + + public ContextImpl(Context parent) { + this.parent = parent; + this.objects = new HashMap<>(); + } + + @Override + public void register(String name, Object o) { + objects.put(name, o); + } + + @Override + public Object get(String name) { + if (objects.containsKey(name)) { + return objects.get(name); + } + return parent.get(name); + } + + @SuppressWarnings("unchecked") + @Override + public <T> T get(Class<T> contract) { + for (Object o : objects.values()) { + if (contract.isInstance(o)) { + return (T) o; + } + } + return parent.get(contract); + } + + @Override + public Context getParent() { + return parent; + } + } +} diff --git a/src/main/java/bjc/data/Either.java b/src/main/java/bjc/data/Either.java index 55518d8..75447f2 100644 --- a/src/main/java/bjc/data/Either.java +++ b/src/main/java/bjc/data/Either.java @@ -1,7 +1,7 @@ package bjc.data; -import java.util.function.BiFunction; -import java.util.function.Function; +import java.util.*; +import java.util.function.*; /** * Represents a pair where only one side has a value. @@ -15,7 +15,7 @@ import java.util.function.Function; * The type that could be on the right. * */ -public class Either<LeftType, RightType> implements IPair<LeftType, RightType> { +public class Either<LeftType, RightType> { /** * Create a new either with the left value occupied. * @@ -72,141 +72,138 @@ public class Either<LeftType, RightType> implements IPair<LeftType, RightType> { } } - @Override - public <BoundLeft, BoundRight> IPair<BoundLeft, BoundRight> bind( - final BiFunction<LeftType, RightType, IPair<BoundLeft, BoundRight>> binder) { - if (binder == null) - throw new NullPointerException("Binder must not be null"); - - return binder.apply(leftVal, rightVal); + /** + * Perform a mapping over this either. + * + * @param <NewLeft> The new left type. + * @param <NewRight> The new right type. + * + * @param leftFunc The function to apply if this is a left either. + * @param rightFunc The function to apply if this is a right either. + * + * @return A new either, containing a value transformed by the appropriate function. + */ + public <NewLeft, NewRight> Either<NewLeft, NewRight> map( + Function<LeftType, NewLeft> leftFunc, + Function<RightType, NewRight> rightFunc) + { + if (isLeft) return left(leftFunc.apply(leftVal)); + else return right(rightFunc.apply(rightVal)); } - - @Override - public <BoundLeft> IPair<BoundLeft, RightType> - bindLeft(final Function<LeftType, IPair<BoundLeft, RightType>> leftBinder) { - if (leftBinder == null) - throw new NullPointerException("Left binder must not be null"); - - if (isLeft) - return leftBinder.apply(leftVal); - - return new Either<>(null, rightVal); + + /** + * Extract the value from this Either. + * + * @param <Common> The common type to extract. + * + * @param leftHandler The function to handle left-values. + * @param rightHandler The function to handle right-values. + * + * @return The result of applying the proper function. + */ + public <Common> Common extract( + Function<LeftType, Common> leftHandler, + Function<RightType, Common> rightHandler) + { + if (isLeft) return leftHandler.apply(leftVal); + else return rightHandler.apply(rightVal); } - - @Override - public <BoundRight> IPair<LeftType, BoundRight> bindRight( - final Function<RightType, IPair<LeftType, BoundRight>> rightBinder) { - if (rightBinder == null) - throw new NullPointerException("Right binder must not be null"); - - if (isLeft) - return new Either<>(leftVal, null); - - return rightBinder.apply(rightVal); + + /** + * Perform an action on this either. + * + * @param leftHandler The handler of left values. + * @param rightHandler The handler of right values. + */ + public void pick( + Consumer<LeftType> leftHandler, Consumer<RightType> rightHandler) + { + if (isLeft) leftHandler.accept(leftVal); + else rightHandler.accept(rightVal); } - @Override - public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> - IPair<CombinedLeft, CombinedRight> - combine(final IPair<OtherLeft, OtherRight> otherPair, - final BiFunction<LeftType, OtherLeft, CombinedLeft> leftCombiner, - final BiFunction<RightType, OtherRight, - CombinedRight> rightCombiner) { - if (otherPair == null) { - throw new NullPointerException("Other pair must not be null"); - } else if (leftCombiner == null) { - throw new NullPointerException("Left combiner must not be null"); - } else if (rightCombiner == null) { - throw new NullPointerException("Right combiner must not be null"); - } - - if (isLeft) { - return otherPair.bind((otherLeft, otherRight) -> { - CombinedLeft cLeft = leftCombiner.apply(leftVal, otherLeft); - - return new Either<>(cLeft, null); - }); - } - - return otherPair.bind((otherLeft, otherRight) -> { - CombinedRight cRight = rightCombiner.apply(rightVal, otherRight); - - return new Either<>(null, cRight); - }); + /** + * Check if this either is left-aligned (has the left value filled, + * not the right value). + * + * @return Whether this either is left-aligned. + */ + public boolean isLeft() { + return isLeft; } - @Override - public <NewLeft> IPair<NewLeft, RightType> - mapLeft(final Function<LeftType, NewLeft> mapper) { - if (mapper == null) - throw new NullPointerException("Mapper must not be null"); - - if (isLeft) - return new Either<>(mapper.apply(leftVal), null); - - return new Either<>(null, rightVal); + /** + * Get the left value of this either if there is one. + * + * @return An optional containing the left value, if there is one. + */ + public Optional<LeftType> getLeft() { + return Optional.ofNullable(leftVal); } - - @Override - public <NewRight> IPair<LeftType, NewRight> - mapRight(final Function<RightType, NewRight> mapper) { - if (mapper == null) - throw new NullPointerException("Mapper must not be null"); - + + /** + * Get the left value of this either, or get a {@link NoSuchElementException} + * if there isn't one. + * + * @return The left value of this either. + * + * @throws NoSuchElementException If this either doesn't have a left value. + */ + public LeftType forceLeft() { if (isLeft) - return new Either<>(leftVal, null); - - return new Either<>(null, mapper.apply(rightVal)); + { + return leftVal; + } else + { + throw new NoSuchElementException("Either has no left value, is right value"); + } } - @Override - public <MergedType> MergedType - merge(final BiFunction<LeftType, RightType, MergedType> merger) { - if (merger == null) - throw new NullPointerException("Merger must not be null"); - - return merger.apply(leftVal, rightVal); + /** + * Get the right value of this either if there is one. + * + * @return An optional containing the right value, if there is one. + */ + public Optional<RightType> getRight() { + return Optional.ofNullable(rightVal); } - + + /** + * Get the right value of this either, or get a {@link NoSuchElementException} + * if there isn't one. + * + * @return The right value of this either. + * + * @throws NoSuchElementException If this either doesn't have a right value. + */ + public RightType forceRight() { + if (isLeft) + { + throw new NoSuchElementException("Either has no right value, has left value"); + } else + { + return rightVal; + } + } + + // Misc. overrides + @Override public int hashCode() { - final int prime = 31; - - int result = 1; - result = prime * result + (isLeft ? 1231 : 1237); - result = prime * result + (leftVal == null ? 0 : leftVal.hashCode()); - result = prime * result + (rightVal == null ? 0 : rightVal.hashCode()); - - return result; + return Objects.hash(isLeft, leftVal, rightVal); } @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof Either<?, ?>)) - return false; - - final Either<?, ?> other = (Either<?, ?>) obj; - - if (isLeft != other.isLeft) - return false; - - if (leftVal == null) { - if (other.leftVal != null) - return false; - } else if (!leftVal.equals(other.leftVal)) - return false; - - if (rightVal == null) { - if (other.rightVal != null) - return false; - } else if (!rightVal.equals(other.rightVal)) - return false; - - return true; + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + + Either<?, ?> other = (Either<?, ?>) obj; + + return isLeft == other.isLeft + && Objects.equals(leftVal, other.leftVal) + && Objects.equals(rightVal, other.rightVal); } @Override diff --git a/src/main/java/bjc/data/GeneratingIterator.java b/src/main/java/bjc/data/GeneratingIterator.java index ffa92cf..f926833 100644 --- a/src/main/java/bjc/data/GeneratingIterator.java +++ b/src/main/java/bjc/data/GeneratingIterator.java @@ -58,4 +58,17 @@ public class GeneratingIterator<E> implements Iterator<E> { return state; } + + /** + * Sets the state of this iterator. + * + * @param newState The new state value. + * + * @return The old state value. + */ + public E setState(E newState) { + E oldState = this.state; + this.state = newState; + return oldState; + } } diff --git a/src/main/java/bjc/data/IHolder.java b/src/main/java/bjc/data/Holder.java index faa3f64..f01812e 100644 --- a/src/main/java/bjc/data/IHolder.java +++ b/src/main/java/bjc/data/Holder.java @@ -18,7 +18,7 @@ import bjc.funcdata.theory.Functor; * @param <ContainedType> * The type of value held. */ -public interface IHolder<ContainedType> extends Functor<ContainedType> { +public interface Holder<ContainedType> extends Functor<ContainedType> { /** * Bind a function across the value in this container. * @@ -30,8 +30,8 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return A holder from binding the value. */ - public <BoundType> IHolder<BoundType> - bind(Function<ContainedType, IHolder<BoundType>> binder); + public <BoundType> Holder<BoundType> + bind(Function<ContainedType, Holder<BoundType>> binder); /** * Apply an action to the value. @@ -51,14 +51,14 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { default <ArgType, ReturnType> Function<Functor<ArgType>, Functor<ReturnType>> fmap(final Function<ArgType, ReturnType> func) { return argumentFunctor -> { - if (!(argumentFunctor instanceof IHolder<?>)) { + if (!(argumentFunctor instanceof Holder<?>)) { final String msg = "This functor only supports mapping over instances of IHolder"; throw new IllegalArgumentException(msg); } - final IHolder<ArgType> holder = (IHolder<ArgType>) argumentFunctor; + final Holder<ArgType> holder = (Holder<ArgType>) argumentFunctor; return holder.map(func); }; @@ -80,7 +80,7 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return The function lifted over the holder. */ - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(Function<ContainedType, NewType> func); /** @@ -88,7 +88,7 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return A lazy version of this holder. */ - public default IHolder<ContainedType> makeLazy() { + public default Holder<ContainedType> makeLazy() { return new WrappedLazy<>(this); } @@ -97,7 +97,7 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return A list version of this holder. */ - public default IHolder<ContainedType> makeList() { + public default Holder<ContainedType> makeList() { return new BoundListHolder<>(new FunctionalList<>(this)); } @@ -106,7 +106,7 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return An optional version of this holder. */ - public default IHolder<ContainedType> makeOptional() { + public default Holder<ContainedType> makeOptional() { return new WrappedOption<>(this); } @@ -123,7 +123,7 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return A holder with the mapped value */ - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(Function<ContainedType, MappedType> mapper); /** @@ -134,7 +134,7 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return The holder itself. */ - public default IHolder<ContainedType> replace(final ContainedType newValue) { + public default Holder<ContainedType> replace(final ContainedType newValue) { return transform(oldValue -> newValue); } @@ -146,7 +146,7 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { * * @return The holder itself, for easy chaining. */ - public IHolder<ContainedType> transform(UnaryOperator<ContainedType> transformer); + public Holder<ContainedType> transform(UnaryOperator<ContainedType> transformer); /** * Unwrap the value contained in this holder so that it is no longer held. @@ -161,4 +161,17 @@ public interface IHolder<ContainedType> extends Functor<ContainedType> { */ public <UnwrappedType> UnwrappedType unwrap(Function<ContainedType, UnwrappedType> unwrapper); + + /** + * Create an instace of IHolder containing a single value. + * + * @param <ElementType> The type of the value contained. + * + * @param contained The value to contain. + * + * @return An instance of IHolder containing that value. + */ + static <ElementType> Holder<ElementType> of(ElementType contained) { + return new Identity<>(contained); + } } diff --git a/src/main/java/bjc/data/IPair.java b/src/main/java/bjc/data/IPair.java deleted file mode 100644 index 5b1298e..0000000 --- a/src/main/java/bjc/data/IPair.java +++ /dev/null @@ -1,249 +0,0 @@ -package bjc.data; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; - -import bjc.funcdata.theory.Bifunctor; - -/** - * Represents a pair of values. - * - * @author ben - * - * @param <LeftType> - * The type of the left side of the pair. - * - * @param <RightType> - * The type of the right side of the pair. - * - */ -public interface IPair<LeftType, RightType> extends Bifunctor<LeftType, RightType> { - /** - * Bind a function across the values in this pair. - * - * @param <BoundLeft> - * The type of the bound left. - * - * @param <BoundRight> - * The type of the bound right. - * - * @param binder - * The function to bind with. - * - * @return The bound pair. - */ - public <BoundLeft, BoundRight> IPair<BoundLeft, BoundRight> - bind(BiFunction<LeftType, RightType, IPair<BoundLeft, BoundRight>> binder); - - /** - * Bind a function to the left value in this pair. - * - * @param <BoundLeft> - * The type of the bound value. - * - * @param leftBinder - * The function to use to bind. - * - * @return A pair with the left type bound. - */ - public <BoundLeft> IPair<BoundLeft, RightType> - bindLeft(Function<LeftType, IPair<BoundLeft, RightType>> leftBinder); - - /** - * Bind a function to the right value in this pair. - * - * @param <BoundRight> - * The type of the bound value. - * - * @param rightBinder - * The function to use to bind. - * - * @return A pair with the right type bound. - */ - public <BoundRight> IPair<LeftType, BoundRight> - bindRight(Function<RightType, IPair<LeftType, BoundRight>> rightBinder); - - /** - * Pairwise combine two pairs together. - * - * @param <OtherLeft> - * The left type of the other pair. - * - * @param <OtherRight> - * The right type of the other pair. - * - * @param otherPair - * The pair to combine with. - * - * @return The pairs, pairwise combined together. - */ - public default <OtherLeft, OtherRight> - IPair<IPair<LeftType, OtherLeft>, IPair<RightType, OtherRight>> - combine(final IPair<OtherLeft, OtherRight> otherPair) { - return combine(otherPair, Pair<LeftType, OtherLeft>::new, - Pair<RightType, OtherRight>::new); - } - - /** - * Combine the contents of two pairs together. - * - * @param <OtherLeft> - * The type of the left value of the other pair. - * - * @param <OtherRight> - * The type of the right value of the other pair. - * - * @param <CombinedLeft> - * The type of the left value of the combined pair. - * - * @param <CombinedRight> - * The type of the right value of the combined pair. - * - * @param otherPair - * The other pair to combine with. - * - * @param leftCombiner - * The function to combine the left values with. - * - * @param rightCombiner - * The function to combine the right values with. - * - * @return A pair with its values combined. - */ - public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> - IPair<CombinedLeft, CombinedRight> - combine(IPair<OtherLeft, OtherRight> otherPair, - BiFunction<LeftType, OtherLeft, CombinedLeft> leftCombiner, - BiFunction<RightType, OtherRight, CombinedRight> rightCombiner); - - /** - * Immediately perfom the specified action with the contents of this pair. - * - * @param consumer - * The action to perform on the pair. - */ - public default void doWith(final BiConsumer<LeftType, RightType> consumer) { - merge((leftValue, rightValue) -> { - consumer.accept(leftValue, rightValue); - - return null; - }); - } - - @Override - default <OldLeft, OldRight, NewLeft> LeftBifunctorMap<OldLeft, OldRight, NewLeft> - fmapLeft(final Function<OldLeft, NewLeft> func) { - return argumentPair -> { - if (!(argumentPair instanceof IPair<?, ?>)) { - final String msg - = "This function can only be applied to instances of IPair"; - - throw new IllegalArgumentException(msg); - } - - final IPair<OldLeft, OldRight> argPair - = (IPair<OldLeft, OldRight>) argumentPair; - - return argPair.mapLeft(func); - }; - } - - @Override - default <OldLeft, OldRight, NewRight> RightBifunctorMap<OldLeft, OldRight, NewRight> - fmapRight(final Function<OldRight, NewRight> func) { - return argumentPair -> { - if (!(argumentPair instanceof IPair<?, ?>)) { - final String msg - = "This function can only be applied to instances of IPair"; - - throw new IllegalArgumentException(msg); - } - - final IPair<OldLeft, OldRight> argPair - = (IPair<OldLeft, OldRight>) argumentPair; - - return argPair.mapRight(func); - }; - } - - /** - * Get the value on the left side of the pair. - * - * @return The value on the left side of the pair. - */ - @Override - public default LeftType getLeft() { - return merge((leftValue, rightValue) -> leftValue); - } - - /** - * Get the value on the right side of the pair. - * - * @return The value on the right side of the pair. - */ - @Override - public default RightType getRight() { - return merge((leftValue, rightValue) -> rightValue); - } - - /** - * Transform the value on the left side of the pair. - * - * Doesn't modify the pair. - * - * @param <NewLeft> - * The new type of the left part of the pair. - * - * @param mapper - * The function to use to transform the left part of the pair. - * - * @return The pair, with its left part transformed. - */ - public <NewLeft> IPair<NewLeft, RightType> - mapLeft(Function<LeftType, NewLeft> mapper); - - /** - * Transform the value on the right side of the pair. - * - * Doesn't modify the pair. - * - * @param <NewRight> - * The new type of the right part of the pair. - * - * @param mapper - * The function to use to transform the right part of the - * pair. - * - * @return The pair, with its right part transformed. - */ - public <NewRight> IPair<LeftType, NewRight> - mapRight(Function<RightType, NewRight> mapper); - - /** - * Merge the two values in this pair into a single value. - * - * @param <MergedType> - * The type of the single value. - * - * @param merger - * The function to use for merging. - * - * @return The pair, merged into a single value. - */ - public <MergedType> MergedType - merge(BiFunction<LeftType, RightType, MergedType> merger); - - /** - * Static pair constructor. - * - * @param left - * The left side of the pair. - * @param right - * The right side of the pair. - * @return A pair, with the specified left/right side. - */ - public static <T1, T2> IPair<T1, T2> pair(T1 left, T2 right) { - return new Pair<>(left, right); - } -} diff --git a/src/main/java/bjc/data/ITree.java b/src/main/java/bjc/data/ITree.java deleted file mode 100644 index e9c829e..0000000 --- a/src/main/java/bjc/data/ITree.java +++ /dev/null @@ -1,287 +0,0 @@ -package bjc.data; - -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; - -import bjc.funcdata.IList; -import bjc.funcdata.bst.TreeLinearizationMethod; - -/** - * A node in a homogeneous tree with a unlimited amount of children. - * - * @author ben - * - * @param <ContainedType> - * The type of data contained in the tree nodes. - * - */ -public interface ITree<ContainedType> { - /** - * Append a child to this node. - * - * @param child - * The child to append to this node. - */ - void addChild(ITree<ContainedType> child); - - /** - * Append a child to this node. - * - * @param child - * The child to append to this node. - */ - void addChild(ContainedType child); - - /** - * Prepend a child to this node. - * - * @param child - * The child to prepend to this node. - */ - void prependChild(ITree<ContainedType> child); - - /** - * Collapse a tree into a single version. - * - * @param <NewType> - * The intermediate type being folded. - * - * @param <ReturnedType> - * The type that is the end result. - * - * @param leafTransform - * The function to use to convert leaf values. - * - * @param nodeCollapser - * The function to use to convert internal nodes and - * their children. - * - * @param resultTransformer - * The function to use to convert a state to the - * returned version. - * - * @return The final transformed state. - */ - <NewType, ReturnedType> ReturnedType collapse( - Function<ContainedType, NewType> leafTransform, - BiFunction<ContainedType, IList<NewType>, NewType> nodeCollapser, - Function<NewType, ReturnedType> resultTransformer); - - /** - * Execute a given action for each of this tree's children. - * - * @param action - * The action to execute for each child. - */ - void doForChildren(Consumer<ITree<ContainedType>> action); - - /** - * Expand the nodes of a tree into trees, and then merge the contents of those - * trees into a single tree. - * - * @param mapper - * The function to use to map values into trees. - * - * @return A tree, with some nodes expanded into trees. - */ - default ITree<ContainedType> - flatMapTree(final Function<ContainedType, ITree<ContainedType>> mapper) { - return topDownTransform(dat -> TopDownTransformResult.PUSHDOWN, node -> { - if (node.getChildrenCount() > 0) { - final ITree<ContainedType> parent = node.transformHead(mapper); - - node.doForChildren(parent::addChild); - - return parent; - } - - return node.transformHead(mapper); - }); - } - - /** - * Get the specified child of this tree. - * - * @param childNo - * The number of the child to get. - * - * @return The specified child of this tree. - */ - default ITree<ContainedType> getChild(final int childNo) { - return transformChild(childNo, child -> child); - } - - /** - * Get a count of the number of direct children this node has. - * - * @return The number of direct children this node has. - */ - int getChildrenCount(); - - /** - * Get a count of the number of direct children this node has. - * - * @return The number of direct children this node has. - */ - default int size() { - return getChildrenCount(); - } - - /** - * Get the data stored in this node. - * - * @return The data stored in this node. - */ - default ContainedType getHead() { - return transformHead(head -> head); - } - - /** - * Rebuild the tree with the same structure, but different nodes. - * - * @param <MappedType> - * The type of the new tree. - * - * @param leafTransformer - * The function to use to transform leaf tokens. - * - * @param internalTransformer - * The function to use to transform internal tokens. - * - * @return The tree, with the nodes changed. - */ - <MappedType> ITree<MappedType> rebuildTree( - Function<ContainedType, MappedType> leafTransformer, - Function<ContainedType, MappedType> internalTransformer); - - /** - * Transform some of the nodes in this tree. - * - * @param nodePicker - * The predicate to use to pick nodes to transform. - * - * @param transformer - * The function to use to transform picked nodes. - */ - void selectiveTransform(Predicate<ContainedType> nodePicker, - UnaryOperator<ContainedType> transformer); - - /** - * Do a top-down transform of the tree. - * - * @param transformPicker - * The function to use to pick how to progress. - * - * @param transformer - * The function used to transform picked subtrees. - * - * @return The tree with the transform applied to picked subtrees. - */ - ITree<ContainedType> topDownTransform( - Function<ContainedType, TopDownTransformResult> transformPicker, - UnaryOperator<ITree<ContainedType>> transformer); - - /** - * Transform one of this nodes children. - * - * @param <TransformedType> - * The type of the transformed value. - * - * @param childNo - * The number of the child to transform. - * - * @param transformer - * The function to use to transform the value. - * - * @return The transformed value. - * - * @throws IllegalArgumentException - * if the childNo is out of bounds (0 <= - * childNo <= childCount()). - */ - <TransformedType> TransformedType transformChild(int childNo, - Function<ITree<ContainedType>, TransformedType> transformer); - - /** - * Transform the value that is the head of this node. - * - * @param <TransformedType> - * The type of the transformed value. - * - * @param transformer - * The function to use to transform the value. - * - * @return The transformed value. - */ - <TransformedType> TransformedType - transformHead(Function<ContainedType, TransformedType> transformer); - - /** - * Transform the tree into a tree with a different type of token. - * - * @param <MappedType> - * The type of the new tree. - * - * @param transformer - * The function to use to transform tokens. - * - * @return A tree with the token types transformed. - */ - default <MappedType> ITree<MappedType> - transformTree(final Function<ContainedType, MappedType> transformer) { - return rebuildTree(transformer, transformer); - } - - /** - * Perform an action on each part of the tree. - * - * @param linearizationMethod - * The way to traverse the tree. - * - * @param action - * The action to perform on each tree node. - */ - void traverse(TreeLinearizationMethod linearizationMethod, - Consumer<ContainedType> action); - - /** - * Find the farthest to right child that satisfies the given predicate. - * - * @param childPred - * The predicate to satisfy. - * - * @return The index of the right-most child that satisfies the predicate, or -1 - * if one doesn't exist. - */ - int revFind(Predicate<ITree<ContainedType>> childPred); - - /** - * Check if this tree contains any nodes that satisfy the predicate. - * - * @param pred - * The predicate to look for. - * - * @return Whether or not any items satisfied the predicate. - */ - default boolean containsMatching(Predicate<ContainedType> pred) { - Toggle<Boolean> tog = new OneWayToggle<>(false, true); - - traverse(TreeLinearizationMethod.POSTORDER, val -> { - if (pred.test(val)) - tog.get(); - }); - - return tog.get(); - } - - /** - * Set the head of the tree. - * - * @param dat - * The value to set as the head of the tree. - */ - void setHead(ContainedType dat); -} diff --git a/src/main/java/bjc/data/Identity.java b/src/main/java/bjc/data/Identity.java index c80c7e1..fc4798e 100644 --- a/src/main/java/bjc/data/Identity.java +++ b/src/main/java/bjc/data/Identity.java @@ -11,7 +11,7 @@ import java.util.function.UnaryOperator; * @param <ContainedType> * The type contained in the holder. */ -public class Identity<ContainedType> implements IHolder<ContainedType> { +public class Identity<ContainedType> implements Holder<ContainedType> { /* The held value. */ private ContainedType heldValue; @@ -31,8 +31,8 @@ public class Identity<ContainedType> implements IHolder<ContainedType> { } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<ContainedType, IHolder<BoundType>> binder) { + public <BoundType> Holder<BoundType> + bind(final Function<ContainedType, Holder<BoundType>> binder) { return binder.apply(heldValue); } @@ -48,26 +48,23 @@ public class Identity<ContainedType> implements IHolder<ContainedType> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof Identity)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Identity)) return false; final Identity<?> other = (Identity<?>) obj; if (heldValue == null) { - if (other.heldValue != null) - return false; - } else if (!heldValue.equals(other.heldValue)) + if (other.heldValue != null) return false; + } else if (!heldValue.equals(other.heldValue)) { return false; + } return true; } @Override - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(final Function<ContainedType, NewType> func) { return val -> { return new Identity<>(func.apply(val)); @@ -75,7 +72,7 @@ public class Identity<ContainedType> implements IHolder<ContainedType> { } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<ContainedType, MappedType> mapper) { return new Identity<>(mapper.apply(heldValue)); } @@ -86,7 +83,7 @@ public class Identity<ContainedType> implements IHolder<ContainedType> { } @Override - public IHolder<ContainedType> + public Holder<ContainedType> transform(final UnaryOperator<ContainedType> transformer) { heldValue = transformer.apply(heldValue); @@ -102,8 +99,8 @@ public class Identity<ContainedType> implements IHolder<ContainedType> { /** * Create a new identity container. * - * @param val - * The contained value. + * @param <ContainedType> The type of the contained value. + * @param val The contained value. * * @return A new identity container. */ @@ -113,7 +110,9 @@ public class Identity<ContainedType> implements IHolder<ContainedType> { /** * Create a new empty identity container. - * + * + * @param <ContainedType> The type of the contained value. + * * @return A new empty identity container. */ public static <ContainedType> Identity<ContainedType> id() { diff --git a/src/main/java/bjc/data/IntHolder.java b/src/main/java/bjc/data/IntHolder.java new file mode 100644 index 0000000..2783669 --- /dev/null +++ b/src/main/java/bjc/data/IntHolder.java @@ -0,0 +1,72 @@ +package bjc.data; + +/** + * Utility class for ints by ref. + * + * @author Ben Culkin + */ +public class IntHolder { + /** + * The int value. + */ + public int val; + + /** + * Create a new int-holder set to 0. + */ + public IntHolder() { + val = 0; + } + + /** + * Create a new int-holder set to a value. + * + * @param i + * The value to set the int to. + */ + public IntHolder(int i) { + val = i; + } + + /** + * Increment the value by one, and return it. + * + * @return The value of the holder. + */ + public int incr() { + return incr(1); + } + + /** + * Increment the value by an amount and return it. + * + * @param i + * The amount to increment by. + * + * @return The value of the holder. + */ + public int incr(int i) { + val += 1; + + return val; + } + + /** + * Get the value. + * + * @return The value. + */ + public int get() { + return val; + } + + /** + * Set the value. + * + * @param i + * The value to set it to. + */ + public void set(int i) { + val = i; + } +} diff --git a/src/main/java/bjc/data/Lazy.java b/src/main/java/bjc/data/Lazy.java index a425232..b237935 100644 --- a/src/main/java/bjc/data/Lazy.java +++ b/src/main/java/bjc/data/Lazy.java @@ -6,7 +6,7 @@ import java.util.function.UnaryOperator; import bjc.data.internals.BoundLazy; import bjc.funcdata.FunctionalList; -import bjc.funcdata.IList; +import bjc.funcdata.ListEx; /** * A holder that holds a means to create a value, but doesn't actually compute @@ -17,7 +17,7 @@ import bjc.funcdata.IList; * @param <ContainedType> * The type of the value being held. */ -public class Lazy<ContainedType> implements IHolder<ContainedType> { +public class Lazy<ContainedType> implements Holder<ContainedType> { /* The supplier of the type. */ private Supplier<ContainedType> valueSupplier; /* The actual type value. */ @@ -26,7 +26,7 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { private boolean valueMaterialized; /* The list of pending actions on the value. */ - private IList<UnaryOperator<ContainedType>> actions = new FunctionalList<>(); + private ListEx<UnaryOperator<ContainedType>> actions = new FunctionalList<>(); /** * Create a new lazy value from the specified seed value. @@ -54,41 +54,44 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { /* Create a new value from a supplier and a list of actions. */ private Lazy(final Supplier<ContainedType> supp, - final IList<UnaryOperator<ContainedType>> pendingActions) { + final ListEx<UnaryOperator<ContainedType>> pendingActions) { valueSupplier = supp; actions = pendingActions; } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<ContainedType, IHolder<BoundType>> binder) { - final IList<UnaryOperator<ContainedType>> pendingActions = new FunctionalList<>(); + public <BoundType> Holder<BoundType> + bind(final Function<ContainedType, Holder<BoundType>> binder) { + final ListEx<UnaryOperator<ContainedType>> pendingActions = new FunctionalList<>(); - actions.forEach(pendingActions::add); + for (UnaryOperator<ContainedType> action : actions) { + pendingActions.add(action); + } + final Supplier<ContainedType> supplier = () -> { - if (valueMaterialized) - return heldValue; - - return valueSupplier.get(); + if (valueMaterialized) return heldValue; + else return valueSupplier.get(); }; return new BoundLazy<>(() -> new Lazy<>(supplier, pendingActions), binder); } @Override - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(final Function<ContainedType, NewType> func) { return val -> new Lazy<>(func.apply(val)); } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<ContainedType, MappedType> mapper) { - final IList<UnaryOperator<ContainedType>> pendingActions = new FunctionalList<>(); - - actions.forEach(pendingActions::add); + final ListEx<UnaryOperator<ContainedType>> pendingActions = new FunctionalList<>(); + + for (UnaryOperator<ContainedType> action : actions) { + pendingActions.add(action); + } return new Lazy<>(() -> { ContainedType currVal = heldValue; @@ -97,7 +100,8 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { currVal = valueSupplier.get(); } - return pendingActions.reduceAux(currVal, UnaryOperator<ContainedType>::apply, + return pendingActions.reduceAux(currVal, + UnaryOperator<ContainedType>::apply, value -> mapper.apply(value)); }); } @@ -107,16 +111,22 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { if (valueMaterialized) { if (actions.isEmpty()) { return String.format("value[v='%s']", heldValue); + } else { + return String.format("value[v='%s'] (has %d pending transforms)", + heldValue, actions.getSize()); } - - return String.format("value[v='%s'] (has pending transforms)", heldValue); } - return "(unmaterialized)"; + if (actions.isEmpty()) { + return"(unmaterialized)"; + } else { + return String.format("(unmaterialized; has %d pending transforms", + actions.getSize()); + } } @Override - public IHolder<ContainedType> + public Holder<ContainedType> transform(final UnaryOperator<ContainedType> transformer) { actions.add(transformer); @@ -132,9 +142,9 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { valueMaterialized = true; } - actions.forEach(action -> { - heldValue = action.apply(heldValue); - }); + for (UnaryOperator<ContainedType> action : actions) { + heldValue = action.apply(heldValue); + } actions = new FunctionalList<>(); @@ -155,12 +165,9 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof Lazy<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Lazy<?>)) return false; final Lazy<?> other = (Lazy<?>) obj; @@ -169,27 +176,29 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { if (valueMaterialized) { if (heldValue == null) { - if (other.heldValue != null) - return false; - } else if (!heldValue.equals(other.heldValue)) + if (other.heldValue != null) return false; + } else if (!heldValue.equals(other.heldValue)) { return false; - } else + } + } else { return false; + } if (actions == null) { - if (other.actions != null) - return false; - } else if (actions.getSize() > 0 || other.actions.getSize() > 0) + if (other.actions != null) return false; + } else if (actions.getSize() > 0 || other.actions.getSize() > 0) { return false; + } return true; } /** * Create a new lazy container with an already present value. - * - * @param val - * The value for the lazy container. + * + * @param <ContainedType> The type of the contained value. + * + * @param val The value for the lazy container. * * @return A new lazy container holding that value. */ @@ -200,8 +209,9 @@ public class Lazy<ContainedType> implements IHolder<ContainedType> { /** * Create a new lazy container with a suspended value. * - * @param supp - * The suspended value for the lazy container. + * @param <ContainedType> The type of the contained value. + * + * @param supp The suspended value for the lazy container. * * @return A new lazy container that will un-suspend the value when necessary. */ diff --git a/src/main/java/bjc/data/LazyPair.java b/src/main/java/bjc/data/LazyPair.java index e668cd4..048254a 100644 --- a/src/main/java/bjc/data/LazyPair.java +++ b/src/main/java/bjc/data/LazyPair.java @@ -18,7 +18,7 @@ import bjc.data.internals.HalfBoundLazyPair; * @param <RightType> * The type on the right side of the pair. */ -public class LazyPair<LeftType, RightType> implements IPair<LeftType, RightType> { +public class LazyPair<LeftType, RightType> implements Pair<LeftType, RightType> { /* The supplier for the left value. */ private Supplier<LeftType> leftSupplier; /* The left value. */ @@ -70,17 +70,16 @@ public class LazyPair<LeftType, RightType> implements IPair<LeftType, RightType> } @Override - public <BoundLeft, BoundRight> IPair<BoundLeft, BoundRight> bind( - final BiFunction<LeftType, RightType, IPair<BoundLeft, BoundRight>> binder) { + public <BoundLeft, BoundRight> Pair<BoundLeft, BoundRight> bind( + final BiFunction<LeftType, RightType, Pair<BoundLeft, BoundRight>> binder) { return new BoundLazyPair<>(leftSupplier, rightSupplier, binder); } @Override - public <BoundLeft> IPair<BoundLeft, RightType> - bindLeft(final Function<LeftType, IPair<BoundLeft, RightType>> leftBinder) { + public <BoundLeft> Pair<BoundLeft, RightType> + bindLeft(final Function<LeftType, Pair<BoundLeft, RightType>> leftBinder) { final Supplier<LeftType> leftSupp = () -> { - if (leftMaterialized) - return leftValue; + if (leftMaterialized) return leftValue; return leftSupplier.get(); }; @@ -89,11 +88,10 @@ public class LazyPair<LeftType, RightType> implements IPair<LeftType, RightType> } @Override - public <BoundRight> IPair<LeftType, BoundRight> bindRight( - final Function<RightType, IPair<LeftType, BoundRight>> rightBinder) { + public <BoundRight> Pair<LeftType, BoundRight> bindRight( + final Function<RightType, Pair<LeftType, BoundRight>> rightBinder) { final Supplier<RightType> rightSupp = () -> { - if (rightMaterialized) - return rightValue; + if (rightMaterialized) return rightValue; return rightSupplier.get(); }; @@ -103,17 +101,19 @@ public class LazyPair<LeftType, RightType> implements IPair<LeftType, RightType> @Override public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> - IPair<CombinedLeft, CombinedRight> - combine(final IPair<OtherLeft, OtherRight> otherPair, + Pair<CombinedLeft, CombinedRight> + combine(final Pair<OtherLeft, OtherRight> otherPair, final BiFunction<LeftType, OtherLeft, CombinedLeft> leftCombiner, final BiFunction<RightType, OtherRight, CombinedRight> rightCombiner) { - return otherPair.bind((otherLeft, otherRight) -> bind((leftVal, rightVal) -> { - final CombinedLeft left = leftCombiner.apply(leftVal, otherLeft); - final CombinedRight right = rightCombiner.apply(rightVal, otherRight); - - return new LazyPair<>(left, right); - })); + return otherPair.bind((otherLeft, otherRight) -> { + return bind((leftVal, rightVal) -> { + final CombinedLeft left = leftCombiner.apply(leftVal, otherLeft); + final CombinedRight right = rightCombiner.apply(rightVal, otherRight); + + return new LazyPair<>(left, right); + }); + }); } @Override @@ -139,18 +139,16 @@ public class LazyPair<LeftType, RightType> implements IPair<LeftType, RightType> } @Override - public <NewLeft> IPair<NewLeft, RightType> + public <NewLeft> Pair<NewLeft, RightType> mapLeft(final Function<LeftType, NewLeft> mapper) { final Supplier<NewLeft> leftSupp = () -> { - if (leftMaterialized) - return mapper.apply(leftValue); + if (leftMaterialized) return mapper.apply(leftValue); return mapper.apply(leftSupplier.get()); }; final Supplier<RightType> rightSupp = () -> { - if (rightMaterialized) - return rightValue; + if (rightMaterialized) return rightValue; return rightSupplier.get(); }; @@ -159,18 +157,16 @@ public class LazyPair<LeftType, RightType> implements IPair<LeftType, RightType> } @Override - public <NewRight> IPair<LeftType, NewRight> + public <NewRight> Pair<LeftType, NewRight> mapRight(final Function<RightType, NewRight> mapper) { final Supplier<LeftType> leftSupp = () -> { - if (leftMaterialized) - return leftValue; + if (leftMaterialized) return leftValue; return leftSupplier.get(); }; final Supplier<NewRight> rightSupp = () -> { - if (rightMaterialized) - return mapper.apply(rightValue); + if (rightMaterialized) return mapper.apply(rightValue); return mapper.apply(rightSupplier.get()); }; @@ -231,37 +227,35 @@ public class LazyPair<LeftType, RightType> implements IPair<LeftType, RightType> @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof LazyPair<?, ?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof LazyPair<?, ?>)) return false; final LazyPair<?, ?> other = (LazyPair<?, ?>) obj; - if (leftMaterialized != other.leftMaterialized) - return false; + if (leftMaterialized != other.leftMaterialized) return false; if (leftMaterialized) { if (leftValue == null) { - if (other.leftValue != null) - return false; - } else if (!leftValue.equals(other.leftValue)) + if (other.leftValue != null) return false; + } else if (!leftValue.equals(other.leftValue)) { return false; - } else + } + } else { return false; + } - if (rightMaterialized != other.rightMaterialized) - return false; + if (rightMaterialized != other.rightMaterialized) return false; + if (rightMaterialized) { if (rightValue == null) { - if (other.rightValue != null) - return false; - } else if (!rightValue.equals(other.rightValue)) + if (other.rightValue != null) return false; + } else if (!rightValue.equals(other.rightValue)) { return false; - } else + } + } else { return false; + } return true; } diff --git a/src/main/java/bjc/data/ListHolder.java b/src/main/java/bjc/data/ListHolder.java index ab3bfa8..c8a75ff 100644 --- a/src/main/java/bjc/data/ListHolder.java +++ b/src/main/java/bjc/data/ListHolder.java @@ -5,7 +5,7 @@ import java.util.function.UnaryOperator; import bjc.data.internals.BoundListHolder; import bjc.funcdata.FunctionalList; -import bjc.funcdata.IList; +import bjc.funcdata.ListEx; /** * A holder that represents a set of non-deterministic computations. @@ -15,8 +15,8 @@ import bjc.funcdata.IList; * @param <ContainedType> * The type of contained value. */ -public class ListHolder<ContainedType> implements IHolder<ContainedType> { - private IList<ContainedType> heldValues; +public class ListHolder<ContainedType> implements Holder<ContainedType> { + private ListEx<ContainedType> heldValues; /** * Create a new list holder. @@ -36,34 +36,34 @@ public class ListHolder<ContainedType> implements IHolder<ContainedType> { } /* Create a new holder with values. */ - private ListHolder(final IList<ContainedType> toHold) { + private ListHolder(final ListEx<ContainedType> toHold) { heldValues = toHold; } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<ContainedType, IHolder<BoundType>> binder) { - final IList<IHolder<BoundType>> boundValues = heldValues.map(binder); + public <BoundType> Holder<BoundType> + bind(final Function<ContainedType, Holder<BoundType>> binder) { + final ListEx<Holder<BoundType>> boundValues = heldValues.map(binder); return new BoundListHolder<>(boundValues); } @Override - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(final Function<ContainedType, NewType> func) { return val -> new ListHolder<>(new FunctionalList<>(func.apply(val))); } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<ContainedType, MappedType> mapper) { - final IList<MappedType> mappedValues = heldValues.map(mapper); + final ListEx<MappedType> mappedValues = heldValues.map(mapper); return new ListHolder<>(mappedValues); } @Override - public IHolder<ContainedType> + public Holder<ContainedType> transform(final UnaryOperator<ContainedType> transformer) { heldValues = heldValues.map(transformer); @@ -93,20 +93,17 @@ public class ListHolder<ContainedType> implements IHolder<ContainedType> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof ListHolder<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof ListHolder<?>)) return false; final ListHolder<?> other = (ListHolder<?>) obj; if (heldValues == null) { - if (other.heldValues != null) - return false; - } else if (!heldValues.equals(other.heldValues)) + if (other.heldValues != null) return false; + } else if (!heldValues.equals(other.heldValues)) { return false; + } return true; } diff --git a/src/main/java/bjc/data/NonCMEIterator.java b/src/main/java/bjc/data/NonCMEIterator.java new file mode 100644 index 0000000..88eaa6b --- /dev/null +++ b/src/main/java/bjc/data/NonCMEIterator.java @@ -0,0 +1,54 @@ +package bjc.data; + +import java.util.*; + +/** + * A Iterator which is guaranteed to never throw {@link ConcurrentModificationException}. + * + * The intended use case for this iterator is that you want to be able to modify + * a list you are iterating over, and not get an immediate exception. + * + * Note that using this is an agreement that you will not complain if it behaves + * oddly in the face of you concurrently modifying the list, as this class was + * designed with the usecase of modifying the list from the same thread, without + * invalidating the iterators. After all, there is a reason that most iterator + * types throw that exception. + * + * However, sometimes you want to play with fire, even if you might get burnt. + * This allows you to do so. + * + * @author Ben Culkin + * + * @param <ElementType> The type being iterated over. + */ +public class NonCMEIterator<ElementType> implements Iterator<ElementType> { + /** + * The list being iterated over. + */ + protected final List<ElementType> source; + + /** + * The index of the current item in the list. + */ + protected int currIndex; + + /** + * Create a new iterator which won't throw {@link ConcurrentModificationException} + * + * @param source The list being iterated over. + */ + public NonCMEIterator(List<ElementType> source) { + this.source = source; + this.currIndex = -1; + } + + @Override + public boolean hasNext() { + return currIndex >= source.size(); + } + + @Override + public ElementType next() { + return source.get(++currIndex); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/data/NonCMEListIterator.java b/src/main/java/bjc/data/NonCMEListIterator.java new file mode 100644 index 0000000..6e74a24 --- /dev/null +++ b/src/main/java/bjc/data/NonCMEListIterator.java @@ -0,0 +1,68 @@ +package bjc.data; + +import java.util.*; + +/** + * A ListIterator which is guaranteed to never throw {@link ConcurrentModificationException}. + * + * The intended use case for this iterator is that you want to be able to modify + * a list you are iterating over, and not get an immediate exception. + * + * Note that using this is an agreement that you will not complain if it behaves + * oddly in the face of you concurrently modifying the list, as this class was + * designed with the usecase of modifying the list from the same thread, without + * invalidating the iterators. After all, there is a reason that most iterator + * types throw that exception. + * + * However, sometimes you want to play with fire, even if you might get burnt. + * This allows you to do so. + * + * @author Ben Culkin + * + * @param <ElementType> The type being iterated over. + */ +public class NonCMEListIterator<ElementType> extends NonCMEIterator<ElementType> implements ListIterator<ElementType> { + /** + * Create a new list iterator which won't throw {@link ConcurrentModificationException} + * + * @param source The list to iterate over. + */ + public NonCMEListIterator(List<ElementType> source) { + super(source); + } + + @Override + public boolean hasPrevious() { + return currIndex > 0; + } + + @Override + public ElementType previous() { + return source.get(--currIndex); + } + + @Override + public int nextIndex() { + return currIndex + 1; + } + + @Override + public int previousIndex() { + return currIndex - 1; + } + + @Override + public void remove() { + source.remove(currIndex); + } + + @Override + public void set(ElementType element) { + source.set(currIndex, element); + } + + @Override + public void add(ElementType element) { + source.add(currIndex, element); + } +} diff --git a/src/main/java/bjc/data/OneWayToggle.java b/src/main/java/bjc/data/OneWayToggle.java index c2e9e4b..b6074b0 100644 --- a/src/main/java/bjc/data/OneWayToggle.java +++ b/src/main/java/bjc/data/OneWayToggle.java @@ -31,23 +31,21 @@ public class OneWayToggle<E> implements Toggle<E> { @Override public E get() { - if (gotFirst) - return second; + if (gotFirst) return second; gotFirst = true; + return first; } @Override public E peek() { - if (gotFirst) - return second; - return first; + if (gotFirst) return second; + else return first; } @Override public void set(boolean gotFirst) { this.gotFirst = gotFirst; } - } diff --git a/src/main/java/bjc/data/Option.java b/src/main/java/bjc/data/Option.java index b5d6d5e..6ca8b13 100644 --- a/src/main/java/bjc/data/Option.java +++ b/src/main/java/bjc/data/Option.java @@ -11,9 +11,17 @@ import java.util.function.UnaryOperator; * @param <ContainedType> * The type of the value that may or may not be held. */ -public class Option<ContainedType> implements IHolder<ContainedType> { +public class Option<ContainedType> implements Holder<ContainedType> { private ContainedType held; - + private boolean isHolding; + + /** + * Create a new empty optional. + */ + public Option() { + isHolding = false; + } + /** * Create a new optional, using the given initial value. * @@ -22,38 +30,35 @@ public class Option<ContainedType> implements IHolder<ContainedType> { */ public Option(final ContainedType seed) { held = seed; + isHolding = true; } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<ContainedType, IHolder<BoundType>> binder) { - if (held == null) - return new Option<>(null); + public <BoundType> Holder<BoundType> + bind(final Function<ContainedType, Holder<BoundType>> binder) { + if (isHolding) return new Option<>(); return binder.apply(held); } @Override - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(final Function<ContainedType, NewType> func) { return val -> new Option<>(func.apply(val)); } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<ContainedType, MappedType> mapper) { - if (held == null) - return new Option<>(null); + if (isHolding) return new Option<>(); return new Option<>(mapper.apply(held)); } @Override - public IHolder<ContainedType> + public Holder<ContainedType> transform(final UnaryOperator<ContainedType> transformer) { - if (held != null) { - held = transformer.apply(held); - } + if (isHolding) held = transformer.apply(held); return this; } @@ -61,8 +66,7 @@ public class Option<ContainedType> implements IHolder<ContainedType> { @Override public <UnwrappedType> UnwrappedType unwrap(final Function<ContainedType, UnwrappedType> unwrapper) { - if (held == null) - return null; + if (isHolding) return null; return unwrapper.apply(held); } @@ -84,20 +88,17 @@ public class Option<ContainedType> implements IHolder<ContainedType> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof Option<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Option<?>)) return false; final Option<?> other = (Option<?>) obj; if (held == null) { - if (other.held != null) - return false; - } else if (!held.equals(other.held)) + if (other.held != null) return false; + } else if (!held.equals(other.held)) { return false; + } return true; } diff --git a/src/main/java/bjc/data/Pair.java b/src/main/java/bjc/data/Pair.java index 610e89c..baf1894 100644 --- a/src/main/java/bjc/data/Pair.java +++ b/src/main/java/bjc/data/Pair.java @@ -1,163 +1,250 @@ package bjc.data; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; +import bjc.funcdata.theory.Bifunctor; + /** - * A pair of values, with nothing special about them. + * Represents a pair of values. * * @author ben * * @param <LeftType> - * The type of the left value. + * The type of the left side of the pair. * * @param <RightType> - * The type of the right value. + * The type of the right side of the pair. + * */ -public class Pair<LeftType, RightType> implements IPair<LeftType, RightType> { - /* The left value. */ - private LeftType leftValue; - /* The right value. */ - private RightType rightValue; - - /** Create a new pair with both sides set to null. */ - public Pair() { - - } - +public interface Pair<LeftType, RightType> extends Bifunctor<LeftType, RightType> { /** - * Create a new pair with both sides set to the specified values. + * Bind a function across the values in this pair. * - * @param left - * The value of the left side. + * @param <BoundLeft> + * The type of the bound left. * - * @param right - * The value of the right side. + * @param <BoundRight> + * The type of the bound right. + * + * @param binder + * The function to bind with. + * + * @return The bound pair. */ - public Pair(final LeftType left, final RightType right) { - leftValue = left; - rightValue = right; - } - - @Override - public <BoundLeft, BoundRight> IPair<BoundLeft, BoundRight> bind( - final BiFunction<LeftType, RightType, IPair<BoundLeft, BoundRight>> binder) { - if (binder == null) - throw new NullPointerException("Binder must not be null."); + public <BoundLeft, BoundRight> Pair<BoundLeft, BoundRight> + bind(BiFunction<LeftType, RightType, Pair<BoundLeft, BoundRight>> binder); - return binder.apply(leftValue, rightValue); - } + /** + * Bind a function to the left value in this pair. + * + * @param <BoundLeft> + * The type of the bound value. + * + * @param leftBinder + * The function to use to bind. + * + * @return A pair with the left type bound. + */ + public <BoundLeft> Pair<BoundLeft, RightType> + bindLeft(Function<LeftType, Pair<BoundLeft, RightType>> leftBinder); - @Override - public <BoundLeft> IPair<BoundLeft, RightType> - bindLeft(final Function<LeftType, IPair<BoundLeft, RightType>> leftBinder) { - if (leftBinder == null) - throw new NullPointerException("Binder must not be null"); + /** + * Bind a function to the right value in this pair. + * + * @param <BoundRight> + * The type of the bound value. + * + * @param rightBinder + * The function to use to bind. + * + * @return A pair with the right type bound. + */ + public <BoundRight> Pair<LeftType, BoundRight> + bindRight(Function<RightType, Pair<LeftType, BoundRight>> rightBinder); - return leftBinder.apply(leftValue); + /** + * Pairwise combine two pairs together. + * + * @param <OtherLeft> + * The left type of the other pair. + * + * @param <OtherRight> + * The right type of the other pair. + * + * @param otherPair + * The pair to combine with. + * + * @return The pairs, pairwise combined together. + */ + public default <OtherLeft, OtherRight> + Pair<Pair<LeftType, OtherLeft>, Pair<RightType, OtherRight>> + combine(final Pair<OtherLeft, OtherRight> otherPair) { + return combine(otherPair, + SimplePair<LeftType, OtherLeft>::new, + SimplePair<RightType, OtherRight>::new); } - @Override - public <BoundRight> IPair<LeftType, BoundRight> bindRight( - final Function<RightType, IPair<LeftType, BoundRight>> rightBinder) { - if (rightBinder == null) - throw new NullPointerException("Binder must not be null"); + /** + * Combine the contents of two pairs together. + * + * @param <OtherLeft> + * The type of the left value of the other pair. + * + * @param <OtherRight> + * The type of the right value of the other pair. + * + * @param <CombinedLeft> + * The type of the left value of the combined pair. + * + * @param <CombinedRight> + * The type of the right value of the combined pair. + * + * @param otherPair + * The other pair to combine with. + * + * @param leftCombiner + * The function to combine the left values with. + * + * @param rightCombiner + * The function to combine the right values with. + * + * @return A pair with its values combined. + */ + public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> + Pair<CombinedLeft, CombinedRight> + combine(Pair<OtherLeft, OtherRight> otherPair, + BiFunction<LeftType, OtherLeft, CombinedLeft> leftCombiner, + BiFunction<RightType, OtherRight, CombinedRight> rightCombiner); - return rightBinder.apply(rightValue); - } + /** + * Immediately perfom the specified action with the contents of this pair. + * + * @param consumer + * The action to perform on the pair. + */ + public default void doWith(final BiConsumer<LeftType, RightType> consumer) { + merge((leftValue, rightValue) -> { + consumer.accept(leftValue, rightValue); - @Override - public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> - IPair<CombinedLeft, CombinedRight> - combine(final IPair<OtherLeft, OtherRight> otherPair, - final BiFunction<LeftType, OtherLeft, CombinedLeft> leftCombiner, - final BiFunction<RightType, OtherRight, - CombinedRight> rightCombiner) { - return otherPair.bind((otherLeft, otherRight) -> { - final CombinedLeft left = leftCombiner.apply(leftValue, otherLeft); - final CombinedRight right = rightCombiner.apply(rightValue, otherRight); - - return new Pair<>(left, right); + return null; }); } @Override - public <NewLeft> IPair<NewLeft, RightType> - mapLeft(final Function<LeftType, NewLeft> mapper) { - if (mapper == null) - throw new NullPointerException("Mapper must not be null"); + default <OldLeft, OldRight, NewLeft> LeftBifunctorMap<OldLeft, OldRight, NewLeft> + fmapLeft(final Function<OldLeft, NewLeft> func) { + return argumentPair -> { + if (!(argumentPair instanceof Pair<?, ?>)) { + final String msg + = "This function can only be applied to instances of IPair"; - return new Pair<>(mapper.apply(leftValue), rightValue); - } + throw new IllegalArgumentException(msg); + } - @Override - public <NewRight> IPair<LeftType, NewRight> - mapRight(final Function<RightType, NewRight> mapper) { - if (mapper == null) - throw new NullPointerException("Mapper must not be null"); + final Pair<OldLeft, OldRight> argPair + = (Pair<OldLeft, OldRight>) argumentPair; - return new Pair<>(leftValue, mapper.apply(rightValue)); + return argPair.mapLeft(func); + }; } @Override - public <MergedType> MergedType - merge(final BiFunction<LeftType, RightType, MergedType> merger) { - if (merger == null) - throw new NullPointerException("Merger must not be null"); + default <OldLeft, OldRight, NewRight> RightBifunctorMap<OldLeft, OldRight, NewRight> + fmapRight(final Function<OldRight, NewRight> func) { + return argumentPair -> { + if (!(argumentPair instanceof Pair<?, ?>)) { + final String msg + = "This function can only be applied to instances of IPair"; - return merger.apply(leftValue, rightValue); - } + throw new IllegalArgumentException(msg); + } - @Override - public String toString() { - return String.format("Pair [leftValue='%s', rightValue='%s']", leftValue, - rightValue); + final Pair<OldLeft, OldRight> argPair + = (Pair<OldLeft, OldRight>) argumentPair; + + return argPair.mapRight(func); + }; } + /** + * Get the value on the left side of the pair. + * + * @return The value on the left side of the pair. + */ @Override - public LeftType getLeft() { - return leftValue; + public default LeftType getLeft() { + return merge((leftValue, rightValue) -> leftValue); } + /** + * Get the value on the right side of the pair. + * + * @return The value on the right side of the pair. + */ @Override - public RightType getRight() { - return rightValue; + public default RightType getRight() { + return merge((leftValue, rightValue) -> rightValue); } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; + /** + * Transform the value on the left side of the pair. + * + * Doesn't modify the pair. + * + * @param <NewLeft> + * The new type of the left part of the pair. + * + * @param mapper + * The function to use to transform the left part of the pair. + * + * @return The pair, with its left part transformed. + */ + public <NewLeft> Pair<NewLeft, RightType> + mapLeft(Function<LeftType, NewLeft> mapper); - result = prime * result + (leftValue == null ? 0 : leftValue.hashCode()); - result = prime * result + (rightValue == null ? 0 : rightValue.hashCode()); + /** + * Transform the value on the right side of the pair. + * + * Doesn't modify the pair. + * + * @param <NewRight> + * The new type of the right part of the pair. + * + * @param mapper + * The function to use to transform the right part of the + * pair. + * + * @return The pair, with its right part transformed. + */ + public <NewRight> Pair<LeftType, NewRight> + mapRight(Function<RightType, NewRight> mapper); - return result; - } + /** + * Merge the two values in this pair into a single value. + * + * @param <MergedType> + * The type of the single value. + * + * @param merger + * The function to use for merging. + * + * @return The pair, merged into a single value. + */ + public <MergedType> MergedType + merge(BiFunction<LeftType, RightType, MergedType> merger); - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof Pair<?, ?>)) - return false; - - final Pair<?, ?> other = (Pair<?, ?>) obj; - - if (leftValue == null) { - if (other.leftValue != null) - return false; - } else if (!leftValue.equals(other.leftValue)) - return false; - - if (rightValue == null) { - if (other.rightValue != null) - return false; - } else if (!rightValue.equals(other.rightValue)) - return false; - - return true; + /** + * Static pair constructor. + * + * @param <Left> The type of the left side. + * @param <Right> The type of the right side. + * @param left The left side of the pair. + * @param right The right side of the pair. + * @return A pair, with the specified left/right side. + */ + public static <Left, Right> Pair<Left, Right> pair(Left left, Right right) { + return new SimplePair<>(left, right); } } diff --git a/src/main/java/bjc/data/QueuedIterator.java b/src/main/java/bjc/data/QueuedIterator.java index f10014e..805fc5e 100644 --- a/src/main/java/bjc/data/QueuedIterator.java +++ b/src/main/java/bjc/data/QueuedIterator.java @@ -9,8 +9,7 @@ import java.util.Iterator; * * @author bjculkin * - * @param <E> - * The type of element this iterator iterates over + * @param <E> The type of element this iterator iterates over */ public class QueuedIterator<E> implements Iterator<E> { private Iterator<E> cur; @@ -19,7 +18,9 @@ public class QueuedIterator<E> implements Iterator<E> { /** * Static method for constructing iterators. - * + * + * @param <E> The type of element this iterator iterates over + * * @return A queued iterator. */ public static <E> QueuedIterator<E> queued() { @@ -29,8 +30,9 @@ public class QueuedIterator<E> implements Iterator<E> { /** * Static method for constructing iterators. * - * @param vals - * The values to iterate over. + * @param <E> The type of element this iterator iterates over + * + * @param vals The values to iterate over. * * @return A queued iterator. */ @@ -42,8 +44,9 @@ public class QueuedIterator<E> implements Iterator<E> { /** * Static method for constructing iterators. * - * @param itrs - * The iterators to use. + * @param <E> The type of element this iterator iterates over + * + * @param itrs The iterators to use. * * @return A queued iterator over the provided iterators. */ @@ -55,8 +58,9 @@ public class QueuedIterator<E> implements Iterator<E> { /** * Static method for constructing iterators. * - * @param itrs - * The iterables to use. + * @param <E> The type of element this iterator iterates over + * + * @param itrs The iterables to use. * * @return A queued iterator over the provided iterables. */ @@ -82,9 +86,7 @@ public class QueuedIterator<E> implements Iterator<E> { public QueuedIterator(Iterator<E>... inits) { this(); - for (Iterator<E> init : inits) { - pending.add(init); - } + for (Iterator<E> init : inits) pending.add(init); } /** @@ -97,9 +99,7 @@ public class QueuedIterator<E> implements Iterator<E> { public QueuedIterator(Iterable<E>... inits) { this(); - for (Iterable<E> init : inits) { - pending.add(init.iterator()); - } + for (Iterable<E> init : inits) pending.add(init.iterator()); } /** @@ -208,8 +208,7 @@ public class QueuedIterator<E> implements Iterator<E> { @Override public boolean hasNext() { while (cur == null || !cur.hasNext()) { - if (pending.isEmpty()) - return false; + if (pending.isEmpty()) return false; cur = pending.pop(); } @@ -220,13 +219,11 @@ public class QueuedIterator<E> implements Iterator<E> { @Override public E next() { while (cur == null || !cur.hasNext()) { - if (pending.isEmpty()) - return null; + if (pending.isEmpty()) return null; cur = pending.pop(); } return cur.next(); } - } diff --git a/src/main/java/bjc/data/ResettableIterator.java b/src/main/java/bjc/data/ResettableIterator.java index 2208fb2..8b7f07a 100644 --- a/src/main/java/bjc/data/ResettableIterator.java +++ b/src/main/java/bjc/data/ResettableIterator.java @@ -56,11 +56,8 @@ public class ResettableIterator<T> implements Iterator<T> { @Override public boolean hasNext() { - if (isRepeating) { - return cacheIterator.hasNext() ? true : backing.hasNext(); - } else { - return backing.hasNext(); - } + if (isRepeating) return cacheIterator.hasNext() ? true : backing.hasNext(); + else return backing.hasNext(); } @Override diff --git a/src/main/java/bjc/data/ReverseListIterator.java b/src/main/java/bjc/data/ReverseListIterator.java new file mode 100644 index 0000000..0b6ec84 --- /dev/null +++ b/src/main/java/bjc/data/ReverseListIterator.java @@ -0,0 +1,72 @@ +package bjc.data; + +import java.util.*; + +/** + * A list iterator which iterates over a list in reverse order. + * + * @author Ben Culkin + * + * @param <Element> The type of element contained. + */ +public class ReverseListIterator<Element> implements ListIterator<Element> +{ + private List<Element> source; + private int currIndex; + + /** + * Create a new reversed iterator from a list. + * + * @param source The list to operate from. + */ + public ReverseListIterator(List<Element> source) + { + this.source = source; + this.currIndex = source.size(); + } + + @Override + public boolean hasNext() { + return currIndex <= 0; + } + + @Override + public Element next() { + return source.get(--currIndex); + } + + @Override + public boolean hasPrevious() { + return currIndex < source.size(); + } + + @Override + public Element previous() { + return source.get(++currIndex); + } + + @Override + public int nextIndex() { + return currIndex - 1; + } + + @Override + public int previousIndex() { + return currIndex + 1; + } + + @Override + public void remove() { + source.remove(currIndex); + } + + @Override + public void set(Element element) { + source.set(currIndex, element); + } + + @Override + public void add(Element element) { + source.add(currIndex, element); + } +} diff --git a/src/main/java/bjc/data/SimplePair.java b/src/main/java/bjc/data/SimplePair.java new file mode 100644 index 0000000..48f5ca3 --- /dev/null +++ b/src/main/java/bjc/data/SimplePair.java @@ -0,0 +1,154 @@ +package bjc.data; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * A pair of values, with nothing special about them. + * + * @author ben + * + * @param <LeftType> + * The type of the left value. + * + * @param <RightType> + * The type of the right value. + */ +public class SimplePair<LeftType, RightType> implements Pair<LeftType, RightType> { + /* The left value. */ + private LeftType leftValue; + /* The right value. */ + private RightType rightValue; + + /** Create a new pair with both sides set to null. */ + public SimplePair() { + // Do nothing :) + } + + /** + * Create a new pair with both sides set to the specified values. + * + * @param left + * The value of the left side. + * + * @param right + * The value of the right side. + */ + public SimplePair(final LeftType left, final RightType right) { + leftValue = left; + rightValue = right; + } + + @Override + public <BoundLeft, BoundRight> Pair<BoundLeft, BoundRight> bind( + final BiFunction<LeftType, RightType, Pair<BoundLeft, BoundRight>> binder) { + if (binder == null) throw new NullPointerException("Binder must not be null."); + + return binder.apply(leftValue, rightValue); + } + + @Override + public <BoundLeft> Pair<BoundLeft, RightType> + bindLeft(final Function<LeftType, Pair<BoundLeft, RightType>> leftBinder) { + if (leftBinder == null) throw new NullPointerException("Binder must not be null"); + + return leftBinder.apply(leftValue); + } + + @Override + public <BoundRight> Pair<LeftType, BoundRight> bindRight( + final Function<RightType, Pair<LeftType, BoundRight>> rightBinder) { + if (rightBinder == null) throw new NullPointerException("Binder must not be null"); + + return rightBinder.apply(rightValue); + } + + @Override + public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> + Pair<CombinedLeft, CombinedRight> + combine(final Pair<OtherLeft, OtherRight> otherPair, + final BiFunction<LeftType, OtherLeft, CombinedLeft> leftCombiner, + final BiFunction<RightType, OtherRight, + CombinedRight> rightCombiner) { + return otherPair.bind((otherLeft, otherRight) -> { + final CombinedLeft left = leftCombiner.apply(leftValue, otherLeft); + final CombinedRight right = rightCombiner.apply(rightValue, otherRight); + + return new SimplePair<>(left, right); + }); + } + + @Override + public <NewLeft> Pair<NewLeft, RightType> + mapLeft(final Function<LeftType, NewLeft> mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + return new SimplePair<>(mapper.apply(leftValue), rightValue); + } + + @Override + public <NewRight> Pair<LeftType, NewRight> + mapRight(final Function<RightType, NewRight> mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + return new SimplePair<>(leftValue, mapper.apply(rightValue)); + } + + @Override + public <MergedType> MergedType + merge(final BiFunction<LeftType, RightType, MergedType> merger) { + if (merger == null) throw new NullPointerException("Merger must not be null"); + + return merger.apply(leftValue, rightValue); + } + + @Override + public String toString() { + return String.format("Pair [leftValue='%s', rightValue='%s']", leftValue, + rightValue); + } + + @Override + public LeftType getLeft() { + return leftValue; + } + + @Override + public RightType getRight() { + return rightValue; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + (leftValue == null ? 0 : leftValue.hashCode()); + result = prime * result + (rightValue == null ? 0 : rightValue.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SimplePair<?, ?>)) return false; + + final SimplePair<?, ?> other = (SimplePair<?, ?>) obj; + + if (leftValue == null) { + if (other.leftValue != null) return false; + } else if (!leftValue.equals(other.leftValue)) { + return false; + } + + if (rightValue == null) { + if (other.rightValue != null) return false; + } else if (!rightValue.equals(other.rightValue)) { + return false; + } + + return true; + } +} diff --git a/src/main/java/bjc/data/SimpleTree.java b/src/main/java/bjc/data/SimpleTree.java new file mode 100644 index 0000000..7d9fb6d --- /dev/null +++ b/src/main/java/bjc/data/SimpleTree.java @@ -0,0 +1,446 @@ +package bjc.data; + +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import bjc.funcdata.FunctionalList; +import bjc.funcdata.ListEx; +import bjc.funcdata.bst.TreeLinearizationMethod; + +/** + * A node in a homogeneous tree. + * + * @author ben + * + * @param <ContainedType> + * The type contained in the tree. + */ +public class SimpleTree<ContainedType> implements Tree<ContainedType> { + /* The data/label for this node. */ + private ContainedType data; + + /* The children of this node. */ + private ListEx<Tree<ContainedType>> children; + + /* Whether this node has children. */ + /* + * @NOTE Why have both this boolean and childCount? Why not just do a childCount + * == 0 whenever you'd check hasChildren? + * + * - Because hasChildren is set once and not reset, and really what it + * indicates is that children has been allocated. + */ + private boolean hasChildren; + /* The number of children this node has. */ + private int childCount = 0; + + /* The ID of this node. */ + private int ID; + /* The next ID to assign to a node. */ + private static int nextID = 0; + + /** + * Create a new leaf node in a tree. + */ + public SimpleTree() { + this(null); + } + + /** + * Create a new leaf node in a tree. + * + * @param leaf + * The data to store as a leaf node. + */ + public SimpleTree(final ContainedType leaf) { + data = leaf; + + hasChildren = false; + + ID = nextID++; + } + + /** + * Create a new tree node with the specified children. + * + * @param leaf + * The data to hold in this node. + * + * @param childrn + * A list of children for this node. + */ + public SimpleTree(final ContainedType leaf, final ListEx<Tree<ContainedType>> childrn) { + this(leaf); + + hasChildren = true; + + childCount = childrn.getSize(); + + children = childrn; + } + + /** + * Create a new tree node with the specified children. + * + * @param leaf + * The data to hold in this node. + * + * @param childrn + * A list of children for this node. + */ + @SafeVarargs + public SimpleTree(final ContainedType leaf, final Tree<ContainedType>... childrn) { + this(leaf); + + hasChildren = true; + + childCount = 0; + + children = new FunctionalList<>(); + + for (final Tree<ContainedType> child : childrn) { + children.add(child); + + childCount++; + } + } + + @Override + public void addChild(final ContainedType child) { + addChild(new SimpleTree<>(child)); + } + + @Override + public void addChild(final Tree<ContainedType> child) { + if (hasChildren == false) { + hasChildren = true; + + children = new FunctionalList<>(); + } + + childCount++; + + children.add(child); + } + + @Override + public void prependChild(final Tree<ContainedType> child) { + if (hasChildren == false) { + hasChildren = true; + + children = new FunctionalList<>(); + } + + childCount++; + + children.prepend(child); + } + + @Override + public void doForChildren(final Consumer<Tree<ContainedType>> action) { + if (childCount > 0) children.forEach(action); + } + + @Override + public int getChildrenCount() { + return childCount; + } + + @Override + public int revFind(final Predicate<Tree<ContainedType>> childPred) { + if (childCount == 0) return -1; + + for (int i = childCount - 1; i >= 0; i--) { + if (childPred.test(getChild(i))) return i; + } + + return -1; + } + + @Override + public void traverse(final TreeLinearizationMethod linearizationMethod, + final Consumer<ContainedType> action) { + if (hasChildren) { + switch (linearizationMethod) { + case INORDER: + if (childCount != 2) { + final String msg = "Can only do in-order traversal for binary trees."; + + throw new IllegalArgumentException(msg); + } + + children.getByIndex(0).traverse(linearizationMethod, action); + + action.accept(data); + + children.getByIndex(1).traverse(linearizationMethod, action); + break; + case POSTORDER: + children.forEach(child -> child.traverse(linearizationMethod, action)); + + action.accept(data); + break; + case PREORDER: + action.accept(data); + + children.forEach(child -> child.traverse(linearizationMethod, action)); + break; + default: + break; + + } + } else { + action.accept(data); + } + } + + @Override + public <NewType, ReturnedType> ReturnedType collapse( + final Function<ContainedType, NewType> leafTransform, + final BiFunction<ContainedType, ListEx<NewType>, NewType> nodeCollapser, + final Function<NewType, ReturnedType> resultTransformer) { + return resultTransformer.apply(internalCollapse(leafTransform, nodeCollapser)); + } + + @Override + public Tree<ContainedType> + flatMapTree(final Function<ContainedType, Tree<ContainedType>> mapper) { + if (hasChildren) { + final Tree<ContainedType> flatMappedData = mapper.apply(data); + + final ListEx<Tree<ContainedType>> mappedChildren + = children.map(child -> child.flatMapTree(mapper)); + + mappedChildren.forEach(flatMappedData::addChild); + + return flatMappedData; + } + + return mapper.apply(data); + } + + /* + * Do a collapse of this tree. + */ + + private <NewType> NewType internalCollapse( + final Function<ContainedType, NewType> leafTransform, + final BiFunction<ContainedType, ListEx<NewType>, NewType> nodeCollapser) { + if (hasChildren) { + final ListEx<NewType> collapsedChildren = children.map(child -> { + final NewType collapsed = child.collapse(leafTransform, nodeCollapser, + subTreeVal -> subTreeVal); + + return collapsed; + }); + + return nodeCollapser.apply(data, collapsedChildren); + } + + return leafTransform.apply(data); + } + + private void internalToString(final StringBuilder builder, final int indentLevel, + final boolean initial) { + if (!initial) { + for (int i = 0; i < indentLevel; i++) builder.append(">\t"); + } + + builder.append("Node #"); + builder.append(ID); + builder.append(": "); + builder.append(data == null ? "(null)" : data.toString()); + builder.append("\n"); + + if (hasChildren) { + children.forEach(child -> { + if (child instanceof SimpleTree<?>) { + final SimpleTree<ContainedType> kid = (SimpleTree<ContainedType>) child; + + kid.internalToString(builder, indentLevel + 1, false); + } else { + for (int i = 0; i < indentLevel + 1; i++) { + builder.append(">\t"); + } + + builder.append("Unknown node of type "); + builder.append(child.getClass().getName()); + builder.append("\n"); + } + }); + } + } + + @Override + public <MappedType> Tree<MappedType> rebuildTree( + final Function<ContainedType, MappedType> leafTransformer, + final Function<ContainedType, MappedType> operatorTransformer) { + if (hasChildren) { + final ListEx<Tree<MappedType>> mappedChildren = + children.map(child -> + child.rebuildTree(leafTransformer, operatorTransformer)); + + final MappedType mapData = operatorTransformer.apply(data); + return new SimpleTree<>(mapData, mappedChildren); + } + + return new SimpleTree<>(leafTransformer.apply(data)); + } + + @Override + public void selectiveTransform(final Predicate<ContainedType> nodePicker, + final UnaryOperator<ContainedType> transformer) { + if (hasChildren) { + children.forEach(child -> child.selectiveTransform(nodePicker, transformer)); + } else { + data = transformer.apply(data); + } + } + + @Override + public Tree<ContainedType> topDownTransform( + final Function<ContainedType, TopDownTransformResult> transformPicker, + final UnaryOperator<Tree<ContainedType>> transformer) { + final TopDownTransformResult transformResult = transformPicker.apply(data); + + switch (transformResult) { + case PASSTHROUGH: + Tree<ContainedType> result = new SimpleTree<>(data); + + if (hasChildren) { + children.forEach(child -> { + final Tree<ContainedType> kid + = child.topDownTransform(transformPicker, transformer); + + result.addChild(kid); + }); + } + + return result; + case SKIP: + return this; + case TRANSFORM: + return transformer.apply(this); + case RTRANSFORM: + return transformer.apply(this).topDownTransform(transformPicker, transformer); + case PUSHDOWN: + result = new SimpleTree<>(data); + + if (hasChildren) { + children.forEach(child -> { + final Tree<ContainedType> kid + = child.topDownTransform(transformPicker, transformer); + + result.addChild(kid); + }); + } + + return transformer.apply(result); + case PULLUP: + final Tree<ContainedType> intermediateResult = transformer.apply(this); + + result = new SimpleTree<>(intermediateResult.getHead()); + + intermediateResult.doForChildren(child -> { + final Tree<ContainedType> kid + = child.topDownTransform(transformPicker, transformer); + + result.addChild(kid); + }); + + return result; + default: + final String msg = String.format("Recieved unknown transform result type %s", + transformResult); + + throw new IllegalArgumentException(msg); + } + } + + @Override + public <TransformedType> TransformedType transformChild(final int childNo, + final Function<Tree<ContainedType>, TransformedType> transformer) { + if (childNo < 0 || childNo > childCount - 1) { + final String msg = String.format("Child index #%d is invalid", childNo); + + throw new IllegalArgumentException(msg); + } + + final Tree<ContainedType> selectedKid = children.getByIndex(childNo); + + return transformer.apply(selectedKid); + } + + @Override + public Tree<ContainedType> getChild(final int childNo) { + if (childNo < 0 || childNo > childCount - 1) { + final String msg = String.format("Child index #%d is invalid", childNo); + + throw new IllegalArgumentException(msg); + } + return children.getByIndex(childNo); + } + + @Override + public <TransformedType> TransformedType + transformHead(final Function<ContainedType, TransformedType> transformer) { + return transformer.apply(data); + } + + @Override + public void setHead(ContainedType dat) { + this.data = dat; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + childCount; + result = prime * result + (children == null ? 0 : children.hashCode()); + result = prime * result + (data == null ? 0 : data.hashCode()); + + return result; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + internalToString(builder, 1, true); + + /* Delete a trailing nl. */ + builder.deleteCharAt(builder.length() - 1); + + return builder.toString(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SimpleTree<?>)) return false; + + final SimpleTree<?> other = (SimpleTree<?>) obj; + + if (data == null) { + if (other.data != null) return false; + } else if (!data.equals(other.data)) { + return false; + } + + if (childCount != other.childCount) return false; + + if (children == null) { + if (other.children != null) return false; + } else if (!children.equals(other.children)) { + return false; + } + + return true; + } +} diff --git a/src/main/java/bjc/data/TopDownTransformIterator.java b/src/main/java/bjc/data/TopDownTransformIterator.java index 3b4b997..28df590 100644 --- a/src/main/java/bjc/data/TopDownTransformIterator.java +++ b/src/main/java/bjc/data/TopDownTransformIterator.java @@ -26,7 +26,7 @@ import java.util.function.Function; * The type of the nodes in the tree. */ public class TopDownTransformIterator<ContainedType> - implements Iterator<ITree<ContainedType>> { + implements Iterator<Tree<ContainedType>> { /** * Alias type for a tree transformation. * @@ -35,8 +35,8 @@ public class TopDownTransformIterator<ContainedType> * @param <ContainedType> * The type contained in the tree. */ - public interface TreeTransform<ContainedType> extends BiFunction<ITree<ContainedType>, - Consumer<Iterator<ITree<ContainedType>>>, ITree<ContainedType>> { + public interface TreeTransform<ContainedType> extends BiFunction<Tree<ContainedType>, + Consumer<Iterator<Tree<ContainedType>>>, Tree<ContainedType>> { // Alias type; no body is needed } @@ -49,19 +49,19 @@ public class TopDownTransformIterator<ContainedType> */ private final TreeTransform<ContainedType> transform; - private ITree<ContainedType> preParent; - private ITree<ContainedType> postParent; + private Tree<ContainedType> preParent; + private Tree<ContainedType> postParent; - private final Deque<ITree<ContainedType>> preChildren; - private final Deque<ITree<ContainedType>> postChildren; + private final Deque<Tree<ContainedType>> preChildren; + private final Deque<Tree<ContainedType>> postChildren; private TopDownTransformIterator<ContainedType> curChild; private boolean done; private boolean initial; - private final Deque<Iterator<ITree<ContainedType>>> toYield; - private Iterator<ITree<ContainedType>> currYield; + private final Deque<Iterator<Tree<ContainedType>>> toYield; + private Iterator<Tree<ContainedType>> currYield; /** * Create a new tree iterator. @@ -76,7 +76,7 @@ public class TopDownTransformIterator<ContainedType> public TopDownTransformIterator( final Function<ContainedType, TopDownTransformResult> pickr, final TreeTransform<ContainedType> transfrm, - final ITree<ContainedType> tree) { + final Tree<ContainedType> tree) { preParent = tree; preChildren = new LinkedList<>(); @@ -96,10 +96,8 @@ public class TopDownTransformIterator<ContainedType> * @param src * The nodes to yield. */ - public void addYield(final Iterator<ITree<ContainedType>> src) { - if (currYield != null) { - toYield.push(currYield); - } + public void addYield(final Iterator<Tree<ContainedType>> src) { + if (currYield != null) toYield.push(currYield); currYield = src; } @@ -117,7 +115,7 @@ public class TopDownTransformIterator<ContainedType> * * @return The next yielded value. */ - public ITree<ContainedType> flushYields(final ITree<ContainedType> val) { + public Tree<ContainedType> flushYields(final Tree<ContainedType> val) { if (currYield != null) { /* * We have non-sentinel values to yield. @@ -128,9 +126,7 @@ public class TopDownTransformIterator<ContainedType> */ toYield.add(new SingleIterator<>(val)); - if (currYield.hasNext()) { - return currYield.next(); - } + if (currYield.hasNext()) return currYield.next(); while (toYield.size() != 0 && !currYield.hasNext()) { currYield = toYield.pop(); @@ -148,18 +144,16 @@ public class TopDownTransformIterator<ContainedType> } @Override - public ITree<ContainedType> next() { - if (done) - throw new NoSuchElementException(); + public Tree<ContainedType> next() { + if (done) throw new NoSuchElementException(); /* * Flush any values that need to be yielded. */ if (currYield != null) { - ITree<ContainedType> yeld = flushYields(null); + Tree<ContainedType> yeld = flushYields(null); - if (yeld != null) - return yeld; + if (yeld != null) return yeld; } if (initial) { @@ -170,7 +164,7 @@ public class TopDownTransformIterator<ContainedType> switch (res) { case PASSTHROUGH: - postParent = new Tree<>(preParent.getHead()); + postParent = new SimpleTree<>(preParent.getHead()); if (preParent.getChildrenCount() != 0) { for (int i = 0; i < preParent.getChildrenCount(); i++) { @@ -204,12 +198,12 @@ public class TopDownTransformIterator<ContainedType> done = true; return flushYields( - transform.apply(new Tree<>(preParent.getHead()), this::addYield)); + transform.apply(new SimpleTree<>(preParent.getHead()), this::addYield)); case PULLUP: - final ITree<ContainedType> intRes + final Tree<ContainedType> intRes = transform.apply(preParent, this::addYield); - postParent = new Tree<>(intRes.getHead()); + postParent = new SimpleTree<>(intRes.getHead()); if (intRes.getChildrenCount() != 0) { for (int i = 0; i < intRes.getChildrenCount(); i++) { @@ -226,9 +220,7 @@ public class TopDownTransformIterator<ContainedType> throw new IllegalArgumentException("Unknown result type " + res); } - if (res != RTRANSFORM) { - initial = false; - } + if (res != RTRANSFORM) initial = false; } if (curChild == null || !curChild.hasNext()) { @@ -236,22 +228,22 @@ public class TopDownTransformIterator<ContainedType> curChild = new TopDownTransformIterator<>(picker, transform, preChildren.pop()); - final ITree<ContainedType> res = curChild.next(); + final Tree<ContainedType> res = curChild.next(); // System.out.println("\t\tTRACE: adding node " + res + " to children"); postChildren.add(res); return flushYields(res); } - ITree<ContainedType> res = null; + Tree<ContainedType> res = null; if (postParent == null) { - res = new Tree<>(preParent.getHead()); + res = new SimpleTree<>(preParent.getHead()); // System.out.println("\t\tTRACE: adding nodes " + postChildren + " to " + // res); - for (final ITree<ContainedType> child : postChildren) { + for (final Tree<ContainedType> child : postChildren) { res.addChild(child); } @@ -262,7 +254,7 @@ public class TopDownTransformIterator<ContainedType> // System.out.println("\t\tTRACE: adding nodes " + postChildren + " to " + // res); - for (final ITree<ContainedType> child : postChildren) { + for (final Tree<ContainedType> child : postChildren) { res.addChild(child); } } @@ -271,7 +263,7 @@ public class TopDownTransformIterator<ContainedType> return flushYields(res); } - final ITree<ContainedType> res = curChild.next(); + final Tree<ContainedType> res = curChild.next(); // System.out.println("\t\tTRACE: adding node " + res + " to children"); postChildren.add(res); diff --git a/src/main/java/bjc/data/Tree.java b/src/main/java/bjc/data/Tree.java index 9a4caa6..3e16e02 100644 --- a/src/main/java/bjc/data/Tree.java +++ b/src/main/java/bjc/data/Tree.java @@ -6,441 +6,281 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; -import bjc.funcdata.FunctionalList; -import bjc.funcdata.IList; +import bjc.funcdata.ListEx; import bjc.funcdata.bst.TreeLinearizationMethod; /** - * A node in a homogeneous tree. + * A node in a homogeneous tree with a unlimited amount of children. * * @author ben * * @param <ContainedType> - * The type contained in the tree. + * The type of data contained in the tree nodes. + * */ -public class Tree<ContainedType> implements ITree<ContainedType> { - /* The data/label for this node. */ - private ContainedType data; - - /* The children of this node. */ - private IList<ITree<ContainedType>> children; - - /* Whether this node has children. */ - /* - * @NOTE Why have both this boolean and childCount? Why not just do a childCount - * == 0 whenever you'd check hasChildren? - Because hasChildren is set once and - * not reset, and really what it indicates is that children has been allocated. +public interface Tree<ContainedType> { + /** + * Append a child to this node. + * + * @param child + * The child to append to this node. */ - private boolean hasChildren; - /* The number of children this node has. */ - private int childCount = 0; - - /* The ID of this node. */ - private int ID; - /* The next ID to assign to a node. */ - private static int nextID = 0; + void addChild(Tree<ContainedType> child); /** - * Create a new leaf node in a tree. + * Append a child to this node. + * + * @param child + * The child to append to this node. */ - public Tree() { - this(null); - } + void addChild(ContainedType child); /** - * Create a new leaf node in a tree. + * Prepend a child to this node. * - * @param leaf - * The data to store as a leaf node. + * @param child + * The child to prepend to this node. */ - public Tree(final ContainedType leaf) { - data = leaf; - - hasChildren = false; - - ID = nextID++; - } + void prependChild(Tree<ContainedType> child); /** - * Create a new tree node with the specified children. + * Collapse a tree into a single version. + * + * @param <NewType> + * The intermediate type being folded. + * + * @param <ReturnedType> + * The type that is the end result. + * + * @param leafTransform + * The function to use to convert leaf values. + * + * @param nodeCollapser + * The function to use to convert internal nodes and + * their children. * - * @param leaf - * The data to hold in this node. + * @param resultTransformer + * The function to use to convert a state to the + * returned version. * - * @param childrn - * A list of children for this node. + * @return The final transformed state. */ - public Tree(final ContainedType leaf, final IList<ITree<ContainedType>> childrn) { - this(leaf); + <NewType, ReturnedType> ReturnedType collapse( + Function<ContainedType, NewType> leafTransform, + BiFunction<ContainedType, ListEx<NewType>, NewType> nodeCollapser, + Function<NewType, ReturnedType> resultTransformer); - hasChildren = true; - - childCount = childrn.getSize(); - - children = childrn; - } + /** + * Execute a given action for each of this tree's children. + * + * @param action + * The action to execute for each child. + */ + void doForChildren(Consumer<Tree<ContainedType>> action); /** - * Create a new tree node with the specified children. + * Expand the nodes of a tree into trees, and then merge the contents of those + * trees into a single tree. * - * @param leaf - * The data to hold in this node. + * @param mapper + * The function to use to map values into trees. * - * @param childrn - * A list of children for this node. + * @return A tree, with some nodes expanded into trees. */ - @SafeVarargs - public Tree(final ContainedType leaf, final ITree<ContainedType>... childrn) { - this(leaf); - - hasChildren = true; - - childCount = 0; - - children = new FunctionalList<>(); - - for (final ITree<ContainedType> child : childrn) { - children.add(child); - - childCount++; - } - } - - @Override - public void addChild(final ContainedType child) { - addChild(new Tree<>(child)); - } - - @Override - public void addChild(final ITree<ContainedType> child) { - if (hasChildren == false) { - hasChildren = true; + default Tree<ContainedType> + flatMapTree(final Function<ContainedType, Tree<ContainedType>> mapper) { + return topDownTransform(dat -> TopDownTransformResult.PUSHDOWN, node -> { + if (node.getChildrenCount() > 0) { + final Tree<ContainedType> parent = node.transformHead(mapper); - children = new FunctionalList<>(); - } - - childCount++; - - children.add(child); - } - - @Override - public void prependChild(final ITree<ContainedType> child) { - if (hasChildren == false) { - hasChildren = true; - - children = new FunctionalList<>(); - } - - childCount++; - - children.prepend(child); - } - - @Override - public void doForChildren(final Consumer<ITree<ContainedType>> action) { - if (childCount > 0) { - children.forEach(action); - } - } - - @Override - public int getChildrenCount() { - return childCount; - } - - @Override - public int revFind(final Predicate<ITree<ContainedType>> childPred) { - if (childCount == 0) { - return -1; - } - - for (int i = childCount - 1; i >= 0; i--) { - if (childPred.test(getChild(i))) - return i; - } - - return -1; - } - - @Override - public void traverse(final TreeLinearizationMethod linearizationMethod, - final Consumer<ContainedType> action) { - if (hasChildren) { - switch (linearizationMethod) { - case INORDER: - if (childCount != 2) { - final String msg = "Can only do in-order traversal for binary trees."; - - throw new IllegalArgumentException(msg); - } - - children.getByIndex(0).traverse(linearizationMethod, action); - - action.accept(data); - - children.getByIndex(1).traverse(linearizationMethod, action); - break; - case POSTORDER: - children.forEach(child -> child.traverse(linearizationMethod, action)); - - action.accept(data); - break; - case PREORDER: - action.accept(data); - - children.forEach(child -> child.traverse(linearizationMethod, action)); - break; - default: - break; + node.doForChildren(parent::addChild); + return parent; } - } else { - action.accept(data); - } - } - - @Override - public <NewType, ReturnedType> ReturnedType collapse( - final Function<ContainedType, NewType> leafTransform, - final BiFunction<ContainedType, IList<NewType>, NewType> nodeCollapser, - final Function<NewType, ReturnedType> resultTransformer) { - return resultTransformer.apply(internalCollapse(leafTransform, nodeCollapser)); - } - - @Override - public ITree<ContainedType> - flatMapTree(final Function<ContainedType, ITree<ContainedType>> mapper) { - if (hasChildren) { - final ITree<ContainedType> flatMappedData = mapper.apply(data); - - final IList<ITree<ContainedType>> mappedChildren - = children.map(child -> child.flatMapTree(mapper)); - - mappedChildren.forEach(flatMappedData::addChild); - - return flatMappedData; - } - return mapper.apply(data); + return node.transformHead(mapper); + }); } - /* - * Do a collapse of this tree. + /** + * Get the specified child of this tree. + * + * @param childNo + * The number of the child to get. * - * @NOTE Why is this protected? I can't see any good reason someone'd want to - * override it. + * @return The specified child of this tree. */ - - private <NewType> NewType internalCollapse( - final Function<ContainedType, NewType> leafTransform, - final BiFunction<ContainedType, IList<NewType>, NewType> nodeCollapser) { - if (hasChildren) { - final IList<NewType> collapsedChildren = children.map(child -> { - final NewType collapsed = child.collapse(leafTransform, nodeCollapser, - subTreeVal -> subTreeVal); - - return collapsed; - }); - - return nodeCollapser.apply(data, collapsedChildren); - } - - return leafTransform.apply(data); + default Tree<ContainedType> getChild(final int childNo) { + return transformChild(childNo, child -> child); } - private void internalToString(final StringBuilder builder, final int indentLevel, - final boolean initial) { - if (!initial) { - for (int i = 0; i < indentLevel; i++) { - builder.append(">\t"); - } - } - - builder.append("Node #"); - builder.append(ID); - builder.append(": "); - builder.append(data == null ? "(null)" : data.toString()); - builder.append("\n"); - - if (hasChildren) { - children.forEach(child -> { - if (child instanceof Tree<?>) { - final Tree<ContainedType> kid = (Tree<ContainedType>) child; - - kid.internalToString(builder, indentLevel + 1, false); - } else { - for (int i = 0; i < indentLevel + 1; i++) { - builder.append(">\t"); - } - - builder.append("Unknown node of type "); - builder.append(child.getClass().getName()); - builder.append("\n"); - } - }); - } - } - - @Override - public <MappedType> ITree<MappedType> rebuildTree( - final Function<ContainedType, MappedType> leafTransformer, - final Function<ContainedType, MappedType> operatorTransformer) { - if (hasChildren) { - final IList<ITree<MappedType>> mappedChildren = children.map(child -> child.rebuildTree(leafTransformer, operatorTransformer)); - - final MappedType mapData = operatorTransformer.apply(data); - return new Tree<>(mapData, mappedChildren); - } - - return new Tree<>(leafTransformer.apply(data)); - } + /** + * Get a count of the number of direct children this node has. + * + * @return The number of direct children this node has. + */ + int getChildrenCount(); - @Override - public void selectiveTransform(final Predicate<ContainedType> nodePicker, - final UnaryOperator<ContainedType> transformer) { - if (hasChildren) { - children.forEach(child -> child.selectiveTransform(nodePicker, transformer)); - } else { - data = transformer.apply(data); - } + /** + * Get a count of the number of direct children this node has. + * + * @return The number of direct children this node has. + */ + default int size() { + return getChildrenCount(); } - @Override - public ITree<ContainedType> topDownTransform( - final Function<ContainedType, TopDownTransformResult> transformPicker, - final UnaryOperator<ITree<ContainedType>> transformer) { - final TopDownTransformResult transformResult = transformPicker.apply(data); - - switch (transformResult) { - case PASSTHROUGH: - ITree<ContainedType> result = new Tree<>(data); - - if (hasChildren) { - children.forEach(child -> { - final ITree<ContainedType> kid - = child.topDownTransform(transformPicker, transformer); - - result.addChild(kid); - }); - } - - return result; - case SKIP: - return this; - case TRANSFORM: - return transformer.apply(this); - case RTRANSFORM: - return transformer.apply(this).topDownTransform(transformPicker, transformer); - case PUSHDOWN: - result = new Tree<>(data); - - if (hasChildren) { - children.forEach(child -> { - final ITree<ContainedType> kid - = child.topDownTransform(transformPicker, transformer); - - result.addChild(kid); - }); - } - - return transformer.apply(result); - case PULLUP: - final ITree<ContainedType> intermediateResult = transformer.apply(this); - - result = new Tree<>(intermediateResult.getHead()); - - intermediateResult.doForChildren(child -> { - final ITree<ContainedType> kid - = child.topDownTransform(transformPicker, transformer); - - result.addChild(kid); - }); - - return result; - default: - final String msg = String.format("Recieved unknown transform result type %s", - transformResult); - - throw new IllegalArgumentException(msg); - } + /** + * Get the data stored in this node. + * + * @return The data stored in this node. + */ + default ContainedType getHead() { + return transformHead(head -> head); } - @Override - public <TransformedType> TransformedType transformChild(final int childNo, - final Function<ITree<ContainedType>, TransformedType> transformer) { - if (childNo < 0 || childNo > childCount - 1) { - final String msg = String.format("Child index #%d is invalid", childNo); + /** + * Rebuild the tree with the same structure, but different nodes. + * + * @param <MappedType> + * The type of the new tree. + * + * @param leafTransformer + * The function to use to transform leaf tokens. + * + * @param internalTransformer + * The function to use to transform internal tokens. + * + * @return The tree, with the nodes changed. + */ + <MappedType> Tree<MappedType> rebuildTree( + Function<ContainedType, MappedType> leafTransformer, + Function<ContainedType, MappedType> internalTransformer); - throw new IllegalArgumentException(msg); - } + /** + * Transform some of the nodes in this tree. + * + * @param nodePicker + * The predicate to use to pick nodes to transform. + * + * @param transformer + * The function to use to transform picked nodes. + */ + void selectiveTransform(Predicate<ContainedType> nodePicker, + UnaryOperator<ContainedType> transformer); - final ITree<ContainedType> selectedKid = children.getByIndex(childNo); + /** + * Do a top-down transform of the tree. + * + * @param transformPicker + * The function to use to pick how to progress. + * + * @param transformer + * The function used to transform picked subtrees. + * + * @return The tree with the transform applied to picked subtrees. + */ + Tree<ContainedType> topDownTransform( + Function<ContainedType, TopDownTransformResult> transformPicker, + UnaryOperator<Tree<ContainedType>> transformer); - return transformer.apply(selectedKid); - } + /** + * Transform one of this nodes children. + * + * @param <TransformedType> + * The type of the transformed value. + * + * @param childNo + * The number of the child to transform. + * + * @param transformer + * The function to use to transform the value. + * + * @return The transformed value. + * + * @throws IllegalArgumentException + * if the childNo is out of bounds (0 <= + * childNo <= childCount()). + */ + <TransformedType> TransformedType transformChild(int childNo, + Function<Tree<ContainedType>, TransformedType> transformer); - @Override - public <TransformedType> TransformedType - transformHead(final Function<ContainedType, TransformedType> transformer) { - return transformer.apply(data); - } + /** + * Transform the value that is the head of this node. + * + * @param <TransformedType> + * The type of the transformed value. + * + * @param transformer + * The function to use to transform the value. + * + * @return The transformed value. + */ + <TransformedType> TransformedType + transformHead(Function<ContainedType, TransformedType> transformer); - @Override - public void setHead(ContainedType dat) { - this.data = dat; + /** + * Transform the tree into a tree with a different type of token. + * + * @param <MappedType> + * The type of the new tree. + * + * @param transformer + * The function to use to transform tokens. + * + * @return A tree with the token types transformed. + */ + default <MappedType> Tree<MappedType> + transformTree(final Function<ContainedType, MappedType> transformer) { + return rebuildTree(transformer, transformer); } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - - result = prime * result + childCount; - result = prime * result + (children == null ? 0 : children.hashCode()); - result = prime * result + (data == null ? 0 : data.hashCode()); - - return result; - } + /** + * Perform an action on each part of the tree. + * + * @param linearizationMethod + * The way to traverse the tree. + * + * @param action + * The action to perform on each tree node. + */ + void traverse(TreeLinearizationMethod linearizationMethod, + Consumer<ContainedType> action); - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); + /** + * Find the farthest to right child that satisfies the given predicate. + * + * @param childPred + * The predicate to satisfy. + * + * @return The index of the right-most child that satisfies the predicate, or -1 + * if one doesn't exist. + */ + int revFind(Predicate<Tree<ContainedType>> childPred); - internalToString(builder, 1, true); + /** + * Check if this tree contains any nodes that satisfy the predicate. + * + * @param pred + * The predicate to look for. + * + * @return Whether or not any items satisfied the predicate. + */ + default boolean containsMatching(Predicate<ContainedType> pred) { + Toggle<Boolean> tog = new OneWayToggle<>(false, true); - /* Delete a trailing nl. */ - builder.deleteCharAt(builder.length() - 1); + traverse(TreeLinearizationMethod.POSTORDER, val -> { + if (pred.test(val)) tog.get(); + }); - return builder.toString(); + return tog.get(); } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof Tree<?>)) - return false; - - final Tree<?> other = (Tree<?>) obj; - - if (data == null) { - if (other.data != null) - return false; - } else if (!data.equals(other.data)) - return false; - - if (childCount != other.childCount) - return false; - - if (children == null) { - if (other.children != null) - return false; - } else if (!children.equals(other.children)) - return false; - - return true; - } + /** + * Set the head of the tree. + * + * @param dat + * The value to set as the head of the tree. + */ + void setHead(ContainedType dat); } diff --git a/src/main/java/bjc/data/ValueToggle.java b/src/main/java/bjc/data/ValueToggle.java index 041a2d5..9e4b83b 100644 --- a/src/main/java/bjc/data/ValueToggle.java +++ b/src/main/java/bjc/data/ValueToggle.java @@ -37,20 +37,14 @@ public class ValueToggle<E> implements Toggle<E> { @Override public E get() { - if (alignment.get()) { - return lft; - } - - return rght; + if (alignment.get()) return lft; + else return rght; } @Override public E peek() { - if (alignment.peek()) { - return lft; - } - - return rght; + if (alignment.peek()) return lft; + else return rght; } @Override diff --git a/src/main/java/bjc/data/internals/BoundLazy.java b/src/main/java/bjc/data/internals/BoundLazy.java index a350a2b..0e0e95c 100644 --- a/src/main/java/bjc/data/internals/BoundLazy.java +++ b/src/main/java/bjc/data/internals/BoundLazy.java @@ -4,10 +4,10 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; -import bjc.data.IHolder; +import bjc.data.Holder; import bjc.data.Lazy; import bjc.funcdata.FunctionalList; -import bjc.funcdata.IList; +import bjc.funcdata.ListEx; /** * Implements a lazy holder that has been bound. @@ -19,21 +19,21 @@ import bjc.funcdata.IList; * The type of the new bound value. */ public class BoundLazy<OldType, BoundContainedType> - implements IHolder<BoundContainedType> { + implements Holder<BoundContainedType> { /* The old value. */ - private final Supplier<IHolder<OldType>> oldSupplier; + private final Supplier<Holder<OldType>> oldSupplier; /* The function to use to transform the old value into a new value. */ - private final Function<OldType, IHolder<BoundContainedType>> binder; + private final Function<OldType, Holder<BoundContainedType>> binder; /* The bound value being held. */ - private IHolder<BoundContainedType> boundHolder; + private Holder<BoundContainedType> boundHolder; /* Whether the bound value has been actualized or not. */ private boolean holderBound; /* Transformations currently pending on the bound value. */ - private final IList<UnaryOperator<BoundContainedType>> actions + private final ListEx<UnaryOperator<BoundContainedType>> actions = new FunctionalList<>(); /** @@ -45,20 +45,20 @@ public class BoundLazy<OldType, BoundContainedType> * @param binder * The function to use to bind the old value to the new one. */ - public BoundLazy(final Supplier<IHolder<OldType>> supp, - final Function<OldType, IHolder<BoundContainedType>> binder) { + public BoundLazy(final Supplier<Holder<OldType>> supp, + final Function<OldType, Holder<BoundContainedType>> binder) { oldSupplier = supp; this.binder = binder; } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<BoundContainedType, IHolder<BoundType>> bindr) { + public <BoundType> Holder<BoundType> + bind(final Function<BoundContainedType, Holder<BoundType>> bindr) { if (bindr == null) throw new NullPointerException("Binder must not be null"); /* Prepare a list of pending actions. */ - final IList<UnaryOperator<BoundContainedType>> pendingActions + final ListEx<UnaryOperator<BoundContainedType>> pendingActions = new FunctionalList<>(); for (UnaryOperator<BoundContainedType> pendAct : actions) { @@ -66,8 +66,8 @@ public class BoundLazy<OldType, BoundContainedType> } /* Create the new supplier of a value. */ - final Supplier<IHolder<BoundContainedType>> typeSupplier = () -> { - IHolder<BoundContainedType> oldHolder = boundHolder; + final Supplier<Holder<BoundContainedType>> typeSupplier = () -> { + Holder<BoundContainedType> oldHolder = boundHolder; /* Bind the value if it hasn't been bound before. */ if (!holderBound) { @@ -83,7 +83,7 @@ public class BoundLazy<OldType, BoundContainedType> } @Override - public <NewType> Function<BoundContainedType, IHolder<NewType>> + public <NewType> Function<BoundContainedType, Holder<NewType>> lift(final Function<BoundContainedType, NewType> func) { if (func == null) throw new NullPointerException("Function to lift must not be null"); @@ -94,13 +94,13 @@ public class BoundLazy<OldType, BoundContainedType> } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<BoundContainedType, MappedType> mapper) { if (mapper == null) throw new NullPointerException("Mapper must not be null"); /* Prepare a list of pending actions. */ - final IList<UnaryOperator<BoundContainedType>> pendingActions + final ListEx<UnaryOperator<BoundContainedType>> pendingActions = new FunctionalList<>(); for (UnaryOperator<BoundContainedType> pendAct : actions) { @@ -109,7 +109,7 @@ public class BoundLazy<OldType, BoundContainedType> /* Prepare the new supplier. */ final Supplier<MappedType> typeSupplier = () -> { - IHolder<BoundContainedType> oldHolder = boundHolder; + Holder<BoundContainedType> oldHolder = boundHolder; /* Bound the value if it hasn't been bound. */ if (!holderBound) { @@ -133,7 +133,7 @@ public class BoundLazy<OldType, BoundContainedType> } @Override - public IHolder<BoundContainedType> + public Holder<BoundContainedType> transform(final UnaryOperator<BoundContainedType> transformer) { if (transformer == null) throw new NullPointerException("Transformer must not be null"); diff --git a/src/main/java/bjc/data/internals/BoundLazyPair.java b/src/main/java/bjc/data/internals/BoundLazyPair.java index e081c04..91981de 100644 --- a/src/main/java/bjc/data/internals/BoundLazyPair.java +++ b/src/main/java/bjc/data/internals/BoundLazyPair.java @@ -4,8 +4,8 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; -import bjc.data.IHolder; -import bjc.data.IPair; +import bjc.data.Holder; +import bjc.data.Pair; import bjc.data.Identity; import bjc.data.LazyPair; @@ -16,17 +16,17 @@ import bjc.data.LazyPair; */ @SuppressWarnings("javadoc") public class BoundLazyPair<OldLeft, OldRight, NewLeft, NewRight> - implements IPair<NewLeft, NewRight> { + implements Pair<NewLeft, NewRight> { /* The supplier of the left value. */ private final Supplier<OldLeft> leftSupplier; /* The supplier of the right value. */ private final Supplier<OldRight> rightSupplier; /* The binder to transform values. */ - private final BiFunction<OldLeft, OldRight, IPair<NewLeft, NewRight>> binder; + private final BiFunction<OldLeft, OldRight, Pair<NewLeft, NewRight>> binder; /* The bound pair. */ - private IPair<NewLeft, NewRight> boundPair; + private Pair<NewLeft, NewRight> boundPair; /* Whether the pair has been bound yet. */ private boolean pairBound; @@ -46,20 +46,20 @@ public class BoundLazyPair<OldLeft, OldRight, NewLeft, NewRight> */ public BoundLazyPair(final Supplier<OldLeft> leftSupp, final Supplier<OldRight> rightSupp, - final BiFunction<OldLeft, OldRight, IPair<NewLeft, NewRight>> bindr) { + final BiFunction<OldLeft, OldRight, Pair<NewLeft, NewRight>> bindr) { leftSupplier = leftSupp; rightSupplier = rightSupp; binder = bindr; } @Override - public <BoundLeft, BoundRight> IPair<BoundLeft, BoundRight> bind( - final BiFunction<NewLeft, NewRight, IPair<BoundLeft, BoundRight>> bindr) { + public <BoundLeft, BoundRight> Pair<BoundLeft, BoundRight> bind( + final BiFunction<NewLeft, NewRight, Pair<BoundLeft, BoundRight>> bindr) { if (bindr == null) throw new NullPointerException("Binder must not be null"); - final IHolder<IPair<NewLeft, NewRight>> newPair = new Identity<>(boundPair); - final IHolder<Boolean> newPairMade = new Identity<>(pairBound); + final Holder<Pair<NewLeft, NewRight>> newPair = new Identity<>(boundPair); + final Holder<Boolean> newPairMade = new Identity<>(pairBound); final Supplier<NewLeft> leftSupp = () -> { if (!newPairMade.getValue()) { @@ -91,13 +91,13 @@ public class BoundLazyPair<OldLeft, OldRight, NewLeft, NewRight> } @Override - public <BoundLeft> IPair<BoundLeft, NewRight> - bindLeft(final Function<NewLeft, IPair<BoundLeft, NewRight>> leftBinder) { + public <BoundLeft> Pair<BoundLeft, NewRight> + bindLeft(final Function<NewLeft, Pair<BoundLeft, NewRight>> leftBinder) { if (leftBinder == null) throw new NullPointerException("Left binder must not be null"); final Supplier<NewLeft> leftSupp = () -> { - IPair<NewLeft, NewRight> newPair = boundPair; + Pair<NewLeft, NewRight> newPair = boundPair; if (!pairBound) { /* @@ -113,13 +113,13 @@ public class BoundLazyPair<OldLeft, OldRight, NewLeft, NewRight> } @Override - public <BoundRight> IPair<NewLeft, BoundRight> - bindRight(final Function<NewRight, IPair<NewLeft, BoundRight>> rightBinder) { + public <BoundRight> Pair<NewLeft, BoundRight> + bindRight(final Function<NewRight, Pair<NewLeft, BoundRight>> rightBinder) { if (rightBinder == null) throw new NullPointerException("Right binder must not be null"); final Supplier<NewRight> rightSupp = () -> { - IPair<NewLeft, NewRight> newPair = boundPair; + Pair<NewLeft, NewRight> newPair = boundPair; if (!pairBound) { /* @@ -136,8 +136,8 @@ public class BoundLazyPair<OldLeft, OldRight, NewLeft, NewRight> @Override public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> - IPair<CombinedLeft, CombinedRight> - combine(final IPair<OtherLeft, OtherRight> otherPair, + Pair<CombinedLeft, CombinedRight> + combine(final Pair<OtherLeft, OtherRight> otherPair, final BiFunction<NewLeft, OtherLeft, CombinedLeft> leftCombiner, final BiFunction<NewRight, OtherRight, CombinedRight> rightCombiner) { if (otherPair == null) { @@ -157,7 +157,7 @@ public class BoundLazyPair<OldLeft, OldRight, NewLeft, NewRight> } @Override - public <NewLeftType> IPair<NewLeftType, NewRight> + public <NewLeftType> Pair<NewLeftType, NewRight> mapLeft(final Function<NewLeft, NewLeftType> mapper) { if (mapper == null) throw new NullPointerException("Mapper must not be null"); @@ -184,7 +184,7 @@ public class BoundLazyPair<OldLeft, OldRight, NewLeft, NewRight> } @Override - public <NewRightType> IPair<NewLeft, NewRightType> + public <NewRightType> Pair<NewLeft, NewRightType> mapRight(final Function<NewRight, NewRightType> mapper) { if (mapper == null) throw new NullPointerException("Mapper must not be null"); diff --git a/src/main/java/bjc/data/internals/BoundListHolder.java b/src/main/java/bjc/data/internals/BoundListHolder.java index 1193c8d..c944252 100644 --- a/src/main/java/bjc/data/internals/BoundListHolder.java +++ b/src/main/java/bjc/data/internals/BoundListHolder.java @@ -3,9 +3,9 @@ package bjc.data.internals; import java.util.function.Function; import java.util.function.UnaryOperator; -import bjc.data.IHolder; +import bjc.data.Holder; import bjc.data.ListHolder; -import bjc.funcdata.IList; +import bjc.funcdata.ListEx; /** * Holds a list, converted into a holder. @@ -13,9 +13,9 @@ import bjc.funcdata.IList; * @author Ben Culkin */ @SuppressWarnings("javadoc") -public class BoundListHolder<ContainedType> implements IHolder<ContainedType> { +public class BoundListHolder<ContainedType> implements Holder<ContainedType> { /* The list of contained holders. */ - private final IList<IHolder<ContainedType>> heldHolders; + private final ListEx<Holder<ContainedType>> heldHolders; /** * Create a new list of holders. @@ -23,17 +23,17 @@ public class BoundListHolder<ContainedType> implements IHolder<ContainedType> { * @param toHold * The list of holders to, well, hold. */ - public BoundListHolder(final IList<IHolder<ContainedType>> toHold) { + public BoundListHolder(final ListEx<Holder<ContainedType>> toHold) { heldHolders = toHold; } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<ContainedType, IHolder<BoundType>> binder) { + public <BoundType> Holder<BoundType> + bind(final Function<ContainedType, Holder<BoundType>> binder) { if (binder == null) throw new NullPointerException("Binder must not be null"); - final IList<IHolder<BoundType>> boundHolders + final ListEx<Holder<BoundType>> boundHolders = heldHolders.map(containedHolder -> { return containedHolder.bind(binder); }); @@ -42,7 +42,7 @@ public class BoundListHolder<ContainedType> implements IHolder<ContainedType> { } @Override - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(final Function<ContainedType, NewType> func) { if (func == null) throw new NullPointerException("Function to lift must not be null"); @@ -53,12 +53,12 @@ public class BoundListHolder<ContainedType> implements IHolder<ContainedType> { } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<ContainedType, MappedType> mapper) { if (mapper == null) throw new NullPointerException("Mapper must not be null"); - final IList<IHolder<MappedType>> mappedHolders + final ListEx<Holder<MappedType>> mappedHolders = heldHolders.map(containedHolder -> { return containedHolder.map(mapper); }); @@ -67,7 +67,7 @@ public class BoundListHolder<ContainedType> implements IHolder<ContainedType> { } @Override - public IHolder<ContainedType> + public Holder<ContainedType> transform(final UnaryOperator<ContainedType> transformer) { if (transformer == null) throw new NullPointerException("Transformer must not be null"); diff --git a/src/main/java/bjc/data/internals/HalfBoundLazyPair.java b/src/main/java/bjc/data/internals/HalfBoundLazyPair.java index 6bcb6ae..849b973 100644 --- a/src/main/java/bjc/data/internals/HalfBoundLazyPair.java +++ b/src/main/java/bjc/data/internals/HalfBoundLazyPair.java @@ -4,8 +4,8 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; -import bjc.data.IHolder; -import bjc.data.IPair; +import bjc.data.Holder; +import bjc.data.Pair; import bjc.data.Identity; import bjc.data.LazyPair; @@ -24,15 +24,15 @@ import bjc.data.LazyPair; */ @SuppressWarnings("javadoc") public class HalfBoundLazyPair<OldType, NewLeft, NewRight> - implements IPair<NewLeft, NewRight> { + implements Pair<NewLeft, NewRight> { /* The supplier of the old value. */ private final Supplier<OldType> oldSupplier; /* The function to transform the old value into a new pair. */ - private final Function<OldType, IPair<NewLeft, NewRight>> binder; + private final Function<OldType, Pair<NewLeft, NewRight>> binder; /* The new bound pair. */ - private IPair<NewLeft, NewRight> boundPair; + private Pair<NewLeft, NewRight> boundPair; /* Has the pair been bound yet or not? */ private boolean pairBound; @@ -46,16 +46,16 @@ public class HalfBoundLazyPair<OldType, NewLeft, NewRight> * The function to use to create the pair from the old value. */ public HalfBoundLazyPair(final Supplier<OldType> oldSupp, - final Function<OldType, IPair<NewLeft, NewRight>> bindr) { + final Function<OldType, Pair<NewLeft, NewRight>> bindr) { oldSupplier = oldSupp; binder = bindr; } @Override - public <BoundLeft, BoundRight> IPair<BoundLeft, BoundRight> bind( - final BiFunction<NewLeft, NewRight, IPair<BoundLeft, BoundRight>> bindr) { - final IHolder<IPair<NewLeft, NewRight>> newPair = new Identity<>(boundPair); - final IHolder<Boolean> newPairMade = new Identity<>(pairBound); + public <BoundLeft, BoundRight> Pair<BoundLeft, BoundRight> bind( + final BiFunction<NewLeft, NewRight, Pair<BoundLeft, BoundRight>> bindr) { + final Holder<Pair<NewLeft, NewRight>> newPair = new Identity<>(boundPair); + final Holder<Boolean> newPairMade = new Identity<>(pairBound); final Supplier<NewLeft> leftSupp = () -> { if (!newPairMade.getValue()) { @@ -81,10 +81,10 @@ public class HalfBoundLazyPair<OldType, NewLeft, NewRight> } @Override - public <BoundLeft> IPair<BoundLeft, NewRight> - bindLeft(final Function<NewLeft, IPair<BoundLeft, NewRight>> leftBinder) { + public <BoundLeft> Pair<BoundLeft, NewRight> + bindLeft(final Function<NewLeft, Pair<BoundLeft, NewRight>> leftBinder) { final Supplier<NewLeft> leftSupp = () -> { - IPair<NewLeft, NewRight> newPair = boundPair; + Pair<NewLeft, NewRight> newPair = boundPair; if (!pairBound) { newPair = binder.apply(oldSupplier.get()); @@ -97,10 +97,10 @@ public class HalfBoundLazyPair<OldType, NewLeft, NewRight> } @Override - public <BoundRight> IPair<NewLeft, BoundRight> - bindRight(final Function<NewRight, IPair<NewLeft, BoundRight>> rightBinder) { + public <BoundRight> Pair<NewLeft, BoundRight> + bindRight(final Function<NewRight, Pair<NewLeft, BoundRight>> rightBinder) { final Supplier<NewRight> rightSupp = () -> { - IPair<NewLeft, NewRight> newPair = boundPair; + Pair<NewLeft, NewRight> newPair = boundPair; if (!pairBound) { newPair = binder.apply(oldSupplier.get()); @@ -114,8 +114,8 @@ public class HalfBoundLazyPair<OldType, NewLeft, NewRight> @Override public <OtherLeft, OtherRight, CombinedLeft, CombinedRight> - IPair<CombinedLeft, CombinedRight> - combine(final IPair<OtherLeft, OtherRight> otherPair, + Pair<CombinedLeft, CombinedRight> + combine(final Pair<OtherLeft, OtherRight> otherPair, final BiFunction<NewLeft, OtherLeft, CombinedLeft> leftCombiner, final BiFunction<NewRight, OtherRight, CombinedRight> rightCombiner) { return otherPair.bind((otherLeft, otherRight) -> bind((leftVal, rightVal) -> { @@ -127,7 +127,7 @@ public class HalfBoundLazyPair<OldType, NewLeft, NewRight> } @Override - public <NewLeftType> IPair<NewLeftType, NewRight> + public <NewLeftType> Pair<NewLeftType, NewRight> mapLeft(final Function<NewLeft, NewLeftType> mapper) { final Supplier<NewLeftType> leftSupp = () -> { if (pairBound) @@ -149,7 +149,7 @@ public class HalfBoundLazyPair<OldType, NewLeft, NewRight> } @Override - public <NewRightType> IPair<NewLeft, NewRightType> + public <NewRightType> Pair<NewLeft, NewRightType> mapRight(final Function<NewRight, NewRightType> mapper) { final Supplier<NewLeft> leftSupp = () -> { if (pairBound) diff --git a/src/main/java/bjc/data/internals/WrappedLazy.java b/src/main/java/bjc/data/internals/WrappedLazy.java index cda86fd..624fb1b 100644 --- a/src/main/java/bjc/data/internals/WrappedLazy.java +++ b/src/main/java/bjc/data/internals/WrappedLazy.java @@ -3,7 +3,7 @@ package bjc.data.internals; import java.util.function.Function; import java.util.function.UnaryOperator; -import bjc.data.IHolder; +import bjc.data.Holder; import bjc.data.Lazy; /** @@ -13,9 +13,9 @@ import bjc.data.Lazy; * @param <ContainedType> * The type of the wrapped value. */ -public class WrappedLazy<ContainedType> implements IHolder<ContainedType> { +public class WrappedLazy<ContainedType> implements Holder<ContainedType> { /* Held value. */ - private final IHolder<IHolder<ContainedType>> held; + private final Holder<Holder<ContainedType>> held; /** * Create a new wrapped lazy value. @@ -23,7 +23,7 @@ public class WrappedLazy<ContainedType> implements IHolder<ContainedType> { * @param wrappedHolder * The holder to make lazy. */ - public WrappedLazy(final IHolder<ContainedType> wrappedHolder) { + public WrappedLazy(final Holder<ContainedType> wrappedHolder) { held = new Lazy<>(wrappedHolder); } @@ -34,15 +34,15 @@ public class WrappedLazy<ContainedType> implements IHolder<ContainedType> { * This is a case where reified generics would be useful, because then the * compiler could know which one we meant without the dummy parameter. */ - private WrappedLazy(final IHolder<IHolder<ContainedType>> wrappedHolder, + private WrappedLazy(final Holder<Holder<ContainedType>> wrappedHolder, @SuppressWarnings("unused") final boolean dummy) { held = wrappedHolder; } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<ContainedType, IHolder<BoundType>> binder) { - final IHolder<IHolder<BoundType>> newHolder = held.map(containedHolder -> { + public <BoundType> Holder<BoundType> + bind(final Function<ContainedType, Holder<BoundType>> binder) { + final Holder<Holder<BoundType>> newHolder = held.map(containedHolder -> { return containedHolder.bind(binder); }); @@ -50,7 +50,7 @@ public class WrappedLazy<ContainedType> implements IHolder<ContainedType> { } @Override - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(final Function<ContainedType, NewType> func) { return val -> { return new Lazy<>(func.apply(val)); @@ -58,9 +58,9 @@ public class WrappedLazy<ContainedType> implements IHolder<ContainedType> { } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<ContainedType, MappedType> mapper) { - final IHolder<IHolder<MappedType>> newHolder = held.map(containedHolder -> { + final Holder<Holder<MappedType>> newHolder = held.map(containedHolder -> { return containedHolder.map(mapper); }); @@ -68,7 +68,7 @@ public class WrappedLazy<ContainedType> implements IHolder<ContainedType> { } @Override - public IHolder<ContainedType> + public Holder<ContainedType> transform(final UnaryOperator<ContainedType> transformer) { held.transform(containedHolder -> { return containedHolder.transform(transformer); diff --git a/src/main/java/bjc/data/internals/WrappedOption.java b/src/main/java/bjc/data/internals/WrappedOption.java index 6becc16..f46d501 100644 --- a/src/main/java/bjc/data/internals/WrappedOption.java +++ b/src/main/java/bjc/data/internals/WrappedOption.java @@ -3,7 +3,7 @@ package bjc.data.internals; import java.util.function.Function; import java.util.function.UnaryOperator; -import bjc.data.IHolder; +import bjc.data.Holder; import bjc.data.Option; /** @@ -13,9 +13,9 @@ import bjc.data.Option; * @param <ContainedType> * The wrapped type. */ -public class WrappedOption<ContainedType> implements IHolder<ContainedType> { +public class WrappedOption<ContainedType> implements Holder<ContainedType> { /* The held value. */ - private final IHolder<IHolder<ContainedType>> held; + private final Holder<Holder<ContainedType>> held; /** * Create a new wrapped option. @@ -23,7 +23,7 @@ public class WrappedOption<ContainedType> implements IHolder<ContainedType> { * @param seedValue * The value to wrap. */ - public WrappedOption(final IHolder<ContainedType> seedValue) { + public WrappedOption(final Holder<ContainedType> seedValue) { held = new Option<>(seedValue); } @@ -31,15 +31,15 @@ public class WrappedOption<ContainedType> implements IHolder<ContainedType> { * The dummy parameter is to ensure the compiler can pick the right method, * because without this method erases to the same type as the public one. */ - private WrappedOption(final IHolder<IHolder<ContainedType>> toHold, + private WrappedOption(final Holder<Holder<ContainedType>> toHold, @SuppressWarnings("unused") final boolean dummy) { held = toHold; } @Override - public <BoundType> IHolder<BoundType> - bind(final Function<ContainedType, IHolder<BoundType>> binder) { - final IHolder<IHolder<BoundType>> newHolder = held.map(containedHolder -> { + public <BoundType> Holder<BoundType> + bind(final Function<ContainedType, Holder<BoundType>> binder) { + final Holder<Holder<BoundType>> newHolder = held.map(containedHolder -> { return containedHolder.bind((containedValue) -> { if (containedValue == null) return new Option<>(null); @@ -52,7 +52,7 @@ public class WrappedOption<ContainedType> implements IHolder<ContainedType> { } @Override - public <NewType> Function<ContainedType, IHolder<NewType>> + public <NewType> Function<ContainedType, Holder<NewType>> lift(final Function<ContainedType, NewType> func) { return val -> { return new Option<>(func.apply(val)); @@ -60,9 +60,9 @@ public class WrappedOption<ContainedType> implements IHolder<ContainedType> { } @Override - public <MappedType> IHolder<MappedType> + public <MappedType> Holder<MappedType> map(final Function<ContainedType, MappedType> mapper) { - final IHolder<IHolder<MappedType>> newHolder = held.map(containedHolder -> { + final Holder<Holder<MappedType>> newHolder = held.map(containedHolder -> { return containedHolder.map((containedValue) -> { if (containedValue == null) return null; @@ -75,7 +75,7 @@ public class WrappedOption<ContainedType> implements IHolder<ContainedType> { } @Override - public IHolder<ContainedType> + public Holder<ContainedType> transform(final UnaryOperator<ContainedType> transformer) { held.transform(containedHolder -> { return containedHolder.transform((containedValue) -> { diff --git a/src/main/java/bjc/esodata/AbbrevMap2.java b/src/main/java/bjc/esodata/AbbrevMap2.java index f131aec..259c963 100644 --- a/src/main/java/bjc/esodata/AbbrevMap2.java +++ b/src/main/java/bjc/esodata/AbbrevMap2.java @@ -36,9 +36,7 @@ public class AbbrevMap2 { */ public void add(String... words) { for (String word : words) { - for (String substr : genAbbrevs(word)) { - backing.add(substr, word); - } + for (String substr : genAbbrevs(word)) backing.add(substr, word); } } @@ -65,9 +63,7 @@ public class AbbrevMap2 { */ public void removeWords(String... words) { for (String word : words) { - for (String substr : genAbbrevs(word)) { - backing.remove(substr, word); - } + for (String substr : genAbbrevs(word)) backing.remove(substr, word); } } @@ -95,10 +91,7 @@ public class AbbrevMap2 { public String deabbrev(String word) { Set<String> st = backing.get(word); - if (st.size() == 1) { - return st.iterator().next(); - } else { - return null; - } + if (st.size() == 1) return st.iterator().next(); + else return null; } } diff --git a/src/main/java/bjc/esodata/DefaultList.java b/src/main/java/bjc/esodata/DefaultList.java index c4535bd..e622301 100644 --- a/src/main/java/bjc/esodata/DefaultList.java +++ b/src/main/java/bjc/esodata/DefaultList.java @@ -90,10 +90,8 @@ public class DefaultList<ValueType> extends AbstractList<ValueType> { @Override public ValueType get(int idx) { - if (idx < 0 || idx >= backing.size()) - return defVal; - - return backing.get(idx); + if (idx < 0 || idx >= backing.size()) return defVal; + else return backing.get(idx); } @Override diff --git a/src/main/java/bjc/esodata/Directory.java b/src/main/java/bjc/esodata/Directory.java index e083cd8..fa47b6f 100644 --- a/src/main/java/bjc/esodata/Directory.java +++ b/src/main/java/bjc/esodata/Directory.java @@ -59,8 +59,7 @@ public interface Directory<K, V> { * @return The new sub-directory, or null if one by that name already exists. */ default Directory<K, V> newSubdirectory(final K key) { - if (hasSubdirectory(key)) - return null; + if (hasSubdirectory(key)) return null; final Directory<K, V> dir = new SimpleDirectory<>(); diff --git a/src/main/java/bjc/esodata/DoubleTape.java b/src/main/java/bjc/esodata/DoubleTape.java index cc7cdb9..30d94a1 100644 --- a/src/main/java/bjc/esodata/DoubleTape.java +++ b/src/main/java/bjc/esodata/DoubleTape.java @@ -112,9 +112,7 @@ public class DoubleTape<T> implements Tape<T>, DoubleSided { public boolean left(final int amt) { final boolean succ = front.left(amt); - if (succ) { - back.right(amt); - } + if (succ) back.right(amt); return succ; } @@ -128,9 +126,7 @@ public class DoubleTape<T> implements Tape<T>, DoubleSided { public boolean right(final int amt) { final boolean succ = front.right(amt); - if (succ) { - back.left(amt); - } + if (succ) back.left(amt); return succ; } @@ -167,26 +163,23 @@ public class DoubleTape<T> implements Tape<T>, DoubleSided { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof DoubleTape<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof DoubleTape<?>)) return false; final DoubleTape<?> other = (DoubleTape<?>) obj; if (back == null) { - if (other.back != null) - return false; - } else if (!back.equals(other.back)) + if (other.back != null) return false; + } else if (!back.equals(other.back)) { return false; + } if (front == null) { - if (other.front != null) - return false; - } else if (!front.equals(other.front)) + if (other.front != null) return false; + } else if (!front.equals(other.front)) { return false; + } return true; } diff --git a/src/main/java/bjc/esodata/FlatNestIterator.java b/src/main/java/bjc/esodata/FlatNestIterator.java new file mode 100644 index 0000000..42981f5 --- /dev/null +++ b/src/main/java/bjc/esodata/FlatNestIterator.java @@ -0,0 +1,103 @@ +package bjc.esodata; + +import java.util.*; + +import bjc.data.*; + +final class FlatNestIterator<Element> implements ListIterator<Element> +{ + private Deque<ListIterator<Either<Element, NestList<Element>>>> iterators; + + public FlatNestIterator(ListIterator<Either<Element, NestList<Element>>> iterator) + { + this.iterators = new ArrayDeque<>(); + this.iterators.add(iterator); + } + + @Override + public boolean hasNext() { + boolean result = false; + do { + result = iterators.peek().hasNext(); + + if (result == false) iterators.pop(); + } while (result == false && iterators.isEmpty() == false); + + return result; + } + + @Override + public Element next() { + Holder<Element> element = Holder.of(null); + do { + element = iterators.peek().next().extract((ele) -> { + return Holder.of(ele); + }, (lst) -> { + // Ignore empty sublists. + if (lst.size() != 0) iterators.push(lst.listIterator()); + + return null; + }); + } while (element == null && hasNext()); + + if (element == null) throw new NoSuchElementException(); + + return element.getValue(); + } + + @Override + public boolean hasPrevious() { + boolean result = false; + do { + result = iterators.peek().hasPrevious(); + + if (result == false) iterators.pop(); + } while (result == false && iterators.isEmpty() == false); + + return result; + } + + @Override + public Element previous() { + Holder<Element> element = Holder.of(null); + do { + element = iterators.peek().previous().extract((ele) -> { + return Holder.of(ele); + }, (lst) -> { + // Ignore empty sublists. + if (lst.size() != 0) iterators.push(lst.listIterator()); + + return null; + }); + } while (element == null && hasPrevious()); + + if (element == null) throw new NoSuchElementException(); + + return element.getValue(); + } + + @Override + public int nextIndex() { + return iterators.peek().nextIndex(); + } + + @Override + public int previousIndex() { + return iterators.peek().previousIndex(); + } + + @Override + public void remove() { + iterators.peek().remove(); + } + + @Override + public void set(Element e) { + iterators.peek().set(Either.left(e)); + } + + @Override + public void add(Element e) { + iterators.peek().add(Either.left(e)); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/esodata/MapSet.java b/src/main/java/bjc/esodata/MapSet.java index a80c482..7d77ad6 100644 --- a/src/main/java/bjc/esodata/MapSet.java +++ b/src/main/java/bjc/esodata/MapSet.java @@ -116,8 +116,7 @@ public class MapSet<KeyType, ValueType> extends AbstractMap<KeyType, ValueType> * @return False if there is no map attached to the key, true otherwise. */ public boolean setMap(String key) { - if (!backing.containsKey(key)) - return false; + if (!backing.containsKey(key)) return false; currentMap = backing.get(key); @@ -165,16 +164,14 @@ public class MapSet<KeyType, ValueType> extends AbstractMap<KeyType, ValueType> @Override public Set<Map.Entry<KeyType, ValueType>> entrySet() { - if (currentMap == null) - throw new NullPointerException("Current map is not set"); + if (currentMap == null) throw new NullPointerException("Current map is not set"); return currentMap.entrySet(); } @Override public ValueType put(KeyType key, ValueType value) { - if (currentMap == null) - throw new NullPointerException("Current map is not set"); + if (currentMap == null) throw new NullPointerException("Current map is not set"); return currentMap.put(key, value); } diff --git a/src/main/java/bjc/esodata/MinMaxList.java b/src/main/java/bjc/esodata/MinMaxList.java new file mode 100644 index 0000000..6747831 --- /dev/null +++ b/src/main/java/bjc/esodata/MinMaxList.java @@ -0,0 +1,238 @@ +package bjc.esodata; + +import java.util.*; + +// @FIXME Nov 15th, 2020 Ben Culkin :RecalcMinMax +// Is there some sort of way to avoid having to recalculate these elements when +// that element is removed? +/** + * A list that automatically tracks the minimum & maximum element of a list. + * + * @author Ben Culkin + * + * @param <ValueType> The type of element stored in the list. + * + */ +public class MinMaxList<ValueType> extends AbstractList<ValueType> { + private final List<ValueType> backing; + private final Comparator<ValueType> picker; + + private ValueType minElement; + private boolean recalcMin = false; + + private ValueType maxElement; + private boolean recalcMax = false; + + // Create constructors + + /** + * Create a new min/max list using the given comparator. + * + * @param picker The comparator to use to determine min/max elements. + */ + public MinMaxList(Comparator<ValueType> picker) { + this(picker, new ArrayList<>()); + } + + /** + * Create a new min/max list using the given comparator. + * + * @param picker The comparator to use to determine min/max elements. + * @param values The values to fill the list from. + */ + @SafeVarargs + public MinMaxList(Comparator<ValueType> picker, ValueType... values) { + this(picker); + + for (ValueType value : values) { + add(value); + } + } + /** + * Create a new min/max list using the given comparator. + * + * @param picker The comparator to use to determine min/max elements. + * @param backing The collection to use values from. + */ + public MinMaxList(Comparator<ValueType> picker, Collection<ValueType> backing) { + this(picker, new ArrayList<>(backing)); + } + + /** + * Create a new min/max list using the given comparator. + * + * @param picker The comparator to use to determine min/max elements. + * @param backing The list to use as a backing list. + */ + public MinMaxList(Comparator<ValueType> picker, List<ValueType> backing) { + this.backing = backing; + this.picker = picker; + + calculateBoth(); + } + + @Override + public ValueType get(int index) { + return backing.get(index); + } + + @Override + public ValueType set(int index, ValueType element) { + ValueType oldElement = backing.set(index, element); + + if (minElement == null) { + minElement = element; + } else if (picker.compare(element, minElement) < 0) { + minElement = element; + recalcMin = false; + } else if (oldElement.equals(minElement)) { + minElement = null; + recalcMin = true; + } + + if (maxElement == null) { + maxElement = element; + } else if (picker.compare(element, maxElement) > 0) { + maxElement = element; + recalcMax = false; + } else if (oldElement.equals(maxElement)) { + maxElement = null; + recalcMax = true; + } + + return oldElement; + } + + @Override + public void add(int index, ValueType element) { + backing.add(index, element); + + if (minElement == null) { + minElement = element; + } else if (picker.compare(element, minElement) < 0) { + minElement = element; + recalcMin = false; + } + + if (maxElement == null) { + maxElement = element; + } else if (picker.compare(element, maxElement) > 0) { + maxElement = element; + recalcMax = false; + } + } + + @Override + public ValueType remove(int index) { + ValueType oldElement = backing.remove(index); + + if (oldElement.equals(minElement)) { + minElement = null; + recalcMin = true; + } + + if (oldElement.equals(maxElement)) { + maxElement = null; + recalcMax = true; + } + + return oldElement; + } + + @Override + public int size() { + return backing.size(); + } + + /** + * Get the minimum element currently stored in this list. + * + * @return The minimum element stored in the list. + */ + public ValueType minimum() { + if (recalcMin) calculateMinimum(); + + return minElement; + } + + /** + * Get the maximum element currently stored in this list. + * + * @return The maximum element stored in the list. + */ + public ValueType maximum() { + if (recalcMax) calculateMaximum(); + + return maxElement; + } + + private void calculateMinimum() { + for (ValueType element : backing) { + if (minElement == null) { + minElement = element; + } else if (picker.compare(element, minElement) < 0) { + minElement = element; + } + } + + recalcMin = false; + } + + private void calculateMaximum() { + for (ValueType element : backing) { + if (maxElement == null) { + maxElement = element; + } else if (picker.compare(element, maxElement) > 0) { + maxElement = element; + } + } + + recalcMax = false; + } + + private void calculateBoth() { + for (ValueType element : backing) { + if (minElement == null) { + minElement = element; + } else if (picker.compare(element, minElement) < 0) { + minElement = element; + } + + if (maxElement == null) { + maxElement = element; + } else if (picker.compare(element, maxElement) > 0) { + maxElement = element; + } + } + + recalcMin = false; + recalcMax = false; + } + + @Override + public String toString() { + return String.format("%s (min: %s) (max: %s)", backing, + recalcMin ? "(unknown)" : minElement, + recalcMax ? "(unknown)" : maxElement); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Objects.hash(backing, picker); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + + MinMaxList<?> other = (MinMaxList<?>) obj; + + return Objects.equals(backing, other.backing) + && Objects.equals(picker, other.picker); + } +} diff --git a/src/main/java/bjc/esodata/Multimap.java b/src/main/java/bjc/esodata/Multimap.java index a79f1b0..fae872e 100644 --- a/src/main/java/bjc/esodata/Multimap.java +++ b/src/main/java/bjc/esodata/Multimap.java @@ -51,9 +51,8 @@ public class Multimap<KeyType, ValueType> { */ public void remove(KeyType key, ValueType value) { // We have no values for that key; bail. - if (!backing.containsKey(key)) - return; - + if (!backing.containsKey(key)) return; + backing.get(key).remove(value); } @@ -76,10 +75,8 @@ public class Multimap<KeyType, ValueType> { * @return A set containing all of the values that have been mapped to that key. */ public Set<ValueType> get(KeyType key) { - if (!backing.containsKey(key)) - return new HashSet<>(); - - return backing.get(key).values(); + if (!backing.containsKey(key)) return new HashSet<>(); + else return backing.get(key).values(); } /** @@ -107,8 +104,7 @@ public class Multimap<KeyType, ValueType> { * mapping. */ public boolean contains(KeyType key, ValueType value) { - if (!backing.containsKey(key)) - return false; + if (!backing.containsKey(key)) return false; return backing.get(key).contains(value) > 0; } diff --git a/src/main/java/bjc/esodata/NestList.java b/src/main/java/bjc/esodata/NestList.java new file mode 100644 index 0000000..eccaf9d --- /dev/null +++ b/src/main/java/bjc/esodata/NestList.java @@ -0,0 +1,389 @@ +package bjc.esodata; + +import static bjc.functypes.Combinators.*; + +import java.util.*; +import java.util.function.*; + +import bjc.data.*; + +/** + * A list which can contain sublists of itself. + * + * N.B: Be careful if you form a recursive list, as there is no form of detection + * in place for that. Some operations may work, but those that do a deep traversal + * of the list will not. + * + * @author Ben Culkin + * + * @param <Element> The type contained in the list. + */ +public class NestList<Element> extends AbstractList<Either<Element, NestList<Element>>> +{ + private final List<Either<Element, NestList<Element>>> backing; + + /** + * Create a new empty nesting list. + */ + public NestList() { + backing = new ArrayList<>(); + } + + /** + * Create a new empty nesting list with the given capacity. + * + * @param cap The capacity for the nesting list. + */ + public NestList(int cap) { + backing = new ArrayList<>(cap); + } + + /** + * Add an element to this list. + * + * @param element The element to add to the list. + * + * @return Whether we could add the element. + */ + public boolean addItem(Element element) { + return backing.add(Either.left(element)); + } + + /** + * Add a sublist to this list. + * + * @param element The sublist to add to this list. + * + * @return Whether we could add the sublist. + */ + public boolean addItem(NestList<Element> element) { + return backing.add(Either.right(element)); + } + /** + * Add elements to this list. + * + * @param elements The elements to add to the list. + * + * @return Whether we could add each element. + */ + public boolean[] addItems(@SuppressWarnings("unchecked") Element... elements) { + boolean[] vals = new boolean[elements.length]; + + for (int i = 0; i < vals.length; i++) + { + vals[i] = addItem(elements[i]); + } + + return vals; + } + + /** + * Add sublists to this list. + * + * @param elements The sublists to add to this list. + * + * @return Whether we could add each sublist. + */ + public boolean[] addItems(@SuppressWarnings("unchecked") NestList<Element>... elements) { + boolean[] vals = new boolean[elements.length]; + + for (int i = 0; i < vals.length; i++) + { + vals[i] = addItem(elements[i]); + } + + return vals; + } + + /** + * Add a sublist with the given elements to this list. + * + * @param elements The elements of the sublist. + * + * @return Whether or not we could add the sublist. + */ + public boolean addSublist(@SuppressWarnings("unchecked") Element... elements) { + NestList<Element> container = new NestList<>(elements.length); + + for (Element ele : elements) { + container.addItem(ele); + } + + return addItem(container); + } + + /** + * Return an iterator over a flattened version of this list. + * + * N.B: In certain cases involving empty sublists, the hasNext() operation\ + * may not be 100% accurate. Be warned. + * + * @return An iterator over a flattened variant of this list. + */ + public ListIterator<Element> flatIterator() { + return new FlatNestIterator<>(listIterator()); + } + + /** + * Flatten one level of nesting from this list. + * + * @return The list with one level of nesting flattened. + */ + public NestList<Element> flatten() { + NestList<Element> flatterList = new NestList<>(size()); + + backing.forEach((element) -> + element.pick(flatterList::addItem, flatterList::addAll) + ); + + return flatterList; + } + + /** + * Flatten this list recursively. + * + * @return A flattened form of this list. + */ + public List<Element> deepFlatten() { + List<Element> flatList = new ArrayList<>(); + + flatIterator().forEachRemaining(flatList::add); + + return flatList; + } + + /** + * Get the total number of elements contained in this list and all sublists. + * + * @return The total number of elements contained in this list. + */ + public int deepSize() { + int size = 0; + + for (Either<Element, NestList<Element>> element : backing) + { + size += element.extract((ele) -> 1, (lst) -> lst.deepSize()); + } + + return size; + } + + /** + * Replace all of the elements in this list in-place with transformed versions. + * + * @param elementOperator The operator to apply to elements. + * @param listOperator The operator to apply to sublists. + */ + public void replace( + UnaryOperator<Element> elementOperator, + UnaryOperator<NestList<Element>> listOperator) { + backing.replaceAll((ele) -> ele.map(elementOperator, listOperator)); + } + + /** + * Perform a shallow mapping over this list. + * + * @param <NewElement> The new element type. + * + * @param elementMapper The function to map elements. + * @param listMapper The function to map lists. + * + * @return A new list containing the mapped elements. + */ + public <NewElement> NestList<NewElement> map( + Function<Element, NewElement> elementMapper, + Function<NestList<Element>, NestList<NewElement>> listMapper) + { + NestList<NewElement> nest = new NestList<>(backing.size()); + + for (Either<Element, NestList<Element>> element : backing) + { + nest.add(element.map(elementMapper, listMapper)); + } + + return nest; + } + + /** + * Perform a recursive mapping over this list. + * + * @param <NewElement> The new element type. + * + * @param mapper The element mapper. + * + * @return A new list with the same structure, but transformed elements. + */ + public <NewElement> NestList<NewElement> deepMap( + Function<Element, NewElement> mapper) + { + return map(mapper, (lst) -> lst.deepMap(mapper)); + } + + /** + * Perform a mapping on the list with controllable recursion. + * + * Inspired by the function of the same name from Raku. + * + * @param <NewElement> The new element type. + * + * @param recurPredicate Determines whether to recur into a list or not. + * @param elementMapper The mapper on elements. + * @param listMapper The mapper on lists we aren't recursing into. + * + * @return A new list with its elements mapped. + */ + public <NewElement> NestList<NewElement> duckMap( + Predicate<NestList<Element>> recurPredicate, + Function<Element, NewElement> elementMapper, + Function<NestList<Element>, NestList<NewElement>> listMapper) + { + return map( + elementMapper, + iftt(recurPredicate, + (list) -> list.duckMap( + recurPredicate, elementMapper, listMapper), + listMapper + ) + ); + } + + /** + * Perform a reduction over this list. + * + * @param <Output> The type of the output value. + * + * @param initial The initial state of the output value. + * @param elementFolder The function to fold elements with. + * @param listFolder The function to fold lists with. + * + * @return The result of reducing the list. + */ + public <Output> Output reduce( + Output initial, + BiFunction<Output, Element, Output> elementFolder, + BiFunction<Output, NestList<Element>, Output> listFolder) + { + Holder<Output> out = Holder.of(initial); + for (Either<Element, NestList<Element>> item : backing) + { + out.transform((state) -> item.extract( + (ele) -> elementFolder.apply(state, ele), + (lst) -> listFolder.apply(state, lst)) + ); + } + + return out.getValue(); + } + + /** + * Perform a recursive reduction over this list. + * + * @param <Output> The type of the output value. + * + * @param initial The initial state of the output value. + * @param elementFolder The function to fold elements with. + * + * @return The result of recursively reducing the list. + */ + public <Output> Output deepReduce( + Output initial, + BiFunction<Output, Element, Output> elementFolder) + { + return reduce( + initial, + elementFolder, + (state, lst) -> lst.deepReduce(state, elementFolder)); + } + + /** + * Conditionally expand elements of this list into the provided list. + * + * @param nest The list to expand elements into. + * @param expandPicker Picks whether or not to expand a list. + * @param recur Whether or not to recursively expand lists. + * + * @return The provided list. + */ + public NestList<Element> expandInto( + NestList<Element> nest, + Predicate<NestList<Element>> expandPicker, + boolean recur) + { + return reduce(nest, + (state, item) -> with(state, (stat) -> stat.addItem(item)), + (state, list) -> { + if (expandPicker.test(list)) { + return list.reduce(nest, + (substate, subitem) + -> with(substate, + (subst) -> subst.addItem(subitem)), + (substate, sublist) + -> with(substate, (subst) -> { + if (recur) { + sublist.expandInto( + subst, + expandPicker, + recur); + } else { + subst.addItem(sublist); + } + })); + } else { + state.addItem(list); + return state; + } + }); + } + // List methods and other things. + + @Override + public boolean add(Either<Element, NestList<Element>> e) { + return backing.add(e); + } + + @Override + public void add(int index, Either<Element, NestList<Element>> element) { + backing.add(index, element); + } + + @Override + public Either<Element, NestList<Element>> get(int index) { + return backing.get(index); + } + + @SuppressWarnings("unlikely-arg-type") + @Override + public boolean remove(Object o) { + return backing.remove(o); + } + + + @Override + public Either<Element, NestList<Element>> remove(int index) { + return backing.remove(index); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Objects.hash(backing); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + + NestList<?> other = (NestList<?>) obj; + + return Objects.equals(backing, other.backing); + } +} diff --git a/src/main/java/bjc/esodata/PushdownMap.java b/src/main/java/bjc/esodata/PushdownMap.java index 54ae939..76dd2db 100644 --- a/src/main/java/bjc/esodata/PushdownMap.java +++ b/src/main/java/bjc/esodata/PushdownMap.java @@ -1,12 +1,10 @@ package bjc.esodata; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; +import java.util.*; +import java.util.function.*; -import bjc.funcdata.FunctionalMap; -import bjc.funcdata.IList; -import bjc.funcdata.IMap; +import bjc.data.*; +import bjc.funcdata.*; /** * A variant of a map where inserting a duplicate key shadows the existing value @@ -22,22 +20,22 @@ import bjc.funcdata.IMap; * @param <ValueType> * The values in the map. */ -public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { +public class PushdownMap<KeyType, ValueType> implements MapEx<KeyType, ValueType> { /* Our backing storage. */ - private final IMap<KeyType, Stack<ValueType>> backing; + private final MapEx<KeyType, Stack<ValueType>> backing; + private boolean isFrozen = false; + private boolean thawEnabled = true; + /** Create a new empty stack-based map. */ public PushdownMap() { backing = new FunctionalMap<>(); } - /** Create a new empty stack-based map using the specified backing. */ - private PushdownMap(final IMap<KeyType, Stack<ValueType>> back) { - backing = back; - } - @Override public void clear() { + if (isFrozen) throw new ObjectFrozen("Can't clear frozen map"); + backing.clear(); } @@ -45,30 +43,15 @@ public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> public boolean containsKey(final KeyType key) { return backing.containsKey(key); } - - @Override - public IMap<KeyType, ValueType> extend() { - return new PushdownMap<>(backing.extend()); - } - + @Override public void forEach(final BiConsumer<KeyType, ValueType> action) { backing.forEach((key, stk) -> action.accept(key, stk.top())); } @Override - public void forEachKey(final Consumer<KeyType> action) { - backing.forEachKey(action); - } - - @Override - public void forEachValue(final Consumer<ValueType> action) { - backing.forEachValue(stk -> action.accept(stk.top())); - } - - @Override - public ValueType get(final KeyType key) { - return backing.get(key).top(); + public Optional<ValueType> get(final KeyType key) { + return backing.get(key).map((stack) -> stack.top()); } @Override @@ -77,23 +60,16 @@ public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> } @Override - public IList<KeyType> keyList() { + public ListEx<KeyType> keyList() { return backing.keyList(); } @Override - public <V2> IMap<KeyType, V2> transform(final Function<ValueType, V2> transformer) { - /* - * @NOTE Can and should we support this? More to the point, maybe this should be - * a map sub-type that does what it needs to? - */ - throw new UnsupportedOperationException("Cannot transform pushdown maps."); - } - - @Override public ValueType put(final KeyType key, final ValueType val) { + if (isFrozen) throw new ObjectFrozen("Can't insert key " + key + " into frozen map"); + if (backing.containsKey(key)) { - final Stack<ValueType> stk = backing.get(key); + final Stack<ValueType> stk = backing.get(key).get(); final ValueType vl = stk.top(); @@ -111,18 +87,19 @@ public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> @Override public ValueType remove(final KeyType key) { - final Stack<ValueType> stk = backing.get(key); + if (isFrozen) throw new ObjectFrozen("Can't remove key " + key + " from frozen map"); - if (stk.size() > 1) { - return stk.pop(); - } + Holder<ValueType> result = Holder.of(null); + + backing.get(key).ifPresent((stk) -> { + if (stk.size() > 1) { + result.replace(stk.pop()); + } else { + result.replace(backing.remove(key).top()); + } + }); - return backing.remove(key).top(); - } - - @Override - public IList<ValueType> valueList() { - return backing.valueList().map(Stack::top); + return result.getValue(); } @Override @@ -137,20 +114,17 @@ public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof PushdownMap<?, ?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof PushdownMap<?, ?>)) return false; final PushdownMap<?, ?> other = (PushdownMap<?, ?>) obj; if (backing == null) { - if (other.backing != null) - return false; - } else if (!backing.equals(other.backing)) + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) { return false; + } return true; } @@ -159,4 +133,43 @@ public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> public String toString() { return String.format("PushdownMap [backing=%s]", backing); } + + @Override + public boolean freeze() { + isFrozen = true; + + return true; + } + + @Override + public boolean thaw() { + if (thawEnabled) { + isFrozen = false; + return true; + } else { + return false; + } + } + + @Override + public boolean deepFreeze() { + thawEnabled = true; + + return freeze(); + } + + @Override + public boolean canFreeze() { + return true; + } + + @Override + public boolean canThaw() { + return thawEnabled; + } + + @Override + public boolean isFrozen() { + return isFrozen; + } } diff --git a/src/main/java/bjc/esodata/QueueStack.java b/src/main/java/bjc/esodata/QueueStack.java index c40721a..e310f16 100644 --- a/src/main/java/bjc/esodata/QueueStack.java +++ b/src/main/java/bjc/esodata/QueueStack.java @@ -29,16 +29,14 @@ public class QueueStack<T> extends Stack<T> { @Override public T pop() { - if (backing.isEmpty()) - throw new StackUnderflow(); + if (backing.isEmpty()) throw new StackUnderflow(); return backing.remove(); } @Override public T top() { - if (backing.isEmpty()) - throw new StackUnderflow(); + if (backing.isEmpty()) throw new StackUnderflow(); return backing.peek(); } @@ -76,20 +74,17 @@ public class QueueStack<T> extends Stack<T> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof QueueStack<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof QueueStack<?>)) return false; final QueueStack<?> other = (QueueStack<?>) obj; if (backing == null) { - if (other.backing != null) - return false; - } else if (!backing.equals(other.backing)) + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) { return false; + } return true; } diff --git a/src/main/java/bjc/esodata/SimpleDirectory.java b/src/main/java/bjc/esodata/SimpleDirectory.java index 672d92f..13a9f3e 100644 --- a/src/main/java/bjc/esodata/SimpleDirectory.java +++ b/src/main/java/bjc/esodata/SimpleDirectory.java @@ -1,7 +1,7 @@ package bjc.esodata; import bjc.funcdata.FunctionalMap; -import bjc.funcdata.IMap; +import bjc.funcdata.MapEx; /** * Simple implementation of {@link Directory}. @@ -18,9 +18,9 @@ import bjc.funcdata.IMap; */ public class SimpleDirectory<K, V> implements Directory<K, V> { /* Our sub-directories. */ - private final IMap<K, Directory<K, V>> children; + private final MapEx<K, Directory<K, V>> children; /* Our data. */ - private final IMap<K, V> data; + private final MapEx<K, V> data; /** Create a new directory. */ public SimpleDirectory() { @@ -30,7 +30,7 @@ public class SimpleDirectory<K, V> implements Directory<K, V> { @Override public Directory<K, V> getSubdirectory(final K key) { - return children.get(key); + return children.get(key).orElse(null); } @Override @@ -50,7 +50,7 @@ public class SimpleDirectory<K, V> implements Directory<K, V> { @Override public V getKey(final K key) { - return data.get(key); + return data.get(key).orElse(null); } @Override @@ -71,26 +71,23 @@ public class SimpleDirectory<K, V> implements Directory<K, V> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof SimpleDirectory<?, ?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SimpleDirectory<?, ?>)) return false; final SimpleDirectory<?, ?> other = (SimpleDirectory<?, ?>) obj; if (children == null) { - if (other.children != null) - return false; - } else if (!children.equals(other.children)) + if (other.children != null) return false; + } else if (!children.equals(other.children)) { return false; + } if (data == null) { - if (other.data != null) - return false; - } else if (!data.equals(other.data)) + if (other.data != null) return false; + } else if (!data.equals(other.data)) { return false; + } return true; } diff --git a/src/main/java/bjc/esodata/SimpleStack.java b/src/main/java/bjc/esodata/SimpleStack.java index b50521e..9c016c3 100644 --- a/src/main/java/bjc/esodata/SimpleStack.java +++ b/src/main/java/bjc/esodata/SimpleStack.java @@ -27,16 +27,14 @@ public class SimpleStack<T> extends Stack<T> { @Override public T pop() { - if (backing.isEmpty()) - throw new StackUnderflow(); + if (backing.isEmpty()) throw new StackUnderflow(); return backing.pop(); } @Override public T top() { - if (backing.isEmpty()) - throw new StackUnderflow(); + if (backing.isEmpty()) throw new StackUnderflow(); return backing.peek(); } @@ -69,20 +67,17 @@ public class SimpleStack<T> extends Stack<T> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof SimpleStack<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SimpleStack<?>)) return false; final SimpleStack<?> other = (SimpleStack<?>) obj; if (backing == null) { - if (other.backing != null) - return false; - } else if (!backing.equals(other.backing)) + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) { return false; + } return true; } diff --git a/src/main/java/bjc/esodata/SingleTape.java b/src/main/java/bjc/esodata/SingleTape.java index ec56e2b..7c3a34f 100644 --- a/src/main/java/bjc/esodata/SingleTape.java +++ b/src/main/java/bjc/esodata/SingleTape.java @@ -45,9 +45,7 @@ public class SingleTape<T> implements Tape<T> { backing = new ArrayList<>(vals.length); - for (T val : vals) { - backing.add(val); - } + for (T val : vals) backing.add(val); } /** @@ -66,9 +64,7 @@ public class SingleTape<T> implements Tape<T> { public SingleTape(Iterable<T> itr) { this(false); - for (T itm : itr) { - backing.add(itm); - } + for (T itm : itr) backing.add(itm); } /** @@ -86,8 +82,7 @@ public class SingleTape<T> implements Tape<T> { @Override public T item() { - if (pos < 0 || pos >= backing.size()) - return null; + if (pos < 0 || pos >= backing.size()) return null; return backing.get(pos); } @@ -114,19 +109,16 @@ public class SingleTape<T> implements Tape<T> { @Override public void insertAfter(final T itm) { - if (pos == backing.size() - 1) { - backing.add(itm); - } else { - backing.add(pos + 1, itm); - } + if (pos == backing.size() - 1) backing.add(itm); + else backing.add(pos + 1, itm); } @Override public T remove() { final T res = backing.remove(pos); - if (pos != 0) { - pos -= 1; - } + + if (pos != 0) pos -= 1; + return res; } @@ -147,8 +139,7 @@ public class SingleTape<T> implements Tape<T> { @Override public boolean left(final int amt) { - if (pos - amt < 0) - return false; + if (pos - amt < 0) return false; pos -= amt; return true; @@ -163,11 +154,10 @@ public class SingleTape<T> implements Tape<T> { public boolean right(final int amt) { if (pos + amt > backing.size()) { if (autoExtend) { - while (pos + amt >= backing.size() - 1) { - backing.add(null); - } - } else + while (pos + amt >= backing.size() - 1) backing.add(null); + } else { return false; + } } pos += amt; @@ -176,15 +166,15 @@ public class SingleTape<T> implements Tape<T> { @Override public boolean seekTo(int tgtPos) { - if (tgtPos < 0) - return false; + if (tgtPos < 0) return false; - if (tgtPos >= backing.size() - 1) - if (autoExtend) - while (tgtPos >= backing.size() - 1) - backing.add(null); - else + if (tgtPos >= backing.size() - 1) { + if (autoExtend) { + while (tgtPos >= backing.size() - 1) backing.add(null); + } else { return false; + } + } pos = tgtPos; @@ -208,20 +198,17 @@ public class SingleTape<T> implements Tape<T> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof SingleTape<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SingleTape<?>)) return false; final SingleTape<?> other = (SingleTape<?>) obj; if (backing == null) { - if (other.backing != null) - return false; - } else if (!backing.equals(other.backing)) + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) { return false; + } return true; } diff --git a/src/main/java/bjc/esodata/SpaghettiStack.java b/src/main/java/bjc/esodata/SpaghettiStack.java index fc3e154..4bd77d3 100644 --- a/src/main/java/bjc/esodata/SpaghettiStack.java +++ b/src/main/java/bjc/esodata/SpaghettiStack.java @@ -37,16 +37,14 @@ class SpaghettiStack<T> extends Stack<T> { @Override public T pop() { - if (backing.isEmpty()) - return parent.pop(); + if (backing.isEmpty()) return parent.pop(); return backing.pop(); } @Override public T top() { - if (backing.isEmpty()) - return parent.top(); + if (backing.isEmpty()) return parent.top(); return backing.top(); } @@ -82,26 +80,23 @@ class SpaghettiStack<T> extends Stack<T> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof SpaghettiStack<?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SpaghettiStack<?>)) return false; final SpaghettiStack<?> other = (SpaghettiStack<?>) obj; if (backing == null) { - if (other.backing != null) - return false; - } else if (!backing.equals(other.backing)) + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) { return false; + } if (parent == null) { - if (other.parent != null) - return false; - } else if (!parent.equals(other.parent)) + if (other.parent != null) return false; + } else if (!parent.equals(other.parent)) { return false; + } return true; } diff --git a/src/main/java/bjc/esodata/Stack.java b/src/main/java/bjc/esodata/Stack.java index 5ee5ef2..360e57d 100644 --- a/src/main/java/bjc/esodata/Stack.java +++ b/src/main/java/bjc/esodata/Stack.java @@ -96,9 +96,7 @@ public abstract class Stack<T> { * The elements to insert. */ public void pushAll(@SuppressWarnings("unchecked") T... elms) { - for (T elm : elms) { - push(elm); - } + for (T elm : elms) push(elm); } /** @@ -108,9 +106,7 @@ public abstract class Stack<T> { * The elements to insert. */ public void pushAll(List<T> elms) { - for (T elm : elms) { - push(elm); - } + for (T elm : elms) push(elm); } /** @@ -124,9 +120,7 @@ public abstract class Stack<T> { public List<T> multipop(int n) { List<T> lst = new LinkedList<>(); - for (int i = 0; i < n; i++) { - lst.add(pop()); - } + for (int i = 0; i < n; i++) lst.add(pop()); return lst; } @@ -142,9 +136,7 @@ public abstract class Stack<T> { public List<T> multipoprev(int n) { LinkedList<T> lst = new LinkedList<>(); - for (int i = 0; i < n; i++) { - lst.addFirst(pop()); - } + for (int i = 0; i < n; i++) lst.addFirst(pop()); return lst; } @@ -160,9 +152,7 @@ public abstract class Stack<T> { * The number of items to drop. */ public void drop(final int n) { - for (int i = 0; i < n; i++) { - pop(); - } + for (int i = 0; i < n; i++) pop(); } /** Drop one item from the stack. */ @@ -201,9 +191,7 @@ public abstract class Stack<T> { public void multidup(final int n, final int m) { List<T> lst = multipoprev(n); - for (int i = 0; i <= m; i++) { - pushAll(lst); - } + for (int i = 0; i <= m; i++) pushAll(lst); } /** @@ -235,15 +223,11 @@ public abstract class Stack<T> { List<T> lst = multipoprev(n); - for (final T nelm : lst) { - push(nelm); - } + for (final T nelm : lst) push(nelm); push(elm); - for (int i = 0; i < m; i++) { - pushAll(lst); - } + for (int i = 0; i < m; i++) pushAll(lst); } /** @@ -555,9 +539,7 @@ public abstract class Stack<T> { public void multiapply(final int n, final int m, final Consumer<Stack<T>> action) { final List<Consumer<Stack<T>>> actions = new ArrayList<>(m); - for (int i = 0; i < m; i++) { - actions.add(action); - } + for (int i = 0; i < m; i++) actions.add(action); multispread(n, actions); } diff --git a/src/main/java/bjc/esodata/TapeLibrary.java b/src/main/java/bjc/esodata/TapeLibrary.java new file mode 100644 index 0000000..922833f --- /dev/null +++ b/src/main/java/bjc/esodata/TapeLibrary.java @@ -0,0 +1,163 @@ +package bjc.esodata; + +import java.util.*; + +/** + * Represents a library of possible tapes, with a single tape 'mounted' or active + * at a time. + * + * @author Ben Culkin + * + * @param <ElementType> The type stored on each tape. + */ +public class TapeLibrary<ElementType> implements TapeView<ElementType> +{ + private final Map<String, Tape<ElementType>> library; + + private String currentLabel; + private Tape<ElementType> currentTape; + + private boolean allowAutoCreation; + + /** + * Get a view of this tape library as a map. + * + * Modifications to this map will apply to the library, but will not + * affect whether a given tape is mounted or not. Be forewarned. + * + * @return A view onto this tape library as a map. + */ + public Map<String, Tape<ElementType>> asMap() + { + return library; + } + + /** + * Create a new empty tape library, with no tape mounted. + */ + public TapeLibrary() + { + library = new HashMap<>(); + } + + /** + * Create a new tape, with a given tape mounted by default. + * + * @param label The label of the tape. + * @param tape The tape to mount. + */ + public TapeLibrary(String label, Tape<ElementType> tape) + { + this(); + library.put(label, tape); + + this.currentLabel = label; + this.currentTape = tape; + } + + @Override + public Tape<ElementType> tapeView() + { + return currentTape; + } + + /** + * Insert a tape into this library. + * + * @param label The label to use for the tape. + * @param tape The tape to add. + * + * @return The tape which previously had that label, or null if there was not one. + */ + public Tape<ElementType> insertTape(String label, Tape<ElementType> tape) + { + return library.put(label, tape); + } + + /** + * Remove a tape from this library. + * + * @param label The label of the tape to remove. + * + * @return The tape which had that label, or null if there was not one. + */ + public Tape<ElementType> removeTape(String label) + { + return library.remove(label); + } + + /** + * Check if this library has a tape with a given label. + * + * @param label The label of the tape to check for. + * + * @return Whether or not the library contains a tape with that label. + */ + public boolean hasTape(String label) + { + return allowAutoCreation ? true : library.containsKey(label); + } + + /** + * Mount a different tape in the library. + * + * @param label The label of the tape to mount. + * + * @return True if the tape was successfully mounted, false otherwise. + */ + public boolean mountTape(String label) + { + if (library.containsKey(label) || allowAutoCreation) + { + currentLabel = label; + currentTape = library.computeIfAbsent( + label, + (ignored) -> new SingleTape<>()); + + return true; + } else { + return false; + } + } + + /** + * Returns the label of the current tape. + * + * @return The label of the current tape, or null if no tape is + * currently 'mounted'. + */ + public String currentLabel() + { + return currentLabel; + } + + /** + * Unmount the currently mounted tape. + */ + public void ejectTape() + { + currentTape = null; + currentLabel = null; + } + + /** + * Check if this tape library currently allows auto-creation of + * non-existing tapes. + * + * @return Whether or not auto-creation of tapes is currently allowed. + */ + public boolean isAllowAutoCreation() { + return allowAutoCreation; + } + + /** + * Set whether or not this library allows auto-creation of non-existing + * tapes. + * + * @param allowAutoCreation Whether tape auto-creation is allowed. + */ + public void setAllowAutoCreation(boolean allowAutoCreation) + { + this.allowAutoCreation = allowAutoCreation; + } +} diff --git a/src/main/java/bjc/esodata/TapeView.java b/src/main/java/bjc/esodata/TapeView.java new file mode 100644 index 0000000..bfc33a9 --- /dev/null +++ b/src/main/java/bjc/esodata/TapeView.java @@ -0,0 +1,102 @@ +package bjc.esodata; + +/** + * An interface which allows you to view a given type as a tape. + * + * @author Ben Culkin + * + * @param <ElementType> The type of element contained in the tape. + */ +public interface TapeView<ElementType> extends Tape<ElementType> +{ + /** + * Return a view of this object as a tape. + * + * @return A view of this object as a tape. + */ + public Tape<ElementType> tapeView(); + + @Override + public default ElementType item() + { + return tapeView().item(); + } + + @Override + public default void item(ElementType itm) + { + tapeView().item(itm); + } + + @Override + public default int size() + { + return tapeView().size(); + } + + @Override + public default int position() + { + return tapeView().position(); + } + + @Override + public default void insertBefore(ElementType itm) + { + tapeView().insertBefore(itm); + } + + @Override + public default void insertAfter(ElementType itm) + { + tapeView().insertAfter(itm); + } + + @Override + public default ElementType remove() + { + return tapeView().remove(); + } + + @Override + public default void first() + { + tapeView().first(); + } + + @Override + public default void last() + { + tapeView().last(); + } + + @Override + public default boolean left() + { + return tapeView().left(); + } + + @Override + public default boolean left(int amt) + { + return tapeView().left(amt); + } + + @Override + public default boolean right() + { + return tapeView().right(); + } + + @Override + public default boolean right(int amt) + { + return tapeView().right(amt); + } + + @Override + public default boolean seekTo(int pos) + { + return tapeView().seekTo(pos); + } +} diff --git a/src/main/java/bjc/esodata/ThresholdSet.java b/src/main/java/bjc/esodata/ThresholdSet.java index ae277e1..9b8560b 100644 --- a/src/main/java/bjc/esodata/ThresholdSet.java +++ b/src/main/java/bjc/esodata/ThresholdSet.java @@ -37,8 +37,7 @@ public class ThresholdSet<KeyType> { int ret = ThresholdSet.this.add(key); // No change to set contents - if (ret > 2) - return false; + if (ret > 2) return false; return true; } @@ -52,8 +51,7 @@ public class ThresholdSet<KeyType> { int ret = ThresholdSet.this.remove(k); // We removed the element. - if (ret == 0) - return true; + if (ret == 0) return true; return false; } @@ -67,8 +65,7 @@ public class ThresholdSet<KeyType> { int ret = ThresholdSet.this.contains(k); // The object is set-visible - if (ret == 1) - return true; + if (ret == 1) return true; return false; } @@ -112,9 +109,7 @@ public class ThresholdSet<KeyType> { public int[] addKeys(@SuppressWarnings("unchecked") KeyType... keys) { int[] ret = new int[keys.length]; - for (int i = 0; i < keys.length; i++) { - ret[i] = add(keys[i]); - } + for (int i = 0; i < keys.length; i++) ret[i] = add(keys[i]); return ret; } @@ -162,9 +157,7 @@ public class ThresholdSet<KeyType> { public int[] removeKeys(@SuppressWarnings("unchecked") KeyType... keys) { int[] ret = new int[keys.length]; - for (int i = 0; i < keys.length; i++) { - ret[i] = remove(keys[i]); - } + for (int i = 0; i < keys.length; i++) ret[i] = remove(keys[i]); return ret; } @@ -217,9 +210,7 @@ public class ThresholdSet<KeyType> { public int[] containsKeys(@SuppressWarnings("unchecked") KeyType... keys) { int[] ret = new int[keys.length]; - for (int i = 0; i < keys.length; i++) { - ret[i] = contains(keys[i]); - } + for (int i = 0; i < keys.length; i++) ret[i] = contains(keys[i]); return ret; } @@ -233,12 +224,9 @@ public class ThresholdSet<KeyType> { * @return The number of times the key occurs; -1 if it doesn't occur. */ public int contains(KeyType key) { - if (keySet.contains(key)) - return 1; - if (!keyMap.containsKey(key)) - return -1; - - return keyMap.get(key); + if (keySet.contains(key)) return 1; + if (!keyMap.containsKey(key)) return -1; + else return keyMap.get(key); } /** diff --git a/src/main/java/bjc/esodata/UnifiedDirectory.java b/src/main/java/bjc/esodata/UnifiedDirectory.java index 2221615..1b630eb 100644 --- a/src/main/java/bjc/esodata/UnifiedDirectory.java +++ b/src/main/java/bjc/esodata/UnifiedDirectory.java @@ -1,7 +1,7 @@ package bjc.esodata; import bjc.funcdata.FunctionalMap; -import bjc.funcdata.IMap; +import bjc.funcdata.MapEx; /** * Simple implementation of {@link Directory}. @@ -18,9 +18,9 @@ import bjc.funcdata.IMap; */ public class UnifiedDirectory<K, V> implements Directory<K, V> { /* Our directory children. */ - private final IMap<K, Directory<K, V>> children; + private final MapEx<K, Directory<K, V>> children; /* Our data children. */ - private final IMap<K, V> data; + private final MapEx<K, V> data; /** Create a new directory. */ public UnifiedDirectory() { @@ -30,7 +30,7 @@ public class UnifiedDirectory<K, V> implements Directory<K, V> { @Override public Directory<K, V> getSubdirectory(final K key) { - return children.get(key); + return children.get(key).orElse(null); } @Override @@ -56,7 +56,7 @@ public class UnifiedDirectory<K, V> implements Directory<K, V> { @Override public V getKey(final K key) { - return data.get(key); + return data.get(key).orElse(null); } @Override @@ -82,26 +82,23 @@ public class UnifiedDirectory<K, V> implements Directory<K, V> { @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof UnifiedDirectory<?, ?>)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof UnifiedDirectory<?, ?>)) return false; final UnifiedDirectory<?, ?> other = (UnifiedDirectory<?, ?>) obj; if (children == null) { - if (other.children != null) - return false; - } else if (!children.equals(other.children)) + if (other.children != null) return false; + } else if (!children.equals(other.children)) { return false; + } if (data == null) { - if (other.data != null) - return false; - } else if (!data.equals(other.data)) + if (other.data != null) return false; + } else if (!data.equals(other.data)) { return false; + } return true; } diff --git a/src/main/java/bjc/funcdata/ExtendedMap.java b/src/main/java/bjc/funcdata/ExtendedMap.java index bd500f4..379ff65 100644 --- a/src/main/java/bjc/funcdata/ExtendedMap.java +++ b/src/main/java/bjc/funcdata/ExtendedMap.java @@ -1,8 +1,7 @@ package bjc.funcdata; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; +import java.util.*; +import java.util.function.*; /** * An extended version of a map, that stores values into a map, but can look @@ -16,12 +15,15 @@ import java.util.function.Function; * @param <ValueType> * The type of the values of the map. */ -class ExtendedMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { +class ExtendedMap<KeyType, ValueType> implements MapEx<KeyType, ValueType> { /* The map we delegate lookups to. */ - private final IMap<KeyType, ValueType> delegate; + private final MapEx<KeyType, ValueType> delegate; /* The map we store things in. */ - private final IMap<KeyType, ValueType> store; + private final MapEx<KeyType, ValueType> store; + private boolean isFrozen = false; + private boolean thawEnabled = true; + /** * Create a new extended map. * @@ -31,28 +33,23 @@ class ExtendedMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { * @param store * The map to store things in. */ - public ExtendedMap(final IMap<KeyType, ValueType> delegate, - final IMap<KeyType, ValueType> store) { + public ExtendedMap(final MapEx<KeyType, ValueType> delegate, + final MapEx<KeyType, ValueType> store) { this.delegate = delegate; this.store = store; } @Override public void clear() { + if (isFrozen) return; + store.clear(); } @Override public boolean containsKey(final KeyType key) { - if (store.containsKey(key)) - return true; - - return delegate.containsKey(key); - } - - @Override - public IMap<KeyType, ValueType> extend() { - return new ExtendedMap<>(this, new FunctionalMap<>()); + if (store.containsKey(key)) return true; + else return delegate.containsKey(key); } @Override @@ -63,25 +60,9 @@ class ExtendedMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { } @Override - public void forEachKey(final Consumer<KeyType> action) { - store.forEachKey(action); - - delegate.forEachKey(action); - } - - @Override - public void forEachValue(final Consumer<ValueType> action) { - store.forEachValue(action); - - delegate.forEachValue(action); - } - - @Override - public ValueType get(final KeyType key) { - if (store.containsKey(key)) - return store.get(key); - - return delegate.get(key); + public Optional<ValueType> get(final KeyType key) { + if (store.containsKey(key)) return store.get(key); + else return delegate.get(key); } @Override @@ -90,8 +71,8 @@ class ExtendedMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { } @Override - public IList<KeyType> keyList() { - IList<KeyType> ilst = new FunctionalList<>(); + public ListEx<KeyType> keyList() { + ListEx<KeyType> ilst = new FunctionalList<>(); ilst.addAll(store.keyList()); ilst.addAll(delegate.keyList()); @@ -100,70 +81,84 @@ class ExtendedMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { } @Override - public <MappedValue> IMap<KeyType, MappedValue> - transform(final Function<ValueType, MappedValue> transformer) { - return new TransformedValueMap<>(this, transformer); - } - - @Override public ValueType put(final KeyType key, final ValueType val) { + if (isFrozen) + throw new ObjectFrozen("Can't insert key " + key + " into frozen map"); + return store.put(key, val); } @Override public ValueType remove(final KeyType key) { - if (!store.containsKey(key)) - return delegate.remove(key); - - return store.remove(key); + if (isFrozen) + throw new ObjectFrozen("Can't remove key " + key + " from frozen map"); + + if (!store.containsKey(key)) return delegate.remove(key); + else return store.remove(key); } @Override - public IList<ValueType> valueList() { - IList<ValueType> ilst = new FunctionalList<>(); + public int hashCode() { + // isFrozen isn't counted + return Objects.hash(delegate, store); + } - ilst.addAll(store.valueList()); - ilst.addAll(delegate.valueList()); + /* Misc. object support. */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; - return ilst; - } + ExtendedMap<?, ?> other = (ExtendedMap<?, ?>) obj; - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (delegate == null ? 0 : delegate.hashCode()); - result = prime * result + (store == null ? 0 : store.hashCode()); - return result; + // isFrozen isn't counted + return Objects.equals(delegate, other.delegate) && Objects.equals(store, other.store); + } + + @Override + public String toString() { + return String.format("ExtendedMap [delegate=%s, store=%s]", delegate, store); } + /* IFreezable support */ + @Override - public boolean equals(final Object obj) { - if (this == obj) + public boolean freeze() { + isFrozen = true; + return true; - if (obj == null) - return false; - if (!(obj instanceof ExtendedMap)) - return false; + } - final ExtendedMap<?, ?> other = (ExtendedMap<?, ?>) obj; + @Override + public boolean deepFreeze() { + thawEnabled = false; - if (delegate == null) { - if (other.delegate != null) - return false; - } else if (!delegate.equals(other.delegate)) - return false; - if (store == null) { - if (other.store != null) - return false; - } else if (!store.equals(other.store)) + return freeze(); + } + + @Override + public boolean thaw() { + if (thawEnabled) { + isFrozen = false; + return true; + } else { return false; + } + } + @Override + public boolean isFrozen() { + return isFrozen; + } + + @Override + public boolean canFreeze() { return true; } - + @Override - public String toString() { - return String.format("ExtendedMap [delegate=%s, store=%s]", delegate, store); + public boolean canThaw() { + return thawEnabled; } -} +}
\ No newline at end of file diff --git a/src/main/java/bjc/funcdata/Freezable.java b/src/main/java/bjc/funcdata/Freezable.java new file mode 100644 index 0000000..e83accb --- /dev/null +++ b/src/main/java/bjc/funcdata/Freezable.java @@ -0,0 +1,73 @@ +package bjc.funcdata; + +/** + * Indicates that an object can switch between immutable and mutable modes. + * + * Note that this only implements 'shallow' immutability. Namely, any sub-objects + * are not made immutable, and if the type is a collection, the elements are still + * as mutable as they were before. + * + * Implementations of this interface may choose to throw {@link ObjectFrozen} if + * you attempt to modify a frozen object, but they may also choose not to. + * + * @author Ben Culkin + */ +public interface Freezable { + /** + * Freezes the internal state of this object, making it immutable. + * + * @return True if the object is frozen, false if it couldn't be frozen. + */ + public boolean freeze(); + /** + * Thaws the internal state of this object, making it mutable. + * + * @return True if the object is thawed, false if it couldn't be thawed. + */ + public boolean thaw(); + + /** + * 'Deep-freeze' this object, making it immutable and disabling the ability to + * thaw it. + * + * @return True if the object was deep-frozen, false if that couldn't happen. + */ + default boolean deepFreeze() { + return false; + } + + /** + * Check if this object can be frozen. + * + * @return Whether or not the object can be frozen. + */ + default boolean canFreeze() { + return false; + } + + /** + * Checks if this object can be thawed. + * + * @return Whether or not the object can be thawed. + */ + default boolean canThaw() { + return false; + } + + /** + * Determines if this object is frozen. + * + * @return True if the object is frozen, false if the object is thawed. + */ + public boolean isFrozen(); + + + /** + * Determines if this object is thawed. + * + * @return True if the object is thawed, false if the object is thawed. + */ + default boolean isThawed() { + return !isFrozen(); + } +} diff --git a/src/main/java/bjc/funcdata/FunctionalList.java b/src/main/java/bjc/funcdata/FunctionalList.java index 2cdfa27..88f49c4 100644 --- a/src/main/java/bjc/funcdata/FunctionalList.java +++ b/src/main/java/bjc/funcdata/FunctionalList.java @@ -12,10 +12,10 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -import bjc.data.IHolder; -import bjc.data.IPair; -import bjc.data.Identity; +import bjc.data.Holder; import bjc.data.Pair; +import bjc.data.Identity; +import bjc.data.SimplePair; /** * A wrapper over another list that provides eager functional operations over @@ -29,7 +29,7 @@ import bjc.data.Pair; * @param <E> * The type in this list */ -public class FunctionalList<E> implements Cloneable, IList<E> { +public class FunctionalList<E> implements Cloneable, ListEx<E> { /* The list used as a backing store */ private final List<E> wrapped; @@ -60,20 +60,21 @@ public class FunctionalList<E> implements Cloneable, IList<E> { * * Takes O(n) time, where n is the number of items specified * - * @param items - * The items to put into this functional list. + * @param <T> The type of items to put into the list. + * + * @param items The items to put into this functional list. + * * @return The returned list. */ @SafeVarargs - public static <T> IList<T> listOf(final T... items) { + public static <T> ListEx<T> listOf(final T... items) { return new FunctionalList<>(items); } /** * Create a new functional list with the specified size. * - * @param size - * The size of the backing list . + * @param size The size of the backing list . */ private FunctionalList(final int size) { wrapped = new ArrayList<>(size); @@ -84,8 +85,7 @@ public class FunctionalList<E> implements Cloneable, IList<E> { * * Takes O(1) time, since it doesn't copy the list. * - * @param backing - * The list to use as a backing list. + * @param backing The list to use as a backing list. */ public FunctionalList(final List<E> backing) { if (backing == null) @@ -137,8 +137,8 @@ public class FunctionalList<E> implements Cloneable, IList<E> { * @return A copy of the list. */ @Override - public IList<E> clone() { - final IList<E> cloned = new FunctionalList<>(); + public ListEx<E> clone() { + final ListEx<E> cloned = new FunctionalList<>(); for (final E element : wrapped) { cloned.add(element); @@ -148,7 +148,7 @@ public class FunctionalList<E> implements Cloneable, IList<E> { } @Override - public <T, F> IList<F> combineWith(final IList<T> rightList, + public <T, F> ListEx<F> combineWith(final ListEx<T> rightList, final BiFunction<E, T, F> itemCombiner) { if (rightList == null) { throw new NullPointerException("Target combine list must not be null"); @@ -156,7 +156,7 @@ public class FunctionalList<E> implements Cloneable, IList<E> { throw new NullPointerException("Combiner must not be null"); } - final IList<F> returned = new FunctionalList<>(); + final ListEx<F> returned = new FunctionalList<>(); /* Get the iterator for the other list. */ final Iterator<T> rightIterator = rightList.toIterable().iterator(); @@ -222,14 +222,14 @@ public class FunctionalList<E> implements Cloneable, IList<E> { } @Override - public <T> IList<T> flatMap(final Function<E, IList<T>> expander) { + public <T> ListEx<T> flatMap(final Function<E, ListEx<T>> expander) { if (expander == null) throw new NullPointerException("Expander must not be null"); - final IList<T> returned = new FunctionalList<>(this.wrapped.size()); + final ListEx<T> returned = new FunctionalList<>(this.wrapped.size()); forEach(element -> { - final IList<T> expandedElement = expander.apply(element); + final ListEx<T> expandedElement = expander.apply(element); if (expandedElement == null) throw new NullPointerException("Expander returned null list"); @@ -257,7 +257,7 @@ public class FunctionalList<E> implements Cloneable, IList<E> { /* * This is held b/c ref'd variables must be final/effectively final. */ - final IHolder<Integer> currentIndex = new Identity<>(0); + final Holder<Integer> currentIndex = new Identity<>(0); wrapped.forEach(element -> { /* Call the action with the index and the value. */ @@ -283,11 +283,11 @@ public class FunctionalList<E> implements Cloneable, IList<E> { } @Override - public IList<E> getMatching(final Predicate<E> predicate) { + public ListEx<E> getMatching(final Predicate<E> predicate) { if (predicate == null) throw new NullPointerException("Predicate must not be null"); - final IList<E> returned = new FunctionalList<>(); + final ListEx<E> returned = new FunctionalList<>(); wrapped.forEach(element -> { if (predicate.test(element)) { @@ -313,17 +313,17 @@ public class FunctionalList<E> implements Cloneable, IList<E> { /* Check if a partition has room for another item. */ private Boolean isPartitionFull(final int numberPerPartition, - final IHolder<IList<E>> currentPartition) { + final Holder<ListEx<E>> currentPartition) { return currentPartition .unwrap(partition -> partition.getSize() >= numberPerPartition); } @Override - public <T> IList<T> map(final Function<E, T> elementTransformer) { + public <T> ListEx<T> map(final Function<E, T> elementTransformer) { if (elementTransformer == null) throw new NullPointerException("Transformer must be not null"); - final IList<T> returned = new FunctionalList<>(this.wrapped.size()); + final ListEx<T> returned = new FunctionalList<>(this.wrapped.size()); forEach(element -> { // Add the transformed item to the result @@ -334,12 +334,12 @@ public class FunctionalList<E> implements Cloneable, IList<E> { } @Override - public <T> IList<IPair<E, T>> pairWith(final IList<T> rightList) { - return combineWith(rightList, Pair<E, T>::new); + public <T> ListEx<Pair<E, T>> pairWith(final ListEx<T> rightList) { + return combineWith(rightList, SimplePair<E, T>::new); } @Override - public IList<IList<E>> partition(final int numberPerPartition) { + public ListEx<ListEx<E>> partition(final int numberPerPartition) { if (numberPerPartition < 1 || numberPerPartition > wrapped.size()) { final String fmt = "%s is an invalid partition size. Must be between 1 and %d"; @@ -348,10 +348,10 @@ public class FunctionalList<E> implements Cloneable, IList<E> { throw new IllegalArgumentException(msg); } - final IList<IList<E>> returned = new FunctionalList<>(); + final ListEx<ListEx<E>> returned = new FunctionalList<>(); /* The current partition being filled. */ - final IHolder<IList<E>> currentPartition = new Identity<>(new FunctionalList<>()); + final Holder<ListEx<E>> currentPartition = new Identity<>(new FunctionalList<>()); this.forEach(element -> { if (isPartitionFull(numberPerPartition, currentPartition)) { @@ -395,7 +395,7 @@ public class FunctionalList<E> implements Cloneable, IList<E> { } /* The current collapsed list. */ - final IHolder<T> currentState = new Identity<>(initialValue); + final Holder<T> currentState = new Identity<>(initialValue); wrapped.forEach(element -> { /* Accumulate a new value into the state. */ @@ -444,7 +444,7 @@ public class FunctionalList<E> implements Cloneable, IList<E> { } @Override - public IList<E> tail() { + public ListEx<E> tail() { return new FunctionalList<>(wrapped.subList(1, getSize())); } diff --git a/src/main/java/bjc/funcdata/FunctionalMap.java b/src/main/java/bjc/funcdata/FunctionalMap.java index aba3dd1..3427a91 100644 --- a/src/main/java/bjc/funcdata/FunctionalMap.java +++ b/src/main/java/bjc/funcdata/FunctionalMap.java @@ -1,15 +1,12 @@ package bjc.funcdata; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; +import java.util.*; +import java.util.function.*; -import bjc.data.IPair; +import bjc.data.*; /** - * Basic implementation of {@link IMap} + * Basic implementation of {@link MapEx} * * @author ben * @@ -19,10 +16,13 @@ import bjc.data.IPair; * @param <ValueType> * The type of the map's values. */ -public class FunctionalMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { +public class FunctionalMap<KeyType, ValueType> implements MapEx<KeyType, ValueType> { /* Our backing store. */ private Map<KeyType, ValueType> wrappedMap; + private boolean isFrozen = false; + private boolean thawEnabled = true; + /** Create a new blank functional map */ public FunctionalMap() { wrappedMap = new HashMap<>(); @@ -35,13 +35,11 @@ public class FunctionalMap<KeyType, ValueType> implements IMap<KeyType, ValueTyp * The entries to put into the map. */ @SafeVarargs - public FunctionalMap(final IPair<KeyType, ValueType>... entries) { + public FunctionalMap(final Pair<KeyType, ValueType>... entries) { this(); - for (final IPair<KeyType, ValueType> entry : entries) { - entry.doWith((key, val) -> { - wrappedMap.put(key, val); - }); + for (final Pair<KeyType, ValueType> entry : entries) { + entry.doWith(wrappedMap::put); } } @@ -52,14 +50,15 @@ public class FunctionalMap<KeyType, ValueType> implements IMap<KeyType, ValueTyp * The map to wrap. */ public FunctionalMap(final Map<KeyType, ValueType> wrap) { - if (wrap == null) - throw new NullPointerException("Map to wrap must not be null"); + if (wrap == null) throw new NullPointerException("Map to wrap must not be null"); wrappedMap = wrap; } @Override public void clear() { + if (isFrozen) throw new ObjectFrozen("Can't clear frozen map"); + wrappedMap.clear(); } @@ -69,37 +68,19 @@ public class FunctionalMap<KeyType, ValueType> implements IMap<KeyType, ValueTyp } @Override - public IMap<KeyType, ValueType> extend() { - return new ExtendedMap<>(this, new FunctionalMap<>()); - } - - @Override public void forEach(final BiConsumer<KeyType, ValueType> action) { wrappedMap.forEach(action); } @Override - public void forEachKey(final Consumer<KeyType> action) { - wrappedMap.keySet().forEach(action); - } + public Optional<ValueType> get(final KeyType key) { + if (key == null) throw new NullPointerException("Key must not be null"); - @Override - public void forEachValue(final Consumer<ValueType> action) { - wrappedMap.values().forEach(action); - } - - @Override - public ValueType get(final KeyType key) { - if (key == null) - throw new NullPointerException("Key must not be null"); - - if (!wrappedMap.containsKey(key)) { - final String msg = String.format("Key %s is not present in the map", key); - - throw new IllegalArgumentException(msg); + if (wrappedMap.containsKey(key)) { + return Optional.of(wrappedMap.get(key)); + } else { + return Optional.empty(); } - - return wrappedMap.get(key); } @Override @@ -108,35 +89,26 @@ public class FunctionalMap<KeyType, ValueType> implements IMap<KeyType, ValueTyp } @Override - public IList<KeyType> keyList() { + public ListEx<KeyType> keyList() { final FunctionalList<KeyType> keys = new FunctionalList<>(); - wrappedMap.keySet().forEach(key -> { - keys.add(key); - }); + wrappedMap.keySet().forEach(keys::add); return keys; } @Override - public <MappedValue> IMap<KeyType, MappedValue> - transform(final Function<ValueType, MappedValue> transformer) { - if (transformer == null) - throw new NullPointerException("Transformer must not be null"); - - return new TransformedValueMap<>(this, transformer); - } - - @Override public ValueType put(final KeyType key, final ValueType val) { - if (key == null) - throw new NullPointerException("Key must not be null"); + if (isFrozen) throw new ObjectFrozen("Can't put key " + key + " into frozen map"); + if (key == null) throw new NullPointerException("Key must not be null"); return wrappedMap.put(key, val); } @Override public ValueType remove(final KeyType key) { + if (isFrozen) throw new ObjectFrozen("Can't remove key " + key + " from frozen map"); + return wrappedMap.remove(key); } @@ -144,18 +116,7 @@ public class FunctionalMap<KeyType, ValueType> implements IMap<KeyType, ValueTyp public String toString() { return wrappedMap.toString(); } - - @Override - public IList<ValueType> valueList() { - final FunctionalList<ValueType> values = new FunctionalList<>(); - - wrappedMap.values().forEach(value -> { - values.add(value); - }); - - return values; - } - + @Override public int hashCode() { final int prime = 31; @@ -166,20 +127,58 @@ public class FunctionalMap<KeyType, ValueType> implements IMap<KeyType, ValueTyp @Override public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof FunctionalMap)) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof FunctionalMap)) return false; final FunctionalMap<?, ?> other = (FunctionalMap<?, ?>) obj; if (wrappedMap == null) { - if (other.wrappedMap != null) - return false; - } else if (!wrappedMap.equals(other.wrappedMap)) + if (other.wrappedMap != null) return false; + } else if (!wrappedMap.equals(other.wrappedMap)) { return false; + } + + return true; + } + + // IFreezable support + @Override + public boolean freeze() { + isFrozen = true; + return true; } + + @Override + public boolean thaw() { + if (thawEnabled) { + isFrozen = false; + return true; + } else { + return false; + } + } + + @Override + public boolean deepFreeze() { + thawEnabled = false; + + return freeze(); + } + + @Override + public boolean canFreeze() { + return true; + } + + @Override + public boolean canThaw() { + return thawEnabled; + } + + @Override + public boolean isFrozen() { + return isFrozen; + } } diff --git a/src/main/java/bjc/funcdata/FunctionalStringTokenizer.java b/src/main/java/bjc/funcdata/FunctionalStringTokenizer.java index 856c153..3344e05 100644 --- a/src/main/java/bjc/funcdata/FunctionalStringTokenizer.java +++ b/src/main/java/bjc/funcdata/FunctionalStringTokenizer.java @@ -128,7 +128,7 @@ public class FunctionalStringTokenizer { * * @return This tokenizer, converted into a list of strings. */ - public IList<String> toList() { + public ListEx<String> toList() { return toList((final String element) -> element); } @@ -144,11 +144,11 @@ public class FunctionalStringTokenizer { * * @return A list containing all of the converted tokens. */ - public <E> IList<E> toList(final Function<String, E> transformer) { + public <E> ListEx<E> toList(final Function<String, E> transformer) { if (transformer == null) throw new NullPointerException("Transformer must not be null"); - final IList<E> returned = new FunctionalList<>(); + final ListEx<E> returned = new FunctionalList<>(); /* Add each token to the list after transforming it. */ forEachToken(token -> { diff --git a/src/main/java/bjc/funcdata/IList.java b/src/main/java/bjc/funcdata/ListEx.java index 0ff8e30..164a71d 100644 --- a/src/main/java/bjc/funcdata/IList.java +++ b/src/main/java/bjc/funcdata/ListEx.java @@ -9,7 +9,7 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; -import bjc.data.IPair; +import bjc.data.Pair; import bjc.functypes.ID; /** @@ -20,7 +20,7 @@ import bjc.functypes.ID; * @param <ContainedType> * The type in this list */ -public interface IList<ContainedType> extends Iterable<ContainedType> { +public interface ListEx<ContainedType> extends Iterable<ContainedType> { /** * Add an item to this list. * @@ -40,7 +40,7 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * @return True if every item was successfully added to the list, false * otherwise. */ - default boolean addAll(final IList<ContainedType> items) { + default boolean addAll(final ListEx<ContainedType> items) { return items.map(this::add).anyMatch(bl -> bl == false); } @@ -136,7 +136,7 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * * @return A new list containing the merged pairs of lists. */ - <OtherType, CombinedType> IList<CombinedType> combineWith(IList<OtherType> list, + <OtherType, CombinedType> ListEx<CombinedType> combineWith(ListEx<OtherType> list, BiFunction<ContainedType, OtherType, CombinedType> combiner); /** @@ -191,8 +191,8 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * @return A new list containing the flattened results of applying the provided * function. */ - <MappedType> IList<MappedType> - flatMap(Function<ContainedType, IList<MappedType>> expander); + <MappedType> ListEx<MappedType> + flatMap(Function<ContainedType, ListEx<MappedType>> expander); /** * Apply a given action for each member of the list. @@ -230,7 +230,7 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * * @return A list containing all elements that match the predicate. */ - IList<ContainedType> getMatching(Predicate<ContainedType> predicate); + ListEx<ContainedType> getMatching(Predicate<ContainedType> predicate); /** * Retrieve the size of the wrapped list. @@ -259,7 +259,7 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * * @return A new list containing the mapped elements of this list. */ - <MappedType> IList<MappedType> map(Function<ContainedType, MappedType> transformer); + <MappedType> ListEx<MappedType> map(Function<ContainedType, MappedType> transformer); /** * Zip two lists into a list of pairs. @@ -272,7 +272,7 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * * @return A list containing pairs of this element and the specified list. */ - <OtherType> IList<IPair<ContainedType, OtherType>> pairWith(IList<OtherType> list); + <OtherType> ListEx<Pair<ContainedType, OtherType>> pairWith(ListEx<OtherType> list); /** * Partition this list into a list of sublists. @@ -285,7 +285,7 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * partition may not be completely full if the size of the list is not a * multiple of partitionSize. */ - IList<IList<ContainedType>> partition(int partitionSize); + ListEx<ListEx<ContainedType>> partition(int partitionSize); /** * Prepend an item to the list. @@ -429,7 +429,7 @@ public interface IList<ContainedType> extends Iterable<ContainedType> { * * @return The list without the first element. */ - IList<ContainedType> tail(); + ListEx<ContainedType> tail(); /** * Convert this list into an array. diff --git a/src/main/java/bjc/funcdata/IMap.java b/src/main/java/bjc/funcdata/MapEx.java index dc5ee00..a7af4c5 100644 --- a/src/main/java/bjc/funcdata/IMap.java +++ b/src/main/java/bjc/funcdata/MapEx.java @@ -1,8 +1,7 @@ package bjc.funcdata; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; +import java.util.*; +import java.util.function.*; /** * Functional wrapper over map providing some useful things. @@ -15,7 +14,7 @@ import java.util.function.Function; * @param <ValueType> * The type of this map's values. */ -public interface IMap<KeyType, ValueType> { +public interface MapEx<KeyType, ValueType> extends Freezable { /** * Execute an action for each entry in the map. * @@ -62,31 +61,7 @@ public interface IMap<KeyType, ValueType> { * * @return The value of the key. */ - ValueType get(KeyType key); - - /** - * Get a value from the map, and return a default value if the key doesn't - * exist. - * - * @param key - * The key to attempt to retrieve. - * - * @param defaultValue - * The value to return if the key doesn't exist. - * - * @return The value associated with the key, or the default value if the key - * doesn't exist. - */ - default ValueType getOrDefault(final KeyType key, final ValueType defaultValue) { - try { - return get(key); - } catch (final IllegalArgumentException iaex) { - /* - * We don't care about this, because it indicates a key is missing. - */ - return defaultValue; - } - } + Optional<ValueType> get(KeyType key); /** * Add an entry to the map. @@ -109,7 +84,9 @@ public interface IMap<KeyType, ValueType> { /** Delete all the values in the map. */ default void clear() { - keyList().forEach(key -> remove(key)); + if (isFrozen()) throw new ObjectFrozen("Can't clear a frozen map"); + + keyList().forEach(MapEx.this::remove); } /** @@ -121,11 +98,47 @@ public interface IMap<KeyType, ValueType> { return keyList().getSize(); } + /** + * Remove the value bound to the key. + * + * @param key + * The key to remove from the map. + * + * @return The previous value for the key in the map, or null if the key wasn't + * in the class. NOTE: Just because you received null, doesn't mean the + * map wasn't changed. It may mean that someone put a null value for + * that key into the map. + */ + ValueType remove(KeyType key); + + /** + * Get a list of all the keys in this map. + * + * @return A list of all the keys in this map. + */ + ListEx<KeyType> keyList(); + + /** + * Get a list of the values in this map. + * + * @return A list of values in this map. + */ + default ListEx<ValueType> valueList() { + final ListEx<ValueType> returns = new FunctionalList<>(); + + for (final KeyType key : keyList()) { + returns.add(get(key).orElse(null)); + } + + return returns; + } + /* * @NOTE Do we want this to be the semantics for transform, or do we want to go * to semantics using something like Isomorphism, or doing a one-time bulk * conversion of the values? */ + /** * Transform the values returned by this map. * @@ -141,7 +154,7 @@ public interface IMap<KeyType, ValueType> { * * @return The map where each value will be transformed after lookup. */ - default <V2> IMap<KeyType, V2> transform(final Function<ValueType, V2> transformer) { + default <V2> MapEx<KeyType, V2> transform(final Function<ValueType, V2> transformer) { return new TransformedValueMap<>(this, transformer); } @@ -151,40 +164,53 @@ public interface IMap<KeyType, ValueType> { * * @return An extended map. */ - IMap<KeyType, ValueType> extend(); + default MapEx<KeyType, ValueType> extend() { + return extend(new FunctionalMap<>()); + }; + /** - * Remove the value bound to the key. - * - * @param key - * The key to remove from the map. - * - * @return The previous value for the key in the map, or null if the key wasn't - * in the class. NOTE: Just because you received null, doesn't mean the - * map wasn't changed. It may mean that someone put a null value for - * that key into the map. + * Extend this map, creating a new map that will delegate queries to + * the current map but store any added values in the provided map. + * + * @param backer The map to store values in. + * + * @return An extended map, with the specified backing map. */ - ValueType remove(KeyType key); - + default MapEx<KeyType, ValueType> extend(MapEx<KeyType, ValueType> backer) { + return new ExtendedMap<>(this, backer); + }; + /** - * Get a list of all the keys in this map. - * - * @return A list of all the keys in this map. + * Static method to create a basic instance of IMap. + * + * @param <KeyType2> The key type of the map. + * @param <ValueType2> The value type of the map. + * + * @param args A series of key-value pairs. You will get an error if you don't + * provide the correct number of arguments (a number divisible by 2); + * however, if you pass arguments of the wrong type, you will not + * get a compile error, and usually won't get a runtime error in + * construction. However, you will get a ClassCastException as soon + * as you do something that attempts to iterate over the keys or values + * of the map. + * + * @return A map, constructed from the provided values. + * + * @throws IllegalArgumentException If you provide an incomplete pair of arguments. */ - IList<KeyType> keyList(); - - /** - * Get a list of the values in this map. - * - * @return A list of values in this map. - */ - default IList<ValueType> valueList() { - final IList<ValueType> returns = new FunctionalList<>(); - - for (final KeyType key : keyList()) { - returns.add(get(key)); + @SuppressWarnings("unchecked") + static <KeyType2, ValueType2> MapEx<KeyType2, ValueType2> of(Object... args) { + if (args.length % 2 != 0) throw new IllegalArgumentException("Args must be in the form of key-value pairs"); + + MapEx<KeyType2, ValueType2> map = new FunctionalMap<>(); + for (int index = 0; index < args.length; index += 2) { + KeyType2 key = (KeyType2) args[index]; + ValueType2 value = (ValueType2) args[index + 1]; + + map.put(key, value); } - - return returns; + + return map; } } diff --git a/src/main/java/bjc/funcdata/ObjectFrozen.java b/src/main/java/bjc/funcdata/ObjectFrozen.java new file mode 100644 index 0000000..a9f58b8 --- /dev/null +++ b/src/main/java/bjc/funcdata/ObjectFrozen.java @@ -0,0 +1,47 @@ +package bjc.funcdata; + +/** + * Exception that implementations of {@link Freezable} can throw if you attempt + * to modify a frozen object. + * + * @author Ben Culkin + * + */ +public class ObjectFrozen extends RuntimeException { + private static final long serialVersionUID = -1567447627139090728L; + + /** + * Create a new ObjectFrozen exception. + */ + public ObjectFrozen() { + super(); + } + + /** + * Create a new ObjectFrozen exception. + * + * @param message The message of the exception. + */ + public ObjectFrozen(String message) { + super(message); + } + + /** + * Create a new ObjectFrozen exception. + * + * @param cause The root cause of this exception. + */ + public ObjectFrozen(Throwable cause) { + super(cause); + } + + /** + * Create a new ObjectFrozen exception. + * + * @param message The message of the exception. + * @param cause The root cause of this exception. + */ + public ObjectFrozen(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/bjc/funcdata/TransformedValueMap.java b/src/main/java/bjc/funcdata/TransformedValueMap.java index 5de6fc3..643f610 100644 --- a/src/main/java/bjc/funcdata/TransformedValueMap.java +++ b/src/main/java/bjc/funcdata/TransformedValueMap.java @@ -1,8 +1,7 @@ package bjc.funcdata; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; +import java.util.*; +import java.util.function.*; /** * A map that transforms values from one type to another @@ -20,12 +19,15 @@ import java.util.function.Function; * */ final class TransformedValueMap<OldKey, OldValue, NewValue> - implements IMap<OldKey, NewValue> { + implements MapEx<OldKey, NewValue> { /* Our backing map. */ - private final IMap<OldKey, OldValue> backing; + private final MapEx<OldKey, OldValue> backing; /* Our transforming function. */ private final Function<OldValue, NewValue> transformer; + private boolean isFrozen = false; + private boolean thawEnabled = true; + /** * Create a new transformed-value loop. * @@ -35,7 +37,7 @@ final class TransformedValueMap<OldKey, OldValue, NewValue> * @param transform * The function to use for the transform. */ - public TransformedValueMap(final IMap<OldKey, OldValue> backingMap, + public TransformedValueMap(final MapEx<OldKey, OldValue> backingMap, final Function<OldValue, NewValue> transform) { backing = backingMap; transformer = transform; @@ -43,6 +45,8 @@ final class TransformedValueMap<OldKey, OldValue, NewValue> @Override public void clear() { + if (isFrozen) throw new ObjectFrozen("Can't clear frozen map"); + backing.clear(); } @@ -52,11 +56,6 @@ final class TransformedValueMap<OldKey, OldValue, NewValue> } @Override - public IMap<OldKey, NewValue> extend() { - return new ExtendedMap<>(this, new FunctionalMap<>()); - } - - @Override public void forEach(final BiConsumer<OldKey, NewValue> action) { backing.forEach((key, value) -> { action.accept(key, transformer.apply(value)); @@ -64,20 +63,8 @@ final class TransformedValueMap<OldKey, OldValue, NewValue> } @Override - public void forEachKey(final Consumer<OldKey> action) { - backing.forEachKey(action); - } - - @Override - public void forEachValue(final Consumer<NewValue> action) { - backing.forEachValue(value -> { - action.accept(transformer.apply(value)); - }); - } - - @Override - public NewValue get(final OldKey key) { - return transformer.apply(backing.get(key)); + public Optional<NewValue> get(final OldKey key) { + return backing.get(key).map(transformer); } @Override @@ -86,23 +73,19 @@ final class TransformedValueMap<OldKey, OldValue, NewValue> } @Override - public IList<OldKey> keyList() { + public ListEx<OldKey> keyList() { return backing.keyList(); } @Override - public <MappedValue> IMap<OldKey, MappedValue> - transform(final Function<NewValue, MappedValue> transform) { - return new TransformedValueMap<>(this, transform); - } - - @Override public NewValue put(final OldKey key, final NewValue value) { throw new UnsupportedOperationException("Can't add items to transformed map"); } @Override public NewValue remove(final OldKey key) { + if (isFrozen) throw new ObjectFrozen("Can't remove key " + key + " from frozen map"); + return transformer.apply(backing.remove(key)); } @@ -110,9 +93,43 @@ final class TransformedValueMap<OldKey, OldValue, NewValue> public String toString() { return backing.toString(); } + + @Override + public boolean freeze() { + isFrozen = true; + + return true; + } + + @Override + public boolean thaw() { + if (thawEnabled) { + isFrozen = false; + return true; + } else { + return false; + } + } @Override - public IList<NewValue> valueList() { - return backing.valueList().map(transformer); + public boolean deepFreeze() { + thawEnabled = false; + + return freeze(); + } + + @Override + public boolean canFreeze() { + return true; + } + + @Override + public boolean canThaw() { + return thawEnabled; + } + + @Override + public boolean isFrozen() { + return isFrozen; } -} +}
\ No newline at end of file diff --git a/src/main/java/bjc/funcdata/bst/BinarySearchTree.java b/src/main/java/bjc/funcdata/bst/BinarySearchTree.java index e22a8da..99b67cd 100644 --- a/src/main/java/bjc/funcdata/bst/BinarySearchTree.java +++ b/src/main/java/bjc/funcdata/bst/BinarySearchTree.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.function.Predicate; import bjc.funcdata.FunctionalList; -import bjc.funcdata.IList; +import bjc.funcdata.ListEx; /** * A binary search tree, with some mild support for functional traversal. @@ -24,7 +24,7 @@ public class BinarySearchTree<T> { private int elementCount; /* The root element of the tree */ - private ITreePart<T> root; + private TreePart<T> root; /** * Create a new tree using the specified way to compare elements. @@ -33,8 +33,7 @@ public class BinarySearchTree<T> { * The thing to use for comparing elements */ public BinarySearchTree(final Comparator<T> cmp) { - if (cmp == null) - throw new NullPointerException("Comparator must not be null"); + if (cmp == null) throw new NullPointerException("Comparator must not be null"); elementCount = 0; comparator = cmp; @@ -49,11 +48,8 @@ public class BinarySearchTree<T> { public void addNode(final T element) { elementCount++; - if (root == null) { - root = new BinarySearchTreeNode<>(element, null, null); - } else { - root.add(element, comparator); - } + if (root == null) root = new BinarySearchTreeNode<>(element, null, null); + else root.add(element, comparator); } /** @@ -70,7 +66,7 @@ public class BinarySearchTree<T> { * * @return Whether the adjusted pivot is with the list. */ - private boolean adjustedPivotInBounds(final IList<T> elements, final int pivot, + private boolean adjustedPivotInBounds(final ListEx<T> elements, final int pivot, final int pivotAdjustment) { return ((pivot - pivotAdjustment) >= 0) && ((pivot + pivotAdjustment) < elements.getSize()); @@ -82,7 +78,7 @@ public class BinarySearchTree<T> { * Takes O(N) time, but also O(N) space. */ public void balance() { - final IList<T> elements = new FunctionalList<>(); + final ListEx<T> elements = new FunctionalList<>(); /* Add each element to the list in sorted order. */ root.forEach(TreeLinearizationMethod.INORDER, element -> elements.add(element)); @@ -139,7 +135,7 @@ public class BinarySearchTree<T> { * * @return The root of the tree. */ - public ITreePart<T> getRoot() { + public TreePart<T> getRoot() { return root; } @@ -184,6 +180,7 @@ public class BinarySearchTree<T> { */ traverse(TreeLinearizationMethod.PREORDER, node -> { nodes.add(node); + return true; }); diff --git a/src/main/java/bjc/funcdata/bst/BinarySearchTreeLeaf.java b/src/main/java/bjc/funcdata/bst/BinarySearchTreeLeaf.java index 0b99cad..9532555 100644 --- a/src/main/java/bjc/funcdata/bst/BinarySearchTreeLeaf.java +++ b/src/main/java/bjc/funcdata/bst/BinarySearchTreeLeaf.java @@ -13,7 +13,7 @@ import java.util.function.Predicate; * @param <T> * The data stored in the tree. */ -public class BinarySearchTreeLeaf<T> implements ITreePart<T> { +public class BinarySearchTreeLeaf<T> implements TreePart<T> { /** The data held in this tree leaf */ protected T data; diff --git a/src/main/java/bjc/funcdata/bst/BinarySearchTreeNode.java b/src/main/java/bjc/funcdata/bst/BinarySearchTreeNode.java index a73f81a..0eef92a 100644 --- a/src/main/java/bjc/funcdata/bst/BinarySearchTreeNode.java +++ b/src/main/java/bjc/funcdata/bst/BinarySearchTreeNode.java @@ -20,10 +20,10 @@ import java.util.function.Predicate; */ public class BinarySearchTreeNode<T> extends BinarySearchTreeLeaf<T> { /* The left child of this node */ - private ITreePart<T> left; + private TreePart<T> left; /* The right child of this node */ - private ITreePart<T> right; + private TreePart<T> right; /** * Create a new node with the specified data and children. @@ -37,8 +37,8 @@ public class BinarySearchTreeNode<T> extends BinarySearchTreeLeaf<T> { * @param rght * The right child of this node. */ - public BinarySearchTreeNode(final T element, final ITreePart<T> lft, - final ITreePart<T> rght) { + public BinarySearchTreeNode(final T element, final TreePart<T> lft, + final TreePart<T> rght) { super(element); this.left = lft; this.right = rght; diff --git a/src/main/java/bjc/funcdata/bst/ITreePart.java b/src/main/java/bjc/funcdata/bst/TreePart.java index bac640d..b451463 100644 --- a/src/main/java/bjc/funcdata/bst/ITreePart.java +++ b/src/main/java/bjc/funcdata/bst/TreePart.java @@ -13,7 +13,7 @@ import java.util.function.Predicate; * @param <T> * The data contained in this part of the tree. */ -public interface ITreePart<T> { +public interface TreePart<T> { /** * Add a element below this tree part somewhere. * diff --git a/src/main/java/bjc/functypes/ArrayChooser.java b/src/main/java/bjc/functypes/ArrayChooser.java new file mode 100644 index 0000000..3baa00c --- /dev/null +++ b/src/main/java/bjc/functypes/ArrayChooser.java @@ -0,0 +1,20 @@ +package bjc.functypes; + +/** + * Function which picks a single element from an array of elements. + * + * @author Ben Culkin + * + * @param <ElementType> The type of element stored in the array. + */ +@FunctionalInterface +public interface ArrayChooser<ElementType> { + /** + * Select a single element from an array of elements. + * + * @param elements The elements to pick from. + * + * @return The selected element. + */ + public ElementType choose(@SuppressWarnings("unchecked") ElementType... elements); +} diff --git a/src/main/java/bjc/functypes/Combinators.java b/src/main/java/bjc/functypes/Combinators.java new file mode 100644 index 0000000..fde13df --- /dev/null +++ b/src/main/java/bjc/functypes/Combinators.java @@ -0,0 +1,297 @@ +package bjc.functypes; + +import static java.util.stream.Collectors.*; + +import java.util.*; +import java.util.function.*; + +import bjc.data.*; + +/** + * A bunch of only slightly related function combinators. + * + * @author Ben Culkin + * + */ +public class Combinators { + /** + * If-then-else combinator. + * + * @param <Input> The input type. + * @param <Output> The output type. + * + * @param in The predicate to run. + * @param ifTrue The condition to run when it is true. + * @param ifFalse The condition to run when it is false. + * + * @return A function which will invoke one or the other action, based on the predicate. + */ + @SuppressWarnings("unused") + public static <Input, Output> Function<Input, Output> iftt( + Predicate<Input> in, + Function<Input, Output> ifTrue, + Function<Input, Output> ifFalse) + { + return arg -> in.test(arg) ? ifTrue.apply(arg) : ifFalse.apply(arg); + } + + /** + * If-then-else expression. + * + * @param <Output> The output type. + * + * @param in The boolean to use. + * @param ifTrue The condition to run when it is true. + * @param ifFalse The condition to run when it is false. + * + * @return A value, based on the provided parameter + */ + public static <Output> Output iftt( + boolean in, + Supplier<Output> ifTrue, + Supplier<Output> ifFalse) + { + return in ? ifTrue.get() : ifFalse.get(); + } + + /** + * Execute an action before calling a function. + * + * @param <Input> The input to the function. + * @param <Output> The output from the function. + * + * @param action The action to run on the input. + * @param terminal The function to call. + * + * @return A function that runs the provided action before calling the function. + */ + public static <Input, Output> Function<Input, Output> beforeThis( + Consumer<Input> action, + Function<Input, Output> terminal) + { + return (arg) -> { + action.accept(arg); + return terminal.apply(arg); + }; + } + + /** + * Execute an action after calling a function. + * + * @param <Input> The input to the function. + * @param <Output> The output to the function. + * + * @param initial The function to call. + * @param action The action to call after doing the function. + * + * @return A function that calls the provided action after the function. + */ + public static <Input, Output> Consumer<Input> andThen( + Function<Input, Output> initial, + Consumer<Output> action) + { + return (arg) -> action.accept(initial.apply(arg)); + } + + /** + * Standalone function composer. + * + * @param <Left> The input type of the initial function. + * @param <Middle> The shared input/output type. + * @param <Right> The output type of the terminal function. + * + * @param initial The first function to call. + * @param terminal The second function to call. + * + * @return A function that composes the provided functions together. + */ + public static <Left, Middle, Right> Function<Left, Right> compose( + Function<Left, Middle> initial, + Function<Middle, Right> terminal) + { + return (arg) -> terminal.apply(initial.apply(arg)); + } + + /** + * Execute a function with some internal state. + * + * @param <Input> The input type of the function. + * @param <Output> The output type of the function. + * @param <State> The type of the internal state. + * + * @param source The function which provides internal state. + * @param action The function to call. + * + * @return A function which hides the production of the internal state. + */ + public static <Input, Output, State> Function<Input, Output> introducing( + Supplier<State> source, + BiFunction<State, Input, Output> action) + { + return (input) -> action.apply(source.get(), input); + } + + /** + * Invoke a given function with null to produce its result. + * + * @param <Input> The input type of the function. + * @param <Output> The output type of the function. + * + * @param action The function to invoke. + * + * @return The result of calling the function with null. + */ + public static <Input, Output> Output invoke(Function<Input, Output> action) + { + return action.apply(null); + } + + /** + * Execute an action a given number of times. + * + * @param times The number of times to execute the action. + * @param action The action to execute. + */ + public static void times(int times, Consumer<Integer> action) + { + for (int i = 0; i < times; i++) action.accept(i); + } + + /** + * Invoke a wrapper instead of invoking a normal function. + * + * @param <Input> The input type to the function. + * @param <Output> The output type to the function. + * + * @param function The function to apply. + * @param wrapper The wrapper around a function. + * + * @return A function that invokes the wrapper instead. + */ + public static <Input, Output> Function<Input, Output> around( + Function<Input, Output> function, + BiFunction<Input, Function<Input, Output>, Output> wrapper) + { + return (input) -> wrapper.apply(input, function); + } + + /** + * Only run a given function when the argument satisfies a condition. + * + * @param <Input> The input type of the function. + * @param <Output> The output type of the function. + * + * @param function The function to run. + * @param guard The guard to use for checking the input. + * + * @return A function which takes the given input, and only calls the + * function if the guard returns true. Otherwise, it will return + * an empty optional. + */ + public static <Input, Output> Function<Input, Optional<Output>> provided( + Function<Input, Output> function, + Predicate<Input> guard) + { + return iftt(guard, + (arg) -> Optional.ofNullable(function.apply(arg)), + (ignored) -> Optional.empty() + ); + } + + /** + * Concatenate two functions together, so that they run on the same argument. + * + * @param <Input> The type of the input. + * @param <Output1> The type of the first output. + * @param <Output2> The type of the second output. + * + * @param funcA The first function to call. + * @param funcB The second function to call. + * + * @return A function that returns a pair of the results of calling both + * functions. + */ + public static + <Input, Output1, Output2> Function<Input, Pair<Output1, Output2>> + concat(Function<Input, Output1> funcA, Function<Input, Output2> funcB) + { + return (arg) -> Pair.pair(funcA.apply(arg), funcB.apply(arg)); + } + + /** + * Concatenate a series of functions together, returning a list of their + * results. + * + * @param <Input> The input type for the functions. + * @param <Output> The output type for the functions. + * + * @param funcs The series of functions to call. + * + * @return A function that calls each of those functions, and returns a + * list of their results. + */ + @SafeVarargs + public static + <Input, Output> Function<Input, List<Output>> + concat(Function<Input, Output>... funcs) + { + List<Function<Input, Output>> funcList = Arrays.asList(funcs); + + // Kind of a nuisance that Java can't properly guess the type of + // our mapper function, but oh well. + + return (arg) -> + funcList.stream() + .map((Function<Function<Input, Output>, Output>) + (func) -> func.apply(arg)) + .collect(toList()); + } + + /** + * Return a function that does a series of actions upon a value, then returns + * that value. + * + * @param <Type> The type given as an argument + * + * @param consumers The actions to perform on the value. + * + * @return A function that performs those arguments on a value. + */ + @SafeVarargs + public static <Type> Function<Type, Type> doWith(Consumer<Type>... consumers) + { + return (arg) -> { + for (Consumer<Type> consumer : consumers) consumer.accept(arg); + return arg; + }; + } + + /** + * Perform a series of actions upon a value, then return that value. + * + * @param <Type> The type given as an argument + * + * @param input The value to use. + * @param consumers The actions to perform on the value. + * + * @return A function that performs those arguments on a value. + */ + @SafeVarargs + public static <Type> Type with(Type input, Consumer<Type>... consumers) + { + return doWith(consumers).apply(input); + } + + /** + * Convert a function into a consumer, ignoring its output. + * + * @param <Input> The input to the function. + * + * @param func The function to convert. + * + * @return A consumer which calls the function, and ignores the output. + */ + public static <Input> Consumer<Input> ignore(Function<Input, ?> func) { + return (inp) -> func.apply(inp); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/functypes/Fixpoints.java b/src/main/java/bjc/functypes/Fixpoints.java new file mode 100644 index 0000000..6d5a250 --- /dev/null +++ b/src/main/java/bjc/functypes/Fixpoints.java @@ -0,0 +1,58 @@ +package bjc.functypes; + +import java.util.*; +import java.util.function.*; + +import bjc.data.*; + +/** + * Function combinators to form 'fixpoints' or recursive functions. + * + * @author Ben Culkin + * + */ +public interface Fixpoints { + /** + * Convert a two-argument function to a single argument recursive one. + * + * @param <InputType> The input type for the function. + * @param <ReturnType> The output type for the function. + * + * @param func The function to make recursive. + * + * @return The newly recursive function. + */ + static <InputType, ReturnType> Function<InputType, ReturnType> fix( + BiFunction<InputType, Function<InputType, ReturnType>, ReturnType> func) { + Holder<Function<InputType, ReturnType>> inner = Holder.of(null); + inner.replace((arg) -> { + return func.apply(arg, inner.getValue()); + }); + return inner.getValue(); + } + + /** + * Convert a two-argument function to a single argument recursive memoized one. + * + * @param <InputType> The input type for the function. + * @param <ReturnType> The output type for the function. + * + * @param func The function to make recursive. + * + * @return The newly recursive memoized function. + */ + static <InputType, ReturnType> Function<InputType, ReturnType> memoFix( + BiFunction<InputType, Function<InputType, ReturnType>, ReturnType> func) { + Map<InputType, ReturnType> memoMap = new HashMap<>(); + + Holder<Function<InputType, ReturnType>> inner = Holder.of(null); + inner.replace((arg) -> + memoMap.computeIfAbsent( + arg, + (newArg) -> func.apply(newArg, inner.getValue()) + ) + ); + + return inner.getValue(); + } +} diff --git a/src/main/java/bjc/functypes/FunctionalIsomorphism.java b/src/main/java/bjc/functypes/FunctionalIsomorphism.java new file mode 100644 index 0000000..8518338 --- /dev/null +++ b/src/main/java/bjc/functypes/FunctionalIsomorphism.java @@ -0,0 +1,50 @@ +package bjc.functypes; + +import java.util.function.Function; + +/** + * A pair of functions to transform between a pair of types. + * + * @author bjculkin + * + * @param <Source> + * The source type of the isomorphism. + * + * @param <Dest> + * The destination type of isomorphism. + */ +public class FunctionalIsomorphism<Source, Dest> implements Isomorphism<Source, Dest> +{ + /* The function to the destination type. */ + private Function<Source, Dest> toFunc; + /* The function to the source type. */ + private Function<Dest, Source> 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 FunctionalIsomorphism(Function<Source, Dest> to, + Function<Dest, Source> from) + { + toFunc = to; + fromFunc = from; + } + + @Override + public Dest to(Source val) + { + return toFunc.apply(val); + } + + @Override + public Source from(Dest val) + { + return fromFunc.apply(val); + } +} diff --git a/src/main/java/bjc/functypes/ID.java b/src/main/java/bjc/functypes/ID.java index f830d66..53a4c84 100644 --- a/src/main/java/bjc/functypes/ID.java +++ b/src/main/java/bjc/functypes/ID.java @@ -10,6 +10,8 @@ import java.util.function.UnaryOperator; public class ID { /** * Create an identity function. + * + * @param <A> The type of the function. * * @return A identity function. */ diff --git a/src/main/java/bjc/functypes/IntArrayChooser.java b/src/main/java/bjc/functypes/IntArrayChooser.java new file mode 100644 index 0000000..416487e --- /dev/null +++ b/src/main/java/bjc/functypes/IntArrayChooser.java @@ -0,0 +1,19 @@ +package bjc.functypes; + +/** + * Int-specialized variant of ArrayChooser. + * + * @author Ben Culkin + * + */ +@FunctionalInterface +public interface IntArrayChooser { + /** + * Choose a single int from an array of ints. + * + * @param ints The array of ints to choose. + * + * @return The chosen int. + */ + public int choose(int... ints); +} diff --git a/src/main/java/bjc/functypes/Isomorphism.java b/src/main/java/bjc/functypes/Isomorphism.java new file mode 100644 index 0000000..db633e6 --- /dev/null +++ b/src/main/java/bjc/functypes/Isomorphism.java @@ -0,0 +1,54 @@ +package bjc.functypes; + +import java.util.function.*; + +/** + * Denotes that two types are essentially equivalent, by providing a means to + * convert between them. + * + * @author Ben Culkin + * + * @param <Source> The first type. + * @param <Dest> The second type. + */ +public interface Isomorphism<Source, Dest> +{ + + /** + * Apply the isomorphism forward. + * + * @param val + * The source value. + * + * @return The destination value. + */ + Dest to(Source val); + + /** + * Apply the isomorphism backward. + * + * @param val + * The destination value. + * + * @return The source value. + */ + Source from(Dest val); + + /** + * Create an isomorphism from a pair of functions. + * + * @param <Src> The source type. + * @param <Dst> The destination type. + * + * @param forward The function from source to destination. + * @param backward The function from destination to source. + * + * @return An isomorphism between the two types. + */ + static <Src, Dst> Isomorphism<Src, Dst> from( + Function<Src, Dst> forward, + Function<Dst, Src> backward) + { + return new FunctionalIsomorphism<>(forward, backward); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/functypes/ListFlattener.java b/src/main/java/bjc/functypes/ListFlattener.java index 3768b84..91abe9d 100644 --- a/src/main/java/bjc/functypes/ListFlattener.java +++ b/src/main/java/bjc/functypes/ListFlattener.java @@ -2,7 +2,7 @@ package bjc.functypes; import java.util.function.Function; -import bjc.funcdata.IList; +import bjc.funcdata.ListEx; /** * A function that flattens a list. @@ -12,7 +12,7 @@ import bjc.funcdata.IList; * @param <S> * The type of value in the list. */ -public interface ListFlattener<S> extends Function<IList<S>, S> { +public interface ListFlattener<S> extends Function<ListEx<S>, S> { /* * Alias */ diff --git a/src/main/java/bjc/functypes/ThrowFunction.java b/src/main/java/bjc/functypes/ThrowFunction.java new file mode 100644 index 0000000..3714cad --- /dev/null +++ b/src/main/java/bjc/functypes/ThrowFunction.java @@ -0,0 +1,184 @@ +package bjc.functypes; + +import java.util.*; +import java.util.function.*; + +/** + * An instance of {@link Function} that can throw an exception. + * + * @author Ben Culkin + * + * @param <InputType> The input to the function. + * @param <ReturnType> The output to the function. + * @param <ExType> The type of exception thrown. + */ +public interface ThrowFunction<InputType, ReturnType, ExType extends Throwable> +{ + /** + * Does the possibly throwing computation embodied by this function. + * + * @param arg The input to the function. + * + * @return The result of the function. + * + * @throws ExType If something went wrong with the function. + */ + public ReturnType apply(InputType arg) throws ExType; + + /** + * Converts this into a {@link Function} by handling any thrown exceptions, + * then mapping the return type to get a consistent return type. + * + * @param <NewReturn> The new return type. + * + * @param clasz The class of the handled exception. This needs to be provided + * because you can't catch a generic exception, and we want to + * make sure that we aren't catching any types of exception that + * we aren't supposed to. + * @param mapper The function which maps the normal return. + * @param handler The handler to use. + * + * @return A function which will either return its proper value, or the result + * of invoking the handler. + */ + @SuppressWarnings("unchecked") + default <NewReturn> Function<InputType, NewReturn> swallowMap( + Class<ExType> clasz, + Function<ReturnType, NewReturn> mapper, + Function<ExType, NewReturn> handler) + { + return (inp) -> { + try { + return mapper.apply(this.apply(inp)); + } catch (Throwable ex) { + if (clasz.isInstance(ex)) { + // Swallow this + return handler.apply((ExType) ex); + } else { + String msg = "Exception of incorrect type to be handled, only " + + clasz.getName() + + " are handled"; + + throw new RuntimeException(msg, ex); + } + } + }; + } + + /** + * Converts this into a {@link Function} by handling any thrown exceptions. + * + * @param clasz The class of the handled exception. This needs to be provided + * because you can't catch a generic exception, and we want to + * make sure that we aren't catching any types of exception that + * we aren't supposed to. + * + * @param handler The handler to use. + * + * @return A function which will either return its proper value, or the result + * of invoking the handler. + */ + default Function<InputType, ReturnType> swallow( + Class<ExType> clasz, Function<ExType, ReturnType> handler) + { + return swallowMap(clasz, (arg) -> arg, handler); + } + + /** + * Convert this to a function which will attempt to recover from the thrown exception. + * + * @param clasz The class of the exception. Needed for type-safety reasons. + * @param rescue The function to use to convert an exception into a safe input value. + * + * @return A function which attempts to recover from a exception. + */ + @SuppressWarnings("unchecked") + default Function<InputType, ReturnType> recover( + Class<ExType> clasz, BiFunction<InputType, ExType, InputType> rescue) + { + return Fixpoints.fix((arg, self) -> { + try { + return this.apply(arg); + } catch (Throwable ex) { + if (clasz.isInstance(ex)) { + // Swallow this + return self.apply(rescue.apply(arg, (ExType) ex)); + } else { + String msg = "Exception of incorrect type to be handled, only " + + clasz.getName() + + " are handled"; + + throw new RuntimeException(msg, ex); + } + } + }); + } + + /** + * Create a {@link Thrower} which will yield the result of calling this + * function with the given argument. + * + * @param arg The argument to use. + * + * @return A thrower which will call this function with the given value. + */ + default Thrower<ReturnType, ExType> suspend(InputType arg) + { + return () -> this.apply(arg); + } + + /** + * Compose two throwing functions together. + * + * @param <NewOutput> The newly output type. + * + * @param func The throwing function to compose this with. + * + * @return A throwing function that composes the two. + */ + default <NewOutput> ThrowFunction<InputType, NewOutput, ExType> + compose( + ThrowFunction<ReturnType, NewOutput, ExType> func) { + return (arg) -> func.apply(this.apply(arg)); + } + + /** Convert this function into one which will return an empty optional if an + * exception is thrown, returning an optional containing the value otherwise. + * + * Note that if this function returns a null value by itself, that will also + * yield an empty nullable. + * + * @param clasz The class of the exception. Needed because of type erasure, + * to ensure that we are catching the proper class. + * + * @return A function which returns an optional instead. + */ + default Function<InputType, Optional<ReturnType>> + makeTotal(Class<ExType> clasz) + { + return swallowMap(clasz, Optional::ofNullable, (ignored) -> Optional.empty()); + } + /** + * ThrowFunctions and functions which return a {@link Thrower} are isomorphic. + * + * @param <InpType> The function input type. + * @param <OutType> The function output type. + * @param <ExType> The exception type. + * + * @return The isomorphism between them. + */ + static <InpType, OutType, ExType extends Throwable> + Isomorphism<ThrowFunction<InpType, OutType, ExType>, Function<InpType, Thrower<OutType, ExType>>> + getIso() + { + // @FIXME Nov 23, 2020 Ben Culkin :EquivISO + // Fix this to strip wrappers when appropriate, so that going + // backwards and forwards leaves you where you started, not under + // two levels of indirection. + return Isomorphism.from((throwFun) -> { + return (arg) -> throwFun.suspend(arg); + }, (thrower) -> { + return (arg) -> thrower.apply(arg).extract(); + }); + } +} diff --git a/src/main/java/bjc/functypes/Thrower.java b/src/main/java/bjc/functypes/Thrower.java new file mode 100644 index 0000000..eb03eaf --- /dev/null +++ b/src/main/java/bjc/functypes/Thrower.java @@ -0,0 +1,165 @@ +package bjc.functypes; + +import java.util.*; +import java.util.function.*; + +/** + * A source for a value that could produce an exception. + * + * @author Ben Culkin + * + * @param <ValueType> The type of value possibly produced. + * @param <ExceptionType> The type of exception possibly thrown. + */ +@FunctionalInterface +public interface Thrower<ValueType, ExceptionType extends Throwable> +{ + /** + * Attempt to get the value. + * + * @return The value from this source. + * + * @throws ExceptionType If something goes wrong getting the value. + */ + ValueType extract() throws ExceptionType; + + /** + * Converts this thrower into a memoized one. + * + * Note that if this does throw an exception, it won't be 'memoized' so + * that the next call will call this Thrower again. + * + * @return A memoizing thrower. + */ + default Thrower<ValueType, ExceptionType> memoize() { + return new MemoizedThrower<>(this); + } + + /** + * Applies a function to the result of this thrower. + * + * @param <NewOutputType> The new type output by the function. + * + * @param func The function to apply. + * + * @return A thrower that is the result of applying the given function. + */ + default <NewOutputType> Thrower<NewOutputType, ExceptionType> bind( + Function<ValueType, Thrower<NewOutputType, ExceptionType>> func) + { + return () -> func.apply(extract()).extract(); + } + /** + * Create a thrower that yields a given value. + * + * @param <ValType> The type of the value. + * + * @param val The value to yield. + * + * @return A thrower that will always yield that value. + */ + static <ValType> + Thrower<ValType, ? extends Throwable> from(ValType val) + { + return () -> val; + } + + /** + * Create a thrower that yields a given value. + * + * @param <ValType> The type of the value. + * + * @param val The value to yield. + * + * @return A thrower that will always yield that value. + */ + static <ValType> + Thrower<ValType, ? extends Throwable> from(Supplier<ValType> val) + { + return val::get; + } + + /** + * Convert a function on values to one over throwers. + * + * @param <Input> The function input type. + * @param <Output> The function output type. + * @param <ExType> The exception possibly thrown. + * + * @param func The function to convert. + * + * @return A function which operates on throwers instead. + */ + static <Input, Output, ExType extends Throwable> + Function<Thrower<Input, ExType>,Thrower<Output, ExType>> fmap( + Function<Input, Output> func) + { + return (input) -> () -> func.apply(input.extract()); + } + + /** + * Convert a list of throwers into a thrower that returns a list. + * + * @param <Output> The type output by the thrower. + * @param <ExType> The type of exception thrown. + * + * @param throwers The list of throwers. + * + * @return A thrower that returns a list of results. + */ + static <Output, ExType extends Throwable> + Thrower<List<Output>, ExType> seq(List<Thrower<Output, ExType>> throwers) + { + return () -> { + List<Output> results = new ArrayList<>(throwers.size()); + for (Thrower<Output, ExType> thrower : throwers) + { + results.add(thrower.extract()); + } + return results; + }; + } + + /** + * Convert a array of throwers into a thrower that returns a list. + * + * @param <Output> The type output by the thrower. + * @param <ExType> The type of exception thrown. + * + * @param throwers The array of throwers. + * + * @return A thrower that returns a list of results. + */ + @SafeVarargs + static <Output, ExType extends Throwable> + Thrower<List<Output>, ExType> seq(Thrower<Output, ExType>... throwers) + { + return () -> { + List<Output> results = new ArrayList<>(throwers.length); + for (Thrower<Output, ExType> thrower : throwers) + { + results.add(thrower.extract()); + } + return results; + }; + } +} + +class MemoizedThrower<ValueType, ExceptionType extends Throwable> +implements Thrower<ValueType, ExceptionType> +{ + private final Thrower<ValueType, ExceptionType> source; + private ValueType val; + + public MemoizedThrower(Thrower<ValueType, ExceptionType> source) + { + this.source = source; + } + + @Override + public ValueType extract() throws ExceptionType + { + if (val == null) val = source.extract(); + return val; + } +} |
