From c82e3b3b2de0633317ec8fc85925e91422820597 Mon Sep 17 00:00:00 2001 From: "Benjamin J. Culkin" Date: Sun, 8 Oct 2017 22:39:59 -0300 Subject: Start splitting into maven modules --- .../main/java/bjc/utils/data/BooleanToggle.java | 76 ++++ .../main/java/bjc/utils/data/CircularIterator.java | 81 +++++ base/src/main/java/bjc/utils/data/Either.java | 173 +++++++++ .../java/bjc/utils/data/GeneratingIterator.java | 53 +++ base/src/main/java/bjc/utils/data/IHolder.java | 153 ++++++++ base/src/main/java/bjc/utils/data/IPair.java | 200 +++++++++++ base/src/main/java/bjc/utils/data/ITree.java | 234 +++++++++++++ base/src/main/java/bjc/utils/data/Identity.java | 118 +++++++ base/src/main/java/bjc/utils/data/Lazy.java | 194 ++++++++++ base/src/main/java/bjc/utils/data/LazyPair.java | 240 +++++++++++++ base/src/main/java/bjc/utils/data/ListHolder.java | 104 ++++++ base/src/main/java/bjc/utils/data/Option.java | 93 +++++ base/src/main/java/bjc/utils/data/Pair.java | 135 +++++++ .../main/java/bjc/utils/data/SingleIterator.java | 41 +++ .../main/java/bjc/utils/data/SingleSupplier.java | 72 ++++ base/src/main/java/bjc/utils/data/Toggle.java | 35 ++ .../bjc/utils/data/TopDownTransformIterator.java | 208 +++++++++++ .../bjc/utils/data/TopDownTransformResult.java | 34 ++ .../java/bjc/utils/data/TransformIterator.java | 46 +++ base/src/main/java/bjc/utils/data/Tree.java | 390 +++++++++++++++++++++ base/src/main/java/bjc/utils/data/ValueToggle.java | 54 +++ .../java/bjc/utils/data/internals/BoundLazy.java | 145 ++++++++ .../bjc/utils/data/internals/BoundLazyPair.java | 199 +++++++++++ .../bjc/utils/data/internals/BoundListHolder.java | 68 ++++ .../utils/data/internals/HalfBoundLazyPair.java | 149 ++++++++ .../java/bjc/utils/data/internals/WrappedLazy.java | 62 ++++ .../bjc/utils/data/internals/WrappedOption.java | 76 ++++ 27 files changed, 3433 insertions(+) create mode 100644 base/src/main/java/bjc/utils/data/BooleanToggle.java create mode 100644 base/src/main/java/bjc/utils/data/CircularIterator.java create mode 100644 base/src/main/java/bjc/utils/data/Either.java create mode 100644 base/src/main/java/bjc/utils/data/GeneratingIterator.java create mode 100644 base/src/main/java/bjc/utils/data/IHolder.java create mode 100644 base/src/main/java/bjc/utils/data/IPair.java create mode 100644 base/src/main/java/bjc/utils/data/ITree.java create mode 100644 base/src/main/java/bjc/utils/data/Identity.java create mode 100644 base/src/main/java/bjc/utils/data/Lazy.java create mode 100644 base/src/main/java/bjc/utils/data/LazyPair.java create mode 100644 base/src/main/java/bjc/utils/data/ListHolder.java create mode 100644 base/src/main/java/bjc/utils/data/Option.java create mode 100644 base/src/main/java/bjc/utils/data/Pair.java create mode 100644 base/src/main/java/bjc/utils/data/SingleIterator.java create mode 100644 base/src/main/java/bjc/utils/data/SingleSupplier.java create mode 100644 base/src/main/java/bjc/utils/data/Toggle.java create mode 100644 base/src/main/java/bjc/utils/data/TopDownTransformIterator.java create mode 100644 base/src/main/java/bjc/utils/data/TopDownTransformResult.java create mode 100644 base/src/main/java/bjc/utils/data/TransformIterator.java create mode 100644 base/src/main/java/bjc/utils/data/Tree.java create mode 100644 base/src/main/java/bjc/utils/data/ValueToggle.java create mode 100644 base/src/main/java/bjc/utils/data/internals/BoundLazy.java create mode 100644 base/src/main/java/bjc/utils/data/internals/BoundLazyPair.java create mode 100644 base/src/main/java/bjc/utils/data/internals/BoundListHolder.java create mode 100644 base/src/main/java/bjc/utils/data/internals/HalfBoundLazyPair.java create mode 100644 base/src/main/java/bjc/utils/data/internals/WrappedLazy.java create mode 100644 base/src/main/java/bjc/utils/data/internals/WrappedOption.java (limited to 'base/src/main/java/bjc/utils/data') diff --git a/base/src/main/java/bjc/utils/data/BooleanToggle.java b/base/src/main/java/bjc/utils/data/BooleanToggle.java new file mode 100644 index 0000000..12e3b2e --- /dev/null +++ b/base/src/main/java/bjc/utils/data/BooleanToggle.java @@ -0,0 +1,76 @@ +package bjc.utils.data; + +/** + * A simple {@link ValueToggle} that swaps between true and false. + * + * @author EVE + * + */ +public class BooleanToggle implements Toggle { + private boolean val; + + /** + * Create a new, initially false, flip-flop. + */ + public BooleanToggle() { + this(false); + } + + /** + * Create a flip-flop with the specified initial value. + * + * @param initial + * The initial value of the flip-flop. + */ + public BooleanToggle(final boolean initial) { + val = initial; + } + + @Override + public Boolean get() { + final boolean res = val; + + val = !res; + + return res; + } + + @Override + public Boolean peek() { + return val; + } + + @Override + public void set(final boolean vl) { + val = vl; + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + + result = prime * result + (val ? 1231 : 1237); + + return result; + } + + @Override + public boolean equals(final Object obj) { + 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; + } + + @Override + public String toString() { + return String.format("BooleanToggle [val=%s]", val); + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/CircularIterator.java b/base/src/main/java/bjc/utils/data/CircularIterator.java new file mode 100644 index 0000000..a708eba --- /dev/null +++ b/base/src/main/java/bjc/utils/data/CircularIterator.java @@ -0,0 +1,81 @@ +package bjc.utils.data; + +import java.util.Iterator; + +/** + * An iterator that repeats elements from a provided iterable. + * + * @author EVE + * + * @param + * The type of the iterable. + */ +public class CircularIterator implements Iterator { + /* + * The iterable, and our current iterator into it. + */ + private Iterable source; + private Iterator curr; + + /* + * Our current element. + */ + private E curElm; + + /* + * Should we actually get new iterators, or just repeat the last + * element? + */ + private boolean doCircle; + + /** + * Create a new circular iterator. + * + * @param src + * The iterable to iterate from. + * + * @param circ + * Should we actually do circular iteration, or just + * repeat the terminal element? + */ + public CircularIterator(final Iterable src, final boolean circ) { + source = src; + curr = source.iterator(); + + doCircle = circ; + } + + /** + * Create a new circular iterator that does actual circular iteration. + * + * @param src + * The iterable to iterate from. + */ + public CircularIterator(final Iterable src) { + this(src, true); + } + + @Override + public boolean hasNext() { + // We always have something + return true; + } + + @Override + public E next() { + if (!curr.hasNext()) { + if (doCircle) { + curr = source.iterator(); + } else return curElm; + } + + curElm = curr.next(); + + return curElm; + } + + @Override + public void remove() { + curr.remove(); + } +} diff --git a/base/src/main/java/bjc/utils/data/Either.java b/base/src/main/java/bjc/utils/data/Either.java new file mode 100644 index 0000000..36b3324 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/Either.java @@ -0,0 +1,173 @@ +package bjc.utils.data; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Represents a pair where only one side has a value + * + * @author ben + * @param + * The type that could be on the left + * @param + * The type that could be on the right + * + */ +public class Either implements IPair { + /** + * Create a new either with the left value occupied + * + * @param + * The type of the left value + * @param + * The type of the empty right value + * @param left + * The value to put on the left + * @return An either with the left side occupied + */ + public static Either left(final LeftType left) { + return new Either<>(left, null); + } + + /** + * Create a new either with the right value occupied + * + * @param + * The type of the empty left value + * @param + * The type of the right value + * @param right + * The value to put on the right + * @return An either with the right side occupied + */ + public static Either right(final RightType right) { + return new Either<>(null, right); + } + + private LeftType leftVal; + + private RightType rightVal; + + private boolean isLeft; + + private Either(final LeftType left, final RightType right) { + if (left == null) { + rightVal = right; + } else { + leftVal = left; + + isLeft = true; + } + } + + @Override + public IPair bind( + final BiFunction> binder) { + if (binder == null) throw new NullPointerException("Binder must not be null"); + + return binder.apply(leftVal, rightVal); + } + + @Override + public IPair bindLeft( + final Function> leftBinder) { + if (leftBinder == null) throw new NullPointerException("Left binder must not be null"); + + if (isLeft) return leftBinder.apply(leftVal); + + return new Either<>(null, rightVal); + } + + @Override + public IPair bindRight( + final Function> rightBinder) { + if (rightBinder == null) throw new NullPointerException("Right binder must not be null"); + + if (isLeft) return new Either<>(leftVal, null); + + return rightBinder.apply(rightVal); + } + + @Override + public IPair combine( + final IPair otherPair, + final BiFunction leftCombiner, + final BiFunction 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) -> { + return new Either<>(leftCombiner.apply(leftVal, otherLeft), null); + }); + + return otherPair.bind((otherLeft, otherRight) -> { + return new Either<>(null, rightCombiner.apply(rightVal, otherRight)); + }); + } + + @Override + public IPair mapLeft(final Function 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); + } + + @Override + public IPair mapRight(final Function mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + if (isLeft) return new Either<>(leftVal, null); + + return new Either<>(null, mapper.apply(rightVal)); + } + + @Override + public MergedType merge(final BiFunction merger) { + if (merger == null) throw new NullPointerException("Merger must not be null"); + + return merger.apply(leftVal, rightVal); + } + + @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; + } + + @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; + } + + @Override + public String toString() { + return String.format("Either [leftVal='%s', rightVal='%s', isLeft=%s]", leftVal, rightVal, isLeft); + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/GeneratingIterator.java b/base/src/main/java/bjc/utils/data/GeneratingIterator.java new file mode 100644 index 0000000..9abca7c --- /dev/null +++ b/base/src/main/java/bjc/utils/data/GeneratingIterator.java @@ -0,0 +1,53 @@ +package bjc.utils.data; + +import java.util.Iterator; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +/** + * An iterator that generates a series of elements from a single element. + * + * @author bjculkin + * + * @param + * The type of element generated. + */ +public class GeneratingIterator implements Iterator { + private E state; + + private UnaryOperator transtion; + + private Predicate stpper; + + /** + * Create a new generative iterator. + * + * @param initial + * The initial state of the generator. + * + * @param transition + * The function to apply to the state. + * + * @param stopper + * The predicate applied to the current state to + * determine when to stop. + */ + public GeneratingIterator(E initial, UnaryOperator transition, Predicate stopper) { + state = initial; + transtion = transition; + stpper = stopper; + } + + @Override + public boolean hasNext() { + return stpper.test(state); + } + + @Override + public E next() { + state = transtion.apply(state); + + return state; + } + +} diff --git a/base/src/main/java/bjc/utils/data/IHolder.java b/base/src/main/java/bjc/utils/data/IHolder.java new file mode 100644 index 0000000..ca0b2ba --- /dev/null +++ b/base/src/main/java/bjc/utils/data/IHolder.java @@ -0,0 +1,153 @@ +package bjc.utils.data; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import bjc.utils.data.internals.BoundListHolder; +import bjc.utils.data.internals.WrappedLazy; +import bjc.utils.data.internals.WrappedOption; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.theory.Functor; + +/** + * A holder of a single value. + * + * @author ben + * + * @param + * The type of value held + */ +public interface IHolder extends Functor { + /** + * Bind a function across the value in this container + * + * @param + * The type of value in this container + * @param binder + * The function to bind to the value + * @return A holder from binding the value + */ + public IHolder bind(Function> binder); + + /** + * Apply an action to the value + * + * @param action + * The action to apply to the value + */ + public default void doWith(final Consumer action) { + transform(value -> { + action.accept(value); + + return value; + }); + } + + @Override + default Function, Functor> fmap( + final Function func) { + return argumentFunctor -> { + if (!(argumentFunctor instanceof IHolder)) { + final String msg = "This functor only supports mapping over instances of IHolder"; + + throw new IllegalArgumentException(msg); + } + + final IHolder holder = (IHolder) argumentFunctor; + + return holder.map(func); + }; + } + + @Override + public default ContainedType getValue() { + return unwrap(value -> value); + } + + /** + * Lifts a function to bind over this holder + * + * @param + * The type of the functions return + * @param func + * The function to lift over the holder + * @return The function lifted over the holder + */ + public Function> lift(Function func); + + /** + * Make this holder lazy + * + * @return A lazy version of this holder + */ + public default IHolder makeLazy() { + return new WrappedLazy<>(this); + } + + /** + * Make this holder a list + * + * @return A list version of this holder + */ + public default IHolder makeList() { + return new BoundListHolder<>(new FunctionalList<>(this)); + } + + /** + * Make this holder optional + * + * @return An optional version of this holder + */ + public default IHolder makeOptional() { + return new WrappedOption<>(this); + } + + /** + * Create a new holder with a mapped version of the value in this + * holder. + * + * Does not change the internal state of this holder + * + * @param + * The type of the mapped value + * @param mapper + * The function to do mapping with + * @return A holder with the mapped value + */ + public IHolder map(Function mapper); + + /** + * Replace the held value with a new one + * + * @param newValue + * The value to hold instead + * @return The holder itself + */ + public default IHolder replace(final ContainedType newValue) { + return transform(oldValue -> { + return newValue; + }); + } + + /** + * Transform the value held in this holder + * + * @param transformer + * The function to transform the value with + * @return The holder itself, for easy chaining + */ + public IHolder transform(UnaryOperator transformer); + + /** + * Unwrap the value contained in this holder so that it is no longer + * held + * + * @param + * The type of the unwrapped value + * @param unwrapper + * The function to use to unwrap the value + * @return The unwrapped held value + */ + public UnwrappedType unwrap(Function unwrapper); +} diff --git a/base/src/main/java/bjc/utils/data/IPair.java b/base/src/main/java/bjc/utils/data/IPair.java new file mode 100644 index 0000000..db8a1cb --- /dev/null +++ b/base/src/main/java/bjc/utils/data/IPair.java @@ -0,0 +1,200 @@ +package bjc.utils.data; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +import bjc.utils.funcdata.theory.Bifunctor; + +/** + * Represents a pair of values + * + * @author ben + * @param + * The type of the left side of the pair + * @param + * The type of the right side of the pair + * + */ +public interface IPair extends Bifunctor { + /** + * Bind a function across the values in this pair + * + * @param + * The type of the bound left + * @param + * The type of the bound right + * @param binder + * The function to bind with + * @return The bound pair + */ + public IPair bind( + BiFunction> binder); + + /** + * Bind a function to the left value in this pair + * + * @param + * The type of the bound value + * @param leftBinder + * The function to use to bind + * @return A pair with the left type bound + */ + public IPair bindLeft( + Function> leftBinder); + + /** + * Bind a function to the right value in this pair + * + * @param + * The type of the bound value + * @param rightBinder + * The function to use to bind + * @return A pair with the right type bound + */ + public IPair bindRight( + Function> rightBinder); + + /** + * Pairwise combine two pairs together + * + * @param + * The left type of the other pair + * @param + * The right type of the other pair + * @param otherPair + * The pair to combine with + * @return The pairs, pairwise combined together + */ + public default IPair, IPair> combine( + final IPair otherPair) { + return combine(otherPair, Pair::new, Pair::new); + } + + /** + * Combine the contents of two pairs together + * + * @param + * The type of the left value of the other pair + * @param + * The type of the right value of the other pair + * @param + * The type of the left value of the combined pair + * @param + * The type of the right value of the combined pair + * @param otherPair + * The other pair to combine with + * @param leftCombiner + * @param rightCombiner + * @return A pair with its values combined + */ + public IPair combine( + IPair otherPair, + BiFunction leftCombiner, + BiFunction 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 consumer) { + merge((leftValue, rightValue) -> { + consumer.accept(leftValue, rightValue); + + return null; + }); + } + + @Override + default LeftBifunctorMap fmapLeft( + final Function 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 argPair = (IPair) argumentPair; + + return argPair.mapLeft(func); + }; + } + + @Override + default RightBifunctorMap + + fmapRight(final Function 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 argPair = (IPair) 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 + * 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 IPair mapLeft(Function mapper); + + /** + * Transform the value on the right side of the pair. Doesn't modify the + * pair + * + * @param + * 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 IPair mapRight(Function mapper); + + /** + * Merge the two values in this pair into a single value + * + * @param + * The type of the single value + * @param merger + * The function to use for merging + * @return The pair, merged into a single value + */ + public MergedType merge(BiFunction merger); +} diff --git a/base/src/main/java/bjc/utils/data/ITree.java b/base/src/main/java/bjc/utils/data/ITree.java new file mode 100644 index 0000000..ff374e8 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/ITree.java @@ -0,0 +1,234 @@ +package bjc.utils.data; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import bjc.utils.funcdata.bst.TreeLinearizationMethod; +import bjc.utils.functypes.ListFlattener; + +/** + * A node in a homogeneous tree with a unlimited amount of children. + * + * @author ben + * + * @param + * The type of data contained in the tree nodes. + * + */ +public interface ITree { + /** + * Append a child to this node. + * + * @param child + * The child to append to this node. + */ + void addChild(ITree child); + + /** + * Prepend a child to this node. + * + * @param child + * The child to prepend to this node. + */ + void prependChild(ITree child); + + /** + * Collapse a tree into a single version. + * + * @param + * The intermediate type being folded. + * + * @param + * 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. + */ + ReturnedType collapse(Function leafTransform, + Function> nodeCollapser, + Function resultTransformer); + + /** + * Execute a given action for each of this tree's children. + * + * @param action + * The action to execute for each child. + */ + void doForChildren(Consumer> 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 flatMapTree(final Function> mapper) { + return topDownTransform(dat -> TopDownTransformResult.PUSHDOWN, node -> { + if (node.getChildrenCount() > 0) { + final ITree 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 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 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 + * The type of the new tree. + * + * @param leafTransformer + * The function to use to transform leaf tokens. + * + * @param operatorTransformer + * The function to use to transform internal tokens. + * + * @return The tree, with the nodes changed. + */ + ITree rebuildTree(Function leafTransformer, + Function operatorTransformer); + + /** + * 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 nodePicker, UnaryOperator 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 topDownTransform(Function transformPicker, + UnaryOperator> transformer); + + /** + * Transform one of this nodes children. + * + * @param + * 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 transformChild(int childNo, + Function, TransformedType> transformer); + + /** + * Transform the value that is the head of this node. + * + * @param + * The type of the transformed value. + * + * @param transformer + * The function to use to transform the value. + * + * @return The transformed value. + */ + TransformedType transformHead(Function transformer); + + /** + * Transform the tree into a tree with a different type of token. + * + * @param + * The type of the new tree. + * + * @param transformer + * The function to use to transform tokens. + * + * @return A tree with the token types transformed. + */ + default ITree transformTree(final Function 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 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> childPred); +} diff --git a/base/src/main/java/bjc/utils/data/Identity.java b/base/src/main/java/bjc/utils/data/Identity.java new file mode 100644 index 0000000..a8c8d70 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/Identity.java @@ -0,0 +1,118 @@ +package bjc.utils.data; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +/** + * @author ben + * + * @param + */ +/** + * Simple implementation of IHolder that has no hidden behavior + * + * @author ben + * + * @param + * The type contained in the holder + */ +public class Identity implements IHolder { + private ContainedType heldValue; + + /** + * Create a holder holding null + */ + public Identity() { + heldValue = null; + } + + /** + * Create a holder holding the specified value + * + * @param value + * The value to hold + */ + public Identity(final ContainedType value) { + heldValue = value; + } + + @Override + public IHolder bind(final Function> binder) { + return binder.apply(heldValue); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + (heldValue == null ? 0 : heldValue.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + 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)) return false; + + return true; + } + + @Override + public Function> lift(final Function func) { + return (val) -> { + return new Identity<>(func.apply(val)); + }; + } + + @Override + public IHolder map(final Function mapper) { + return new Identity<>(mapper.apply(heldValue)); + } + + @Override + public String toString() { + return String.format("Identity [heldValue=%s]", heldValue); + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + heldValue = transformer.apply(heldValue); + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + return unwrapper.apply(heldValue); + } + + /** + * Create a new identity container. + * + * @param val + * The contained value. + * + * @return A new identity container. + */ + public static Identity id(final ContainedType val) { + return new Identity<>(val); + } + + /** + * Create a new empty identity container. + * + * @return A new empty identity container. + */ + public static Identity id() { + return new Identity<>(); + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/Lazy.java b/base/src/main/java/bjc/utils/data/Lazy.java new file mode 100644 index 0000000..ca41b62 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/Lazy.java @@ -0,0 +1,194 @@ +package bjc.utils.data; + +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +import bjc.utils.data.internals.BoundLazy; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; + +/** + * A holder that holds a means to create a value, but doesn't actually compute + * the value until it's needed + * + * @author ben + * + * @param + */ +public class Lazy implements IHolder { + private Supplier valueSupplier; + + private IList> actions = new FunctionalList<>(); + + private boolean valueMaterialized; + + private ContainedType heldValue; + + /** + * Create a new lazy value from the specified seed value + * + * @param value + * The seed value to use + */ + public Lazy(final ContainedType value) { + heldValue = value; + + valueMaterialized = true; + } + + /** + * Create a new lazy value from the specified value source + * + * @param supp + * The source of a value to use + */ + public Lazy(final Supplier supp) { + valueSupplier = new SingleSupplier<>(supp); + + valueMaterialized = false; + } + + private Lazy(final Supplier supp, final IList> pendingActions) { + valueSupplier = supp; + + actions = pendingActions; + } + + @Override + public IHolder bind(final Function> binder) { + final IList> pendingActions = new FunctionalList<>(); + + actions.forEach(pendingActions::add); + + final Supplier supplier = () -> { + if (valueMaterialized) return heldValue; + + return valueSupplier.get(); + }; + + return new BoundLazy<>(() -> { + return new Lazy<>(supplier, pendingActions); + }, binder); + } + + @Override + public Function> lift(final Function func) { + return val -> { + return new Lazy<>(func.apply(val)); + }; + } + + @Override + public IHolder map(final Function mapper) { + final IList> pendingActions = new FunctionalList<>(); + + actions.forEach(pendingActions::add); + + return new Lazy<>(() -> { + ContainedType currVal = heldValue; + + if (!valueMaterialized) { + currVal = valueSupplier.get(); + } + + return pendingActions.reduceAux(currVal, UnaryOperator::apply, + value -> mapper.apply(value)); + }); + } + + @Override + public String toString() { + if (valueMaterialized) { + if (actions.isEmpty()) + return String.format("value[v='%s']", heldValue); + else return String.format("value[v='%s'] (has pending transforms)", heldValue); + } + + return "(unmaterialized)"; + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + actions.add(transformer); + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + if (!valueMaterialized) { + heldValue = valueSupplier.get(); + + valueMaterialized = true; + } + + actions.forEach(action -> { + heldValue = action.apply(heldValue); + }); + + actions = new FunctionalList<>(); + + return unwrapper.apply(heldValue); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + (actions == null ? 0 : actions.hashCode()); + result = prime * result + (heldValue == null ? 0 : heldValue.hashCode()); + result = prime * result + (valueMaterialized ? 1231 : 1237); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Lazy)) return false; + + final Lazy other = (Lazy) obj; + + if (valueMaterialized != other.valueMaterialized) return false; + + if (valueMaterialized) { + if (heldValue == null) { + if (other.heldValue != null) return false; + } else if (!heldValue.equals(other.heldValue)) return false; + } else return false; + + if (actions == null) { + 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. + * + * @return A new lazy container holding that value. + */ + public static Lazy lazy(final ContainedType val) { + return new Lazy<>(val); + } + + /** + * Create a new lazy container with a suspended value. + * + * @param supp + * The suspended value for the lazy container. + * + * @return A new lazy container that will un-suspend the value when + * necessary. + */ + public static Lazy lazy(final Supplier supp) { + return new Lazy<>(supp); + } +} diff --git a/base/src/main/java/bjc/utils/data/LazyPair.java b/base/src/main/java/bjc/utils/data/LazyPair.java new file mode 100644 index 0000000..5cb85f3 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/LazyPair.java @@ -0,0 +1,240 @@ +package bjc.utils.data; + +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import bjc.utils.data.internals.BoundLazyPair; +import bjc.utils.data.internals.HalfBoundLazyPair; + +/** + * A lazy implementation of a pair + * + * @author ben + * + * @param + * The type on the left side of the pair + * @param + * The type on the right side of the pair + * + */ +public class LazyPair implements IPair { + private LeftType leftValue; + private RightType rightValue; + + private Supplier leftSupplier; + private Supplier rightSupplier; + + private boolean leftMaterialized; + private boolean rightMaterialized; + + /** + * Create a new lazy pair, using the set values + * + * @param leftVal + * The value for the left side of the pair + * @param rightVal + * The value for the right side of the pair + */ + public LazyPair(final LeftType leftVal, final RightType rightVal) { + leftValue = leftVal; + rightValue = rightVal; + + leftMaterialized = true; + rightMaterialized = true; + } + + /** + * Create a new lazy pair from the given value sources + * + * @param leftSupp + * The source for a value on the left side of the pair + * @param rightSupp + * The source for a value on the right side of the pair + */ + public LazyPair(final Supplier leftSupp, final Supplier rightSupp) { + // Use single suppliers to catch double-instantiation bugs + leftSupplier = new SingleSupplier<>(leftSupp); + rightSupplier = new SingleSupplier<>(rightSupp); + + leftMaterialized = false; + rightMaterialized = false; + } + + @Override + public IPair bind( + final BiFunction> binder) { + return new BoundLazyPair<>(leftSupplier, rightSupplier, binder); + } + + @Override + public IPair bindLeft( + final Function> leftBinder) { + final Supplier leftSupp = () -> { + if (leftMaterialized) return leftValue; + + return leftSupplier.get(); + }; + + return new HalfBoundLazyPair<>(leftSupp, leftBinder); + } + + @Override + public IPair bindRight( + final Function> rightBinder) { + final Supplier rightSupp = () -> { + if (rightMaterialized) return rightValue; + + return rightSupplier.get(); + }; + + return new HalfBoundLazyPair<>(rightSupp, rightBinder); + } + + @Override + public IPair combine( + final IPair otherPair, + final BiFunction leftCombiner, + final BiFunction rightCombiner) { + 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 + public LeftType getLeft() { + if (!leftMaterialized) { + leftValue = leftSupplier.get(); + + leftMaterialized = true; + } + + return leftValue; + } + + @Override + public RightType getRight() { + if (!rightMaterialized) { + rightValue = rightSupplier.get(); + + rightMaterialized = true; + } + + return rightValue; + } + + @Override + public IPair mapLeft(final Function mapper) { + final Supplier leftSupp = () -> { + if (leftMaterialized) return mapper.apply(leftValue); + + return mapper.apply(leftSupplier.get()); + }; + + final Supplier rightSupp = () -> { + if (rightMaterialized) return rightValue; + + return rightSupplier.get(); + }; + + return new LazyPair<>(leftSupp, rightSupp); + } + + @Override + public IPair mapRight(final Function mapper) { + final Supplier leftSupp = () -> { + if (leftMaterialized) return leftValue; + + return leftSupplier.get(); + }; + + final Supplier rightSupp = () -> { + if (rightMaterialized) return mapper.apply(rightValue); + + return mapper.apply(rightSupplier.get()); + }; + + return new LazyPair<>(leftSupp, rightSupp); + } + + @Override + public MergedType merge(final BiFunction merger) { + if (!leftMaterialized) { + leftValue = leftSupplier.get(); + + leftMaterialized = true; + } + + if (!rightMaterialized) { + rightValue = rightSupplier.get(); + + rightMaterialized = true; + } + + return merger.apply(leftValue, rightValue); + } + + @Override + public String toString() { + String leftVal; + String rightVal; + + if (leftMaterialized) { + leftVal = leftValue.toString(); + } else { + leftVal = "(un-materialized)"; + } + + if (rightMaterialized) { + rightVal = rightValue.toString(); + } else { + rightVal = "(un-materialized)"; + } + + return String.format("pair[l=%s,r=%s]", leftVal, rightVal); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + (leftMaterialized ? 1231 : 1237); + result = prime * result + (leftValue == null ? 0 : leftValue.hashCode()); + result = prime * result + (rightMaterialized ? 1231 : 1237); + 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 LazyPair)) return false; + + final LazyPair other = (LazyPair) obj; + + if (leftMaterialized != other.leftMaterialized) return false; + + if (leftMaterialized) { + if (leftValue == null) { + if (other.leftValue != null) return false; + } else if (!leftValue.equals(other.leftValue)) return false; + } else 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)) return false; + } else return false; + + return true; + } +} diff --git a/base/src/main/java/bjc/utils/data/ListHolder.java b/base/src/main/java/bjc/utils/data/ListHolder.java new file mode 100644 index 0000000..142057c --- /dev/null +++ b/base/src/main/java/bjc/utils/data/ListHolder.java @@ -0,0 +1,104 @@ +package bjc.utils.data; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import bjc.utils.data.internals.BoundListHolder; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; + +/** + * A holder that represents a set of non-deterministic computations + * + * @author ben + * + * @param + * The type of contained value + */ +public class ListHolder implements IHolder { + private IList heldValues; + + /** + * Create a new list holder + * + * @param values + * The possible values for the computation + */ + @SafeVarargs + public ListHolder(final ContainedType... values) { + heldValues = new FunctionalList<>(); + + if (values != null) { + for (final ContainedType containedValue : values) { + heldValues.add(containedValue); + } + } + } + + private ListHolder(final IList toHold) { + heldValues = toHold; + } + + @Override + public IHolder bind(final Function> binder) { + final IList> boundValues = heldValues.map(binder); + + return new BoundListHolder<>(boundValues); + } + + @Override + public Function> lift(final Function func) { + return val -> { + return new ListHolder<>(new FunctionalList<>(func.apply(val))); + }; + } + + @Override + public IHolder map(final Function mapper) { + final IList mappedValues = heldValues.map(mapper); + + return new ListHolder<>(mappedValues); + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + heldValues = heldValues.map(transformer); + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + return unwrapper.apply(heldValues.randItem()); + } + + @Override + public String toString() { + return String.format("ListHolder [heldValues=%s]", heldValues); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + (heldValues == null ? 0 : heldValues.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + 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)) return false; + + return true; + } +} diff --git a/base/src/main/java/bjc/utils/data/Option.java b/base/src/main/java/bjc/utils/data/Option.java new file mode 100644 index 0000000..37e0cde --- /dev/null +++ b/base/src/main/java/bjc/utils/data/Option.java @@ -0,0 +1,93 @@ +package bjc.utils.data; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +/** + * A holder that may or may not contain a value + * + * @author ben + * + * @param + * The type of the value that may or may not be held + */ +public class Option implements IHolder { + private ContainedType held; + + /** + * Create a new optional, using the given initial value + * + * @param seed + * The initial value for the optional + */ + public Option(final ContainedType seed) { + held = seed; + } + + @Override + public IHolder bind(final Function> binder) { + if (held == null) return new Option<>(null); + + return binder.apply(held); + } + + @Override + public Function> lift(final Function func) { + return val -> { + return new Option<>(func.apply(val)); + }; + } + + @Override + public IHolder map(final Function mapper) { + if (held == null) return new Option<>(null); + + return new Option<>(mapper.apply(held)); + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + if (held != null) { + held = transformer.apply(held); + } + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + if (held == null) return null; + + return unwrapper.apply(held); + } + + @Override + public String toString() { + return String.format("Option [held='%s']", held); + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (held == null ? 0 : held.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + 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)) return false; + + return true; + } +} diff --git a/base/src/main/java/bjc/utils/data/Pair.java b/base/src/main/java/bjc/utils/data/Pair.java new file mode 100644 index 0000000..e6796ba --- /dev/null +++ b/base/src/main/java/bjc/utils/data/Pair.java @@ -0,0 +1,135 @@ +package bjc.utils.data; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * A pair of values, with nothing special about them. + * + * @author ben + * + * @param + * The type of the left value + * @param + * The type of the right value + */ +public class Pair implements IPair { + // The left value + private LeftType leftValue; + + // The right value + private RightType rightValue; + + /** + * Create a new pair with both sides set to null + */ + public Pair() { + + } + + /** + * 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 Pair(final LeftType left, final RightType right) { + leftValue = left; + rightValue = right; + } + + @Override + public IPair bind( + final BiFunction> binder) { + if (binder == null) throw new NullPointerException("Binder must not be null."); + + return binder.apply(leftValue, rightValue); + } + + @Override + public IPair bindLeft( + final Function> leftBinder) { + if (leftBinder == null) throw new NullPointerException("Binder must not be null"); + + return leftBinder.apply(leftValue); + } + + @Override + public IPair bindRight( + final Function> rightBinder) { + if (rightBinder == null) throw new NullPointerException("Binder must not be null"); + + return rightBinder.apply(rightValue); + } + + @Override + public IPair combine( + final IPair otherPair, + final BiFunction leftCombiner, + final BiFunction 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); + }); + } + + @Override + public IPair mapLeft(final Function mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + return new Pair<>(mapper.apply(leftValue), rightValue); + } + + @Override + public IPair mapRight(final Function mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + return new Pair<>(leftValue, mapper.apply(rightValue)); + } + + @Override + public MergedType merge(final BiFunction 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 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 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; + } +} diff --git a/base/src/main/java/bjc/utils/data/SingleIterator.java b/base/src/main/java/bjc/utils/data/SingleIterator.java new file mode 100644 index 0000000..4069c3f --- /dev/null +++ b/base/src/main/java/bjc/utils/data/SingleIterator.java @@ -0,0 +1,41 @@ +package bjc.utils.data; + +import java.util.Iterator; + +/** + * An iterator that will only ever yield one item. + * + * @author EVE + * + * @param + * The type of the item. + */ +public class SingleIterator implements Iterator { + private final T itm; + + private boolean yielded; + + /** + * Create a iterator that yields a single item. + * + * @param item + * The item to yield. + */ + public SingleIterator(final T item) { + itm = item; + + yielded = false; + } + + @Override + public boolean hasNext() { + return !yielded; + } + + @Override + public T next() { + yielded = true; + + return itm; + } +} diff --git a/base/src/main/java/bjc/utils/data/SingleSupplier.java b/base/src/main/java/bjc/utils/data/SingleSupplier.java new file mode 100644 index 0000000..c675ebf --- /dev/null +++ b/base/src/main/java/bjc/utils/data/SingleSupplier.java @@ -0,0 +1,72 @@ +package bjc.utils.data; + +import java.util.function.Supplier; + +/** + * A supplier that can only supply one value. + * + * Attempting to retrieve another value will cause an exception to be thrown. + * + * @author ben + * + * @param + * The supplied type + */ +public class SingleSupplier implements Supplier { + private static long nextID = 0; + + private final Supplier source; + + private boolean gotten; + + private final long id; + + /* + * This is bad practice, but I want to know where the single + * instantiation was, in case of duplicate initiations. + */ + private Exception instSite; + + /** + * Create a new single supplier from an existing value + * + * @param supp + * The supplier to give a single value from + */ + public SingleSupplier(final Supplier supp) { + source = supp; + + gotten = false; + + id = nextID++; + } + + @Override + public T get() { + if (gotten == true) { + final String msg = String.format( + "Attempted to retrieve value more than once from single supplier #%d", id); + + final IllegalStateException isex = new IllegalStateException(msg); + + isex.initCause(instSite); + + throw isex; + } + + gotten = true; + + try { + throw new IllegalStateException("Previous instantiation here."); + } catch (final IllegalStateException isex) { + instSite = isex; + } + + return source.get(); + } + + @Override + public String toString() { + return String.format("SingleSupplier [source='%s', gotten=%s, id=%s]", source, gotten, id); + } +} diff --git a/base/src/main/java/bjc/utils/data/Toggle.java b/base/src/main/java/bjc/utils/data/Toggle.java new file mode 100644 index 0000000..1e10dae --- /dev/null +++ b/base/src/main/java/bjc/utils/data/Toggle.java @@ -0,0 +1,35 @@ +package bjc.utils.data; + +/** + * A stateful holder that swaps between two values of the same type. + * + * @author EVE + * + * @param + * The value stored in the toggle. + */ +public interface Toggle { + /** + * Retrieve the currently-aligned value of this toggle, and swap the + * alignment. + * + * @return The previously-aligned value. + */ + E get(); + + /** + * Retrieve the currently-aligned value without altering the alignment. + * + * @return The currently-aligned value. + */ + E peek(); + + /** + * Change the alignment of the toggle. + * + * @param isLeft + * Whether the toggle should be left-aligned or not. + */ + void set(boolean isLeft); + +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/TopDownTransformIterator.java b/base/src/main/java/bjc/utils/data/TopDownTransformIterator.java new file mode 100644 index 0000000..1b87e52 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/TopDownTransformIterator.java @@ -0,0 +1,208 @@ +package bjc.utils.data; + +import static bjc.utils.data.TopDownTransformResult.RTRANSFORM; + +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +/* + * FIXME something is broken in here. fix it. + */ +public class TopDownTransformIterator implements Iterator> { + private final Function picker; + private final BiFunction, Consumer>>, ITree> transform; + + private ITree preParent; + private ITree postParent; + + private final Deque> preChildren; + private final Deque> postChildren; + + private TopDownTransformIterator curChild; + + private boolean done; + private boolean initial; + + private final Deque>> toYield; + private Iterator> curYield; + + public TopDownTransformIterator(final Function pickr, + final BiFunction, Consumer>>, ITree> transfrm, + final ITree tree) { + preParent = tree; + + preChildren = new LinkedList<>(); + postChildren = new LinkedList<>(); + toYield = new LinkedList<>(); + + picker = pickr; + transform = transfrm; + + done = false; + initial = true; + } + + public void addYield(final Iterator> src) { + if (curYield != null) { + toYield.push(curYield); + } + + curYield = src; + } + + @Override + public boolean hasNext() { + return !done; + } + + public ITree flushYields(final ITree val) { + if (curYield != null) { + toYield.add(new SingleIterator<>(val)); + + if (curYield.hasNext()) + return curYield.next(); + else { + while (toYield.size() != 0 && !curYield.hasNext()) { + curYield = toYield.pop(); + } + + if (toYield.size() == 0 && !curYield.hasNext()) { + curYield = null; + return val; + } else return curYield.next(); + } + } else return val; + } + + @Override + public ITree next() { + if (done) throw new NoSuchElementException(); + + if (curYield != null) { + if (curYield.hasNext()) + return curYield.next(); + else { + while (toYield.size() != 0 && !curYield.hasNext()) { + curYield = toYield.pop(); + } + + if (toYield.size() == 0 && !curYield.hasNext()) { + curYield = null; + } else return curYield.next(); + } + } + + if (initial) { + final TopDownTransformResult res = picker.apply(preParent.getHead()); + + switch (res) { + case PASSTHROUGH: + postParent = new Tree<>(preParent.getHead()); + + if (preParent.getChildrenCount() != 0) { + for (int i = 0; i < preParent.getChildrenCount(); i++) { + preChildren.add(preParent.getChild(i)); + } + + // Return whatever the first child is + break; + } else { + done = true; + return flushYields(postParent); + } + case SKIP: + done = true; + return flushYields(preParent); + case TRANSFORM: + done = true; + return flushYields(transform.apply(preParent, this::addYield)); + case RTRANSFORM: + preParent = transform.apply(preParent, this::addYield); + return flushYields(preParent); + case PUSHDOWN: + if (preParent.getChildrenCount() != 0) { + for (int i = 0; i < preParent.getChildrenCount(); i++) { + preChildren.add(preParent.getChild(i)); + } + + // Return whatever the first child is + break; + } else { + done = true; + return flushYields(transform.apply(new Tree<>(preParent.getHead()), + this::addYield)); + } + case PULLUP: + final ITree intRes = transform.apply(preParent, this::addYield); + + postParent = new Tree<>(intRes.getHead()); + + if (intRes.getChildrenCount() != 0) { + for (int i = 0; i < intRes.getChildrenCount(); i++) { + preChildren.add(intRes.getChild(i)); + } + + // Return whatever the first child is + break; + } else { + done = true; + return flushYields(postParent); + } + default: + throw new IllegalArgumentException("Unknown result type " + res); + } + + if (res != RTRANSFORM) { + initial = false; + } + } + + if (curChild == null || !curChild.hasNext()) { + if (preChildren.size() != 0) { + curChild = new TopDownTransformIterator<>(picker, transform, preChildren.pop()); + + final ITree res = curChild.next(); + System.out.println("\t\tTRACE: adding node " + res + " to children"); + postChildren.add(res); + + return flushYields(res); + } else { + ITree res = null; + + if (postParent == null) { + res = new Tree<>(preParent.getHead()); + + System.out.println("\t\tTRACE: adding nodes " + postChildren + " to " + res); + + for (final ITree child : postChildren) { + res.addChild(child); + } + + // res = transform.apply(res, + // this::addYield); + } else { + res = postParent; + + System.out.println("\t\tTRACE: adding nodes " + postChildren + " to " + res); + for (final ITree child : postChildren) { + res.addChild(child); + } + } + + done = true; + return flushYields(res); + } + } else { + final ITree res = curChild.next(); + System.out.println("\t\tTRACE: adding node " + res + " to children"); + postChildren.add(res); + + return flushYields(res); + } + } +} diff --git a/base/src/main/java/bjc/utils/data/TopDownTransformResult.java b/base/src/main/java/bjc/utils/data/TopDownTransformResult.java new file mode 100644 index 0000000..ed41eae --- /dev/null +++ b/base/src/main/java/bjc/utils/data/TopDownTransformResult.java @@ -0,0 +1,34 @@ +package bjc.utils.data; + +/** + * Represents the results for doing a top-down transform of a tree + * + * @author ben + * + */ +public enum TopDownTransformResult { + /** + * Do not do anything to this node, and ignore its children + */ + SKIP, + /** + * Transform this node, and don't touch its children + */ + TRANSFORM, + /** + * Transform this node, then do a top-down transform on the result + */ + RTRANSFORM, + /** + * Ignore this node, and traverse its children + */ + PASSTHROUGH, + /** + * Traverse the nodes of this children, then transform it + */ + PUSHDOWN, + /** + * Transform this node, then traverse its children + */ + PULLUP; +} diff --git a/base/src/main/java/bjc/utils/data/TransformIterator.java b/base/src/main/java/bjc/utils/data/TransformIterator.java new file mode 100644 index 0000000..50f28b1 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/TransformIterator.java @@ -0,0 +1,46 @@ +package bjc.utils.data; + +import java.util.Iterator; +import java.util.function.Function; + +/** + * An iterator that transforms values from one type to another. + * + * @author EVE + * + * @param + * The source iterator type. + * + * @param + * The destination iterator type. + */ +public class TransformIterator implements Iterator { + private final Iterator source; + + private final Function transform; + + /** + * Create a new transform iterator. + * + * @param source + * The source iterator to use. + * + * @param transform + * The transform to apply. + */ + public TransformIterator(final Iterator source, final Function transform) { + this.source = source; + this.transform = transform; + } + + @Override + public boolean hasNext() { + return source.hasNext(); + } + + @Override + public D next() { + return transform.apply(source.next()); + } + +} diff --git a/base/src/main/java/bjc/utils/data/Tree.java b/base/src/main/java/bjc/utils/data/Tree.java new file mode 100644 index 0000000..a52f699 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/Tree.java @@ -0,0 +1,390 @@ +package bjc.utils.data; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.bst.TreeLinearizationMethod; +import bjc.utils.functypes.ListFlattener; + +/** + * A node in a homogeneous tree. + * + * @author ben + * + * @param + */ +public class Tree implements ITree { + private ContainedType data; + + private IList> children; + private boolean hasChildren; + private int childCount = 0; + + private int ID; + private static int nextID = 0; + + /** + * Create a new leaf node in a tree. + * + * @param leaf + * The data to store as a leaf node. + */ + public Tree(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 Tree(final ContainedType leaf, final IList> 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 Tree(final ContainedType leaf, final ITree... childrn) { + this(leaf); + + hasChildren = true; + + childCount = 0; + + children = new FunctionalList<>(); + + for (final ITree child : childrn) { + children.add(child); + + childCount++; + } + } + + @Override + public void addChild(final ITree child) { + if (hasChildren == false) { + hasChildren = true; + + children = new FunctionalList<>(); + } + + childCount++; + + children.add(child); + } + + @Override + public void prependChild(final ITree child) { + if (hasChildren == false) { + hasChildren = true; + + children = new FunctionalList<>(); + } + + childCount++; + + children.prepend(child); + } + + @Override + public void doForChildren(final Consumer> action) { + if (childCount > 0) { + children.forEach(action); + } + } + + @Override + public int getChildrenCount() { + return childCount; + } + + @Override + public int revFind(final Predicate> childPred) { + if (childCount == 0) + return -1; + else { + 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 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 ReturnedType collapse(final Function leafTransform, + final Function> nodeCollapser, + final Function resultTransformer) { + return resultTransformer.apply(internalCollapse(leafTransform, nodeCollapser)); + } + + @Override + public ITree flatMapTree(final Function> mapper) { + if (hasChildren) { + final ITree flatMappedData = mapper.apply(data); + + final IList> mappedChildren = children + .map(child -> child.flatMapTree(mapper)); + + mappedChildren.forEach(child -> flatMappedData.addChild(child)); + + return flatMappedData; + } + + return mapper.apply(data); + } + + protected NewType internalCollapse(final Function leafTransform, + final Function> nodeCollapser) { + if (hasChildren) { + final Function, NewType> nodeTransformer = nodeCollapser.apply(data); + + final IList collapsedChildren = children.map(child -> { + final NewType collapsed = child.collapse(leafTransform, nodeCollapser, + subTreeVal -> subTreeVal); + + return collapsed; + }); + + return nodeTransformer.apply(collapsedChildren); + } + + return leafTransform.apply(data); + } + + protected void internalToString(final StringBuilder builder, final int indentLevel, final boolean 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 kid = (Tree) child; + + kid.internalToString(builder, indentLevel + 1, false); + } else { + for (int i = 0; i < indentLevel + 1; i++) { + builder.append(">\t"); + } + + builder.append("Unknown node\n"); + } + }); + } + } + + @Override + public ITree rebuildTree(final Function leafTransformer, + final Function operatorTransformer) { + if (hasChildren) { + final IList> mappedChildren = children.map(child -> { + return child.rebuildTree(leafTransformer, operatorTransformer); + }); + + return new Tree<>(operatorTransformer.apply(data), mappedChildren); + } + + return new Tree<>(leafTransformer.apply(data)); + } + + @Override + public void selectiveTransform(final Predicate nodePicker, + final UnaryOperator transformer) { + if (hasChildren) { + children.forEach(child -> child.selectiveTransform(nodePicker, transformer)); + } else { + data = transformer.apply(data); + } + } + + @Override + public ITree topDownTransform( + final Function transformPicker, + final UnaryOperator> transformer) { + final TopDownTransformResult transformResult = transformPicker.apply(data); + + switch (transformResult) { + case PASSTHROUGH: + ITree result = new Tree<>(data); + + if (hasChildren) { + children.forEach(child -> { + final ITree 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 kid = child.topDownTransform(transformPicker, + transformer); + + result.addChild(kid); + }); + } + + return transformer.apply(result); + case PULLUP: + final ITree intermediateResult = transformer.apply(this); + + result = new Tree<>(intermediateResult.getHead()); + + intermediateResult.doForChildren(child -> { + final ITree kid = child.topDownTransform(transformPicker, transformer); + + result.addChild(kid); + }); + + return result; + default: + final String msg = String.format("Recieved unknown transform result %s", transformResult); + + throw new IllegalArgumentException(msg); + } + } + + @Override + public TransformedType transformChild(final int childNo, + final Function, TransformedType> transformer) { + if (childNo < 0 || childNo > childCount - 1) { + final String msg = String.format("Child index #%d is invalid", childNo); + + throw new IllegalArgumentException(msg); + } + + final ITree selectedKid = children.getByIndex(childNo); + + return transformer.apply(selectedKid); + } + + @Override + public TransformedType transformHead( + final Function transformer) { + return transformer.apply(data); + } + + @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); + + 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 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; + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/ValueToggle.java b/base/src/main/java/bjc/utils/data/ValueToggle.java new file mode 100644 index 0000000..9193896 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/ValueToggle.java @@ -0,0 +1,54 @@ +package bjc.utils.data; + +/** + * A simple implementation of {@link Toggle}. + * + * @author EVE + * + * @param + * The type of value to toggle between. + */ +public class ValueToggle implements Toggle { + private final E lft; + private final E rght; + + private final BooleanToggle alignment; + + /** + * Create a new toggle. + * + * All toggles start right-aligned. + * + * @param left + * The value when the toggle is left-aligned. + * + * @param right + * The value when the toggle is right-aligned. + */ + public ValueToggle(final E left, final E right) { + lft = left; + + rght = right; + + alignment = new BooleanToggle(); + } + + @Override + public E get() { + if (alignment.get()) + return lft; + else return rght; + } + + @Override + public E peek() { + if (alignment.peek()) + return lft; + else return rght; + } + + @Override + public void set(final boolean isLeft) { + alignment.set(isLeft); + } +} diff --git a/base/src/main/java/bjc/utils/data/internals/BoundLazy.java b/base/src/main/java/bjc/utils/data/internals/BoundLazy.java new file mode 100644 index 0000000..f71d32b --- /dev/null +++ b/base/src/main/java/bjc/utils/data/internals/BoundLazy.java @@ -0,0 +1,145 @@ +package bjc.utils.data.internals; + +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +import bjc.utils.data.IHolder; +import bjc.utils.data.Lazy; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; + +/* + * Implements a lazy holder that has been bound + */ +public class BoundLazy implements IHolder { + /* + * The old value + */ + private final Supplier> oldSupplier; + + /* + * The function to use to transform the old value into a new value + */ + private final Function> binder; + + /* + * The bound value being held + */ + private IHolder boundHolder; + + /* + * Whether the bound value has been actualized or not + */ + private boolean holderBound; + + /* + * Transformations currently pending on the bound value + */ + private final IList> actions = new FunctionalList<>(); + + /* + * Create a new bound lazy value + */ + public BoundLazy(final Supplier> supp, + final Function> binder) { + oldSupplier = supp; + this.binder = binder; + } + + @Override + public IHolder bind(final Function> bindr) { + if (bindr == null) throw new NullPointerException("Binder must not be null"); + + /* + * Prepare a list of pending actions + */ + final IList> pendingActions = new FunctionalList<>(); + actions.forEach(pendingActions::add); + + /* + * Create the new supplier of a value + */ + final Supplier> typeSupplier = () -> { + IHolder oldHolder = boundHolder; + + /* + * Bind the value if it hasn't been bound before + */ + if (!holderBound) { + oldHolder = oldSupplier.get().unwrap(binder); + } + + /* + * Apply all the pending actions + */ + return pendingActions.reduceAux(oldHolder, (action, state) -> { + return state.transform(action); + }, (value) -> value); + }; + + return new BoundLazy<>(typeSupplier, bindr); + } + + @Override + public Function> lift( + final Function func) { + if (func == null) throw new NullPointerException("Function to lift must not be null"); + + return (val) -> { + return new Lazy<>(func.apply(val)); + }; + } + + @Override + public IHolder map(final Function mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + // Prepare a list of pending actions + final IList> pendingActions = new FunctionalList<>(); + actions.forEach(pendingActions::add); + + // Prepare the new supplier + final Supplier typeSupplier = () -> { + IHolder oldHolder = boundHolder; + + // Bound the value if it hasn't been bound + if (!holderBound) { + oldHolder = oldSupplier.get().unwrap(binder); + } + + return pendingActions.reduceAux(oldHolder.getValue(), (action, state) -> { + return action.apply(state); + }, (value) -> mapper.apply(value)); + }; + + return new Lazy<>(typeSupplier); + } + + @Override + public String toString() { + if (holderBound) return boundHolder.toString(); + + return "(unmaterialized)"; + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + if (transformer == null) throw new NullPointerException("Transformer must not be null"); + + actions.add(transformer); + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + if (unwrapper == null) throw new NullPointerException("Unwrapper must not be null"); + + if (!holderBound) { + boundHolder = oldSupplier.get().unwrap(binder::apply); + } + + return boundHolder.unwrap(unwrapper); + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/internals/BoundLazyPair.java b/base/src/main/java/bjc/utils/data/internals/BoundLazyPair.java new file mode 100644 index 0000000..df6e60b --- /dev/null +++ b/base/src/main/java/bjc/utils/data/internals/BoundLazyPair.java @@ -0,0 +1,199 @@ +package bjc.utils.data.internals; + +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import bjc.utils.data.IHolder; +import bjc.utils.data.IPair; +import bjc.utils.data.Identity; +import bjc.utils.data.LazyPair; + +/* + * Implements a lazy pair that has been bound + */ +public class BoundLazyPair implements IPair { + /* + * The supplier of the left value + */ + private final Supplier leftSupplier; + /* + * The supplier of the right value + */ + private final Supplier rightSupplier; + + /* + * The binder to transform values + */ + private final BiFunction> binder; + + /* + * The bound pair + */ + private IPair boundPair; + + /* + * Whether the pair has been bound yet + */ + private boolean pairBound; + + public BoundLazyPair(final Supplier leftSupp, final Supplier rightSupp, + final BiFunction> bindr) { + leftSupplier = leftSupp; + rightSupplier = rightSupp; + binder = bindr; + } + + @Override + public IPair bind( + final BiFunction> bindr) { + if (bindr == null) throw new NullPointerException("Binder must not be null"); + + final IHolder> newPair = new Identity<>(boundPair); + final IHolder newPairMade = new Identity<>(pairBound); + + final Supplier leftSupp = () -> { + if (!newPairMade.getValue()) { + newPair.replace(binder.apply(leftSupplier.get(), rightSupplier.get())); + + newPairMade.replace(true); + } + + return newPair.unwrap((pair) -> pair.getLeft()); + }; + + final Supplier rightSupp = () -> { + if (!newPairMade.getValue()) { + newPair.replace(binder.apply(leftSupplier.get(), rightSupplier.get())); + + newPairMade.replace(true); + } + + return newPair.unwrap((pair) -> pair.getRight()); + }; + + return new BoundLazyPair<>(leftSupp, rightSupp, bindr); + } + + @Override + public IPair bindLeft( + final Function> leftBinder) { + if (leftBinder == null) throw new NullPointerException("Left binder must not be null"); + + final Supplier leftSupp = () -> { + IPair newPair = boundPair; + + if (!pairBound) { + newPair = binder.apply(leftSupplier.get(), rightSupplier.get()); + } + + return newPair.getLeft(); + }; + + return new HalfBoundLazyPair<>(leftSupp, leftBinder); + } + + @Override + public IPair bindRight( + final Function> rightBinder) { + if (rightBinder == null) throw new NullPointerException("Right binder must not be null"); + + final Supplier rightSupp = () -> { + IPair newPair = boundPair; + + if (!pairBound) { + newPair = binder.apply(leftSupplier.get(), rightSupplier.get()); + } + + return newPair.getRight(); + }; + + return new HalfBoundLazyPair<>(rightSupp, rightBinder); + } + + @Override + public IPair combine( + final IPair otherPair, + final BiFunction leftCombiner, + final BiFunction 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"); + + return otherPair.bind((otherLeft, otherRight) -> { + return bind((leftVal, rightVal) -> { + return new LazyPair<>(leftCombiner.apply(leftVal, otherLeft), + rightCombiner.apply(rightVal, otherRight)); + }); + }); + } + + @Override + public IPair mapLeft(final Function mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + final Supplier leftSupp = () -> { + if (!pairBound) { + final NewLeft leftVal = binder.apply(leftSupplier.get(), rightSupplier.get()).getLeft(); + + return mapper.apply(leftVal); + } + + return mapper.apply(boundPair.getLeft()); + }; + + final Supplier rightSupp = () -> { + if (!pairBound) return binder.apply(leftSupplier.get(), rightSupplier.get()).getRight(); + + return boundPair.getRight(); + }; + + return new LazyPair<>(leftSupp, rightSupp); + } + + @Override + public IPair mapRight(final Function mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + final Supplier leftSupp = () -> { + if (!pairBound) return binder.apply(leftSupplier.get(), rightSupplier.get()).getLeft(); + + return boundPair.getLeft(); + }; + + final Supplier rightSupp = () -> { + if (!pairBound) { + final NewRight rightVal = binder.apply(leftSupplier.get(), rightSupplier.get()) + .getRight(); + + return mapper.apply(rightVal); + } + + return mapper.apply(boundPair.getRight()); + }; + + return new LazyPair<>(leftSupp, rightSupp); + } + + @Override + public MergedType merge(final BiFunction merger) { + if (merger == null) throw new NullPointerException("Merger must not be null"); + + if (!pairBound) { + boundPair = binder.apply(leftSupplier.get(), rightSupplier.get()); + + pairBound = true; + } + + return boundPair.merge(merger); + } + + @Override + public String toString() { + if (pairBound) return boundPair.toString(); + + return "(un-materialized)"; + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/internals/BoundListHolder.java b/base/src/main/java/bjc/utils/data/internals/BoundListHolder.java new file mode 100644 index 0000000..f3799fd --- /dev/null +++ b/base/src/main/java/bjc/utils/data/internals/BoundListHolder.java @@ -0,0 +1,68 @@ +package bjc.utils.data.internals; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import bjc.utils.data.IHolder; +import bjc.utils.data.ListHolder; +import bjc.utils.funcdata.IList; + +/* + * Holds a list, converted into a holder + */ +public class BoundListHolder implements IHolder { + private final IList> heldHolders; + + public BoundListHolder(final IList> toHold) { + heldHolders = toHold; + } + + @Override + public IHolder bind(final Function> binder) { + if (binder == null) throw new NullPointerException("Binder must not be null"); + + final IList> boundHolders = heldHolders.map((containedHolder) -> { + return containedHolder.bind(binder); + }); + + return new BoundListHolder<>(boundHolders); + } + + @Override + public Function> lift(final Function func) { + if (func == null) throw new NullPointerException("Function to lift must not be null"); + + return (val) -> { + return new ListHolder<>(func.apply(val)); + }; + } + + @Override + public IHolder map(final Function mapper) { + if (mapper == null) throw new NullPointerException("Mapper must not be null"); + + final IList> mappedHolders = heldHolders.map((containedHolder) -> { + return containedHolder.map(mapper); + }); + + return new BoundListHolder<>(mappedHolders); + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + if (transformer == null) throw new NullPointerException("Transformer must not be null"); + + heldHolders.forEach((containedHolder) -> { + containedHolder.transform(transformer); + }); + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + if (unwrapper == null) throw new NullPointerException("Unwrapper must not be null"); + + return heldHolders.randItem().unwrap(unwrapper); + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/internals/HalfBoundLazyPair.java b/base/src/main/java/bjc/utils/data/internals/HalfBoundLazyPair.java new file mode 100644 index 0000000..8cac38b --- /dev/null +++ b/base/src/main/java/bjc/utils/data/internals/HalfBoundLazyPair.java @@ -0,0 +1,149 @@ +package bjc.utils.data.internals; + +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import bjc.utils.data.IHolder; +import bjc.utils.data.IPair; +import bjc.utils.data.Identity; +import bjc.utils.data.LazyPair; + +/* + * A lazy pair, with only one side bound + */ +public class HalfBoundLazyPair implements IPair { + private final Supplier oldSupplier; + + private final Function> binder; + + private IPair boundPair; + private boolean pairBound; + + public HalfBoundLazyPair(final Supplier oldSupp, + final Function> bindr) { + oldSupplier = oldSupp; + binder = bindr; + } + + @Override + public IPair bind( + final BiFunction> bindr) { + final IHolder> newPair = new Identity<>(boundPair); + final IHolder newPairMade = new Identity<>(pairBound); + + final Supplier leftSupp = () -> { + if (!newPairMade.getValue()) { + newPair.replace(binder.apply(oldSupplier.get())); + newPairMade.replace(true); + } + + return newPair.unwrap((pair) -> pair.getLeft()); + }; + + final Supplier rightSupp = () -> { + if (!newPairMade.getValue()) { + newPair.replace(binder.apply(oldSupplier.get())); + newPairMade.replace(true); + } + + return newPair.unwrap((pair) -> pair.getRight()); + }; + + return new BoundLazyPair<>(leftSupp, rightSupp, bindr); + } + + @Override + public IPair bindLeft( + final Function> leftBinder) { + final Supplier leftSupp = () -> { + IPair newPair = boundPair; + + if (!pairBound) { + newPair = binder.apply(oldSupplier.get()); + } + + return newPair.getLeft(); + }; + + return new HalfBoundLazyPair<>(leftSupp, leftBinder); + } + + @Override + public IPair bindRight( + final Function> rightBinder) { + final Supplier rightSupp = () -> { + IPair newPair = boundPair; + + if (!pairBound) { + newPair = binder.apply(oldSupplier.get()); + } + + return newPair.getRight(); + }; + + return new HalfBoundLazyPair<>(rightSupp, rightBinder); + } + + @Override + public IPair combine( + final IPair otherPair, + final BiFunction leftCombiner, + final BiFunction rightCombiner) { + return otherPair.bind((otherLeft, otherRight) -> { + return bind((leftVal, rightVal) -> { + return new LazyPair<>(leftCombiner.apply(leftVal, otherLeft), + rightCombiner.apply(rightVal, otherRight)); + }); + }); + } + + @Override + public IPair mapLeft(final Function mapper) { + final Supplier leftSupp = () -> { + if (pairBound) return mapper.apply(boundPair.getLeft()); + + final NewLeft leftVal = binder.apply(oldSupplier.get()).getLeft(); + + return mapper.apply(leftVal); + }; + + final Supplier rightSupp = () -> { + if (pairBound) return boundPair.getRight(); + + return binder.apply(oldSupplier.get()).getRight(); + }; + + return new LazyPair<>(leftSupp, rightSupp); + } + + @Override + public IPair mapRight(final Function mapper) { + final Supplier leftSupp = () -> { + if (pairBound) return boundPair.getLeft(); + + return binder.apply(oldSupplier.get()).getLeft(); + }; + + final Supplier rightSupp = () -> { + if (pairBound) return mapper.apply(boundPair.getRight()); + + final NewRight rightVal = binder.apply(oldSupplier.get()).getRight(); + + return mapper.apply(rightVal); + }; + + return new LazyPair<>(leftSupp, rightSupp); + } + + @Override + public MergedType merge(final BiFunction merger) { + if (!pairBound) { + boundPair = binder.apply(oldSupplier.get()); + + pairBound = true; + } + + return boundPair.merge(merger); + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/data/internals/WrappedLazy.java b/base/src/main/java/bjc/utils/data/internals/WrappedLazy.java new file mode 100644 index 0000000..4175724 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/internals/WrappedLazy.java @@ -0,0 +1,62 @@ +package bjc.utils.data.internals; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import bjc.utils.data.IHolder; +import bjc.utils.data.Lazy; + +public class WrappedLazy implements IHolder { + private final IHolder> held; + + public WrappedLazy(final IHolder wrappedHolder) { + held = new Lazy<>(wrappedHolder); + } + + // This has an extra parameter, because otherwise it erases to the same + // as the public one + private WrappedLazy(final IHolder> wrappedHolder, final boolean dummy) { + held = wrappedHolder; + } + + @Override + public IHolder bind(final Function> binder) { + final IHolder> newHolder = held.map((containedHolder) -> { + return containedHolder.bind(binder); + }); + + return new WrappedLazy<>(newHolder, false); + } + + @Override + public Function> lift(final Function func) { + return (val) -> { + return new Lazy<>(func.apply(val)); + }; + } + + @Override + public IHolder map(final Function mapper) { + final IHolder> newHolder = held.map((containedHolder) -> { + return containedHolder.map(mapper); + }); + + return new WrappedLazy<>(newHolder, false); + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + held.transform((containedHolder) -> { + return containedHolder.transform(transformer); + }); + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + return held.unwrap((containedHolder) -> { + return containedHolder.unwrap(unwrapper); + }); + } +} diff --git a/base/src/main/java/bjc/utils/data/internals/WrappedOption.java b/base/src/main/java/bjc/utils/data/internals/WrappedOption.java new file mode 100644 index 0000000..512c699 --- /dev/null +++ b/base/src/main/java/bjc/utils/data/internals/WrappedOption.java @@ -0,0 +1,76 @@ +package bjc.utils.data.internals; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import bjc.utils.data.IHolder; +import bjc.utils.data.Option; + +public class WrappedOption implements IHolder { + private final IHolder> held; + + public WrappedOption(final IHolder seedValue) { + held = new Option<>(seedValue); + } + + private WrappedOption(final IHolder> toHold, final boolean dummy) { + held = toHold; + } + + @Override + public IHolder bind(final Function> binder) { + final IHolder> newHolder = held.map((containedHolder) -> { + return containedHolder.bind((containedValue) -> { + if (containedValue == null) return new Option<>(null); + + return binder.apply(containedValue); + }); + }); + + return new WrappedOption<>(newHolder, false); + } + + @Override + public Function> lift(final Function func) { + return (val) -> { + return new Option<>(func.apply(val)); + }; + } + + @Override + public IHolder map(final Function mapper) { + final IHolder> newHolder = held.map((containedHolder) -> { + return containedHolder.map((containedValue) -> { + if (containedValue == null) return null; + + return mapper.apply(containedValue); + }); + }); + + return new WrappedOption<>(newHolder, false); + } + + @Override + public IHolder transform(final UnaryOperator transformer) { + held.transform((containedHolder) -> { + return containedHolder.transform((containedValue) -> { + if (containedValue == null) return null; + + return transformer.apply(containedValue); + }); + }); + + return this; + } + + @Override + public UnwrappedType unwrap(final Function unwrapper) { + return held.unwrap((containedHolder) -> { + return containedHolder.unwrap((containedValue) -> { + if (containedValue == null) return null; + + return unwrapper.apply(containedValue); + }); + }); + } +} -- cgit v1.2.3