Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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