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

stonehorse.grit.map.hash.PHashMap Maven / Gradle / Ivy

Go to download

Persistent Collections for Java. Immutable containers with mutation as expression

The newest version!
package stonehorse.grit.map.hash;


import stonehorse.grit.SerializedMap;
import stonehorse.grit.tools.ImmutableIterator;
import stonehorse.grit.PersistentMap;
import stonehorse.grit.map.*;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;

import static java.util.Objects.requireNonNull;
import static stonehorse.candy.Choices.cond;
import static stonehorse.candy.Choices.ifelse;
import static stonehorse.candy.Choices.mapOr;
import static stonehorse.grit.tools.Util.hash;

/*
A persistent rendition of Phil Bagwell's Hash Array Mapped Trie

Uses path copying for persistence
HashCollision leaves vs. extended hashing
NodeOp polymorphism vs. conditionals
No sub-tree pools or root-resizing

*/
// https://idea.popcount.org/2012-07-25-introduction-to-hamt/

public class PHashMap extends APMap implements EphemerableMap, PersistentMap, Serializable{

	final private static Object NOT_FOUND = new Object();
	final int count;
	final Node root;
	final boolean hasNull;
	final V nullValue;

	final public static PHashMap EMPTY = new PHashMap<>(0,
			null, false, null);

	@SuppressWarnings("unchecked")
	public static  PHashMap empty() {
		return (PHashMap) EMPTY;
	}

	private PHashMap(int count, Node root, boolean hasNull, V nullValue) {
		this.count = count;
		this.root = root;
		this.hasNull = hasNull;
		this.nullValue = nullValue;
	}

	static  PHashMap create(int count, Node root, boolean hasNull, V nullValue){
		//TODO return empty if count is 0. but first make sure all nodes are cleaned up when no longer used

		return new PHashMap<>(count, root, hasNull, nullValue);
	}

	 private Object writeReplace() throws ObjectStreamException {
			return new SerializedMap(this);
		}

	@Override
	public PHashMap with(T key, V val) {
		return mapOr(key,
				k -> withKey(val, k),
				() -> withNull(val));
	}

	private PHashMap withKey(V val, T k) {
		Result r = assoc(rootOrEmpty(), k, val);
		return cond(
                () -> isReplaced(r), () -> create(count, r.node, hasNull, nullValue),
                () -> isResized(r), () -> create(count + 1, r.node, hasNull, nullValue),
                () -> this);
	}

	private Node rootOrEmpty() {
		return root == null ? BitmapIndexedNode.EMPTY : root;
	}


	private boolean isReplaced(Result r){
	return (r.node!=root && !r.isResized);
	}

	private boolean isResized(Result r){
		return r.isResized;
	}

	private PHashMap withNull(V val) {
		return ifelse (
				nullValueIs(val),
				()-> this,
				()-> create(
						ifelse(hasNull,
								()-> count,
								()-> count + 1),
						root,
						true,
						val));
	}

	private boolean nullValueIs(V val) {
		return hasNull && val == nullValue;
	}

	private Result assoc(Node node, Object key, Object val ){
		return node.with(0, hash(key), key, val, false);
	}

	@Override
	public PHashMap ensureKey(T key, V val) {
		if (!has(key))
			return with(key, val);
		return this;
	}

	@Override
	public PHashMap without(Object key) {
		return cond(
				() -> key == null,
				this::withoutNull,

				() -> root == null || root.isEmpty(),
				() -> ifelse(hasNull(),
						() -> this,
						()->empty()),
				() -> withoutInTree(key));
	}

	private PHashMap withoutInTree(Object key) {
		Node newroot = root.without(0, hash(key), key);
		return cond(
				() -> newroot == root,
				() -> this,
				() -> (!hasNull() && (newroot == null || root.isEmpty())),
				() -> empty(),
				() -> create(count - 1, newroot, hasNull, nullValue));
	}

	private PHashMap withoutNull() {
		return ifelse(
				hasNull ,
				()-> create(count - 1, root, false, null),
                ()-> this);
	}

	@Override
	public boolean containsKey(Object key) {
		return has(key);
	}

	@Override
	public boolean has(Object key) {
		return cond(
				()->key == null,
				()->hasNull,
				()->root!=null,
				()->root.find(0, hash(key), key, NOT_FOUND) != NOT_FOUND,
				()->false);
	}

	@Override
	public int size() {
		return count;
	}

	@Override
	public boolean isEmpty() {
		return count == 0;
	}



	@Override
	public V get(Object key) {
		return ifelse(key == null,
				() -> ifelse(hasNull,
						() ->  nullValue,
						() -> null),
				() -> ifelse(root != null,
						() -> (V) root.find(0, hash(key), key, null),
						() -> null));
	}

	
	public EphemeralHashMap ephemeral() {
		return  EphemeralHashMap.of(this);
	}



	@Override
	public Iterator> iterator() {

		final Iterator> i= Iteration.iterable(root, hasNull, nullValue).iterator();
		return new ImmutableIterator>() {
			@Override
			public boolean hasNext() {
				return i.hasNext();
			}

			@Override
			public Entry next() {
				return i.next();
			}
		};
	}

	@Override
	public PersistentMap withAll(Map map) {
		EphemeralMap m= ephemeral();
			m=m.withAll(map);
		return m.persistent();
	}

	
	@Override
	public PersistentMap withoutAll(Iterable keys) {
		EphemeralMap m= ephemeral();
		return m.withoutAll(keys).persistent();
	}

	public Node root() {
		return root;
	}

	public boolean hasNull() {
		return hasNull;
	}


	@Override
	public boolean containsValue(Object value) {
		return new ValueCollection<>(this).contains(value);
	}

	public String dump(){
		return "count: "+size()+
				System.lineSeparator()+
				(hasNull()?"null:"+nullValue+System.lineSeparator():"")+
				"root:" +Objects.toString(root());
	}

	@Override
	public APMap withoutWhen(Object key, Predicate predicate) {
		requireNonNull(predicate);
		return cond(()->!has(key),
				()->this,
				()->predicate.test(get(key)),
				()->without(key),
				()->this);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy