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

com.github.krukow.clj_lang.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 com.github.krukow.clj_lang;

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

import com.github.krukow.clj_ds.PersistentMap;
import com.github.krukow.clj_ds.TransientMap;

/*
 A 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
 */

public class PersistentHashMap extends APersistentMap implements IEditableCollection>, IObj, PersistentMap {

final int count;
final INode root;
final boolean hasNull;
final V nullValue;
final IPersistentMap _meta;

final public static PersistentHashMap EMPTY = new PersistentHashMap(0, null, false, null);
final private static Object NOT_FOUND = new Object();

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

@SuppressWarnings("unchecked")
static public  PersistentHashMap create(Map other){
	ITransientMap ret = EMPTY.asTransient();
	for(Map.Entry e : other.entrySet())
		{
		ret = ret.assoc(e.getKey(), e.getValue());
		}
	return (PersistentHashMap) ret.persistentMap();
}

/*
 * @param init {key1,val1,key2,val2,...}
 */
@SuppressWarnings("unchecked")
public static  PersistentHashMap create(Object... init){
	ITransientMap ret = EMPTY.asTransient();
	for(int i = 0; i < init.length; i += 2)
		{
			K k = (K) init[i];
			V v = (V) init[i+1];
		ret = ret.assoc(k, v);
		}
	return (PersistentHashMap) ret.persistentMap();
}

public static  PersistentHashMap createWithCheck(Object... init){
	ITransientMap ret = EMPTY.asTransient();
	for(int i = 0; i < init.length; i += 2)
		{
		ret = ret.assoc((K) init[i], (V) init[i + 1]);
		if(ret.count() != i/2 + 1)
			throw new IllegalArgumentException("Duplicate key: " + init[i]);
		}
	return (PersistentHashMap) ret.persistentMap();
}

static public  PersistentHashMap create(ISeq items){
	ITransientMap ret = EMPTY.asTransient();
	for(; items != null; items = items.next().next())
		{
		if(items.next() == null)
			throw new IllegalArgumentException(String.format("No value supplied for key: %s", items.first()));
		ret = ret.assoc((K) items.first(), (V) RT.second(items));
		}
	return (PersistentHashMap) ret.persistentMap();
}

static public  PersistentHashMap createWithCheck(ISeq items){
	ITransientMap ret = EMPTY.asTransient();
	for(int i=0; items != null; items = items.next().next(), ++i)
		{
		if(items.next() == null)
			throw new IllegalArgumentException(String.format("No value supplied for key: %s", items.first()));
		ret = ret.assoc((K) items.first(), (V) RT.second(items));
		if(ret.count() != i + 1)
			throw new IllegalArgumentException("Duplicate key: " + items.first());
		}
	return (PersistentHashMap) ret.persistentMap();
}

/*
 * @param init {key1,val1,key2,val2,...}
 */
@SuppressWarnings("unchecked")
public static  PersistentHashMap create(IPersistentMap meta, Object... init){
	return create(init).withMeta(meta);
}

PersistentHashMap(int count, INode root, boolean hasNull, V nullValue){
	this.count = count;
	this.root = root;
	this.hasNull = hasNull;
	this.nullValue = nullValue;
	this._meta = null;
}

public PersistentHashMap(IPersistentMap meta, int count, INode root, boolean hasNull, V nullValue){
	this._meta = meta;
	this.count = count;
	this.root = root;
	this.hasNull = hasNull;
	this.nullValue = nullValue;
}

static int hash(Object k){
	return Util.hasheq(k);
}

public boolean containsKey(Object key){
	if(key == null)
		return hasNull;
	return (root != null) ? root.find(0, hash(key), key, NOT_FOUND) != NOT_FOUND : false;
}

public IMapEntry entryAt(K key){
	if(key == null)
		return hasNull ? new MapEntry(null, nullValue) : null;
	return (root != null) ? root.find(0, hash(key), key) : null;
}

public IPersistentMap assoc(K key, V val){
	if(key == null) {
		if(hasNull && val == nullValue)
			return this;
		return new PersistentHashMap(meta(), hasNull ? count : count + 1, root, true, val);
	}
	Box addedLeaf = new Box(null);
	INode newroot = (root == null ? BitmapIndexedNode.EMPTY : root) 
			.assoc(0, hash(key), key, val, addedLeaf);
	if(newroot == root)
		return this;
	return new PersistentHashMap(meta(), addedLeaf.val == null ? count : count + 1, newroot, hasNull, nullValue);
}

public V valAt(K key, V notFound){
	if(key == null)
		return hasNull ? nullValue : notFound;
	return (V) (root != null ? root.find(0, hash(key), key, notFound) : notFound);
}

public V valAt(K key){
	return valAt(key, null);
}

public IPersistentMap assocEx(K key, V val) {
	if(containsKey(key))
		throw Util.runtimeException("Key already present");
	return assoc(key, val);
}

public IPersistentMap without(K key){
	if(key == null)
		return hasNull ? new PersistentHashMap(meta(), count - 1, root, false, null) : this;
	if(root == null)
		return this;
	INode newroot = root.without(0, hash(key), key);
	if(newroot == root)
		return this;
	return new PersistentHashMap(meta(), count - 1, newroot, hasNull, nullValue); 
}

public Iterator> iterator2(){
	return new Iterator>() {
		ISeq> seq = seq();

		public boolean hasNext() {
			return seq != null;
		}

		@Override
		public Map.Entry next() {
			Entry first = seq.first();
			seq = seq.next();
			return first;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	};
}

public Iterator> iterator(){
	final Iterator> s = root != null ? root.nodeIt(false) : new EmptyIterator(); 
	return hasNull ? new Iterator>(){
		Iterator> i = s;
		boolean nullReady = true;
		public boolean hasNext() {
			return nullReady || i.hasNext();
		}

		@Override
		public Map.Entry next() {
			if (nullReady) {
				nullReady = false;
				return new MapEntry(null, PersistentHashMap.this.nullValue);
			}
			return i.next();
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
		
	} : s;
}

public Iterator> reverseIterator(){
	final Iterator> s = root != null ? root.nodeIt(true) : new EmptyIterator(); 
	return hasNull ? new Iterator>(){
		Iterator> i = s;
		boolean nullReady = true;
		public boolean hasNext() {
			return nullReady || i.hasNext();
		}

		@Override
		public Map.Entry next() {
			if (i.hasNext()) {
				return i.next();
			} else if (nullReady) {
				nullReady = false;
				return new MapEntry(null, PersistentHashMap.this.nullValue);
			}
			throw new IllegalStateException();
			
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
		
	} : s;
}

public Object kvreduce(IFn f, Object init){
    init = hasNull?f.invoke(init,null,nullValue):init;
	if(RT.isReduced(init))
		return ((IDeref)init).deref();
	if(root != null){
        return root.kvreduce(f,init);
    }
    return init;
}

public Object fold(long n, final IFn combinef, final IFn reducef,
                   IFn fjinvoke, final IFn fjtask, final IFn fjfork, final IFn fjjoin){
	//we are ignoring n for now
	Callable top = new Callable(){
		public Object call() throws Exception{
			Object ret = combinef.invoke();
			if(root != null)
				ret = combinef.invoke(ret, root.fold(combinef,reducef,fjtask,fjfork,fjjoin));
			return hasNull?
			       combinef.invoke(ret,reducef.invoke(combinef.invoke(),null,nullValue))
			       :ret;
		}
	};
	return fjinvoke.invoke(top);
}

public int count(){
	return count;
}

public ISeq> seq(){
	ISeq> s = root != null ? root.nodeSeq() : null; 
	return hasNull ? new Cons>(new MapEntry(null, nullValue), s) : s;
}

public Iterator> iteratorFrom(K key){
	if (hasNull) {throw new UnsupportedOperationException("not supported for maps with null entries yet");}
	Iterator> s = root != null ? root.nodeItFrom(0, hash(key), key) : new EmptyIterator(); 
	return s;
}

public IPersistentCollection empty(){
	return EMPTY.withMeta(meta());	
}

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

public PersistentHashMap withMeta(IPersistentMap meta){
	return new PersistentHashMap(meta, count, root, hasNull, nullValue);
}

public TransientHashMap asTransient() {
	return new TransientHashMap(this);
}

public IPersistentMap meta(){
	return _meta;
}

static final class TransientHashMap extends ATransientMap implements TransientMap {
	AtomicReference edit;
	INode root;
	int count;
	boolean hasNull;
	V nullValue;
	final Box leafFlag = new Box(null);


	TransientHashMap(PersistentHashMap m) {
		this(new AtomicReference(Thread.currentThread()), m.root, m.count, m.hasNull, m.nullValue);
	}
	
	TransientHashMap(AtomicReference edit, INode root, int count, boolean hasNull, V nullValue) {
		this.edit = edit;
		this.root = root; 
		this.count = count; 
		this.hasNull = hasNull;
		this.nullValue = nullValue;
	}

	TransientHashMap doAssoc(K key, V val) {
		if (key == null) {
			if (this.nullValue != val)
				this.nullValue = (V) val;
			if (!hasNull) {
				this.count++;
				this.hasNull = true;
			}
			return this;
		}
//		Box leafFlag = new Box(null);
		leafFlag.val = null;
		INode n = (root == null ? BitmapIndexedNode.EMPTY : root)
			.assoc(edit, 0, hash(key), key, val, leafFlag);
		if (n != this.root)
			this.root = n; 
		if(leafFlag.val != null) this.count++;
		return this;
	}

	TransientHashMap doWithout(K key) {
		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, hash(key), key, leafFlag);
		if (n != root)
			this.root = n;
		if(leafFlag.val != null) this.count--;
		return this;
	}

	PersistentHashMap doPersistent() {
		edit.set(null);
		return new PersistentHashMap(count, root, hasNull, nullValue);
	}

	V doValAt(K key, V notFound) {
		if (key == null)
			if (hasNull)
				return nullValue;
			else
				return notFound;
		if (root == null)
			return null;
		return (V) root.find(0, hash(key), key, notFound);
	}

	int doCount() {
		return count;
	}
	
	void ensureEditable(){
		Thread owner = edit.get();
		if(owner == Thread.currentThread())
			return;
		if(owner != null)
			throw new IllegalAccessError("Transient used by non-owner thread");
		throw new IllegalAccessError("Transient used after persistent! call");
	}

	public IPersistentCollection persistent() {
		// TODO Auto-generated method stub
		return persistentMap();
	}
	
	@Override
	public PersistentMap persist() {
		return (PersistentMap) persistent();
	}
	
	@Override
	public TransientMap plus(K key, V val) {
		return (TransientMap) assoc(key, val);
	}
	
	@Override
	public TransientMap minus(K key) {
		return (TransientMap) without(key);
	}

}

static interface INode extends Serializable {
	INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf);

	Iterator nodeIt(boolean reverse);
	
	Iterator nodeItFrom(int shift, int hash, Object key);

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

	IMapEntry find(int shift, int hash, Object key);

	Object find(int shift, int hash, Object key, Object notFound);

	ISeq nodeSeq();

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

	INode without(AtomicReference edit, int shift, int hash, Object key, Box removedLeaf);
	
    public Object kvreduce(IFn f, Object init);

	Object fold(IFn combinef, IFn reducef, IFn fjtask, IFn fjfork, IFn fjjoin);
}

final static class ArrayNode implements INode{
	int count;
	final INode[] array;
	final AtomicReference edit;

	ArrayNode(AtomicReference edit, int count, INode[] array){
		this.array = array;
		this.edit = edit;
		this.count = count;
	}
	
	public Iterator nodeItFrom(int shift, int hash, Object key) {
		return new ArrayNodeIterator(this, shift, hash, key);
	}
	
	static class ArrayNodeIterator implements Iterator {
		int index;
		Iterator current;
		INode[] array;
		int shift, hash;
		Object key;
		public ArrayNodeIterator(ArrayNode an) {
			array = an.array;
			moveCurIfNeeded();
		}
		public ArrayNodeIterator(ArrayNode an, int shift, int hash, Object key) {
			array = an.array;
			this.shift = shift; 
			this.hash = hash;
			this.key = key;
			moveCurTo();
		}
		
		private void moveCurTo() {
			index = mask(hash, shift);
			INode node = array[index];
			if(node == null)
				return;
			current = node.nodeItFrom(shift + 5, hash, key);
			index += 1;
			
		}
		
		public boolean hasNext() {
			while (current != null && !current.hasNext()) {
				moveCurIfNeeded();
			}
			return current != null && current.hasNext(); 
		}

		private void moveCurIfNeeded() {
			if (current != null && current.hasNext()) return;
			while (index < array.length && array[index] == null) {index += 1;};
			current = (index == array.length) ? null : array[index++].nodeIt(false); 
		}

		@Override
		public Object next() {
			return current.next();
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();			
		}
		
	}
	static final class ReverseArrayNodeIterator implements Iterator {
		int index;
		Iterator current;
		INode[] array;
		int shift, hash;
		Object key;
		public ReverseArrayNodeIterator(ArrayNode an) {
			this.array = an.array;
			this.index = array.length-1;
			moveCurIfNeeded();
		}
		private void moveCurIfNeeded() {
			if (current != null && current.hasNext()) return;
			while (index >= 0 && array[index] == null) {index -= 1;};
			current = (index < 0) ? null : array[index--].nodeIt(true); 
		}
		public boolean hasNext() {
			while (current != null && !current.hasNext()) {
				moveCurIfNeeded();
			}
			return current != null && current.hasNext(); 
		}


		@Override
		public Object next() {
			return current.next();
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();			
		}
	}
	
	public Iterator nodeIt(boolean reverse) {
		return reverse?new ReverseArrayNodeIterator(this): new ArrayNodeIterator(this);
	}

	public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){
		int idx = mask(hash, shift);
		INode node = array[idx];
		if(node == null)
			return new ArrayNode(null, count + 1, cloneAndSet(array, idx, BitmapIndexedNode.EMPTY.assoc(shift + 5, hash, key, val, addedLeaf)));			
		INode n = node.assoc(shift + 5, hash, key, val, addedLeaf);
		if(n == node)
			return this;
		return new ArrayNode(null, count, cloneAndSet(array, idx, n));
	}

	public INode without(int shift, int hash, Object 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(null, count - 1, cloneAndSet(array, idx, n));
		} else 
			return new ArrayNode(null, count, cloneAndSet(array, idx, n));
	}

	public IMapEntry find(int shift, int hash, Object key){
		int idx = mask(hash, shift);
		INode node = array[idx];
		if(node == null)
			return null;
		return node.find(shift + 5, hash, key); 
	}

	public Object find(int shift, int hash, Object key, Object notFound){
		int idx = mask(hash, shift);
		INode node = array[idx];
		if(node == null)
			return notFound;
		return node.find(shift + 5, hash, key, notFound); 
	}
	
	public ISeq nodeSeq(){
		return Seq.create(array);
	}

    public Object kvreduce(IFn f, Object init){
        for(INode node : array){
            if(node != null){
                init = node.kvreduce(f,init);
	            if(RT.isReduced(init))
		            return ((IDeref)init).deref();
	            }
	        }
        return init;
    }

	public Object fold(final IFn combinef, final IFn reducef,
	                   final IFn fjtask, final IFn fjfork, final IFn fjjoin){
		List tasks = new ArrayList();
		for(final INode node : array){
			if(node != null){
				tasks.add(new Callable(){
					public Object call() throws Exception{
						return node.fold(combinef, reducef, fjtask, fjfork, fjjoin);
					}
				});
				}
			}

		return foldTasks(tasks,combinef,fjtask,fjfork,fjjoin);
		}

	static public Object foldTasks(List tasks, final IFn combinef,
	                          final IFn fjtask, final IFn fjfork, final IFn fjjoin){

		if(tasks.isEmpty())
			return combinef.invoke();

		if(tasks.size() == 1){
			Object ret = null;
			try
				{
				return tasks.get(0).call();
				}
			catch(Exception e)
				{
				//aargh
				}
			}

		List t1 = tasks.subList(0,tasks.size()/2);
		final List t2 = tasks.subList(tasks.size()/2, tasks.size());

		Object forked = fjfork.invoke(fjtask.invoke(new Callable() {
			public Object call() throws Exception{
				return foldTasks(t2,combinef,fjtask,fjfork,fjjoin);
			}
		}));

		return combinef.invoke(foldTasks(t1,combinef,fjtask,fjfork,fjjoin),fjjoin.invoke(forked));
	}

	private ArrayNode ensureEditable(AtomicReference edit){
		if(this.edit == edit)
			return this;
		return new ArrayNode(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(edit, bitmap, newArray);
	}

	public INode assoc(AtomicReference edit, int shift, int hash, Object key, Object val, Box addedLeaf){
		int idx = mask(hash, shift);
		INode node = array[idx];
		if(node == null) {
			ArrayNode editable = editAndSet(edit, idx, BitmapIndexedNode.EMPTY.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);
	}	

	public INode without(AtomicReference edit, int shift, int hash, Object 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, n);
			editable.count--;
			return editable;
		}
		return editAndSet(edit, idx, n);
	}
	
	static class Seq extends ASeq {
		final INode[] nodes;
		final int i;
		final ISeq s; 
		
		static ISeq create(INode[] nodes) {
			return create(null, nodes, 0, null);
		}
		
		private static ISeq create(IPersistentMap meta, INode[] nodes, int i, ISeq s) {
			if (s != null)
				return new Seq(meta, nodes, i, s);
			for(int j = i; j < nodes.length; j++)
				if (nodes[j] != null) {
					ISeq ns = nodes[j].nodeSeq();
					if (ns != null)
						return new Seq(meta, nodes, j + 1, ns);
				}
			return null;
		}
		
		private Seq(IPersistentMap meta, INode[] nodes, int i, ISeq s) {
			super(meta);
			this.nodes = nodes;
			this.i = i;
			this.s = s;
		}

		public Obj withMeta(IPersistentMap meta) {
			return new Seq(meta, nodes, i, s);
		}

		public Object first() {
			return s.first();
		}

		public ISeq next() {
			return create(null, nodes, i, s.next());
		}
		
	}

	
	
}

final static class BitmapIndexedNode implements INode{
	static final BitmapIndexedNode EMPTY = new BitmapIndexedNode(null, 0, new Object[0]);
	
	int bitmap;
	Object[] array;
	final AtomicReference edit;

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

	BitmapIndexedNode(AtomicReference edit, int bitmap, Object[] array){
		this.bitmap = bitmap;
		this.array = array;
		this.edit = edit;
	}
	
	public Iterator nodeItFrom(int shift, int hash, Object key) {
		return new BitmapIndexedNodeIterator(this, shift, hash, key);
	}
	public Iterator nodeIt(boolean reverse) {
		return reverse? new ReverseBitmapIndexedNodeIterator(this) : new BitmapIndexedNodeIterator(this);
	}
	
	static class BitmapIndexedNodeIterator implements Iterator{
		BitmapIndexedNode node;
		
		int index;
		int N;
		Iterator current;
		public BitmapIndexedNodeIterator(BitmapIndexedNode node) {
			this.node = node;
			N = node.array.length;
			moveCurIfNeeded();
		}
		public BitmapIndexedNodeIterator(BitmapIndexedNode bitmapIndexedNode,
				int shift, int hash, Object key) {
			this.node = bitmapIndexedNode;
			N = node.array.length;
			moveCurTo(shift, hash, key);
		}
		private void moveCurTo(int shift, int hash, Object key) {
			int bit = bitpos(hash, shift);
			if((node.bitmap & bit) == 0)
				return;
			index = 2*node.index(bit);
			Object keyOrNull = node.array[index];
			Object valOrNode = node.array[index+1];
			if(keyOrNull == null) {
				index += 2;
				INode val = ((INode) valOrNode);
				if (val != null) {
					Iterator nodeIt  = val.nodeItFrom(shift + 5, hash, key);
					if (nodeIt.hasNext()) {
						current = nodeIt;
						return;
					}
				} 
			} else {
				if(Util.equals(key, keyOrNull)) {
					return;//OK index points to key
				} else {
					throw new IllegalArgumentException("Key not found: "+key);
				}
					
			}
				
		}
		public boolean hasNext() {
			moveCurIfNeeded();
			if (current == null && index >= N) {
				return false;
			}
			return true;
		}
		//current != null => current.hasNext or index points to a valid key
		private void moveCurIfNeeded() {
			if (current != null && current.hasNext()) return;
			current = null;
			while (index < N) {
				Object keyOrNull = node.array[index];
				Object valOrNode = node.array[index+1];
				if (keyOrNull == null) {
					index += 2;
					INode val = ((INode) valOrNode);
					if (val != null) {
						Iterator nodeIt  = val.nodeIt(false);
						if (nodeIt.hasNext()) {
							current = nodeIt;
							return;
						}
					} 
				} else {
					return;
				}
			} 
		}

		@Override
		public Object next() {
			if (current != null) {
				return current.next();
			} else {
				Object keyOrNull = node.array[index++];
				Object valOrNode = node.array[index++];
				return new MapEntry(keyOrNull, valOrNode);
			}
				
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();			
		}		
		
	}
	
	static final class ReverseBitmapIndexedNodeIterator implements Iterator {
		BitmapIndexedNode node;
		
		int index;
		Iterator current;
		public ReverseBitmapIndexedNodeIterator(BitmapIndexedNode node) {
			this.node = node;
			index = node.array.length-1;
			moveCurIfNeeded();
		}

		public boolean hasNext() {
			moveCurIfNeeded();
			if (current == null && index < 0) {
				return false;
			}
			return true;
		}
		//current != null => current.hasNext or index points to a valid key
		private void moveCurIfNeeded() {
			if (current != null && current.hasNext()) return;
			current = null;
			while (index >= 0) {
				Object valOrNode = node.array[index];
				Object keyOrNull = node.array[index-1];
				if (keyOrNull == null) {
					index -= 2;
					INode val = ((INode) valOrNode);
					if (val != null) {
						Iterator nodeIt  = val.nodeIt(true);
						if (nodeIt.hasNext()) {
							current = nodeIt;
							return;
						}
					} 
				} else {
					return;
				}
			} 
		}


		@Override
		public void remove() {
			throw new UnsupportedOperationException();			
		}		
		@Override
		public Object next() {
			if (current != null) {
				return current.next();
			} else {
				Object valOrNode = node.array[index--];
				Object keyOrNull = node.array[index--];
				return new MapEntry(keyOrNull, valOrNode);
			}
				
		}
	}
	
	public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){
		int bit = bitpos(hash, shift);
		int idx = index(bit);
		if((bitmap & bit) != 0) {
			Object keyOrNull = 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(null, bitmap, cloneAndSet(array, 2*idx+1, n));
			} 
			if(Util.equiv(key, keyOrNull)) {
				if(val == valOrNode)
					return this;
				return new BitmapIndexedNode(null, bitmap, cloneAndSet(array, 2*idx+1, val));
			} 
			addedLeaf.val = addedLeaf;
			return new BitmapIndexedNode(null, bitmap, 
					cloneAndSet(array, 
							2*idx, null, 
							2*idx+1, createNode(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.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.assoc(shift + 5, hash(array[j]), array[j], array[j+1], addedLeaf);
						j += 2;
					}
				return new ArrayNode(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(null, bitmap | bit, newArray);
			}
		}
	}

	public INode without(int shift, int hash, Object key){
		int bit = bitpos(hash, shift);
		if((bitmap & bit) == 0)
			return this;
		int idx = index(bit);
		Object keyOrNull = 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(null, bitmap, cloneAndSet(array, 2*idx+1, n));
			if (bitmap == bit) 
				return null;
			return new BitmapIndexedNode(null, bitmap ^ bit, removePair(array, idx));
		}
		if(Util.equiv(key, keyOrNull))
			// TODO: collapse
			return new BitmapIndexedNode(null, bitmap ^ bit, removePair(array, idx));
		return this;
	}
	
	public IMapEntry find(int shift, int hash, Object key){
		int bit = bitpos(hash, shift);
		if((bitmap & bit) == 0)
			return null;
		int idx = index(bit);
		Object keyOrNull = array[2*idx];
		Object valOrNode = array[2*idx+1];
		if(keyOrNull == null)
			return ((INode) valOrNode).find(shift + 5, hash, key);
		if(Util.equiv(key, keyOrNull))
			return new MapEntry(keyOrNull, valOrNode);
		return null;
	}

	public Object find(int shift, int hash, Object key, Object notFound){
		int bit = bitpos(hash, shift);
		if((bitmap & bit) == 0)
			return notFound;
		int idx = index(bit);
		Object keyOrNull = array[2*idx];
		Object valOrNode = array[2*idx+1];
		if(keyOrNull == null)
			return ((INode) valOrNode).find(shift + 5, hash, key, notFound);
		if(Util.equiv(key, keyOrNull))
			return valOrNode;
		return notFound;
	}

	public ISeq nodeSeq(){
		return NodeSeq.create(array);
	}

   public Object kvreduce(IFn f, Object init){
        return NodeSeq.kvreduce(array,f,init);
   }

	public Object fold(IFn combinef, IFn reducef, IFn fjtask, IFn fjfork, IFn fjjoin){
		return NodeSeq.kvreduce(array, reducef, combinef.invoke());
	}

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

	public INode assoc(AtomicReference edit, int shift, int hash, Object key, Object val, Box addedLeaf){
		int bit = bitpos(hash, shift);
		int idx = index(bit);
		if((bitmap & bit) != 0) {
			Object keyOrNull = 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(Util.equiv(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(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.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.assoc(edit, shift + 5, hash(array[j]), array[j], array[j+1], addedLeaf);
						j += 2;
					}
				return new ArrayNode(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;
			}
		}
	}

	public INode without(AtomicReference edit, int shift, int hash, Object key, Box removedLeaf){
		int bit = bitpos(hash, shift);
		if((bitmap & bit) == 0)
			return this;
		int idx = index(bit);
		Object keyOrNull = 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(Util.equiv(key, keyOrNull)) {
			removedLeaf.val = removedLeaf;
			// TODO: collapse
			return editAndRemovePair(edit, bit, idx); 			
		}
		return this;
	}
}

final static class HashCollisionNode implements INode{

	final int hash;
	int count;
	Object[] array;
	final AtomicReference edit;

	HashCollisionNode(AtomicReference edit, int hash, int count, Object... array){
		this.edit = edit;
		this.hash = hash;
		this.count = count;
		this.array = array;
	}
	
	static final class HashCollisionNodeIterator implements Iterator {
		Object[] array;
		int index;
		int count;
		public HashCollisionNodeIterator(HashCollisionNode node) {
			
			this.array = node.array;
			this.count = node.count;
		}
		public HashCollisionNodeIterator(HashCollisionNode hashCollisionNode,
				int shift, int hash, Object key) {
			this.array = hashCollisionNode.array;
			this.count = hashCollisionNode.count;
			int idx = hashCollisionNode.findIndex(key);
			index = idx == -1 ? count * 2 : idx;
			
		}
		public boolean hasNext() {
			 return index < count * 2;
		}
		
		public Object next() {
			Object k = array[index++];
			Object v = array[index++];
			return new MapEntry(k, v);
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();			
		}
		
	}
	static final class ReverseHashCollisionNodeIterator implements Iterator {
		Object[] array;
		int index;
		int count;
		public ReverseHashCollisionNodeIterator(HashCollisionNode node) {
			this.array = node.array;
			this.count = node.count;
			this.index = count*2 - 1;
		}
		public boolean hasNext() {
			 return index >= 0;
		}
		
		public Object next() {
			Object v = array[index--];
			Object k = array[index--];
			return new MapEntry(k, v);
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();			
		}
		
	}
	
	public Iterator nodeItFrom(int shift, int hash, Object key) {
		return new HashCollisionNodeIterator(this,shift,hash,key);
	}
	public Iterator nodeIt(boolean reverse) {
		return reverse?new ReverseHashCollisionNodeIterator(this) : new HashCollisionNodeIterator(this);
	}

	
	public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){
		if(hash == this.hash) {
			int idx = findIndex(key);
			if(idx != -1) {
				if(array[idx + 1] == val)
					return this;
				return new HashCollisionNode(null, hash, count, cloneAndSet(array, idx + 1, val));
			}
			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 new HashCollisionNode(edit, hash, count + 1, newArray);
		}
		// nest it in a bitmap node
		return new BitmapIndexedNode(null, bitpos(this.hash, shift), new Object[] {null, this})
			.assoc(shift, hash, key, val, addedLeaf);
	}

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

	public IMapEntry find(int shift, int hash, Object key){
		int idx = findIndex(key);
		if(idx < 0)
			return null;
		if(Util.equiv(key, array[idx]))
			return new MapEntry(array[idx], array[idx+1]);
		return null;
	}

	public Object find(int shift, int hash, Object key, Object notFound){
		int idx = findIndex(key);
		if(idx < 0)
			return notFound;
		if(Util.equiv(key, array[idx]))
			return array[idx+1];
		return notFound;
	}

	public ISeq nodeSeq(){
		return NodeSeq.create(array);
	}

   public Object kvreduce(IFn f, Object init){
        return NodeSeq.kvreduce(array,f,init);
   }

	public Object fold(IFn combinef, IFn reducef, IFn fjtask, IFn fjfork, IFn fjjoin){
		return NodeSeq.kvreduce(array, reducef, combinef.invoke());
	}

	public int findIndex(Object key){
		for(int i = 0; i < 2*count; i+=2)
			{
			if(Util.equiv(key, 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(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(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;
	}


	public INode assoc(AtomicReference edit, int shift, int hash, Object key, Object 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(edit, bitpos(this.hash, shift), new Object[] {null, this, null, null})
			.assoc(edit, shift, hash, key, val, addedLeaf);
	}	

	public INode without(AtomicReference edit, int shift, int hash, Object 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());
		IPersistentMap map = PersistentHashMap.EMPTY;
		//IPersistentMap map = new PersistentTreeMap();
		//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);
		IPersistentMap 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(int shift, Object key1, Object val1, int key2hash, Object key2, Object val2) {
	int key1hash = hash(key1);
	if(key1hash == key2hash)
		return new HashCollisionNode(null, key1hash, 2, new Object[] {key1, val1, key2, val2});
	Box _ = new Box(null);
	AtomicReference edit = new AtomicReference();
	return BitmapIndexedNode.EMPTY
		.assoc(edit, shift, key1hash, key1, val1, _)
		.assoc(edit, shift, key2hash, key2, val2, _);
}

private static INode createNode(AtomicReference edit, int shift, Object key1, Object val1, int key2hash, Object key2, Object val2) {
	int key1hash = hash(key1);
	if(key1hash == key2hash)
		return new HashCollisionNode(null, key1hash, 2, new Object[] {key1, val1, key2, val2});
	Box _ = new Box(null);
	return BitmapIndexedNode.EMPTY
		.assoc(edit, shift, key1hash, key1, val1, _)
		.assoc(edit, shift, key2hash, key2, val2, _);
}

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

static final class NodeSeq extends ASeq {
	final Object[] array;
	final int i;
	final ISeq s;
	
	NodeSeq(Object[] array, int i) {
		this(null, array, i, null);
	}

	static ISeq create(Object[] array) {
		return create(array, 0, null);
	}

    static public Object kvreduce(Object[] array, IFn f, Object init){
        for(int i=0;i zero() {
		return (PersistentMap) empty();
	}
	
	@Override
	public PersistentMap plus(K key, V val) {
		return (PersistentMap) assoc(key, val);
	}
	
	@Override
	public PersistentMap plusEx(K key, V val) {
		return (PersistentMap) assocEx(key, val);
	}
	
	@Override
	public PersistentMap minus(K key) {
		return (PersistentMap) without(key);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy