package fj.data; import fj.F; import fj.F2; import fj.Function; import fj.Monoid; import fj.Ord; import fj.P; import fj.P2; import fj.P3; import static fj.Function.*; import static fj.data.Either.right; import static fj.data.Option.some; import static fj.function.Booleans.not; import fj.Ordering; import static fj.Ordering.GT; import static fj.Ordering.LT; import java.util.Iterator; /** * Provides an in-memory, immutable set, implemented as a red/black tree. */ public abstract class Set implements Iterable { private Set(final Ord ord) { this.ord = ord; } private enum Color { R, B } private final Ord ord; public final boolean isEmpty() { return this instanceof Empty; } @SuppressWarnings({"ClassEscapesDefinedScope"}) abstract Color color(); abstract Set l(); abstract A head(); abstract Set r(); /** * Returns the order of this Set. * * @return the order of this Set. */ public final Ord ord() { return ord; } private static final class Empty extends Set { private Empty(final Ord ord) { super(ord); } public Color color() { return Color.B; } public Set l() { throw new Error("Left on empty set."); } public Set r() { throw new Error("Right on empty set."); } public A head() { throw new Error("Head on empty set."); } } private static final class Tree extends Set { private final Color c; private final Set a; private final A x; private final Set b; private Tree(final Ord ord, final Color c, final Set a, final A x, final Set b) { super(ord); this.c = c; this.a = a; this.x = x; this.b = b; } public Color color() { return c; } public Set l() { return a; } public A head() { return x; } public Set r() { return b; } } /** * Updates, with the given function, the first element in the set that is equal to the given element, * according to the order. * * @param a An element to replace. * @param f A function to transforms the found element. * @return A pair of: (1) True if an element was found that matches the given element, otherwise false. * (2) A new set with the given function applied to the first set element * that was equal to the given element. */ public final P2> update(final A a, final F f) { return isEmpty() ? P.p(false, this) : tryUpdate(a, f).either(new F>>() { public P2> f(final A a2) { return P.p(true, delete(a).insert(a2)); } }, Function.>>identity()); } private Either>> tryUpdate(final A a, final F f) { if (isEmpty()) return right(P.p(false, this)); else if (ord.isLessThan(a, head())) return l().tryUpdate(a, f).right().map(new F>, P2>>() { public P2> f(final P2> set) { return set._1() ? P.p(true, (Set) new Tree(ord, color(), set._2(), head(), r())) : set; } }); else if (ord.eq(a, head())) { final A h = f.f(head()); return ord.eq(head(), h) ? Either .>>right(P.p(true, (Set) new Tree(ord, color(), l(), h, r()))) : Either.>>left(h); } else return r().tryUpdate(a, f).right().map(new F>, P2>>() { public P2> f(final P2> set) { return set._1() ? P.p(true, (Set) new Tree(ord, color(), l(), head(), set._2())) : set; } }); } /** * The empty set. * * @param ord An order for the type of elements. * @return the empty set. */ public static Set empty(final Ord ord) { return new Empty(ord); } /** * Checks if the given element is a member of this set. * * @param x An element to check for membership in this set. * @return true if the given element is a member of this set. */ public final boolean member(final A x) { return !isEmpty() && (ord.isLessThan(x, head()) && l().member(x) || ord.eq(head(), x) || r().member(x)); } /** * First-class membership check. * * @return A function that returns true if the given element if a member of the given set. */ public static F, F> member() { return curry(new F2, A, Boolean>() { public Boolean f(final Set s, final A a) { return s.member(a); } }); } /** * Inserts the given element into this set. * * @param x An element to insert into this set. * @return A new set with the given element inserted. */ public final Set insert(final A x) { return ins(x).makeBlack(); } /** * First-class insertion function. * * @return A function that inserts a given element into a given set. */ public static F, Set>> insert() { return curry(new F2, Set>() { public Set f(final A a, final Set set) { return set.insert(a); } }); } private Set ins(final A x) { return isEmpty() ? new Tree(ord, Color.R, empty(ord), x, empty(ord)) : ord.isLessThan(x, head()) ? balance(ord, color(), l().ins(x), head(), r()) : ord.eq(x, head()) ? new Tree(ord, color(), l(), x, r()) : balance(ord, color(), l(), head(), r().ins(x)); } private Set makeBlack() { return new Tree(ord, Color.B, l(), head(), r()); } @SuppressWarnings({"SuspiciousNameCombination"}) private static Tree tr(final Ord o, final Set a, final A x, final Set b, final A y, final Set c, final A z, final Set d) { return new Tree(o, Color.R, new Tree(o, Color.B, a, x, b), y, new Tree(o, Color.B, c, z, d)); } private static Set balance(final Ord ord, final Color c, final Set l, final A h, final Set r) { return c == Color.B && l.isTR() && l.l().isTR() ? tr(ord, l.l().l(), l.l().head(), l.l().r(), l.head(), l.r(), h, r) : c == Color.B && l.isTR() && l.r().isTR() ? tr(ord, l.l(), l.head(), l.r().l(), l.r().head(), l.r().r(), h, r) : c == Color.B && r.isTR() && r.l().isTR() ? tr(ord, l, h, r.l().l(), r.l().head(), r.l().r(), r.head(), r.r()) : c == Color.B && r.isTR() && r.r().isTR() ? tr(ord, l, h, r.l(), r.head(), r.r().l(), r.r().head(), r.r().r()) : new Tree(ord, c, l, h, r); } private boolean isTR() { return !isEmpty() && color() == Color.R; } /** * Returns an iterator over this set. * * @return an iterator over this set. */ public final Iterator iterator() { return toStream().iterator(); } /** * Returns a set with a single element. * * @param o An order for the type of element. * @param a An element to put in a set. * @return A new set with the given element in it. */ public static Set single(final Ord o, final A a) { return empty(o).insert(a); } /** * Maps the given function across this set. * * @param o An order for the elements of the new set. * @param f A function to map across this set. * @return The set of the results of applying the given function to the elements of this set. */ public final Set map(final Ord o, final F f) { return iterableSet(o, toStream().map(f)); } /** * Folds this Set using the given monoid. * * @param f A transformation from this Set's elements, to the monoid. * @param m The monoid to fold this Set with. * @return The result of folding the Set with the given monoid. */ public final B foldMap(final F f, final Monoid m) { return isEmpty() ? m.zero() : m.sum(m.sum(r().foldMap(f, m), f.f(head())), l().foldMap(f, m)); } /** * Returns a list representation of this set. * * @return a list representation of this set. */ public final List toList() { return foldMap(List.cons(List.nil()), Monoid.listMonoid()); } /** * Returns a stream representation of this set. * * @return a stream representation of this set. */ public final Stream toStream() { return foldMap(Stream.single(), Monoid.streamMonoid()); } /** * Binds the given function across this set. * * @param o An order for the elements of the target set. * @param f A function to bind across this set. * @return A new set after applying the given function and joining the resulting sets. */ public final Set bind(final Ord o, final F> f) { return join(o, map(Ord.setOrd(o), f)); } /** * Add all the elements of the given set to this set. * * @param s A set to add to this set. * @return A new set containing all elements of both sets. */ public final Set union(final Set s) { return iterableSet(ord, s.toStream().append(toStream())); } /** * A first class function for {@link #union(Set)}. * * @return A function that adds all the elements of one set to another set. * @see #union(Set) */ public static F, F, Set>> union() { return curry(new F2, Set, Set>() { public Set f(final Set s1, final Set s2) { return s1.union(s2); } }); } /** * Filters elements from this set by returning only elements which produce true * when the given function is applied to them. * * @param f The predicate function to filter on. * @return A new set whose elements all match the given predicate. */ public final Set filter(final F f) { return iterableSet(ord, toStream().filter(f)); } /** * Deletes the given element from this set. * * @param a an element to remove. * @return A new set containing all the elements of this set, except the given element. */ public final Set delete(final A a) { return minus(single(ord, a)); } /** * First-class deletion function. * * @return A function that deletes a given element from a given set. */ public final F, Set>> delete() { return curry(new F2, Set>() { public Set f(final A a, final Set set) { return set.delete(a); } }); } /** * Remove all elements from this set that do not occur in the given set. * * @param s A set of elements to retain. * @return A new set which is the intersection of this set and the given set. */ public final Set intersect(final Set s) { return filter(Set.member().f(s)); } /** * A first class function for {@link #intersect(Set)}. * * @return A function that intersects two given sets. * @see #intersect(Set) */ public static F, F, Set>> intersect() { return curry(new F2, Set, Set>() { public Set f(final Set s1, final Set s2) { return s1.intersect(s2); } }); } /** * Remove all elements from this set that occur in the given set. * * @param s A set of elements to delete. * @return A new set which contains only the elements of this set that do not occur in the given set. */ public final Set minus(final Set s) { return filter(compose(not, Set.member().f(s))); } /** * A first class function for {@link #minus(Set)}. * * @return A function that removes all elements of one set from another set. * @see #minus(Set) */ public static F, F, Set>> minus() { return curry(new F2, Set, Set>() { public Set f(final Set s1, final Set s2) { return s1.minus(s2); } }); } /** * Returns the size of this set. * * @return The number of elements in this set. */ public final int size() { final F one = constant(1); return foldMap(one, Monoid.intAdditionMonoid); } /** * Splits this set at the given element. Returns a product-3 of: *
    *
  • A set containing all the elements of this set which are less than the given value.
  • *
  • An option of a value equal to the given value, if one was found in this set, otherwise None. *
  • A set containing all the elements of this set which are greater than the given value.
  • *
* * @param a A value at which to split this set. * @return Two sets and an optional value, where all elements in the first set are less than the given value * and all the elements in the second set are greater than the given value, and the optional value is the * given value if found, otherwise None. */ public final P3, Option
, Set> split(final A a) { if (isEmpty()) return P.p(empty(ord), Option.none(), empty(ord)); else { final A h = head(); final Ordering i = ord.compare(a, h); if (i == LT) { final P3, Option, Set> lg = l().split(a); return P.p(lg._1(), lg._2(), lg._3().insert(h).union(r())); } else if (i == GT) { final P3, Option, Set> lg = r().split(a); return P.p(lg._1().insert(h).union(l()), lg._2(), lg._3()); } else return P.p(l(), some(h), r()); } } /** * Returns true if this set is a subset of the given set. * * @param s A set which is a superset of this set if this method returns true. * @return true if this set is a subset of the given set. */ public final boolean subsetOf(final Set s) { if (isEmpty() || s.isEmpty()) return isEmpty(); else { final P3, Option, Set> find = s.split(head()); return find._2().isSome() && l().subsetOf(find._1()) && r().subsetOf(find._3()); } } /** * Join a set of sets into a single set. * * @param s A set of sets. * @param o An order for the elements of the new set. * @return A new set which is the join of the given set of sets. */ public static Set join(final Ord o, final Set> s) { final F, Set> id = identity(); return s.foldMap(id, Monoid.setMonoid(o)); } /** * Return the elements of the given iterable as a set. * * @param o An order for the elements of the new set. * @param as An iterable of elements to add to a set. * @return A new set containing the elements of the given iterable. */ public static Set iterableSet(final Ord o, final Iterable as) { Set s = empty(o); for (final A a : as) s = s.insert(a); return s; } /** * Constructs a set from the given elements. * * @param o An order for the elements of the new set. * @param as The elements to add to a set. * @return A new set containing the elements of the given iterable. */ public static Set set(final Ord o, final A ... as) { Set s = empty(o); for (final A a : as) s = s.insert(a); return s; } }