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 org.organicdesign.fp.FunctionUtils;
import org.organicdesign.fp.Option;
import org.organicdesign.fp.function.Function0;
import org.organicdesign.fp.function.Function1;
import org.organicdesign.fp.function.Function2;
import org.organicdesign.fp.function.Function3;
import org.organicdesign.fp.tuple.Tuple2;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

/**
 Rich Hickey's persistent 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
 */
public class PersistentHashMap implements ImMapTrans {

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

    // TODO: Replace with Mutable.Ref, or make methods return Tuple2.
    private static class Box {
        public Object val;
        public Box(Object val) { this.val = val; }
    }

    // TODO: Consider getting rid of this.
    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);
        TransientHashMap map = m.asTransient();
        for (Map.Entry entry : es) {
            if (entry != null) {
                map = map.assoc(entry.getKey(), entry.getValue());
            }
        }
        return map.persistent();
    }

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

    // ========================================= Instance =========================================
    private final Equator equator;
    private final int count;
    private final INode root;
    private final boolean hasNull;
    private final V nullValue;

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

//    /** 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 ? count : count + 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 ? count : count + 1, newroot,
                                       hasNull, nullValue);
    }

    @Override public TransientHashMap asTransient() {
        return new TransientHashMap<>(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 : entrySet()) {
                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 is cut and pasted exactly to the Transient version of this class below.
    @Override public UnmodIterator> iterator() {
        final UnmodIterator> rootIter = (root == null) ? UnmodIterator.empty()
                                                                    : root.iterator();
        if (hasNull) {
            return new UnmodIterator>() {
                private boolean seen = false;
                @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();
                    }
                }
            };
        } else {
            return rootIter;
        }
    }

    @Override public final PersistentHashMap persistent() { 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 count; }

    /** {@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, count - 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, count - 1, newroot, hasNull, nullValue);
    }

    static final class TransientHashMap implements ImMapTrans {
        private AtomicReference edit;
        private final Equator equator;
        private INode root;
        private int count;
        private boolean hasNull;
        private V nullValue;
        private final Box leafFlag = new Box(null);

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

        TransientHashMap(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 TransientHashMap 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 ImMapTrans asTransient() { 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 an exact cut-and paste of the Persistent version of this class above.
        @Override public UnmodIterator> iterator() {
            final UnmodIterator> rootIter = (root == null) ? UnmodIterator.empty()
                                                                        : root.iterator();
            if (hasNull) {
                return new UnmodIterator>() {
                    private boolean seen = false;
                    @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();
                        }
                    }
                };
            } else {
                return rootIter;
            }
        }

        @Override public final TransientHashMap 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 persistent() {
            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("Transient used after persistent! call");
        }
    }

    interface INode extends Serializable {
        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();
    }

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

//        static class Seq implements Sequence> {
//            final INode[] nodes;
//            final int i;
//            final Sequence> s;
//
//            static  Sequence> create(INode[] nodes) {
//                return create(nodes, 0, null);
//            }
//
//            private static  Sequence> create(INode[] nodes, int i, Sequence> s) {
//                if ( (s != null) && (s != Sequence.EMPTY_SEQUENCE) ) { return new Seq<>(nodes, i, s); }
//
//                for(int j = i; j < nodes.length; j++) {
//                    if (nodes[j] != null) {
//                        Sequence> ns = nodes[j].nodeSeq();
//                        if (ns != null) {
//                            return new Seq<>(nodes, j + 1, ns);
//                        }
//                    }
//                }
//                return Sequence.emptySequence();
//            }
//
//            private Seq(INode[] nodes, int i, Sequence> s) {
//                super();
//                this.nodes = nodes;
//                this.i = i;
//                this.s = s;
//            }
//
//            @Override public Option> head() {
//                return ( (s != null) && (s != Sequence.EMPTY_SEQUENCE) )
//                       ? s.head()
//                       : Option.none();
//            }
//
//            @Override public Sequence> tail() {
//                if ( (s != null) && (s != Sequence.EMPTY_SEQUENCE) ) {
//                    return create(nodes, i, s.tail());
//                }
//                return create(nodes, i, null);
//
//            }
//        }

        static class Iter implements UnmodIterator> {
            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")
    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.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, 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;
        }
    }

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

        public 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("count = " + map.count() + ", 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("count = " + 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);
    }

    static final class NodeIter implements UnmodIterator> {
        private static final UnEntry ABSENCE = new UnEntry() {
            @Override public Object getKey() {
                throw new UnsupportedOperationException("This class is a sentinel value.  If you can see this, something is terribly wrong.");
            }
            @Override public Object getValue() {
                throw new UnsupportedOperationException("This class is a sentinel value.  If you can see this, something is terribly wrong.");
            }
        };
        @SuppressWarnings("unchecked")
        private static  UnEntry absence() { return (UnEntry) ABSENCE; }

        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 != ABSENCE || nextIter != null) {
                return true;
            }
            return advance();
        }

        @Override public UnEntry next(){
            UnEntry ret = nextEntry;
            if(ret != ABSENCE) {
                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