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

org.organicdesign.fp.collections.PersistentHashMap Maven / Gradle / Ivy

Go to download

Immutable Clojure collections and a Transformation abstraction for Java 8+, immutably, type-safely, and with good performance. Name will change to "Paguro" in November 2016.

There is a newer version: 2.0.13
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.
 **/

package org.organicdesign.fp.collections;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicReference;

import org.organicdesign.fp.FunctionUtils;
import org.organicdesign.fp.Option;
import org.organicdesign.fp.collections.PersistentTreeMap.Box;
import org.organicdesign.fp.tuple.Tuple2;

import static org.organicdesign.fp.FunctionUtils.emptyUnmodIterator;

/**
 Rich Hickey's immutable rendition of Phil Bagwell's Hash Array Mapped Trie.

 Uses path copying for persistence,
 HashCollision leaves vs. extended hashing,
 Node polymorphism vs. conditionals,
 No sub-tree pools or root-resizing.
 Any errors are my own (said Rich, but now says Glen 2015-06-06).

 This file is a derivative work based on a Clojure collection licensed under the Eclipse Public
 License 1.0 Copyright Rich Hickey.  Errors are Glen Peterson's.
 */
public class PersistentHashMap implements ImUnsortedMap, Serializable {

//    static private  R doKvreduce(Object[] array, Function3 f, R init) {
//        for (int i = 0; i < array.length; i += 2) {
//            if (array[i] != null) {
//                init = f.apply(init, k(array, i), v(array, i + 1));
//            } else {
//                INode node = iNode(array, i + 1);
//                if (node != null)
//                    init = node.kvreduce(f, init);
//            }
//            if (isReduced(init)) {
//                return init;
//            }
//        }
//        return init;
//    }

    private static class Iter implements UnmodIterator> {
//        , Serializable {
//        // For serializable.  Make sure to change whenever internal data format changes.
//        private static final long serialVersionUID = 20160903192900L;

        private boolean seen = false;
        private final UnmodIterator> rootIter;
        private final V nullValue;
        private Iter(UnmodIterator> ri, V nv) { rootIter = ri; nullValue = nv; }

        @Override public boolean hasNext() {
            if (!seen) {
                return true;
            } else {
                return rootIter.hasNext();
            }
        }

        @Override public UnEntry next(){
            if (!seen) {
                seen = true;
                return Tuple2.of(null, nullValue);
            } else {
                return rootIter.next();
            }
        }
    }

//    private static final class Reduced {
//        Object val;
//        public Reduced(Object val) { this.val = val; }
////        public Object deref() { return val; }
//    }
//
//    private static boolean isReduced(Object r){
//        return (r instanceof Reduced);
//    }

    private static int mask(int hash, int shift){
        //return ((hash << shift) >>> 27);// & 0x01f;
        return (hash >>> shift) & 0x01f;
    }

    // A method call is slow, but it keeps the cast localized.
    @SuppressWarnings("unchecked")
    private static  K k(Object[] array, int i) { return (K) array[i]; }

    // A method call is slow, but it keeps the cast localized.
    @SuppressWarnings("unchecked")
    private static  V v(Object[] array, int i) { return (V) array[i]; }

    // A method call is slow, but it keeps the cast localized.
    @SuppressWarnings("unchecked")
    private static  INode iNode(Object[] array, int i) { return (INode) array[i]; }


//    interface IFn {}

    final public static PersistentHashMap EMPTY =
            new PersistentHashMap<>(null, 0, null, false, null);

    @SuppressWarnings("unchecked")
    public static  PersistentHashMap empty() { return (PersistentHashMap) EMPTY; }

    @SuppressWarnings("unchecked")
    public static  PersistentHashMap empty(Equator e) {
        return new PersistentHashMap<>(e, 0, null, false, null);
    }

//    final private static Object NOT_FOUND = new Object();

//    /** Returns a new PersistentHashMap of the given keys and their paired values. */
//    public static  PersistentHashMap of() {
//        return empty();
//    }

    /**
     Returns a new PersistentHashMap of the given keys and their paired values, skipping any null
     Entries.
     */
    public static  PersistentHashMap ofEq(Equator eq, Iterable> es) {
        if (es == null) { return empty(eq); }
        PersistentHashMap m = empty(eq);
        MutableHashMap map = m.mutable();
        for (Map.Entry entry : es) {
            if (entry != null) {
                map.assoc(entry.getKey(), entry.getValue());
            }
        }
        return map.immutable();
    }

    /**
     Returns a new PersistentHashMap of the given keys and their paired values.  There is also a
     varargs version of this method: {@link org.organicdesign.fp.StaticImports#map(Map.Entry...)}.  Use
     the {@link org.organicdesign.fp.StaticImports#tup(Object, Object)} method to define key/value
     pairs briefly and easily.

     @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 and any number of null values.  Null k/v pairs will be silently ignored.

     @return a new PersistentHashMap of the given key/value pairs
      */
    public static  PersistentHashMap of(Iterable> kvPairs) {
        if (kvPairs == null) { return empty(); }
        PersistentHashMap m = empty();
        MutableHashMap map = m.mutable();
        for (Map.Entry entry : kvPairs) {
            if (entry != null) {
                map.assoc(entry.getKey(), entry.getValue());
            }
        }
        return map.immutable();
    }

    // ==================================== Instance Variables ====================================
    private final Equator equator;
    private final int size;
    private transient final INode root;
    private final boolean hasNull;
    private final V nullValue;

    // ======================================== Constructor ========================================
    private PersistentHashMap(Equator eq, int sz, INode root, boolean hasNull,
                              V nullValue) {
        this.equator = (eq == null) ? Equator.defaultEquator() : eq;
        this.size = sz;
        this.root = root;
        this.hasNull = hasNull;
        this.nullValue = nullValue;
    }

    // ======================================= 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 = 20160903192900L;

    // Check out Josh Bloch Item 78, p. 312 for an explanation of what's going on here.
    private static class SerializationProxy implements Serializable {
        private final Equator equator;
        private final int size;
        private transient ImUnsortedMap theMap;
        SerializationProxy(PersistentHashMap phm) {
            equator = phm.equator;
            size = phm.size;
            theMap = phm;
        }

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

            // Write out all elements in the proper order
            for (UnEntry entry : theMap) {
                s.writeObject(entry.getKey());
                s.writeObject(entry.getValue());
            }
        }

        private static final long serialVersionUID = 20160827174100L;

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

        private Object readResolve() { return theMap.immutable(); }
    }

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

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

    // ===================================== Instance Methods =====================================
//    /** Not sure I like this - could disappear. */
//    boolean hasNull() { return hasNull; }

    /** {@inheritDoc} */
    @Override public Equator equator() { return equator; }

    @Override public PersistentHashMap assoc(K key, V val) {
        if(key == null) {
            if (hasNull && (val == nullValue)) { return this; }
            return new PersistentHashMap<>(equator, hasNull ? size : size + 1, root, true, val);
        }
        Box addedLeaf = new Box<>(null);
        INode newroot = (root == null ? BitmapIndexedNode.empty(equator) : root);
        newroot = newroot.assoc(0, equator.hash(key), key, val, addedLeaf);
        if (newroot == root) {
            return this;
        }
        return new PersistentHashMap<>(equator, addedLeaf.val == null ? size : size + 1, newroot,
                                       hasNull, nullValue);
    }

    @Override public MutableHashMap mutable() {
        return new MutableHashMap<>(this);
    }

    @Override public Option> entry(K key) {
        if (key == null) {
            return hasNull ? Option.of(Tuple2.of(null, nullValue)) : Option.none();
        }
        if (root == null) {
            return Option.none();
        }
        UnEntry entry = root.find(0, equator.hash(key), key);
        return Option.someOrNullNoneOf(entry);
    }

    /**
     This is compatible with java.util.Map but that means it wrongly allows comparisons with
     SortedMaps, which are necessarily not commutative.  It also ignores the Equator.  As always,
     for meaningful equals, define an equator.

     @param other the other (hopefully unsorted) map to compare to.
     @return true if these maps contain the same elements, regardless of order.
     */
    @Override public boolean equals(Object other) {
        if (other == this) { return true; }
        if ( !(other instanceof Map) ) { return false; }

        Map that = (Map) other;
        if (that.size() != size()) { return false; }

        try {
            for (Entry e : this) {
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(that.get(key)==null && that.containsKey(key))) {
                        return false;
                    }
                } else {
                    if (!value.equals(that.get(key))) {
                        return false;
                    }
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

    @Override public int hashCode() { return UnmodIterable.hashCode(this); }

    // This identical to the Mutable version of this class below.
    @Override public UnmodIterator> iterator() {
        final UnmodIterator> rootIter = (root == null) ? emptyUnmodIterator()
                                                                    : root.iterator();
        return (hasNull) ? new Iter<>(rootIter, nullValue)
                         : rootIter;
    }

    @Override public final PersistentHashMap immutable() { return this; }

//    public  R kvreduce(Function3 f, R init) {
//        init = hasNull ? f.apply(init, null, nullValue) : init;
//        if(RT.isReduced(init))
//            return ((IDeref)init).deref();
//        if(root != null){
//            init = root.kvreduce(f,init);
//            if(RT.isReduced(init))
//                return ((IDeref)init).deref();
//            else
//                return init;
//        }
//        return init;
//    }

//    public  R fold(long n, final Function2 combinef, final Function3 reducef,
//                      Function1,R> fjinvoke, final Function1 fjtask,
//                      final Function1 fjfork, final Function1 fjjoin){
//        //we are ignoring n for now
//        Function0 top = () -> {
//            R ret = combinef.apply(null,null);
//            if(root != null)
//                ret = combinef.apply(ret, root.fold(combinef, reducef, fjtask, fjfork, fjjoin));
//            return hasNull ? combinef.apply(ret, reducef.apply(combinef.apply(null,null), null,
//                                            nullValue))
//                           : ret;
//        };
//        return fjinvoke.apply(top);
//    }

//    @SuppressWarnings("unchecked")
//    @Override public Sequence> seq() {
////        System.out.println("root: " + root);
//        Sequence> s = root != null ? root.nodeSeq() : Sequence.emptySequence();
//        return hasNull ? s.prepend((UnEntry) Tuple2.of((K) null, nullValue)) : s;
//    }

    /** {@inheritDoc} */
    @Override public int size() { return size; }

    /** {@inheritDoc} */
    @Override public String toString() { return UnmodIterable.toString("PersistentHashMap", this); }

    @SuppressWarnings("unchecked")
    @Override public PersistentHashMap without(K key){
        if(key == null)
            return hasNull ? new PersistentHashMap<>(equator, size - 1, root, false, null) : this;
        if(root == null)
            return this;
        INode newroot = root.without(0, equator.hash(key), key);
        if(newroot == root)
            return this;
        return new PersistentHashMap<>(equator, size - 1, newroot, hasNull, nullValue);
    }

    static final class MutableHashMap implements MutableUnsortedMap {
        private AtomicReference edit;
        private final Equator equator;
        private INode root;
        private int count;
        private boolean hasNull;
        private V nullValue;
        // This is a boolean reference, with value either being null, or set to point to the
        // box itself.  It might be clearer to replace this with an AtomicBoolean or similar.
        // I think the reason this can be a field instead of a local variable is that the
        // MutableHashMap is not intended to be thread safe, thus no-one will call one method
        // while another thread calls another method.  Presumably having this here saves the cost
        // of allocating a local variable.  Setting it to null or itself saves storing anything
        // in memory.  Why did Rich go to all of this trouble?  Does it make a difference, or
        // could he have just passed a local AtomicBoolean or made remove() return a pair (Boolean
        // and INode) or even OneOf(left INode, right INode)?
        private final Box leafFlag = new Box<>(null);

        MutableHashMap(PersistentHashMap m) {
            this(m.equator(), new AtomicReference<>(Thread.currentThread()), m.root, m.size,
                 m.hasNull, m.nullValue);
        }

        MutableHashMap(Equator e, AtomicReference edit, INode root, int count,
                       boolean hasNull, V nullValue) {
            this.equator = (e == null) ? Equator.defaultEquator() : e;
            this.edit = edit;
            this.root = root;
            this.count = count;
            this.hasNull = hasNull;
            this.nullValue = nullValue;
        }

        @Override public Equator equator() { return equator; }

        @Override public MutableHashMap assoc(K key, V val) {
            ensureEditable();
            if (key == null) {
                if (this.nullValue != val)
                    this.nullValue = val;
                if (!hasNull) {
                    this.count++;
                    this.hasNull = true;
                }
                return this;
            }
//        Box leafFlag = new Box(null);
            leafFlag.val = null;
            INode n = (root == null ? BitmapIndexedNode.empty(equator) : root);
            n = n.assoc(edit, 0, equator.hash(key), key, val, leafFlag);
            if (n != this.root)
                this.root = n;
            if(leafFlag.val != null) this.count++;
            return this;
        }

        @Override public Option> entry(K key) {
            ensureEditable();
            if (key == null) {
                return hasNull ? Option.of(Tuple2.of(null, nullValue)) : Option.none();
            }
            if (root == null) {
                return Option.none();
            }
            UnEntry entry = root.find(0, equator.hash(key), key);
            return Option.someOrNullNoneOf(entry);
        }

//        @Override
//        @SuppressWarnings("unchecked")
//        public Sequence> seq() {
//            Sequence> s = root != null ? root.nodeSeq() : Sequence.emptySequence();
//            return hasNull ? s.prepend((UnEntry) Tuple2.of((K) null, nullValue)) : s;
//        }

        // This is a duplicate of the same method in the Persistent version of this class above.
        @Override public UnmodIterator> iterator() {
            final UnmodIterator> rootIter = (root == null) ? emptyUnmodIterator()
                                                                        : root.iterator();
            return (hasNull) ? new Iter<>(rootIter, nullValue)
                             : rootIter;
        }

        @Override public final MutableHashMap without(K key) {
            ensureEditable();
            if (key == null) {
                if (!hasNull) return this;
                hasNull = false;
                nullValue = null;
                this.count--;
                return this;
            }
            if (root == null) return this;
// Box leafFlag = new Box(null);
            leafFlag.val = null;
            INode n = root.without(edit, 0, equator.hash(key), key, leafFlag);
            if (n != root)
                this.root = n;
            if(leafFlag.val != null) this.count--;
            return this;
        }

        @Override public final PersistentHashMap immutable() {
            ensureEditable();
            edit.set(null);
            return new PersistentHashMap<>(equator, count, root, hasNull, nullValue);
        }

        @Override public final int size() {
            ensureEditable();
            return count;
        }

        void ensureEditable() {
            if(edit.get() == null)
                throw new IllegalAccessError("Mutable used after immutable! call");
        }
    }

    private interface INode {
        INode assoc(int shift, int hash, K key, V val, Box addedLeaf);

        INode without(int shift, int hash, K key);

        UnEntry find(int shift, int hash, K key);

//        V findVal(int shift, int hash, K key, V notFound);

//        Sequence> nodeSeq();

        INode assoc(AtomicReference edit, int shift, int hash, K key, V val,
                         Box addedLeaf);

        INode without(AtomicReference edit, int shift, int hash, K key,
                           Box removedLeaf);

//         R kvreduce(Function3 f, R init);

//         R fold(Function2 combinef, Function3 reducef,
//                   final Function1 fjtask,
//                   final Function1 fjfork, final Function1 fjjoin);

        UnmodIterator> iterator();
    }

    private final static class ArrayNode implements INode, UnmodIterable> {
        private final Equator equator;
        int count;
        final INode[] array;
        final AtomicReference edit;

        ArrayNode(Equator eq, AtomicReference edit, int count, INode[] array){
            this.equator = eq;
            this.array = array;
            this.edit = edit;
            this.count = count;
        }

        @Override public INode assoc(int shift, int hash, K key, V val, Box addedLeaf) {
            int idx = mask(hash, shift);
            INode node = array[idx];
            if (node == null) {
                BitmapIndexedNode e = BitmapIndexedNode.empty(equator);
                INode n = e.assoc(shift + 5, hash, key, val, addedLeaf);
                return new ArrayNode<>(equator, null, count + 1, cloneAndSet(array, idx, n));
            }
            INode n = node.assoc(shift + 5, hash, key, val, addedLeaf);
            if (n == node) {
                return this;
            }
            return new ArrayNode<>(equator, null, count, cloneAndSet(array, idx, n));
        }

        @Override public INode without(int shift, int hash, K key){
            int idx = mask(hash, shift);
            INode node = array[idx];
            if(node == null)
                return this;
            INode n = node.without(shift + 5, hash, key);
            if(n == node)
                return this;
            if (n == null) {
                if (count <= 8) {
                    // shrink
                    return pack(null, idx);
                }
                return new ArrayNode<>(equator, null, count - 1, cloneAndSet(array, idx, null));
            } else
                return new ArrayNode<>(equator, null, count, cloneAndSet(array, idx, n));
        }

        @Override public UnmodMap.UnEntry find(int shift, int hash, K key) {
            int idx = mask(hash, shift);
            INode node = array[idx];
            if(node == null)
                return null;
            return node.find(shift + 5, hash, key);
        }

//        @Override public V findVal(int shift, int hash, K key, V notFound){
//            int idx = mask(hash, shift);
//            INode node = array[idx];
//            if(node == null)
//                return notFound;
//            return node.findVal(shift + 5, hash, key, notFound);
//        }

//        @Override public Sequence> nodeSeq(){ return Seq.create(array); }

        @Override public UnmodIterator> iterator() {
            return new Iter<>(array);
        }

//        @Override public  R kvreduce(Function3 f, R init){
//            for(INode node : array){
//                if(node != null){
//                    init = node.kvreduce(f,init);
//                    if(isReduced(init))
//                        return init;
//                }
//            }
//            return init;
//        }
//        @Override public  R fold(Function2 combinef, Function3 reducef,
//                                    final Function1 fjtask,
//                                    final Function1 fjfork,
//                                    final Function1 fjjoin){
//            List> tasks = new ArrayList<>();
//            for(final INode node : array){
//                if(node != null){
//                    tasks.add(() -> node.fold(combinef, reducef, fjtask, fjfork, fjjoin));
//                }
//            }
//
//            return foldTasks(tasks,combinef,fjtask,fjfork,fjjoin);
//        }

//        static private  R foldTasks(List> tasks, final Function2 combinef,
//                                       final Function1 fjtask,
//                                       final Function1 fjfork,
//                                       final Function1 fjjoin) {
//
//            if(tasks.isEmpty())
//                return combinef.apply(null,null);
//
//            if(tasks.size() == 1){
//                try {
//                    return tasks.get(0).call();
//                } catch (RuntimeException re) {
//                    throw re;
//                } catch (Exception e) {
//                    throw new RuntimeException(e);
//                }
//            }
//
//            List> t1 = tasks.subList(0,tasks.size()/2);
//            final List> t2 = tasks.subList(tasks.size()/2, tasks.size());
//
//            Object forked = fjfork.apply(fjtask.apply(() -> foldTasks(t2, combinef, fjtask,
//                                         fjfork, fjjoin)));
//
//            return combinef.apply(foldTasks(t1, combinef, fjtask, fjfork, fjjoin),
//                                  fjjoin.apply(forked));
//        }


        private ArrayNode ensureEditable(AtomicReference edit){
            if(this.edit == edit)
                return this;
            return new ArrayNode<>(equator, edit, count, this.array.clone());
        }

        private ArrayNode editAndSet(AtomicReference edit, int i, INode n) {
            ArrayNode editable = ensureEditable(edit);
            editable.array[i] = n;
            return editable;
        }


        private INode pack(AtomicReference edit, int idx) {
            Object[] newArray = new Object[2*(count - 1)];
            int j = 1;
            int bitmap = 0;
            for(int i = 0; i < idx; i++)
                if (array[i] != null) {
                    newArray[j] = array[i];
                    bitmap |= 1 << i;
                    j += 2;
                }
            for(int i = idx + 1; i < array.length; i++)
                if (array[i] != null) {
                    newArray[j] = array[i];
                    bitmap |= 1 << i;
                    j += 2;
                }
            return new BitmapIndexedNode<>(equator, edit, bitmap, newArray);
        }

        @Override public INode assoc(AtomicReference edit, int shift, int hash,
                                          K key, V val, Box addedLeaf) {
            int idx = mask(hash, shift);
            INode node = array[idx];
            if(node == null) {
                BitmapIndexedNode en = BitmapIndexedNode.empty(equator);
                ArrayNode editable = editAndSet(edit, idx, en.assoc(edit, shift + 5, hash,
                                                                         key, val, addedLeaf));
                editable.count++;
                return editable;
            }
            INode n = node.assoc(edit, shift + 5, hash, key, val, addedLeaf);
            if(n == node)
                return this;
            return editAndSet(edit, idx, n);
        }

        @Override
        public INode without(AtomicReference edit, int shift, int hash, K key,
                                  Box removedLeaf) {
            int idx = mask(hash, shift);
            INode node = array[idx];
            if (node == null) {
                return this;
            }
            INode n = node.without(edit, shift + 5, hash, key, removedLeaf);
            if (n == node) {
                return this;
            }
            if (n == null) {
                if (count <= 8) // shrink
                    return pack(edit, idx);
                ArrayNode editable = editAndSet(edit, idx, null);
                editable.count--;
                return editable;
            }
            return editAndSet(edit, idx, n);
        }

        @Override public String toString() {
            return UnmodIterable.toString("ArrayNode", this);
        }

        private static class Iter implements UnmodIterator> {
//            , Serializable {
//            // For serializable.  Make sure to change whenever internal data format changes.
//            private static final long serialVersionUID = 20160903192900L;

            private final INode[] array;
            private int i = 0;
            private UnmodIterator> nestedIter;

            private Iter(INode[] array){
                this.array = array;
            }

            @Override public boolean hasNext(){
                while(true) {
                    if (nestedIter != null) {
                        if (nestedIter.hasNext()) {
                            return true;
                        } else {
                            nestedIter = null;
                        }
                    }
                    if (i < array.length) {
                        INode node = array[i++];
                        if (node != null) {
                            nestedIter = node.iterator();
                        }
                    } else {
                        return false;
                    }
                }
            }

            @Override public UnEntry next(){
                if (hasNext()) {
                    return nestedIter.next();
                } else {
                    throw new NoSuchElementException();
                }
            }
        }
    } // end class ArrayNode

    @SuppressWarnings("unchecked")
    private final static class BitmapIndexedNode implements INode {
//        static final BitmapIndexedNode EMPTY = new BitmapIndexedNode(null, 0, new Object[0]);

        static final  BitmapIndexedNode empty(Equator e) {
            return new BitmapIndexedNode(e, null, 0, new Object[0]);
        }

        private final Equator equator;
        int bitmap;
        // even numbered cells are key or null, odd are val or node.
        Object[] array;
        final AtomicReference edit;

        @Override public String toString() {
            return "BitmapIndexedNode(" + bitmap + "," + FunctionUtils.arrayToString(array) + "," +
                   edit + ")";
        }

        final int index(int bit) { return Integer.bitCount(bitmap & (bit - 1)); }

        BitmapIndexedNode(Equator equator, AtomicReference edit, int bitmap,
                          Object[] array) {
            this.equator = equator;
            this.bitmap = bitmap;
            this.array = array;
            this.edit = edit;
        }

        @Override public INode assoc(int shift, int hash, K key, V val, Box addedLeaf) {
            int bit = bitpos(hash, shift);
            int idx = index(bit);
            if((bitmap & bit) != 0) {
                K keyOrNull = k(array, 2*idx);
                Object valOrNode = array[2*idx+1];
                if(keyOrNull == null) {
                    INode n = ((INode) valOrNode).assoc(shift + 5, hash, key, val, addedLeaf);
                    if(n == valOrNode)
                        return this;
                    return new BitmapIndexedNode<>(equator, null, bitmap,
                                                   cloneAndSet(array, 2*idx+1, n));
                }
                if(equator.eq(key, keyOrNull)) {
                    if(val == valOrNode)
                        return this;
                    return new BitmapIndexedNode<>(equator, null, bitmap,
                                                   cloneAndSet(array, 2*idx+1, val));
                }
                addedLeaf.val = addedLeaf;
                return new BitmapIndexedNode<>(equator, null, bitmap,
                                               cloneAndSet(array, 2*idx, null, 2*idx+1,
                                                           createNode(equator, shift + 5, keyOrNull,
                                                                      valOrNode, hash, key, val)));
            } else {
                int n = Integer.bitCount(bitmap);
                if(n >= 16) {
                    INode[] nodes = new INode[32];
                    int jdx = mask(hash, shift);
                    nodes[jdx] = empty(equator).assoc(shift + 5, hash, key, val, addedLeaf);
                    int j = 0;
                    for(int i = 0; i < 32; i++)
                        if(((bitmap >>> i) & 1) != 0) {
                            if (array[j] == null)
                                nodes[i] = (INode) array[j+1];
                            else
                                nodes[i] = empty(equator).assoc(shift + 5,
                                                                equator.hash(k(array, j)),
                                                                k(array, j), array[j + 1],
                                                                addedLeaf);
                            j += 2;
                        }
                    return new ArrayNode(equator, null, n + 1, nodes);
                } else {
                    Object[] newArray = new Object[2*(n+1)];
                    System.arraycopy(array, 0, newArray, 0, 2*idx);
                    newArray[2*idx] = key;
                    addedLeaf.val = addedLeaf;
                    newArray[2*idx+1] = val;
                    System.arraycopy(array, 2*idx, newArray, 2*(idx+1), 2*(n-idx));
                    return new BitmapIndexedNode<>(equator, null, bitmap | bit, newArray);
                }
            }
        }

        @Override public INode without(int shift, int hash, K key){
            int bit = bitpos(hash, shift);
            if((bitmap & bit) == 0)
                return this;
            int idx = index(bit);
            K keyOrNull = (K) array[2*idx];
            Object valOrNode = array[2*idx+1];
            if(keyOrNull == null) {
                INode n = ((INode) valOrNode).without(shift + 5, hash, key);
                if (n == valOrNode)
                    return this;
                if (n != null)
                    return new BitmapIndexedNode<>(equator, null, bitmap, cloneAndSet(array,
                                                                                      2*idx+1, n));
                if (bitmap == bit)
                    return null;
                return new BitmapIndexedNode<>(equator, null, bitmap ^ bit, removePair(array, idx));
            }
            if(equator.eq(key, keyOrNull))
                // TODO: collapse
                return new BitmapIndexedNode<>(equator, null, bitmap ^ bit, removePair(array, idx));
            return this;
        }

        @Override public UnEntry find(int shift, int hash, K key){
            int bit = bitpos(hash, shift);
            if((bitmap & bit) == 0)
                return null;
            int idx = index(bit);
            K keyOrNull = k(array, 2*idx);
            Object valOrNode = array[2*idx+1];
            if(keyOrNull == null)
                return ((INode) valOrNode).find(shift + 5, hash, key);
            if(equator.eq(key, keyOrNull))
                return Tuple2.of(keyOrNull, (V) valOrNode);
            return null;
        }

//        @Override public V findVal(int shift, int hash, K key, V notFound) {
//            int bit = bitpos(hash, shift);
//            if ((bitmap & bit) == 0) {
//                return notFound;
//            }
//            int idx = index(bit);
//            K keyOrNull = k(array, 2 * idx);
//            if (keyOrNull == null) {
//                INode n = iNode(array, 2 * idx + 1);
//                return n.findVal(shift + 5, hash, key, notFound);
//            }
//            if (equator.eq(key, keyOrNull)) {
//                return v(array, 2 * idx + 1);
//            }
//            return notFound;
//        }

//        @Override public Sequence> nodeSeq() { return NodeSeq.create(array); }

        @Override public UnmodIterator> iterator(){
            return new NodeIter<>(array);
        }

//        @Override public  R kvreduce(Function3 f, R init){
//            return doKvreduce(array, f, init);
//        }

//        @Override public  R fold(Function2 combinef, Function3 reducef,
//                                    final Function1 fjtask,
//                                    final Function1 fjfork,
//                                    final Function1 fjjoin){
//            return doKvreduce(array, reducef, combinef.apply(null, null));
//        }

        private BitmapIndexedNode ensureEditable(AtomicReference edit){
            if(this.edit == edit)
                return this;
            int n = Integer.bitCount(bitmap);
            Object[] newArray = new Object[n >= 0 ? 2*(n+1) : 4]; // make room for next assoc
            System.arraycopy(array, 0, newArray, 0, 2*n);
            return new BitmapIndexedNode<>(equator, edit, bitmap, newArray);
        }

        private BitmapIndexedNode editAndSet(AtomicReference edit, int i, Object a) {
            BitmapIndexedNode editable = ensureEditable(edit);
            editable.array[i] = a;
            return editable;
        }

        private BitmapIndexedNode editAndSet(AtomicReference edit, int i, Object a,
                                                  int j, Object b) {
            BitmapIndexedNode editable = ensureEditable(edit);
            editable.array[i] = a;
            editable.array[j] = b;
            return editable;
        }

        private BitmapIndexedNode editAndRemovePair(AtomicReference edit, int bit,
                                                         int i) {
            if (bitmap == bit)
                return null;
            BitmapIndexedNode editable = ensureEditable(edit);
            editable.bitmap ^= bit;
            System.arraycopy(editable.array, 2*(i+1), editable.array, 2*i,
                             editable.array.length - 2*(i+1));
            editable.array[editable.array.length - 2] = null;
            editable.array[editable.array.length - 1] = null;
            return editable;
        }

        @Override public INode assoc(AtomicReference edit, int shift, int hash,
                                          K key, V val, Box addedLeaf) {
            int bit = bitpos(hash, shift);
            int idx = index(bit);
            if((bitmap & bit) != 0) {
                K keyOrNull = k(array, 2*idx);
                Object valOrNode = array[2*idx+1];
                if(keyOrNull == null) {
                    INode n = ((INode) valOrNode).assoc(edit, shift + 5, hash, key, val,
                                                                  addedLeaf);
                    if(n == valOrNode)
                        return this;
                    return editAndSet(edit, 2*idx+1, n);
                }
                if(equator.eq(key, keyOrNull)) {
                    if(val == valOrNode)
                        return this;
                    return editAndSet(edit, 2*idx+1, val);
                }
                addedLeaf.val = addedLeaf;
                return editAndSet(edit, 2*idx, null, 2*idx+1,
                                  createNode(equator, edit, shift + 5, keyOrNull, valOrNode, hash,
                                             key, val));
            } else {
                int n = Integer.bitCount(bitmap);
                if(n*2 < array.length) {
                    addedLeaf.val = addedLeaf;
                    BitmapIndexedNode editable = ensureEditable(edit);
                    System.arraycopy(editable.array, 2*idx, editable.array, 2*(idx+1), 2*(n-idx));
                    editable.array[2*idx] = key;
                    editable.array[2*idx+1] = val;
                    editable.bitmap |= bit;
                    return editable;
                }
                if(n >= 16) {
                    INode[] nodes = new INode[32];
                    int jdx = mask(hash, shift);
                    nodes[jdx] = empty(equator).assoc(edit, shift + 5, hash, key, val, addedLeaf);
                    int j = 0;
                    for(int i = 0; i < 32; i++)
                        if(((bitmap >>> i) & 1) != 0) {
                            if (array[j] == null)
                                nodes[i] = (INode) array[j+1];
                            else
                                nodes[i] = empty(equator).assoc(edit, shift + 5,
                                                                equator.hash(k(array, j)),
                                                                k(array, j), array[j + 1],
                                                                addedLeaf);
                            j += 2;
                        }
                    return new ArrayNode(equator, edit, n + 1, nodes);
                } else {
                    Object[] newArray = new Object[2*(n+4)];
                    System.arraycopy(array, 0, newArray, 0, 2*idx);
                    newArray[2*idx] = key;
                    addedLeaf.val = addedLeaf;
                    newArray[2*idx+1] = val;
                    System.arraycopy(array, 2*idx, newArray, 2*(idx+1), 2*(n-idx));
                    BitmapIndexedNode editable = ensureEditable(edit);
                    editable.array = newArray;
                    editable.bitmap |= bit;
                    return editable;
                }
            }
        }

        @Override public INode without(AtomicReference edit, int shift, int hash,
                                            K key, Box removedLeaf){
            int bit = bitpos(hash, shift);
            if((bitmap & bit) == 0)
                return this;
            int idx = index(bit);
            K keyOrNull = k(array, 2*idx);
            Object valOrNode = array[2*idx+1];
            if(keyOrNull == null) {
                INode n = ((INode) valOrNode).without(edit, shift + 5, hash, key, removedLeaf);
                if (n == valOrNode)
                    return this;
                if (n != null)
                    return editAndSet(edit, 2*idx+1, n);
                if (bitmap == bit)
                    return null;
                return editAndRemovePair(edit, bit, idx);
            }
            if(equator.eq(key, keyOrNull)) {
                removedLeaf.val = removedLeaf;
                // TODO: collapse
                return editAndRemovePair(edit, bit, idx);
            }
            return this;
        }
    }

    private final static class HashCollisionNode implements INode{
        private final Equator equator;
        final int hash;
        int count;
        Object[] array;
        final AtomicReference edit;

        HashCollisionNode(Equator eq, AtomicReference edit, int hash, int count,
                          Object... array){
            this.equator = eq;
            this.edit = edit;
            this.hash = hash;
            this.count = count;
            this.array = array;
        }

        @Override public INode assoc(int shift, int hash, K key, V val, Box addedLeaf) {
            if(hash == this.hash) {
                int idx = findIndex(key);
                if(idx != -1) {
                    if(array[idx + 1] == val)
                        return this;
                    return new HashCollisionNode<>(equator, null, hash, count,
                                                   cloneAndSet(array, idx + 1, val));
                }
                Object[] newArray = new Object[2 * (count + 1)];
                System.arraycopy(array, 0, newArray, 0, 2 * count);
                newArray[2 * count] = key;
                newArray[2 * count + 1] = val;
                addedLeaf.val = addedLeaf;
                return new HashCollisionNode<>(equator, edit, hash, count + 1, newArray);
            }
            // nest it in a bitmap node
            return new BitmapIndexedNode(equator, null, bitpos(this.hash, shift),
                                              new Object[] {null, this})
                    .assoc(shift, hash, key, val, addedLeaf);
        }

        @Override public INode without(int shift, int hash, K key){
            int idx = findIndex(key);
            if(idx == -1)
                return this;
            if(count == 1)
                return null;
            return new HashCollisionNode<>(equator, null, hash, count - 1,
                                           removePair(array, idx/2));
        }

        @Override public UnmodMap.UnEntry find(int shift, int hash, K key){
            int idx = findIndex(key);
            if(idx < 0)
                return null;
            if(equator.eq(key, k(array, idx)))
                return Tuple2.of(k(array, idx), v(array, idx + 1));
            return null;
        }

//        @Override public V findVal(int shift, int hash, K key, V notFound){
//            int idx = findIndex(key);
//            if(idx < 0)
//                return notFound;
//            if (equator.eq(key, k(array, idx))) {
//                return v(array, idx + 1);
//            }
//            return notFound;
//        }

//        @Override public Sequence> nodeSeq() { return NodeSeq.create(array); }

        @Override public UnmodIterator> iterator() { return new NodeIter<>(array); }

//        @Override public  R kvreduce(Function3 f, R init){
//            return doKvreduce(array, f, init);
//        }

//        @Override public  R fold(Function2 combinef, Function3 reducef,
//                                    final Function1 fjtask,
//                                    final Function1 fjfork,
//                                    final Function1 fjjoin){
//            return doKvreduce(array, reducef, combinef.apply(null, null));
//        }

        private int findIndex(K key){
            for (int i = 0; i < 2*count; i+=2) {
                if (equator.eq(key, k(array, i))) { return i; }
            }
            return -1;
        }

        private HashCollisionNode ensureEditable(AtomicReference edit){
            if(this.edit == edit)
                return this;
            Object[] newArray = new Object[2*(count+1)]; // make room for next assoc
            System.arraycopy(array, 0, newArray, 0, 2*count);
            return new HashCollisionNode<>(equator, edit, hash, count, newArray);
        }

        private HashCollisionNode ensureEditable(AtomicReference edit, int count,
                                                      Object[] array){
            if(this.edit == edit) {
                this.array = array;
                this.count = count;
                return this;
            }
            return new HashCollisionNode<>(equator, edit, hash, count, array);
        }

        private HashCollisionNode editAndSet(AtomicReference edit, int i, Object a) {
            HashCollisionNode editable = ensureEditable(edit);
            editable.array[i] = a;
            return editable;
        }

        private HashCollisionNode editAndSet(AtomicReference edit, int i, Object a,
                                                  int j, Object b) {
            HashCollisionNode editable = ensureEditable(edit);
            editable.array[i] = a;
            editable.array[j] = b;
            return editable;
        }


        @Override public INode assoc(AtomicReference edit, int shift, int hash,
                                          K key, V val, Box addedLeaf) {
            if(hash == this.hash) {
                int idx = findIndex(key);
                if(idx != -1) {
                    if(array[idx + 1] == val)
                        return this;
                    return editAndSet(edit, idx+1, val);
                }
                if (array.length > 2*count) {
                    addedLeaf.val = addedLeaf;
                    HashCollisionNode editable =
                            editAndSet(edit, 2*count, key, 2*count+1, val);
                    editable.count++;
                    return editable;
                }
                Object[] newArray = new Object[array.length + 2];
                System.arraycopy(array, 0, newArray, 0, array.length);
                newArray[array.length] = key;
                newArray[array.length + 1] = val;
                addedLeaf.val = addedLeaf;
                return ensureEditable(edit, count + 1, newArray);
            }
            // nest it in a bitmap node
            return new BitmapIndexedNode(equator, edit, bitpos(this.hash, shift),
                                              new Object[] {null, this, null, null})
                    .assoc(edit, shift, hash, key, val, addedLeaf);
        }

        @Override public INode without(AtomicReference edit, int shift, int hash,
                                            K key, Box removedLeaf) {
            int idx = findIndex(key);
            if(idx == -1)
                return this;
            removedLeaf.val = removedLeaf;
            if(count == 1)
                return null;
            HashCollisionNode editable = ensureEditable(edit);
            editable.array[idx] = editable.array[2*count-2];
            editable.array[idx+1] = editable.array[2*count-1];
            editable.array[2*count-2] = editable.array[2*count-1] = null;
            editable.count--;
            return editable;
        }
    }

/*
public static void main(String[] args){
    try
        {
        ArrayList words = new ArrayList();
        Scanner s = new Scanner(new File(args[0]));
        s.useDelimiter(Pattern.compile("\\W"));
        while(s.hasNext())
            {
            String word = s.next();
            words.add(word);
            }
        System.out.println("words: " + words.size());
        ImMap map = PersistentHashMap.EMPTY;
        //ImMap map = new PersistentHashMap();
        //Map ht = new Hashtable();
        Map ht = new HashMap();
        Random rand;

        System.out.println("Building map");
        long startTime = System.nanoTime();
        for(Object word5 : words)
            {
            map = map.assoc(word5, word5);
            }
        rand = new Random(42);
        ImMap snapshotMap = map;
        for(int i = 0; i < words.size() / 200; i++)
            {
            map = map.without(words.get(rand.nextInt(words.size() / 2)));
            }
        long estimatedTime = System.nanoTime() - startTime;
        System.out.println("size = " + map.size() + ", time: " + estimatedTime / 1000000);

        System.out.println("Building ht");
        startTime = System.nanoTime();
        for(Object word1 : words)
            {
            ht.put(word1, word1);
            }
        rand = new Random(42);
        for(int i = 0; i < words.size() / 200; i++)
            {
            ht.remove(words.get(rand.nextInt(words.size() / 2)));
            }
        estimatedTime = System.nanoTime() - startTime;
        System.out.println("size = " + ht.size() + ", time: " + estimatedTime / 1000000);

        System.out.println("map lookup");
        startTime = System.nanoTime();
        int c = 0;
        for(Object word2 : words)
            {
            if(!map.contains(word2))
                ++c;
            }
        estimatedTime = System.nanoTime() - startTime;
        System.out.println("notfound = " + c + ", time: " + estimatedTime / 1000000);
        System.out.println("ht lookup");
        startTime = System.nanoTime();
        c = 0;
        for(Object word3 : words)
            {
            if(!ht.containsKey(word3))
                ++c;
            }
        estimatedTime = System.nanoTime() - startTime;
        System.out.println("notfound = " + c + ", time: " + estimatedTime / 1000000);
        System.out.println("snapshotMap lookup");
        startTime = System.nanoTime();
        c = 0;
        for(Object word4 : words)
            {
            if(!snapshotMap.contains(word4))
                ++c;
            }
        estimatedTime = System.nanoTime() - startTime;
        System.out.println("notfound = " + c + ", time: " + estimatedTime / 1000000);
        }
    catch(FileNotFoundException e)
        {
        e.printStackTrace();
        }

}
*/

    private static  INode[] cloneAndSet(INode[] array, int i, INode a) {
        INode[] clone = array.clone();
        clone[i] = a;
        return clone;
    }

    private static Object[] cloneAndSet(Object[] array, int i, Object a) {
        Object[] clone = array.clone();
        clone[i] = a;
        return clone;
    }

    private static Object[] cloneAndSet(Object[] array, int i, Object a, int j, Object b) {
        Object[] clone = array.clone();
        clone[i] = a;
        clone[j] = b;
        return clone;
    }

    private static Object[] removePair(Object[] array, int i) {
        Object[] newArray = new Object[array.length - 2];
        System.arraycopy(array, 0, newArray, 0, 2*i);
        System.arraycopy(array, 2*(i+1), newArray, 2*i, newArray.length - 2*i);
        return newArray;
    }

    private static  INode createNode(Equator equator, int shift, K key1, V val1,
                                               int key2hash, K key2, V val2) {
        int key1hash = equator.hash(key1);
        if(key1hash == key2hash)
            return new HashCollisionNode<>(equator, null, key1hash, 2,
                                           new Object[] {key1, val1, key2, val2});
        Box addedLeaf = new Box<>(null);
        AtomicReference edit = new AtomicReference<>();
        return BitmapIndexedNode.empty(equator)
                .assoc(edit, shift, key1hash, key1, val1, addedLeaf)
                .assoc(edit, shift, key2hash, key2, val2, addedLeaf);
    }

    private static  INode createNode(Equator equator, AtomicReference edit,
                                               int shift, K key1, V val1, int key2hash,
                                               K key2, V val2) {
        int key1hash = equator.hash(key1);
        if(key1hash == key2hash)
            return new HashCollisionNode<>(equator, null, key1hash, 2,
                                           new Object[] {key1, val1, key2, val2});
        Box addedLeaf = new Box<>(null);
        return BitmapIndexedNode.empty(equator)
                .assoc(edit, shift, key1hash, key1, val1, addedLeaf)
                .assoc(edit, shift, key2hash, key2, val2, addedLeaf);
    }

    private static int bitpos(int hash, int shift){
        return 1 << mask(hash, shift);
    }

    private static final class NodeIter implements UnmodIterator> {
//        , Serializable {
//        // For serializable.  Make sure to change whenever internal data format changes.
//        private static final long serialVersionUID = 20160903192900L;

        enum Absent implements UnEntry {
            ENTRY {
                @Override public Object getKey() {
                    throw new UnsupportedOperationException("Should be Unreachable.");
                }
                @Override public Object getValue() {
                    throw new UnsupportedOperationException("Should be Unreachable.");
                }
            }
        }
        @SuppressWarnings("unchecked")
        private static  UnEntry absence() { return (UnEntry) Absent.ENTRY; }

        final Object[] array;
        private int mutableIndex = 0;
        private UnEntry nextEntry = absence();
        private Iterator> nextIter;

        NodeIter(Object[] array){
            this.array = array;
        }

        private boolean advance(){
//            while (i < array.length) {
//                K key = k(array, i);
//                Object nodeOrVal = array[i+1];
//                i += 2;
//                if (key != null) {
//                    nextEntry = Tuple2.of(key, (V) nodeOrVal);
//                    return true;
//                } else if(nodeOrVal != null) {
//                    Iterator> iter = ((INode) nodeOrVal).iterator();
//                    if (iter != null && iter.hasNext()) {
//                        nextIter = iter;
//                        return true;
//                    }
//                }
//            }
            while (mutableIndex < array.length) {
                int i = mutableIndex;
                mutableIndex = i + 2;
                if (array[i] != null) {
                    nextEntry = Tuple2.of(k(array, i), v(array, i + 1));
                    return true;
                } else {
                    INode node = iNode(array, i + 1);
                    if (node != null) {
                        Iterator> iter = node.iterator();
                        if (iter != null && iter.hasNext()) {
                            nextIter = iter;
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        @Override public boolean hasNext(){
            if (nextEntry != Absent.ENTRY || nextIter != null) {
                return true;
            }
            return advance();
        }

        @Override public UnEntry next(){
            UnEntry ret = nextEntry;
            if(ret != Absent.ENTRY) {
                nextEntry = absence();
                return ret;
            } else if(nextIter != null) {
                ret = nextIter.next();
                if ( !nextIter.hasNext()) {
                    nextIter = null;
                }
                return ret;
            } else if(advance()) {
                return next();
            }
            throw new NoSuchElementException();
        }
    }

//    static final class NodeSeq implements Sequence> {
//        private final Object[] array;
//        private final int i;
//        private final Sequence> s;
//
//        static  Sequence> create(Object[] array) {
//            return create(array, 0, null);
//        }
//
//        private static  Sequence> create(Object[] array, int i,
//                                                              Sequence> s) {
//            if ( (s != null) && (s != Sequence.EMPTY_SEQUENCE) ) {
//                return new NodeSeq<>(array, i, s);
//            }
//
//            for (int j = i; j < array.length; j += 2) {
//                if (array[j] != null) { return new NodeSeq<>(array, j, null); }
//
//                INode node = iNode(array, j + 1);
//                if (node != null) {
//                    Sequence> nodeSeq = node.nodeSeq();
//
//                    if (nodeSeq != null) { return new NodeSeq<>(array, j + 2, nodeSeq); }
//                }
//            }
//            return Sequence.emptySequence();
//        }
//
//        private NodeSeq(Object[] array, int i, Sequence> s) {
//            super();
//            this.array = array;
//            this.i = i;
//            this.s = s;
//        }
//
//        @Override public Option> head() {
//            return ( (s != null) && (s != Sequence.EMPTY_SEQUENCE) ) ? s.head() :
//                   i < array.length - 1 ? Option.of(Tuple2.of(k(array, i), v(array, i+1))) :
//                   Option.none();
//        }
//
//        @Override public Sequence> tail() {
//            if ( (s != null) && (s != Sequence.EMPTY_SEQUENCE) ) {
//                return create(array, i, s.tail());
//            }
//            return create(array, i + 2, null);
//        }
//
//        @Override public String toString() { return UnmodIterable.toString("NodeSeq", this); }
//
//    } // end class NodeSeq
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy