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

org.pkl.thirdparty.paguro.collections.PersistentTreeMap Maven / Gradle / Ivy

Go to download

Shaded fat Jar for pkl-config-java, a Java config library based on the Pkl config language.

There is a newer version: 0.27.1
Show newest version
/*
 Copyright (c) Rich Hickey. All rights reserved. The use and distribution terms for this software
 are covered by the Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
 which can be found in the file epl-v10.html at the root of this distribution. By using this
 software in any fashion, you are agreeing to be bound by the terms of this license. You must not
 remove this notice, or any other, from this software.
 */

/* rich May 20, 2006 */
package org.pkl.thirdparty.paguro.collections;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Stack;

import org.pkl.thirdparty.jetbrains.annotations.NotNull;
import org.pkl.thirdparty.paguro.function.Fn1;
import org.pkl.thirdparty.paguro.oneOf.Option;
import org.pkl.thirdparty.paguro.tuple.Tuple2;

import static org.pkl.thirdparty.paguro.FunctionUtils.stringify;

/**
 Persistent Red Black Tree. Note that instances of this class are constant values
 i.e. add/remove etc return new values.

 See Okasaki, Kahrs, Larsen et al

 This file is a derivative work based on a Clojure collection licensed under the Eclipse Public
 License 1.0 Copyright Rich Hickey

 @author Rich Hickey: Original author
 @author Glen Peterson: Added generic types, static factories, custom serialization, and made Nodes
         extend Tuple2.  All errors are Glen's.
 */

public class PersistentTreeMap extends AbstractUnmodMap
        implements ImSortedMap, Serializable {

    /**
     This is a throw-away class used internally by PersistentTreeMap and PersistentHashMap like
     a mutable Option class to hold either null, or some value.  I don't want to remove this
     without checking the effect on performance.
     */
    static class Box {
        E val;
        Box(E val) { this.val = val; }
    }

    /**
     Returns a new PersistentTreeMap of the given comparable keys and their paired values, skipping
     any null Entries.
     */
    public static ,V> PersistentTreeMap
    of(Iterable> es) {
        if (es == null) { return empty(); }
        PersistentTreeMap map = new PersistentTreeMap<>(Equator.defaultComparator(), null, 0);
        for (Map.Entry entry : es) {
            if (entry != null) {
                map = map.assoc(entry.getKey(), entry.getValue());
            }
        }
        return map;
    }

    /**
     Returns a new PersistentTreeMap of the specified comparator and the given key/value pairs.

     @param comp A comparator (on the keys) that defines the sort order inside the new map.  This
     becomes a permanent part of the map and all sub-maps or appended maps derived from it.  If you
     want to use a null key, make sure the comparator treats nulls correctly in all circumstances!

     @param kvPairs Key/value pairs (to go into the map).  In the case of a duplicate key, later
     values in the input list overwrite the earlier ones.  The resulting map can contain zero or
     one null key (if your comparator knows how to sort nulls) and any number of null values.  Null
     k/v pairs will be silently ignored.

     @return a new PersistentTreeMap of the specified comparator and the given key/value pairs
     */
    public static  PersistentTreeMap
    ofComp(Comparator comp, Iterable> kvPairs) {
        if (kvPairs == null) { return new PersistentTreeMap<>(comp, null, 0); }
        PersistentTreeMap map = new PersistentTreeMap<>(comp, null, 0);
        for (Map.Entry entry : kvPairs) {
            if (entry != null) {
                map = map.assoc(entry.getKey(), entry.getValue());
            }
        }
        return map;
    }

    /**
     Be extremely careful with this because it uses the default comparator, which only works for
     items that implement Comparable (have a "natural ordering").  An attempt to use it with other
     items will blow up at runtime.  Either a withComparator() method will be added, or this will
     be removed.
     */
    final static public PersistentTreeMap EMPTY =
            new PersistentTreeMap<>(Equator.defaultComparator(), null, 0);

    /**
     Be extremely careful with this because it uses the default comparator, which only works for
     items that implement Comparable (have a "natural ordering").  An attempt to use it with other
     items will blow up at runtime.  Either a withComparator() method will be added, or this will
     be removed.
     */
    @SuppressWarnings("unchecked")
    public static , V> PersistentTreeMap empty() {
        return (PersistentTreeMap) EMPTY;
    }

    /** Returns a new empty PersistentTreeMap that will use the specified comparator. */
    public static  PersistentTreeMap empty(Comparator c) {
        return new PersistentTreeMap<>(c, null, 0);
    }

    /**
     This would be private, except that PersistentTreeSet needs to check that the wrapped
     comparator is serializable.
     */
    static final class KeyComparator implements Comparator>, Serializable {
        private static final long serialVersionUID = 20160827174100L;

        private final Comparator wrappedComparator;

        private KeyComparator(Comparator c) { wrappedComparator = c; }

        @Override public int compare(Map.Entry a, Map.Entry b) {
            return wrappedComparator.compare(a.getKey(), b.getKey());
        }

        @Override public String toString() {
            return "KeyComparator(" + wrappedComparator + ")";
        }

        /**
         This would be private, except that PersistentTreeSet needs to check that the wrapped
         comparator is serializable.
         */
        Comparator unwrap() { return wrappedComparator; }
    }

    // ==================================== Instance Variables ====================================
    private final Comparator comp;
    private final transient Node tree;
    private final int size;

    // ======================================== Constructor ========================================
    private PersistentTreeMap(@NotNull Comparator c, Node t, int n) {
        comp = c; tree = t; size = n;
    }

//    /** Returns a new PersistentTreeMap of the given comparable keys and their paired values. */
//    public static ,V> PersistentTreeMap of() {
//        return empty();
//    }

    // ======================================= Serialization =======================================
    // This class has a custom serialized form designed to be as small as possible.  It does not
    // have the same internal structure as an instance of this class.

    // For serializable.  Make sure to change whenever internal data format changes.
    private static final long serialVersionUID = 20160904095000L;

    // Check out Josh Bloch Item 78, p. 312 for an explanation of what's going on here.
    private static class SerializationProxy implements Serializable {
        // For serializable.  Make sure to change whenever internal data format changes.
        private static final long serialVersionUID = 20160904095000L;

        private final Comparator comparator;
        private final int size;
        private transient PersistentTreeMap theMap;
        SerializationProxy(PersistentTreeMap phm) {
            comparator = phm.comp;
            if ( !(comparator instanceof Serializable) ) {
                throw new IllegalStateException("Comparator must equal serializable." +
                                                "  Instead it was " + comparator);
            }
            size = phm.size;
            theMap = phm;
        }

        // Taken from Josh Bloch Item 75, p. 298
        private void writeObject(ObjectOutputStream s) throws IOException {
            s.defaultWriteObject();

            // Serializing in iteration-order yields a worst-case deserialization because
            // without re-balancing (rotating nodes) such an order yields an completely unbalanced
            // linked list internal structure.
            //       4
            //      /
            //     3
            //    /
            //   2
            //  /
            // 1
            //
            // That seems unnecessary since before Serialization we might have something like this
            // which, while not perfect, requires no re-balancing:
            //
            //                    11
            //            ,------'  `----.
            //           8                14
            //        ,-' `-.            /  \
            //       4       9         13    15
            //    ,-' `-.     \       /        \
            //   2       6     10   12          16
            //  / \     / \
            // 1   3   5   7
            //
            // If we serialize the middle value (n/2) first.  Then the n/4 and 3n/4,
            // followed by n/8, 3n/8, 5n/8, 7n/8, then n/16, 3n/16, etc.  Finally, the odd-numbered
            // values last.  That gives us the order:
            // 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15
            //
            // Deserializing in that order yields an ideally balanced tree without any shuffling:
            //               8
            //        ,-----' `-------.
            //       4                 12
            //    ,-' `-.          ,--'  `--.
            //   2       6       10          14
            //  / \     / \     /  \        /  \
            // 1   3   5   7   9    11    13    15
            //
            // That would be ideal, but I don't see how to do that without a significant
            // intermediate data structure.
            //
            // A good improvement could be made by serializing breadth-first instead of depth first
            // to at least yield a tree no worse than the original without requiring shuffling.
            //
            // This improvement does not change the serialized form, or break compatibility.
            // But it has a superior ordering for deserialization without (or with minimal)
            // rotations.

//            System.out.println("Serializing tree map...");
            if (theMap.tree != null) {
                Queue> queue = new ArrayDeque<>();
                queue.add(theMap.tree);
                while (queue.peek() != null) {
                    Node node = queue.remove();
//                    System.out.println("Node: " + node);
                    s.writeObject(node.getKey());
                    s.writeObject(node.getValue());
                    Node child = node.left();
                    if (child != null) {
                        queue.add(child);
                    }
                    child = node.right();
                    if (child != null) {
                        queue.add(child);
                    }
                }
            }
//            for (UnEntry entry : theMap) {
//                s.writeObject(entry.getKey());
//                s.writeObject(entry.getValue());
//            }
        }

        @SuppressWarnings("unchecked")
        private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            theMap = new PersistentTreeMap<>(comparator, null, 0);
            for (int i = 0; i < size; i++) {
                theMap = theMap.assoc((K) s.readObject(), (V) s.readObject());
            }
        }

        private Object readResolve() { return theMap; }
    }

    private Object writeReplace() { return new SerializationProxy<>(this); }

    private void readObject(java.io.ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        throw new InvalidObjectException("Proxy required");
    }

    // ===================================== Instance Methods =====================================
    /**
     Returns a view of the mappings contained in this map.  The set should actually contain
     UnmodMap.UnEntry items, but that return signature is illegal in Java, so you'll just have to
     remember.
     */
    @NotNull
    @Override public ImSortedSet> entrySet() {
        // This is the pretty way to do it.
        return this.fold(PersistentTreeSet.ofComp(new KeyComparator<>(comp)),
                         PersistentTreeSet::put);
    }

//    public static final Equator EQUATOR = new Equator() {
//        @Override
//        public int hash(SortedMap kvSortedMap) {
//            return UnmodIterable.hashCode(kvSortedMap.entrySet());
//        }
//
//        @Override
//        public boolean eq(SortedMap o1, SortedMap o2) {
//            if (o1 == o2) { return true; }
//            if ( o1.size() != o2.size() ) { return false; }
//            return UnmodSortedIterable.equals(UnmodSortedIterable.castFromSortedMap(o1),
//                                              UnmodSortedIterable.castFromSortedMap(o2));
//        }
//    };

//    /** Returns a view of the keys contained in this map. */
//    @Override public ImSet keySet() { return PersistentTreeSet.ofMap(this); }

    /** {@inheritDoc} */
    @NotNull
    @Override public ImSortedMap subMap(K fromKey, K toKey) {
        int diff = comp.compare(fromKey, toKey);

        if (diff > 0) {
            throw new IllegalArgumentException("fromKey is greater than toKey");
        }
        UnEntry last = last();
        K lastKey = last.getKey();
        int compFromKeyLastKey = comp.compare(fromKey, lastKey);

        // If no intersect, return empty. We aren't checking the toKey vs. the firstKey() because
        // that's a single pass through the iterator loop which is probably as cheap as checking
        // here.
        if ( (diff == 0) || (compFromKeyLastKey > 0) )  {
            return new PersistentTreeMap<>(comp, null, 0);
        }
        // If map is entirely contained, just return it.
        if ( (comp.compare(fromKey, firstKey()) <= 0) &&
                    (comp.compare(toKey, lastKey) > 0) ) {
            return this;
        }
        // Don't iterate through entire map for only the last item.
        if (compFromKeyLastKey == 0) {
            return ofComp(comp, Collections.singletonList(last));
        }

        ImSortedMap ret = new PersistentTreeMap<>(comp, null, 0);
        UnmodIterator> iter = this.iterator();
        while (iter.hasNext()) {
            UnEntry next = iter.next();
            K key = next.getKey();
            if (comp.compare(toKey, key) <= 0) {
                break;
            }
            if (comp.compare(fromKey, key) > 0) {
                continue;
            }
            ret = ret.assoc(key, next.getValue());
        }
        return ret;
    }

//    String debugStr() {
//        return "PersistentTreeMap(size=" + size +
//               " comp=" + comp +
//               " tree=" + tree + ")";
//    }

//    /** {@inheritDoc} */
//    @Override public UnmodCollection values() {
//        class ValueColl implements UnmodCollection, UnmodSortedIterable {
//            private final Fn0>> iterFactory;
//            private ValueColl(Fn0>> f) { iterFactory = f; }
//
//            @Override public int size() { return size; }
//
//            @Override public UnmodSortedIterator iterator() {
//                final UnmodSortedIterator> iter = iterFactory.apply();
//                return new UnmodSortedIterator() {
//                    @Override public boolean hasNext() { return iter.hasNext(); }
//                    @Override public B next() { return iter.next().getValue(); }
//                };
//            }
//            @Override public int hashCode() { return UnmodIterable.hashCode(this); }
//            @Override public boolean equals(Object o) {
//                if (this == o) { return true; }
//                if ( !(o instanceof UnmodSortedIterable) ) { return false; }
//                return UnmodSortedIterable.equals(this, (UnmodSortedIterable) o);
//            }
//            @Override public String toString() {
//                return UnmodSortedIterable.toString("ValueColl", this);
//            }
//        }
//        return new ValueColl<>(() -> this.iterator());
//    }

    /** {@inheritDoc} */
    @NotNull
    @Override public Option> head() {
        Node t = tree;
        if (t != null) {
            while (t.left() != null) {
                t = t.left();
            }
        }
        return Option.some(t);
    }

    /** {@inheritDoc} */
    @NotNull
    @Override public ImSortedMap tailMap(K fromKey) {
        UnEntry last = last();
        K lastKey = last.getKey();
        int compFromKeyLastKey = comp.compare(fromKey, lastKey);

        // If no intersect, return empty. We aren't checking the toKey vs. the firstKey() because
        // that's a single pass through the iterator loop which is probably as cheap as checking
        // here.
        if (compFromKeyLastKey > 0) {
            return new PersistentTreeMap<>(comp, null, 0);
        }
        // If map is entirely contained, just return it.
        if (comp.compare(fromKey, firstKey()) <= 0) {
            return this;
        }
        // Don't iterate through entire map for only the last item.
        if (compFromKeyLastKey == 0) {
            return ofComp(comp, Collections.singletonList(last));
        }

        ImSortedMap ret = new PersistentTreeMap<>(comp, null, 0);
        UnmodIterator> iter = this.iterator();
        while (iter.hasNext()) {
            UnEntry next = iter.next();
            K key = next.getKey();
            if (comp.compare(fromKey, key) > 0) {
                continue;
            }
            ret = ret.assoc(key, next.getValue());
        }
        return ret;
    }

//    /** {@inheritDoc} */
//    @Override public Sequence> tail() {
//        if (size() > 1) {
//            return without(firstKey());
////            // The iterator is designed to do this quickly.  It also prevents an infinite loop.
////            UnmodIterator> iter = this.iterator();
////            // Drop the head
////            iter.next();
////            return tailMap(iter.next().getKey());
//        }
//        return Sequence.emptySequence();
//    }

//    @SuppressWarnings("unchecked")
//    static public  PersistentTreeMap create(ISeq items) {
//        PersistentTreeMap ret = empty();
//        for (; items != null; items = items.next().next()) {
//            if (items.next() == null)
//                throw new IllegalArgumentException(String.format("No value supplied for key: %s",
//                                                                 items.head()));
//            ret = ret.assoc((K) items.head(), (V) RT.second(items));
//        }
//        return ret;
//    }

//    @SuppressWarnings("unchecked")
//    static public 
//    PersistentTreeMap create(Comparator comp, ISeq items) {
//        PersistentTreeMap ret = new PersistentTreeMap<>(comp);
//        for (; items != null; items = items.next().next()) {
//            if (items.next() == null)
//                throw new IllegalArgumentException(String.format("No value supplied for key: %s",
//                                                                 items.head()));
//            ret = ret.assoc((K) items.head(), (V) RT.second(items));
//        }
//        return ret;
//    }

    /**
     Returns the comparator used to order the keys in this map, or null if it uses
     Fn2.DEFAULT_COMPARATOR (for compatibility with java.util.SortedMap).
     */
    @Override public Comparator comparator() {
        return (comp == Equator.Comp.DEFAULT) ? null : comp;
    }

//    /** Returns true if the map contains the given key. */
//    @SuppressWarnings("unchecked")
//    @Override public boolean containsKey(Object key) {
//        return entryAt((K) key) != null;
//    }

//    /** Returns the value associated with the given key. */
//    @SuppressWarnings("unchecked")
//    @Override
//    public V get(Object key) {
//        if (key == null) { return null; }
//        Entry entry = entryAt((K) key);
//        if (entry == null) { return null; }
//        return entry.getValue();
//    }

// public PersistentTreeMap assocEx(K key, V val) {
// Inherits default implementation of assocEx from IPersistentMap

    /** {@inheritDoc} */
    @NotNull
    @Override public PersistentTreeMap assoc(K key, V val) {
        Box> found = new Box<>(null);
        Node t = add(tree, key, val, found);
        //null == already contains key
        if (t == null) {
            Node foundNode = found.val;

            //note only get same collection on identity of val, not equals()
            if (foundNode.getValue() == val) {
                return this;
            }
            return new PersistentTreeMap<>(comp, replace(tree, key, val), size);
        }
        return new PersistentTreeMap<>(comp, t.blacken(), size + 1);
    }

    /** {@inheritDoc} */
    @NotNull
    @Override public PersistentTreeMap without(K key) {
        Box> found = new Box<>(null);
        Node t = remove(tree, key, found);
        if (t == null) {
            //null == doesn't contain key
            if (found.val == null) {
                return this;
            }
            //empty
            return new PersistentTreeMap<>(comp, null, 0);
        }
        return new PersistentTreeMap<>(comp, t.blacken(), size - 1);
    }

//    @Override
//    public ISeq> seq() {
//        if (size > 0)
//            return Iter.create(tree, true, size);
//        return null;
//    }
//
//    @Override
//    public ISeq> rseq() {
//        if (size > 0)
//            return Iter.create(tree, false, size);
//        return null;
//    }

//    @Override
//    public Object entryKey(Map.Entry entry) {
//        return entry.getKey();
//    }

//    // This lets you make a sequence of map entries from this HashMap.
//// The other methods on Sorted seem to care only about the key, and the implementations of them
//// here work that way.  This one, however, returns a sequence of Map.Entry or Node
//// If I understood why, maybe I could do better.
//    @SuppressWarnings("unchecked")
//    @Override
//    public ISeq> seq(boolean ascending) {
//        if (size > 0)
//            return Iter.create(tree, ascending, size);
//        return null;
//    }

//    @SuppressWarnings("unchecked")
//    @Override
//    public ISeq> seqFrom(Object key, boolean ascending) {
//        if (size > 0) {
//            ISeq> stack = null;
//            Node t = tree;
//            while (t != null) {
//                int c = doCompare((K) key, t.key);
//                if (c == 0) {
//                    stack = RT.cons(t, stack);
//                    return new Iter<>(stack, ascending);
//                } else if (ascending) {
//                    if (c < 0) {
//                        stack = RT.cons(t, stack);
//                        t = t.left();
//                    } else
//                        t = t.right();
//                } else {
//                    if (c > 0) {
//                        stack = RT.cons(t, stack);
//                        t = t.right();
//                    } else
//                        t = t.left();
//                }
//            }
//            if (stack != null)
//                return new Iter<>(stack, ascending);
//        }
//        return null;
//    }

    /** {@inheritDoc} */
    @NotNull
    @Override
    public UnmodSortedIterator> iterator() { return iterator(Tuple2::of); }

    @NotNull
    @Override
    public UnmodSortedIterator keyIterator() { return iterator(Node::getKey); }

    @NotNull
    @Override
    public UnmodSortedIterator valIterator() { return iterator(Node::getValue); }

    public  UnmodSortedIterator iterator(Fn1,R> aFn) { return new NodeIterator<>(tree, true, aFn); }

//    public NodeIterator reverseIterator() { return new NodeIterator<>(tree, false); }

    /** Returns the first key in this map or throws a NoSuchElementException if the map is empty. */
    @Override public K firstKey() {
        if (size() < 1) { throw new NoSuchElementException("this map is empty"); }
        return head().get().getKey();
    }

    /** Returns the last key in this map or throws a NoSuchElementException if the map is empty. */
    @Override public K lastKey() {
        UnEntry max = last();
        if (max == null) {
            throw new NoSuchElementException("this map is empty");
        }
        return max.getKey();
    }

    /** Returns the last key/value pair in this map, or null if the map is empty. */
    public UnEntry last() {
        Node t = tree;
        if (t != null) {
            while (t.right() != null)
                t = t.right();
        }
        return t;
    }

//    public int depth() {
//        return depth(tree);
//    }

//    int depth(Node t) {
//        if (t == null)
//            return 0;
//        return 1 + Math.max(depth(t.left()), depth(t.right()));
//    }

// public Object valAt(Object key){
// Default implementation now inherited from ILookup

    /** Returns the number of key/value mappings in this map. */
    @Override public int size() { return size; }

    /**
     Returns an Option of the key/value pair matching the given key, or Option.none() if the key is
     not found.
     */
    @NotNull
    @Override public Option> entry(K key) {
        Node t = tree;
        while (t != null) {
            int c = comp.compare(key, t.getKey());
            if (c == 0)
                return Option.some(t);
            else if (c < 0)
                t = t.left();
            else
                t = t.right();
        }
        return Option.none(); // t; // t is always null
    }

//    // In TreeMap, this is final Entry getEntry(Object key)
//    /** Returns the key/value pair matching the given key, or null if the key is not found. */
//    public UnEntry entryAt(K key) {
//        Node t = tree;
//        while (t != null) {
//            int c = comp.compare(key, t.key);
//            if (c == 0)
//                return t;
//            else if (c < 0)
//                t = t.left();
//            else
//                t = t.right();
//        }
//        return null; // t; // t is always null
//    }

    private Node add(Node t, K key, V val, Box> found) {
        if (t == null) {
//            if (val == null)
//                return new Red<>(key);
            return new Red<>(key, val);
        }
        int c = comp.compare(key, t.getKey());
        if (c == 0) {
            found.val = t;
            return null;
        }
        Node ins = add(c < 0 ? t.left() : t.right(),
                            key, val, found);
        if (ins == null) //found below
            return null;
        if (c < 0)
            return t.addLeft(ins);
        return t.addRight(ins);
    }

    private Node remove(Node t, K key, Box> found) {
        if (t == null)
            return null; //not found indicator
        int c = comp.compare(key, t.getKey());
        if (c == 0) {
            found.val = t;
            return append(t.left(), t.right());
        }
        Node del = remove(c < 0 ? t.left() : t.right(),
                               key, found);
        if (del == null && found.val == null) //not found below
            return null;
        if (c < 0) {
            if (t.left() instanceof PersistentTreeMap.Black)
                return balanceLeftDel(t.getKey(), t.getValue(), del, t.right());
            else
                return red(t.getKey(), t.getValue(), del, t.right());
        }
        if (t.right() instanceof PersistentTreeMap.Black)
            return balanceRightDel(t.getKey(), t.getValue(), t.left(), del);
        return red(t.getKey(), t.getValue(), t.left(), del);
//		return t.removeLeft(del);
//	return t.removeRight(del);
    }

    //static 
//Node concat(Node left, Node right){
    @SuppressWarnings("unchecked")
    private static  Node append(Node left,
                                   Node right) {
        if (left == null)
            return (Node) right;
        else if (right == null)
            return (Node) left;
        else if (left instanceof PersistentTreeMap.Red) {
            if (right instanceof PersistentTreeMap.Red) {
                Node app = append(left.right(), right.left());
                if (app instanceof PersistentTreeMap.Red)
                    return red(app.getKey(), app.getValue(),
                               red(left.getKey(), left.getValue(), left.left(), app.left()),
                               red(right.getKey(), right.getValue(), app.right(), right.right()));
                else
                    return red(left.getKey(), left.getValue(), left.left(),
                               red(right.getKey(), right.getValue(), app, right.right()));
            } else
                return red(left.getKey(), left.getValue(), left.left(), append(left.right(), right));
        } else if (right instanceof PersistentTreeMap.Red)
            return red(right.getKey(), right.getValue(), append(left, right.left()), right.right());
        else //black/black
        {
            Node app = append(left.right(), right.left());
            if (app instanceof PersistentTreeMap.Red)
                return red(app.getKey(), app.getValue(),
                           black(left.getKey(), left.getValue(), left.left(), app.left()),
                           black(right.getKey(), right.getValue(), app.right(), right.right()));
            else
                return balanceLeftDel(left.getKey(), left.getValue(), left.left(),
                                      black(right.getKey(), right.getValue(), app, right.right()));
        }
    }

    private static 
    Node balanceLeftDel(K1 key, V1 val,
                             Node del,
                             Node right) {
        if (del instanceof PersistentTreeMap.Red)
            return red(key, val, del.blacken(), right);
        else if (right instanceof PersistentTreeMap.Black)
            return rightBalance(key, val, del, right.redden());
        else if (right instanceof PersistentTreeMap.Red && right.left() instanceof PersistentTreeMap.Black)
            return red(right.left().getKey(), right.left().getValue(),
                       black(key, val, del, right.left().left()),
                       rightBalance(right.getKey(), right.getValue(), right.left().right(),
                                    right.right().redden()));
        else
            throw new UnsupportedOperationException("Invariant violation");
    }

    private static 
    Node balanceRightDel(K1 key, V1 val,
                              Node left,
                              Node del) {
        if (del instanceof PersistentTreeMap.Red)
            return red(key, val, left, del.blacken());
        else if (left instanceof PersistentTreeMap.Black)
            return leftBalance(key, val, left.redden(), del);
        else if (left instanceof PersistentTreeMap.Red && left.right() instanceof PersistentTreeMap.Black)
            return red(left.right().getKey(), left.right().getValue(),
                       leftBalance(left.getKey(), left.getValue(), left.left().redden(), left.right().left()),
                       black(key, val, left.right().right(), del));
        else
            throw new UnsupportedOperationException("Invariant violation");
    }

    private static 
    Node leftBalance(K1 key, V1 val,
                          Node ins,
                          Node right) {
        if (ins instanceof PersistentTreeMap.Red && ins.left() instanceof PersistentTreeMap.Red)
            return red(ins.getKey(), ins.getValue(), ins.left().blacken(),
                       black(key, val, ins.right(), right));
        else if (ins instanceof PersistentTreeMap.Red && ins.right() instanceof PersistentTreeMap.Red)
            return red(ins.right().getKey(), ins.right().getValue(),
                       black(ins.getKey(), ins.getValue(), ins.left(), ins.right().left()),
                       black(key, val, ins.right().right(), right));
        else
            return black(key, val, ins, right);
    }


    private static 
    Node rightBalance(K1 key, V1 val,
                           Node left,
                           Node ins) {
        if (ins instanceof PersistentTreeMap.Red && ins.right() instanceof PersistentTreeMap.Red)
            return red(ins.getKey(), ins.getValue(), black(key, val, left, ins.left()),
                       ins.right().blacken());
        else if (ins instanceof PersistentTreeMap.Red && ins.left() instanceof PersistentTreeMap.Red)
            return red(ins.left().getKey(), ins.left().getValue(),
                       black(key, val, left, ins.left().left()),
                       black(ins.getKey(), ins.getValue(), ins.left().right(), ins.right()));
        else
            return black(key, val, left, ins);
    }

    private Node replace(Node t, K key, V val) {
        int c = comp.compare(key, t.getKey());
        return t.replace(t.getKey(),
                         c == 0 ? val : t.getValue(),
                         c < 0 ? replace(t.left(), key, val) : t.left(),
                         c > 0 ? replace(t.right(), key, val) : t.right());
    }

    @SuppressWarnings({"unchecked", "RedundantCast", "Convert2Diamond"})
    private static 
    Red red(K1 key, V1 val,
                 Node left,
                 Node right) {
        if (left == null && right == null) {
//            if (val == null)
//                return new Red(key, val);
            return new Red(key, val);
        }
//        if (val == null)
//            return new RedBranch((K) key, (Node) left, (Node) right);
        return new RedBranch((K) key, (V) val, (Node) left, (Node) right);
    }

    @SuppressWarnings({"unchecked", "RedundantCast", "Convert2Diamond"})
    private static 
    Black black(K1 key, V1 val,
                     Node left,
                     Node right) {
        if (left == null && right == null) {
//            if (val == null)
//                return new Black<>(key);
            return new Black(key, val);
        }
//        if (val == null)
//            return new BlackBranch((K) key, (Node) left, (Node) right);
        return new BlackBranch((K) key, (V) val, (Node) left, (Node) right);
    }

//    public static class Reduced {
//        public final A val;
//        private Reduced(A a) { val = a; }
//    }

    private static abstract class Node extends Tuple2 {
        Node(K key, V val) { super(key, val); }

        Node left() { return null; }

        Node right() { return null; }

        abstract Node addLeft(Node ins);

        abstract Node addRight(Node ins);

        @SuppressWarnings("UnusedDeclaration")
        abstract Node removeLeft(Node del);

        @SuppressWarnings("UnusedDeclaration")
        abstract Node removeRight(Node del);

        abstract Node blacken();

        abstract Node redden();

        Node balanceLeft(Node parent) {
            return black(parent._1, parent._2, this, parent.right());
        }

        Node balanceRight(Node parent) {
            return black(parent._1, parent._2, parent.left(), this);
        }

        abstract Node replace(K key, V val, Node left, Node right);

        @Override public String toString() {
            return stringify(_1) + "=" + stringify(_2);
        }

//        public  R kvreduce(Fn3 f, R init) {
//            if (left() != null) {
//                init = left().kvreduce(f, init);
//                if (init instanceof Reduced)
//                    return init;
//            }
//            init = f.apply(init, key(), val());
//            if (init instanceof Reduced)
//                return init;
//
//            if (right() != null) {
//                init = right().kvreduce(f, init);
//            }
//            return init;
//        }
    } // end class Node.

    private static class Black extends Node {
        Black(K key, V val) { super(key, val); }

        @Override Node addLeft(Node ins) { return ins.balanceLeft(this); }

        @Override Node addRight(Node ins) { return ins.balanceRight(this); }

        @Override Node removeLeft(Node del) {
            return balanceLeftDel(_1, _2, del, right());
        }

        @Override Node removeRight(Node del) {
            return balanceRightDel(_1, _2, left(), del);
        }

        @Override Node blacken() { return this; }

        @Override Node redden() { return new Red<>(_1, _2); }

        @Override
        Node replace(K key, V val, Node left, Node right) {
            return black(key, val, left, right);
        }
    }

    private static class BlackBranch extends Black {
        final transient Node left;
        final transient Node right;

        BlackBranch(K key, V val, Node l, Node r) {
            super(key, val); left = l; right = r;
        }

        @Override public Node left() { return left; }

        @Override public Node right() { return right; }

        @Override Node redden() { return new RedBranch<>(_1, _2, left, right); }
    }

    private static class Red extends Node {
        Red(K key, V val) { super(key, val); }

        @Override Node addLeft(Node ins) { return red(_1, _2, ins, right()); }

        @Override Node addRight(Node ins) { return red(_1, _2, left(), ins); }

        @Override Node removeLeft(Node del) { return red(_1, _2, del, right()); }

        @Override Node removeRight(Node del) { return red(_1, _2, left(), del); }

        @Override Node blacken() { return new Black<>(_1, _2); }

        @Override
        Node redden() { throw new UnsupportedOperationException("Invariant violation"); }

        @Override
        Node replace(K key, V val, Node left, Node right) {
            return red(key, val, left, right);
        }
    }

    private static class RedBranch extends Red {
        final transient Node left;
        final transient Node right;

        RedBranch(K key, V val, Node left, Node right) {
            super(key, val);
            this.left = left;
            this.right = right;
        }

        @Override public Node left() { return left; }

        @Override public Node right() { return right; }

        @Override Node balanceLeft(Node parent) {
            if (left instanceof PersistentTreeMap.Red)
                return red(_1, _2, left.blacken(),
                           black(parent.getKey(), parent.getValue(), right, parent.right()));
            else if (right instanceof PersistentTreeMap.Red)
                return red(right.getKey(), right.getValue(), black(_1, _2, left, right.left()),
                           black(parent.getKey(), parent.getValue(), right.right(), parent.right()));
            else
                return super.balanceLeft(parent);

        }

        @Override Node balanceRight(Node parent) {
            if (right instanceof PersistentTreeMap.Red)
                return red(_1, _2,
                           black(parent.getKey(), parent.getValue(), parent.left(), left),
                           right.blacken());
            else if (left instanceof PersistentTreeMap.Red)
                return red(left.getKey(), left.getValue(),
                           black(parent.getKey(), parent.getValue(), parent.left(), left.left()),
                           black(_1, _2, left.right(), right));
            else
                return super.balanceRight(parent);
        }

        @Override Node blacken() { return new BlackBranch<>(_1, _2, left, right); }
    }


//    static public class Iter extends ASeq> {
//        final ISeq> stack;
//        final boolean asc;
//        final int cnt;
//
//        public Iter(ISeq> stack, boolean asc) {
//            this.stack = stack;
//            this.asc = asc;
//            this.cnt = -1;
//        }
//
//        public Iter(ISeq> stack, boolean asc, int cnt) {
//            this.stack = stack;
//            this.asc = asc;
//            this.cnt = cnt;
//        }
//
//        Iter(ISeq> stack, boolean asc, int cnt) {
//            super();
//            this.stack = stack;
//            this.asc = asc;
//            this.cnt = cnt;
//        }
//
//        static  Iter create(Node t, boolean asc, int cnt) {
//            return new Iter<>(push(t, null, asc), asc, cnt);
//        }
//
//        static  ISeq> push(Node t, ISeq> stack, boolean asc) {
//            while (t != null) {
//                stack = RT.cons(t, stack);
//                t = asc ? t.left() : t.right();
//            }
//            return stack;
//        }
//
//        @Override
//        public Node head() {
//            return stack.head();
//        }
//
//        @Override
//        public ISeq> next() {
//            Node t = stack.head();
//            ISeq> nextstack = push(asc ? t.right() : t.left(), stack.next(), asc);
//            if (nextstack != null) {
//                return new Iter<>(nextstack, asc, cnt - 1);
//            }
//            return null;
//        }
//
//        @Override
//        public int count() {
//            if (cnt < 0)
//                return super.count();
//            return cnt;
//        }
//    }

    /**
     This currently returns chunks of the inner tree structure that implement Map.Entry.
     They are not serializable and should not be made so.  I can alter this to return nice,
     neat, Tuple2 objects which are serializable, but we've made it this far without so...
     */
    private static class NodeIterator implements UnmodSortedIterator {
        //, Serializable {
        // For serializable.  Make sure to change whenever internal data format changes.
        // private static final long serialVersionUID = 20160827174100L;

        private Stack> stack = new Stack<>();
        private final boolean asc;
        private Fn1, R> aFn;

        NodeIterator(Node t, boolean asc, Fn1,R> aFn) {
            this.asc = asc;
            this.aFn = aFn;
            push(t);
        }

        private void push(Node t) {
            while (t != null) {
                stack.push(t);
                t = asc ? t.left() : t.right();
            }
        }

        @Override public boolean hasNext() {
            return !stack.isEmpty();
        }

        @SuppressWarnings("unchecked")
        @Override public R next() {
            Node t = stack.pop();
            push(asc ? t.right() : t.left());

            return aFn.apply(t);
        }
    }

//    static class KeyIterator implements Iterator {
//        NodeIterator it;
//
//        KeyIterator(NodeIterator it) {
//            this.it = it;
//        }
//
//        @Override
//        public boolean hasNext() {
//            return it.hasNext();
//        }
//
//        @Override
//        public K next() {
//            return it.next().getKey();
//        }
//
//        @Override
//        public void remove() {
//            throw new UnsupportedOperationException();
//        }
//    }
//
//    static class ValIterator implements Iterator {
//        NodeIterator it;
//
//        ValIterator(NodeIterator it) {
//            this.it = it;
//        }
//
//        @Override
//        public boolean hasNext() {
//            return it.hasNext();
//        }
//
//        @Override
//        public V next() {
//            return it.next().getValue();
//        }
//
//        @Override
//        public void remove() {
//            throw new UnsupportedOperationException();
//        }
//    }
}