All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fj.data.Zipper Maven / Gradle / Ivy

Go to download

Functional Java is an open source library that supports closures for the Java programming language

There is a newer version: 5.0
Show newest version
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 (l, a, r) -> zipper(l, a, r); } /** * 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 a -> a.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 as -> as.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 as -> as.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., Zipper>p2())), this); final Stream> right = Stream.unfold( p -> p.next().map(join(P., Zipper>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(); } }