programming-examples/java/Data_Structures/HashArrayMappedTrie.java
2019-11-15 12:59:38 +01:00

636 lines
20 KiB
Java

package com.jwetherell.algorithms.data_structures;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.jwetherell.algorithms.data_structures.interfaces.IMap;
/**
* A hash array mapped trie (HAMT) is an implementation of an associative
* array that combines the characteristics of a hash table and an array mapped
* trie. It is a refined version of the more general notion of a hash tree.
*
* This implementation is 32-bit and steps in 5-bit intervals, maximum tree
* height of 7.
*
* http://en.wikipedia.org/wiki/Hash_array_mapped_trie
*
* @author Justin Wetherell <phishman3579@gmail.com>
*/
@SuppressWarnings("unchecked")
public class HashArrayMappedTrie<K, V> implements IMap<K,V> {
private static final byte MAX_BITS = 32;
private static final byte BITS = 5;
private static final byte MAX_DEPTH = MAX_BITS/BITS; // 6
private static final int MASK = (int)Math.pow(2, BITS)-1;
private Node root = null;
private int size = 0;
/**
* Get the "BITS" length integer starting at height*BITS position.
*
* e.g. BITS=5 height=1 value==266 big-endian=100001010 (shifts height*BITS off the right) return=1000 (8 in decimal)
*/
private static final int getPosition(int height, int value) {
return (value >>> height*BITS) & MASK;
}
private V put(ArrayNode parent, Node node, byte height, int key, V value) {
byte newHeight = height;
if (node instanceof KeyValueNode) {
KeyValueNode<V> kvNode = (KeyValueNode<V>) node;
if (key==kvNode.key) {
// Key already exists as key-value pair, replace value
kvNode.value = value;
return value;
}
// Key already exists but doesn't match current key
KeyValueNode<V> oldParent = kvNode;
int newParentPosition = getPosition(newHeight-1, key);
int oldParentPosition = getPosition(newHeight, oldParent.key);
int childPosition = getPosition(newHeight, key);
ArrayNode newParent = new ArrayNode(parent, key, newHeight);
newParent.parent = parent;
if (parent==null) {
// Only the root doesn't have a parent, so new root
root = newParent;
} else {
// Add the child to the parent in it's parent's position
parent.addChild(newParentPosition, newParent);
}
if (oldParentPosition != childPosition) {
// The easy case, the two children have different positions in parent
newParent.addChild(oldParentPosition, oldParent);
oldParent.parent = newParent;
newParent.addChild(childPosition, new KeyValueNode<V>(newParent, key, value));
return null;
}
while (oldParentPosition == childPosition) {
// Handle the case when the new children map to same position.
newHeight++;
if (newHeight>MAX_DEPTH) {
// We have found two keys which match exactly. I really don't know what to do.
throw new RuntimeException("Yikes! Found two keys which match exactly.");
}
newParentPosition = getPosition(newHeight-1, key);
ArrayNode newParent2 = new ArrayNode(newParent, key, newHeight);
newParent.addChild(newParentPosition, newParent2);
oldParentPosition = getPosition(newHeight, oldParent.key);
childPosition = getPosition(newHeight, key);
if (oldParentPosition != childPosition) {
newParent2.addChild(oldParentPosition, oldParent);
oldParent.parent = newParent2;
newParent2.addChild(childPosition, new KeyValueNode<V>(newParent2, key, value));
} else {
newParent = newParent2;
}
}
return null;
} else if (node instanceof ArrayNode) {
ArrayNode arrayRoot = (ArrayNode) node;
int position = getPosition(arrayRoot.height, key);
Node child = arrayRoot.getChild(position);
if (child==null) {
// Found an empty slot in parent
arrayRoot.addChild(position, new KeyValueNode<V>(arrayRoot, key, value));
return null;
}
return put(arrayRoot, child, (byte)(newHeight+1), key, value);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public V put(K key, V value) {
int intKey = key.hashCode();
V toReturn = null;
if (root==null)
root = new KeyValueNode<V>(null, intKey, value);
else
toReturn = put(null, root, (byte)0, intKey, value);
if (toReturn==null) size++;
return toReturn;
}
private Node find(Node node, int key) {
if (node instanceof KeyValueNode) {
KeyValueNode<V> kvNode = (KeyValueNode<V>) node;
if (kvNode.key==key)
return kvNode;
return null;
} else if (node instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode)node;
int position = getPosition(arrayNode.height, key);
Node possibleNode = arrayNode.getChild(position);
if (possibleNode==null)
return null;
return find(possibleNode,key);
}
return null;
}
private Node find(int key) {
if (root==null) return null;
return find(root, key);
}
/**
* {@inheritDoc}
*/
@Override
public V get(K key) {
Node node = find(key.hashCode());
if (node==null)
return null;
if (node instanceof KeyValueNode)
return ((KeyValueNode<V>)node).value;
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean contains(K key) {
Node node = find(key.hashCode());
return (node!=null);
}
/**
* {@inheritDoc}
*/
@Override
public V remove(K key) {
Node node = find(key.hashCode());
if (node==null)
return null;
if (node instanceof ArrayNode)
return null;
KeyValueNode<V> kvNode = (KeyValueNode<V>)node;
V value = kvNode.value;
if (node.parent==null) {
// If parent is null, removing the root
root = null;
} else {
ArrayNode parent = node.parent;
// Remove child from parent
int position = getPosition(parent.height, node.key);
parent.removeChild(position);
// Go back up the tree, pruning any array nodes which no longer have children.
int numOfChildren = parent.getNumberOfChildren();
while (numOfChildren==0) {
node = parent;
parent = node.parent;
if (parent==null) {
// Reached root of the tree, need to stop
root = null;
break;
}
position = getPosition(parent.height, node.key);
parent.removeChild(position);
numOfChildren = parent.getNumberOfChildren();
}
}
kvNode.key = 0;
kvNode.value = null;
size--;
return value;
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
root = null;
size = 0;
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return size;
}
private static <V> boolean validate(ArrayNode parent, KeyValueNode<V> child) {
if (parent==null || parent.height==0) return true;
int parentPosition = getPosition(parent.height-1,parent.key);
int childPosition = getPosition(parent.height-1,child.key);
return (childPosition==parentPosition);
}
private static <V> boolean validate(ArrayNode parent, ArrayNode node) {
if (parent!=null) {
if (parent.key != (node.parent.key)) return false;
if (parent.height+1 != node.height) return false;
}
int children = 0;
for (int i=0; i<node.children.length; i++) {
Node child = node.children[i];
if (child!=null) {
children++;
if (child instanceof KeyValueNode) {
KeyValueNode<V> kvChild = (KeyValueNode<V>) child;
if (!validate(node,kvChild)) return false;
} else if (child instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) child;
if (!validate(node, arrayNode)) return false;
} else {
return false;
}
}
}
boolean result = (children==node.getNumberOfChildren());
if (!result) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public boolean validate() {
if (root==null) return true;
Node child = root;
if (child instanceof KeyValueNode) {
KeyValueNode<V> kvChild = (KeyValueNode<V>) child;
if (!validate(null, kvChild)) return false;
} else if (child instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) child;
if (!validate(null, arrayNode)) return false;
} else {
return false;
}
return true;
}
/**
* Convert a big-endian binary string to a little-endian binary string
* and also pads with zeros to make it "BITS" length.
*
* e.g. BITS=5 value==6 big-endian=110 little-endian=011 (pad) return=01100
*/
private static final String toBinaryString(int value) {
StringBuilder builder = new StringBuilder(Integer.toBinaryString(value));
builder = builder.reverse();
while (builder.length()<BITS) {
builder.append('0');
}
return builder.toString();
}
protected static class Node {
protected ArrayNode parent = null;
protected int key = 0;
}
private static final class ArrayNode extends Node {
private byte height = 0;
private int bitmap = 0;
private Node[] children = new Node[2];
private ArrayNode(ArrayNode parent, int key, byte height) {
this.parent = parent;
this.key = key;
this.height = height;
}
/**
* Set the bit at position (zero based)
*
* e.g. bitmap=0000 position==3 result=1000
*/
private void set(int position) {
bitmap |= (1 << position);
}
/**
* Unset the bit at position (zero based)
*
* e.g. bitmap==110 position=1 result=100
*/
private void unset(int position) {
bitmap &= ~(1 << position);
}
/**
* Is the bit at position set (zero based)
*
* e.g. bitmap=01110 position=3 result=true
*/
private boolean isSet(int position) {
return ((bitmap &(1 << position)) >>> position)==1;
}
/**
* Count the number of 1s in the bitmap between 0 and position (zero based)
*
* Because each 1 in the bitmap means a child exists, you can use the 1s count
* to calculate the index of the child at the given position.
*
* e.g. bitmap=01110010 position=5 result=3
*/
private int getIndex(int pos){
int position = pos;
position = (1 << position)-1;
return Integer.bitCount(bitmap & position);
}
/**
* Calculates the height by doubling the size with a max size of MAX_BITS
*/
private static int calcNewLength(int size) {
int newSize = size;
if (newSize==MAX_BITS) return newSize;
newSize = (newSize + (newSize>>1));
if (newSize>MAX_BITS) newSize = MAX_BITS;
return newSize;
}
private void addChild(int position, Node child) {
boolean overwrite = false;
if (isSet(position)) overwrite = true;
set(position);
int idx = getIndex(position);
if (!overwrite) {
int len = calcNewLength(getNumberOfChildren());
// Array size changed, copy the array to the new array
if (len>children.length) {
Node[] temp = new Node[len];
System.arraycopy(children, 0, temp, 0, children.length);
children = temp;
}
// Move existing elements up to make room
if (children[idx]!=null)
System.arraycopy(children, idx, children, idx+1, (children.length-(idx+1)));
}
children[idx] = child;
}
private void removeChild(int position) {
if (!isSet(position)) return;
unset(position);
int lastIdx = getNumberOfChildren();
int idx = getIndex(position);
// Shift the entire array down one spot starting at idx
System.arraycopy(children, idx+1, children, idx, (children.length-(idx+1)));
children[lastIdx] = null;
}
private Node getChild(int position) {
if (!isSet(position)) return null;
int idx = getIndex(position);
return children[idx];
}
private int getNumberOfChildren() {
return Integer.bitCount(bitmap);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("height=").append(height).append(" key=").append(toBinaryString(key)).append("\n");
for (int i=0; i<MAX_BITS; i++) {
Node c = getChild(i);
if (c!=null) builder.append(c.toString()).append(", ");
}
return builder.toString();
}
}
private static final class KeyValueNode<V> extends Node {
private V value;
private KeyValueNode(ArrayNode parent, int key, V value) {
this.parent = parent;
this.key = key;
this.value = value;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("key=").append(toBinaryString(key)).append(" value=").append(value);
return builder.toString();
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return TreePrinter.getString(this);
}
protected static class TreePrinter {
public static <K, V> String getString(HashArrayMappedTrie<K,V> tree) {
if (tree.root == null) return "Tree has no nodes.";
return getString(null, tree.root, -1, "", true);
}
private static <V> String getString(Node parent, Node node, int height, String prefix, boolean isTail) {
StringBuilder builder = new StringBuilder();
if (node instanceof KeyValueNode) {
KeyValueNode<V> kvNode = (KeyValueNode<V>) node;
int position = getPosition(height,kvNode.key);
builder.append(prefix + (isTail ? "└── " : "├── ") + ((parent==null)?null:toBinaryString(position)) + "=" + toBinaryString(kvNode.key) + "=" + kvNode.value + "\n");
} else {
ArrayNode arrayNode = (ArrayNode) node;
int position = (arrayNode.parent==null)?-1:getPosition(height,arrayNode.key);
builder.append(prefix + (isTail ? "└── " : "├── ") + ((parent==null)?null:toBinaryString(position)) + " height=" + ((height<0)?null:height) + " bitmap=" + toBinaryString(arrayNode.bitmap) + "\n");
List<Node> children = new LinkedList<Node>();
for (int i=0; i<MAX_BITS; i++) {
Node child = arrayNode.getChild(i);
if (child != null) children.add(child);
}
for (int i = 0; i<(children.size()-1); i++) {
builder.append(getString(arrayNode, children.get(i), height+1, prefix+(isTail ? " " : ""), false));
}
if (children.size() >= 1) {
builder.append(getString(arrayNode, children.get(children.size()-1), height+1, prefix+(isTail ? " " : ""), true));
}
}
return builder.toString();
}
}
/**
* {@inheritDoc}
*/
@Override
public Map<K,V> toMap() {
return (new JavaCompatibleMap<K,V>(this));
}
private static class JavaCompatibleIteratorWrapper<K,V> implements java.util.Iterator<java.util.Map.Entry<K, V>> {
private HashArrayMappedTrie<K,V> map = null;
private java.util.Iterator<java.util.Map.Entry<K, V>> iter = null;
private java.util.Map.Entry<K, V> lastEntry = null;
public JavaCompatibleIteratorWrapper(HashArrayMappedTrie<K,V> map, java.util.Iterator<java.util.Map.Entry<K, V>> iter) {
this.map = map;
this.iter = iter;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasNext() {
if (iter==null) return false;
return iter.hasNext();
}
/**
* {@inheritDoc}
*/
@Override
public java.util.Map.Entry<K, V> next() {
if (iter==null) return null;
lastEntry = iter.next();
return lastEntry;
}
/**
* {@inheritDoc}
*/
@Override
public void remove() {
if (iter==null || lastEntry==null) return;
map.remove(lastEntry.getKey());
iter.remove();
}
}
private static class JavaCompatibleMapEntry<K,V> extends java.util.AbstractMap.SimpleEntry<K,V> {
private static final long serialVersionUID = -2943023801121889519L;
public JavaCompatibleMapEntry(K key, V value) {
super(key, value);
}
}
private static class JavaCompatibleMap<K, V> extends java.util.AbstractMap<K,V> {
private HashArrayMappedTrie<K,V> map = null;
protected JavaCompatibleMap(HashArrayMappedTrie<K,V> map) {
this.map = map;
}
/**
* {@inheritDoc}
*/
@Override
public V put(K key, V value) {
return map.put(key, value);
}
/**
* {@inheritDoc}
*/
@Override
public V remove(Object key) {
return map.remove((K)key);
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
map.clear();
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsKey(Object key) {
return map.contains((K)key);
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return map.size();
}
private void addToSet(java.util.Set<java.util.Map.Entry<K, V>> set, Node node) {
if (node instanceof KeyValueNode) {
KeyValueNode<V> kvNode = (KeyValueNode<V>) node;
set.add(new JavaCompatibleMapEntry<K,V>((K)new Integer(kvNode.key), kvNode.value));
} else if (node instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) node;
for (int i=0; i<MAX_BITS; i++) {
Node child = arrayNode.getChild(i);
if (child!=null) addToSet(set, child);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public java.util.Set<java.util.Map.Entry<K, V>> entrySet() {
java.util.Set<java.util.Map.Entry<K, V>> set = new java.util.HashSet<java.util.Map.Entry<K, V>>() {
private static final long serialVersionUID = 1L;
/**
* {@inheritDoc}
*/
@Override
public java.util.Iterator<java.util.Map.Entry<K, V>> iterator() {
return (new JavaCompatibleIteratorWrapper<K,V>(map, super.iterator()));
}
};
if (map.root!=null) addToSet(set,map.root);
return set;
}
}
}