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