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

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

/*
 *   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.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicReference;

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

import static org.organicdesign.fp.collections.UnmodIterator.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 extends AbstractUnmodMap
        implements ImMap, Serializable {

//    static private  R doKvreduce(Object[] array, Fn3 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() {
            //noinspection SimplifiableIfStatement
            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; }

    /** Works around some type inference limitations of Java 8. */
    public static  MutableHashMap emptyMutable() {
        return PersistentHashMap.empty().mutable();
    }

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

    /** Works around some type inference limitations of Java 8. */
    public static  MutableHashMap emptyMutable(Equator e) {
        return PersistentHashMap.empty(e).mutable();
    }


//    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.
     */
    @SuppressWarnings("WeakerAccess")
    public static  PersistentHashMap ofEq(Equator eq, Iterable> es) {
        if (es == null) { return empty(eq); }
        MutableHashMap map = emptyMutable(eq);
        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 ImMap 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();
            MutableMap tempMap =
                    new PersistentHashMap(equator, 0, null, false, null).mutable();
            for (int i = 0; i < size; i++) {
                tempMap.assoc(s.readObject(), s.readObject());
            }
            theMap = tempMap.immutable();
        }

        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 =====================================
//    /** 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.some(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 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;
    }

//    public  R kvreduce(Fn3 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 Fn2 combinef, final Fn3 reducef,
//                      Fn1,R> fjinvoke, final Fn1 fjtask,
//                      final Fn1 fjfork, final Fn1 fjjoin){
//        //we are ignoring n for now
//        Fn0 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; }

    @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);
    }

    public static final class MutableHashMap extends AbstractUnmodMap
            implements MutableMap {

        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);

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

        private 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.some(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;
        }

        private 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(Fn3 f, R init);

//         R fold(Fn2 combinef, Fn3 reducef,
//                   final Fn1 fjtask,
//                   final Fn1 fjfork, final Fn1 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(Fn3 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(Fn2 combinef, Fn3 reducef,
//                                    final Fn1 fjtask,
//                                    final Fn1 fjfork,
//                                    final Fn1 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 Fn2 combinef,
//                                       final Fn1 fjtask,
//                                       final Fn1 fjfork,
//                                       final Fn1 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  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 + "," + Arrays.toString(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, 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(Fn3 f, R init){
//            return doKvreduce(array, f, init);
//        }

//        @Override public  R fold(Fn2 combinef, Fn3 reducef,
//                                    final Fn1 fjtask,
//                                    final Fn1 fjfork,
//                                    final Fn1 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,
                                                  int j, Object b) {
            BitmapIndexedNode editable = ensureEditable(edit);
            editable.array[i] = null;
            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, 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(Fn3 f, R init){
//            return doKvreduce(array, f, init);
//        }

//        @Override public  R fold(Fn2 combinef, Fn3 reducef,
//                                    final Fn1 fjtask,
//                                    final Fn1 fjfork,
//                                    final Fn1 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, int j, Object b) {
        Object[] clone = array.clone();
        clone[i] = null;
        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(){
            //noinspection SimplifiableIfStatement
            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