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

fj.data.Set 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.*;

import static fj.Function.*;
import static fj.data.Either.right;
import static fj.data.Option.some;
import static fj.function.Booleans.not;

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(a2 -> 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(set -> 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(set -> 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);
  }

  @Override
  public boolean equals(Object other) {
    return Equal.equals0(Set.class, this, other, () -> Equal.setEqual(Equal.anyEqual()));
  }

  @Override
  public int hashCode() {
    return Hash.setHash(Hash.anyHash()).hash(this);
  }

  @Override
  public String toString() {
    return Show.setShow(Show.anyShow()).showS(this);
  }

  /**
   * 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((s, a) -> 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((a, set) -> 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((s1, s2) -> 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((a, set) -> 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((s1, s2) -> 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((s1, s2) -> 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; } /** * Constructs a set from the given elements. * * @param o An order for the elements of the new set. * @param list The elements to add to a set. * @return A new set containing the elements of the given list. */ public static Set set(final Ord o, List list) { Set s = empty(o); for (final A a : list) s = s.insert(a); return s; } }