fj.data.Zipper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of functionaljava Show documentation
Show all versions of functionaljava Show documentation
Functional Java is an open source library that supports closures for the Java programming language
package fj.data;
import fj.Equal;
import fj.F;
import fj.F2;
import fj.F2Functions;
import fj.F3;
import fj.Function;
import fj.Ord;
import fj.P;
import fj.P1;
import fj.P2;
import fj.P3;
import fj.Show;
import fj.function.Integers;
import java.util.Iterator;
import static fj.Function.compose;
import static fj.Function.curry;
import static fj.Function.flip;
import static fj.Function.join;
import static fj.Function.uncurryF2;
import static fj.data.Option.none;
import static fj.data.Option.some;
import static fj.data.Stream.nil;
import static fj.data.Stream.repeat;
/**
* Provides a pointed stream, which is a non-empty zipper-like stream structure that tracks an index (focus)
* position in a stream. Focus can be moved forward and backwards through the stream, elements can be inserted
* before or after the focused position, and the focused item can be deleted.
*
* Based on the pointedlist library by Jeff Wheeler.
*/
public final class Zipper implements Iterable> {
private final Stream left;
private final A focus;
private final Stream right;
private Zipper(final Stream left, final A focus, final Stream right) {
this.left = left;
this.focus = focus;
this.right = right;
}
/**
* Creates a new Zipper with the given streams before and after the focus, and the given focused item.
*
* @param left The stream of elements before the focus.
* @param focus The element under focus.
* @param right The stream of elements after the focus.
* @return a new Zipper with the given streams before and after the focus, and the given focused item.
*/
public static Zipper zipper(final Stream left, final A focus, final Stream right) {
return new Zipper<>(left, focus, right);
}
/**
* Creates a new Zipper from the given triple.
*
* @param p A triple of the elements before the focus, the focus element, and the elements after the focus,
* respectively.
* @return a new Zipper created from the given triple.
*/
public static Zipper zipper(final P3, A, Stream> p) {
return new Zipper<>(p._1(), p._2(), p._3());
}
/**
* First-class constructor of zippers.
*
* @return A function that yields a new zipper given streams on the left and right and a focus element.
*/
public static F3, A, Stream, Zipper> zipper() {
return Zipper::zipper;
}
/**
* Returns the product-3 representation of this Zipper.
*
* @return the product-3 representation of this Zipper.
*/
public P3, A, Stream> p() {
return P.p(left, focus, right);
}
/**
* A first-class function that yields the product-3 representation of a given Zipper.
*
* @return A first-class function that yields the product-3 representation of a given Zipper.
*/
public static F, P3, A, Stream>> p_() {
return Zipper::p;
}
/**
* An Ord instance for Zippers.
*
* @param o An Ord instance for the element type.
* @return An Ord instance for Zippers.
*/
public static Ord> ord(final Ord o) {
final Ord> so = Ord.streamOrd(o);
return Ord.p3Ord(so, o, so).contramap(Zipper.p_());
}
/**
* An Equal instance for Zippers.
*
* @param e An Equal instance for the element type.
* @return An Equal instance for Zippers.
*/
public static Equal> eq(final Equal e) {
final Equal> se = Equal.streamEqual(e);
return Equal.p3Equal(se, e, se).contramap(Zipper.p_());
}
/**
* A Show instance for Zippers.
*
* @param s A Show instance for the element type.
* @return A Show instance for Zippers.
*/
public static Show> show(final Show s) {
final Show> ss = Show.streamShow(s);
return Show.p3Show(ss, s, ss).contramap(Zipper.p_());
}
/**
* Maps the given function across the elements of this zipper (covariant functor pattern).
*
* @param f A function to map across this zipper.
* @return A new zipper with the given function applied to all elements.
*/
public Zipper map(final F f) {
return zipper(left.map(f), f.f(focus), right.map(f));
}
/**
* Performs a right-fold reduction across this zipper.
*
* @param f The function to apply on each element of this zipper.
* @param z The beginning value to start the application from.
* @return the final result after the right-fold reduction.
*/
public B foldRight(final F> f, final B z) {
return left.foldLeft(flip(f),
right.cons(focus).foldRight(compose(
Function., B, B>andThen().f(P1.__1()), f), z));
}
/**
* Creates a new zipper with a single element.
*
* @param a The focus element of the new zipper.
* @return a new zipper with a single element which is in focus.
*/
public static Zipper single(final A a) {
return zipper(Stream.nil(), a, Stream.nil());
}
/**
* Possibly create a zipper if the provided stream has at least one element, otherwise None.
* The provided stream's head will be the focus of the zipper, and the rest of the stream will follow
* on the right side.
*
* @param a The stream from which to create a zipper.
* @return a new zipper if the provided stream has at least one element, otherwise None.
*/
@SuppressWarnings("IfMayBeConditional")
public static Option> fromStream(final Stream a) {
if (a.isEmpty())
return none();
else
return some(zipper(Stream.nil(), a.head(), a.tail()._1()));
}
/**
* Possibly create a zipper if the provided stream has at least one element, otherwise None.
* The provided stream's last element will be the focus of the zipper, following the rest of the stream in order,
* to the left.
*
* @param a The stream from which to create a zipper.
* @return a new zipper if the provided stream has at least one element, otherwise None.
*/
public static Option> fromStreamEnd(final Stream a) {
if (a.isEmpty())
return none();
else {
final Stream xs = a.reverse();
return some(zipper(xs.tail()._1(), xs.head(), Stream.nil()));
}
}
/**
* Returns the focus element of this zipper.
*
* @return the focus element of this zipper.
*/
public A focus() {
return focus;
}
/**
* Possibly moves the focus to the next element in the list.
*
* @return An optional zipper with the focus moved one element to the right, if there are elements to the right of
* focus, otherwise None.
*/
public Option> next() {
return right.isEmpty() ? Option.none() : some(tryNext());
}
/**
* Attempts to move the focus to the next element, or throws an error if there are no more elements.
*
* @return A zipper with the focus moved one element to the right, if there are elements to the right of
* focus, otherwise throws an error.
*/
public Zipper tryNext() {
if (right.isEmpty())
throw new Error("Tried next at the end of a zipper.");
else
return zipper(left.cons(focus), right.head(), right.tail()._1());
}
/**
* Possibly moves the focus to the previous element in the list.
*
* @return An optional zipper with the focus moved one element to the left, if there are elements to the left of
* focus, otherwise None.
*/
public Option> previous() {
return left.isEmpty() ? Option.none() : some(tryPrevious());
}
/**
* Attempts to move the focus to the previous element, or throws an error if there are no more elements.
*
* @return A zipper with the focus moved one element to the left, if there are elements to the left of
* focus, otherwise throws an error.
*/
public Zipper tryPrevious() {
if (left.isEmpty())
throw new Error("Tried previous at the beginning of a zipper.");
else
return zipper(left.tail()._1(), left.head(), right.cons(focus));
}
/**
* First-class version of the next() function.
*
* @return A function that moves the given zipper's focus to the next element.
*/
public static F, Option>> next_() {
return Zipper::next;
}
/**
* First-class version of the previous() function.
*
* @return A function that moves the given zipper's focus to the previous element.
*/
public static F, Option>> previous_() {
return Zipper::previous;
}
/**
* Inserts an element to the left of the focus, then moves the focus to the new element.
*
* @param a A new element to insert into this zipper.
* @return A new zipper with the given element in focus, and the current focus element on its right.
*/
public Zipper insertLeft(final A a) {
return zipper(left, a, right.cons(focus));
}
/**
* Inserts an element to the right of the focus, then moves the focus to the new element.
*
* @param a A new element to insert into this zipper.
* @return A new zipper with the given element in focus, and the current focus element on its left.
*/
public Zipper insertRight(final A a) {
return zipper(left.cons(focus), a, right);
}
/**
* Possibly deletes the element at the focus, then moves the element on the left into focus.
* If no element is on the left, focus on the element to the right.
* Returns None if the focus element is the only element in this zipper.
*
* @return A new zipper with this zipper's focus element removed, or None if deleting the focus element
* would cause the zipper to be empty.
*/
public Option> deleteLeft() {
return left.isEmpty() && right.isEmpty()
? Option.none()
: some(zipper(left.isEmpty() ? left : left.tail()._1(),
left.isEmpty() ? right.head() : left.head(),
left.isEmpty() ? right.tail()._1() : right));
}
/**
* Possibly deletes the element at the focus, then moves the element on the right into focus.
* If no element is on the right, focus on the element to the left.
* Returns None if the focus element is the only element in this zipper.
*
* @return A new zipper with this zipper's focus element removed, or None if deleting the focus element
* would cause the zipper to be empty.
*/
public Option> deleteRight() {
return left.isEmpty() && right.isEmpty()
? Option.none()
: some(zipper(right.isEmpty() ? left.tail()._1() : left,
right.isEmpty() ? left.head() : right.head(),
right.isEmpty() ? right : right.tail()._1()));
}
/**
* Deletes all elements in the zipper except the focus.
*
* @return A new zipper with the focus element as the only element.
*/
public Zipper deleteOthers() {
final Stream nil = nil();
return zipper(nil, focus, nil);
}
/**
* Returns the length of this zipper.
*
* @return the length of this zipper.
*/
public int length() {
return foldRight(Function.constant(Integers.add.f(1)), 0);
}
/**
* Returns whether the focus is on the first element.
*
* @return true if the focus is on the first element, otherwise false.
*/
public boolean atStart() {
return left.isEmpty();
}
/**
* Returns whether the focus is on the last element.
*
* @return true if the focus is on the last element, otherwise false.
*/
public boolean atEnd() {
return right.isEmpty();
}
/**
* Creates a zipper of variations of this zipper, in which each element is focused,
* with this zipper as the focus of the zipper of zippers (comonad pattern).
*
* @return a zipper of variations of the provided zipper, in which each element is focused,
* with this zipper as the focus of the zipper of zippers.
*/
public Zipper> positions() {
final Stream> left = Stream.unfold(
p -> p.previous().map(join(P.p2())), this);
final Stream> right = Stream.unfold(
p -> p.next().map(join(P.p2())), this);
return zipper(left, this, right);
}
/**
* Maps over variations of this zipper, such that the given function is applied to each variation (comonad pattern).
*
* @param f The comonadic function to apply for each variation of this zipper.
* @return A new zipper, with the given function applied for each variation of this zipper.
*/
public Zipper cobind(final F, B> f) {
return positions().map(f);
}
/**
* Zips the elements of this zipper with a boolean that indicates whether that element has focus.
* All of the booleans will be false, except the focused element.
*
* @return A new zipper of pairs, with each element of this zipper paired with a boolean that is true if that
* element has focus, and false otherwise.
*/
public Zipper> zipWithFocus() {
return zipper(left.zip(repeat(false)), P.p(focus, true), right.zip(repeat(false)));
}
/**
* Move the focus to the specified index.
*
* @param n The index to which to move the focus.
* @return A new zipper with the focus moved to the specified index, or none if there is no such index.
*/
public Option> move(final int n) {
final int ll = left.length();
final int rl = right.length();
Option> p = some(this);
if (n < 0 || n >= length())
return none();
else if (ll >= n)
for (int i = ll - n; i > 0; i--)
p = p.bind(Zipper.previous_());
else if (rl >= n)
for (int i = rl - n; i > 0; i--)
p = p.bind(Zipper.next_());
return p;
}
/**
* A first-class version of the move function.
*
* @return A function that moves the focus of the given zipper to the given index.
*/
public static F, Option>>> move() {
return curry((i, a) -> a.move(i));
}
/**
* Moves the focus to the element matching the given predicate, if present.
*
* @param p A predicate to match.
* @return A new zipper with the nearest matching element focused if it is present in this zipper.
*/
public Option> find(final F p) {
if (p.f(focus()))
return some(this);
else {
final Zipper> ps = positions();
return ps.lefts().interleave(ps.rights()).find(zipper -> p.f(zipper.focus()));
}
}
/**
* Returns the index of the focus.
*
* @return the index of the focus.
*/
public int index() {
return left.length();
}
/**
* Move the focus to the next element. If the last element is focused, loop to the first element.
*
* @return A new zipper with the next element focused, unless the last element is currently focused, in which case
* the first element becomes focused.
*/
public Zipper cycleNext() {
if (left.isEmpty() && right.isEmpty())
return this;
else if (right.isEmpty()) {
final Stream xs = left.reverse();
return zipper(Stream.nil(), xs.head(), xs.tail()._1().snoc(P.p(focus)));
} else
return tryNext();
}
/**
* Move the focus to the previous element. If the first element is focused, loop to the last element.
*
* @return A new zipper with the previous element focused, unless the first element is currently focused,
* in which case the last element becomes focused.
*/
public Zipper cyclePrevious() {
if (left.isEmpty() && right.isEmpty())
return this;
else if (left.isEmpty()) {
final Stream xs = right.reverse();
return zipper(xs.tail()._1().snoc(P.p(focus)), xs.head(), Stream.nil());
} else
return tryPrevious();
}
/**
* Possibly deletes the element at the focus, then move the element on the left into focus. If no element is on the
* left, focus on the last element. If the deletion will cause the list to be empty, return None.
*
* @return A new zipper with the focused element removed, and focus on the previous element to the left, or the last
* element if there is no element to the left.
*/
public Option> deleteLeftCycle() {
if (left.isEmpty() && right.isEmpty())
return none();
else if (left.isNotEmpty())
return some(zipper(left.tail()._1(), left.head(), right));
else {
final Stream xs = right.reverse();
return some(zipper(xs.tail()._1(), xs.head(), Stream.nil()));
}
}
/**
* Possibly deletes the element at the focus, then move the element on the right into focus. If no element is on the
* right, focus on the first element. If the deletion will cause the list to be empty, return None.
*
* @return A new zipper with the focused element removed, and focus on the next element to the right, or the first
* element if there is no element to the right.
*/
public Option> deleteRightCycle() {
if (left.isEmpty() && right.isEmpty())
return none();
else if (right.isNotEmpty())
return some(zipper(left, right.head(), right.tail()._1()));
else {
final Stream xs = left.reverse();
return some(zipper(Stream.nil(), xs.head(), xs.tail()._1()));
}
}
/**
* Replaces the element in focus with the given element.
*
* @param a An element to replace the focused element with.
* @return A new zipper with the given element in focus.
*/
public Zipper replace(final A a) {
return zipper(left, a, right);
}
/**
* Returns the Stream representation of this zipper.
*
* @return A stream that contains all the elements of this zipper.
*/
public Stream toStream() {
return left.reverse().snoc(P.p(focus)).append(right);
}
/**
* Returns a Stream of the elements to the left of focus.
*
* @return a Stream of the elements to the left of focus.
*/
public Stream lefts() {
return left;
}
/**
* Returns a Stream of the elements to the right of focus.
*
* @return a Stream of the elements to the right of focus.
*/
public Stream rights() {
return right;
}
/**
* Zips this Zipper with another, applying the given function lock-step over both zippers in both directions.
* The structure of the resulting Zipper is the structural intersection of the two Zippers.
*
* @param bs A Zipper to zip this one with.
* @param f A function with which to zip together the two Zippers.
* @return The result of applying the given function over this Zipper and the given Zipper, location-wise.
*/
public Zipper zipWith(final Zipper bs, final F2 f) {
return F2Functions.zipZipperM(f).f(this, bs);
}
/**
* Zips this Zipper with another, applying the given function lock-step over both zippers in both directions.
* The structure of the resulting Zipper is the structural intersection of the two Zippers.
*
* @param bs A Zipper to zip this one with.
* @param f A function with which to zip together the two Zippers.
* @return The result of applying the given function over this Zipper and the given Zipper, location-wise.
*/
public Zipper zipWith(final Zipper bs, final F> f) {
return zipWith(bs, uncurryF2(f));
}
/**
* Returns an iterator of all the positions of this Zipper, starting from the leftmost position.
*
* @return An iterator of all the positions of this Zipper, starting from the leftmost position.
*/
public Iterator> iterator() { return positions().toStream().iterator(); }
}