io.vavr.collection.HashArrayMappedTrie Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vavr Show documentation
Show all versions of vavr Show documentation
Vavr is an object-functional library for Java 8+
/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/
*
* Copyright 2014-2019 Vavr, http://vavr.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.vavr.collection;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.collection.HashArrayMappedTrieModule.EmptyNode;
import io.vavr.control.Option;
import java.io.Serializable;
import java.util.Objects;
import static java.lang.Integer.bitCount;
import static java.util.Arrays.copyOf;
import static io.vavr.collection.HashArrayMappedTrieModule.Action.PUT;
import static io.vavr.collection.HashArrayMappedTrieModule.Action.REMOVE;
/**
* An immutable Hash array mapped trie (HAMT).
*
* @author Ruslan Sennov
*/
interface HashArrayMappedTrie extends Iterable> {
static HashArrayMappedTrie empty() {
return EmptyNode.instance();
}
boolean isEmpty();
int size();
Option get(K key);
V getOrElse(K key, V defaultValue);
boolean containsKey(K key);
HashArrayMappedTrie put(K key, V value);
HashArrayMappedTrie remove(K key);
@Override
Iterator> iterator();
/**
* Provide unboxed access to the keys in the trie.
*/
Iterator keysIterator();
/**
* Provide unboxed access to the values in the trie.
*/
Iterator valuesIterator();
}
interface HashArrayMappedTrieModule {
enum Action {
PUT, REMOVE
}
class LeafNodeIterator extends AbstractIterator> {
// buckets levels + leaf level = (Integer.SIZE / AbstractNode.SIZE + 1) + 1
private final static int MAX_LEVELS = Integer.SIZE / AbstractNode.SIZE + 2;
private final int total;
private final Object[] nodes = new Object[MAX_LEVELS];
private final int[] indexes = new int[MAX_LEVELS];
private int level;
private int ptr = 0;
LeafNodeIterator(AbstractNode root) {
total = root.size();
level = downstairs(nodes, indexes, root, 0);
}
@Override
public boolean hasNext() {
return ptr < total;
}
@SuppressWarnings("unchecked")
@Override
protected LeafNode getNext() {
Object node = nodes[level];
while (!(node instanceof LeafNode)) {
node = findNextLeaf();
}
ptr++;
if (node instanceof LeafList) {
final LeafList leaf = (LeafList) node;
nodes[level] = leaf.tail;
return leaf;
} else {
nodes[level] = EmptyNode.instance();
return (LeafSingleton) node;
}
}
@SuppressWarnings("unchecked")
private Object findNextLeaf() {
AbstractNode node = null;
while (level > 0) {
level--;
indexes[level]++;
node = getChild((AbstractNode) nodes[level], indexes[level]);
if (node != null) {
break;
}
}
level = downstairs(nodes, indexes, node, level + 1);
return nodes[level];
}
private static int downstairs(Object[] nodes, int[] indexes, AbstractNode root, int level) {
while (true) {
nodes[level] = root;
indexes[level] = 0;
root = getChild(root, 0);
if (root == null) {
break;
} else {
level++;
}
}
return level;
}
@SuppressWarnings("unchecked")
private static AbstractNode getChild(AbstractNode node, int index) {
if (node instanceof IndexedNode) {
final Object[] subNodes = ((IndexedNode) node).subNodes;
return index < subNodes.length ? (AbstractNode) subNodes[index] : null;
} else if (node instanceof ArrayNode) {
final ArrayNode arrayNode = (ArrayNode) node;
return index < AbstractNode.BUCKET_SIZE ? (AbstractNode) arrayNode.subNodes[index] : null;
}
return null;
}
}
/**
* An abstract base class for nodes of a HAMT.
*
* @param Key type
* @param Value type
*/
abstract class AbstractNode implements HashArrayMappedTrie {
static final int SIZE = 5;
static final int BUCKET_SIZE = 1 << SIZE;
static final int MAX_INDEX_NODE = BUCKET_SIZE >> 1;
static final int MIN_ARRAY_NODE = BUCKET_SIZE >> 2;
static int hashFragment(int shift, int hash) {
return (hash >>> shift) & (BUCKET_SIZE - 1);
}
static int toBitmap(int hash) {
return 1 << hash;
}
static int fromBitmap(int bitmap, int bit) {
return bitCount(bitmap & (bit - 1));
}
static Object[] update(Object[] arr, int index, Object newElement) {
final Object[] newArr = copyOf(arr, arr.length);
newArr[index] = newElement;
return newArr;
}
static Object[] remove(Object[] arr, int index) {
final Object[] newArr = new Object[arr.length - 1];
System.arraycopy(arr, 0, newArr, 0, index);
System.arraycopy(arr, index + 1, newArr, index, arr.length - index - 1);
return newArr;
}
static Object[] insert(Object[] arr, int index, Object newElem) {
final Object[] newArr = new Object[arr.length + 1];
System.arraycopy(arr, 0, newArr, 0, index);
newArr[index] = newElem;
System.arraycopy(arr, index, newArr, index + 1, arr.length - index);
return newArr;
}
abstract Option lookup(int shift, int keyHash, K key);
abstract V lookup(int shift, int keyHash, K key, V defaultValue);
abstract AbstractNode modify(int shift, int keyHash, K key, V value, Action action);
Iterator> nodes() {
return new LeafNodeIterator<>(this);
}
@Override
public Iterator> iterator() {
return nodes().map(node -> Tuple.of(node.key(), node.value()));
}
@Override
public Iterator keysIterator() {
return nodes().map(LeafNode::key);
}
@Override
public Iterator valuesIterator() {
return nodes().map(LeafNode::value);
}
@Override
public Option get(K key) {
return lookup(0, Objects.hashCode(key), key);
}
@Override
public V getOrElse(K key, V defaultValue) {
return lookup(0, Objects.hashCode(key), key, defaultValue);
}
@Override
public boolean containsKey(K key) {
return get(key).isDefined();
}
@Override
public HashArrayMappedTrie put(K key, V value) {
return modify(0, Objects.hashCode(key), key, value, PUT);
}
@Override
public HashArrayMappedTrie remove(K key) {
return modify(0, Objects.hashCode(key), key, null, REMOVE);
}
@Override
public final String toString() {
return iterator().map(t -> t._1 + " -> " + t._2).mkString("HashArrayMappedTrie(", ", ", ")");
}
}
/**
* The empty node.
*
* @param Key type
* @param Value type
*/
final class EmptyNode extends AbstractNode implements Serializable {
private static final long serialVersionUID = 1L;
private static final EmptyNode, ?> INSTANCE = new EmptyNode<>();
private EmptyNode() {
}
@SuppressWarnings("unchecked")
static EmptyNode instance() {
return (EmptyNode) INSTANCE;
}
@Override
Option lookup(int shift, int keyHash, K key) {
return Option.none();
}
@Override
V lookup(int shift, int keyHash, K key, V defaultValue) {
return defaultValue;
}
@Override
AbstractNode modify(int shift, int keyHash, K key, V value, Action action) {
return (action == REMOVE) ? this : new LeafSingleton<>(keyHash, key, value);
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public int size() {
return 0;
}
@Override
public Iterator> nodes() {
return Iterator.empty();
}
/**
* Instance control for object serialization.
*
* @return The singleton instance of EmptyNode.
* @see Serializable
*/
private Object readResolve() {
return INSTANCE;
}
}
/**
* Representation of a HAMT leaf.
*
* @param Key type
* @param Value type
*/
abstract class LeafNode extends AbstractNode {
abstract K key();
abstract V value();
abstract int hash();
static AbstractNode mergeLeaves(int shift, LeafNode leaf1, LeafSingleton leaf2) {
final int h1 = leaf1.hash();
final int h2 = leaf2.hash();
if (h1 == h2) {
return new LeafList<>(h1, leaf2.key(), leaf2.value(), leaf1);
}
final int subH1 = hashFragment(shift, h1);
final int subH2 = hashFragment(shift, h2);
final int newBitmap = toBitmap(subH1) | toBitmap(subH2);
if (subH1 == subH2) {
final AbstractNode newLeaves = mergeLeaves(shift + SIZE, leaf1, leaf2);
return new IndexedNode<>(newBitmap, newLeaves.size(), new Object[] { newLeaves });
} else {
return new IndexedNode<>(newBitmap, leaf1.size() + leaf2.size(),
subH1 < subH2 ? new Object[] { leaf1, leaf2 } : new Object[] { leaf2, leaf1 });
}
}
@Override
public boolean isEmpty() {
return false;
}
}
/**
* Representation of a HAMT leaf node with single element.
*
* @param Key type
* @param Value type
*/
final class LeafSingleton extends LeafNode implements Serializable {
private static final long serialVersionUID = 1L;
private final int hash;
private final K key;
private final V value;
LeafSingleton(int hash, K key, V value) {
this.hash = hash;
this.key = key;
this.value = value;
}
private boolean equals(int keyHash, K key) {
return keyHash == hash && Objects.equals(key, this.key);
}
@Override
Option lookup(int shift, int keyHash, K key) {
return Option.when(equals(keyHash, key), value);
}
@Override
V lookup(int shift, int keyHash, K key, V defaultValue) {
return equals(keyHash, key) ? value : defaultValue;
}
@Override
AbstractNode modify(int shift, int keyHash, K key, V value, Action action) {
if (keyHash == hash && Objects.equals(key, this.key)) {
return (action == REMOVE) ? EmptyNode.instance() : new LeafSingleton<>(hash, key, value);
} else {
return (action == REMOVE) ? this : mergeLeaves(shift, this, new LeafSingleton<>(keyHash, key, value));
}
}
@Override
public int size() {
return 1;
}
@Override
public Iterator> nodes() {
return Iterator.of(this);
}
@Override
int hash() {
return hash;
}
@Override
K key() {
return key;
}
@Override
V value() {
return value;
}
}
/**
* Representation of a HAMT leaf node with more than one element.
*
* @param Key type
* @param Value type
*/
final class LeafList extends LeafNode implements Serializable {
private static final long serialVersionUID = 1L;
private final int hash;
private final K key;
private final V value;
private final int size;
private final LeafNode tail;
LeafList(int hash, K key, V value, LeafNode tail) {
this.hash = hash;
this.key = key;
this.value = value;
this.size = 1 + tail.size();
this.tail = tail;
}
@Override
Option lookup(int shift, int keyHash, K key) {
if (hash != keyHash) {
return Option.none();
}
return nodes().find(node -> Objects.equals(node.key(), key)).map(LeafNode::value);
}
@Override
V lookup(int shift, int keyHash, K key, V defaultValue) {
if (hash != keyHash) {
return defaultValue;
}
V result = defaultValue;
final Iterator> iterator = nodes();
while (iterator.hasNext()) {
final LeafNode node = iterator.next();
if (Objects.equals(node.key(), key)) {
result = node.value();
break;
}
}
return result;
}
@Override
AbstractNode modify(int shift, int keyHash, K key, V value, Action action) {
if (keyHash == hash) {
final AbstractNode filtered = removeElement(key);
if (action == REMOVE) {
return filtered;
} else {
return new LeafList<>(hash, key, value, (LeafNode) filtered);
}
} else {
return (action == REMOVE) ? this : mergeLeaves(shift, this, new LeafSingleton<>(keyHash, key, value));
}
}
private static AbstractNode mergeNodes(LeafNode leaf1, LeafNode leaf2) {
if (leaf2 == null) {
return leaf1;
}
if (leaf1 instanceof LeafSingleton) {
return new LeafList<>(leaf1.hash(), leaf1.key(), leaf1.value(), leaf2);
}
if (leaf2 instanceof LeafSingleton) {
return new LeafList<>(leaf2.hash(), leaf2.key(), leaf2.value(), leaf1);
}
LeafNode result = leaf1;
LeafNode tail = leaf2;
while (tail instanceof LeafList) {
final LeafList list = (LeafList) tail;
result = new LeafList<>(list.hash, list.key, list.value, result);
tail = list.tail;
}
return new LeafList<>(tail.hash(), tail.key(), tail.value(), result);
}
private AbstractNode removeElement(K k) {
if (Objects.equals(k, this.key)) {
return tail;
}
LeafNode leaf1 = new LeafSingleton<>(hash, key, value);
LeafNode leaf2 = tail;
boolean found = false;
while (!found && leaf2 != null) {
if (Objects.equals(k, leaf2.key())) {
found = true;
} else {
leaf1 = new LeafList<>(leaf2.hash(), leaf2.key(), leaf2.value(), leaf1);
}
leaf2 = leaf2 instanceof LeafList ? ((LeafList) leaf2).tail : null;
}
return mergeNodes(leaf1, leaf2);
}
@Override
public int size() {
return size;
}
@Override
public Iterator> nodes() {
return new AbstractIterator>() {
LeafNode node = LeafList.this;
@Override
public boolean hasNext() {
return node != null;
}
@Override
public LeafNode getNext() {
final LeafNode result = node;
if (node instanceof LeafSingleton) {
node = null;
} else {
node = ((LeafList) node).tail;
}
return result;
}
};
}
@Override
int hash() {
return hash;
}
@Override
K key() {
return key;
}
@Override
V value() {
return value;
}
}
/**
* Representation of a HAMT indexed node.
*
* @param Key type
* @param Value type
*/
final class IndexedNode extends AbstractNode implements Serializable {
private static final long serialVersionUID = 1L;
private final int bitmap;
private final int size;
private final Object[] subNodes;
IndexedNode(int bitmap, int size, Object[] subNodes) {
this.bitmap = bitmap;
this.size = size;
this.subNodes = subNodes;
}
@SuppressWarnings("unchecked")
@Override
Option lookup(int shift, int keyHash, K key) {
final int frag = hashFragment(shift, keyHash);
final int bit = toBitmap(frag);
if ((bitmap & bit) != 0) {
final AbstractNode n = (AbstractNode) subNodes[fromBitmap(bitmap, bit)];
return n.lookup(shift + SIZE, keyHash, key);
} else {
return Option.none();
}
}
@SuppressWarnings("unchecked")
@Override
V lookup(int shift, int keyHash, K key, V defaultValue) {
final int frag = hashFragment(shift, keyHash);
final int bit = toBitmap(frag);
if ((bitmap & bit) != 0) {
final AbstractNode n = (AbstractNode) subNodes[fromBitmap(bitmap, bit)];
return n.lookup(shift + SIZE, keyHash, key, defaultValue);
} else {
return defaultValue;
}
}
@SuppressWarnings("unchecked")
@Override
AbstractNode modify(int shift, int keyHash, K key, V value, Action action) {
final int frag = hashFragment(shift, keyHash);
final int bit = toBitmap(frag);
final int index = fromBitmap(bitmap, bit);
final int mask = bitmap;
final boolean exists = (mask & bit) != 0;
final AbstractNode atIndx = exists ? (AbstractNode) subNodes[index] : null;
final AbstractNode child =
exists ? atIndx.modify(shift + SIZE, keyHash, key, value, action)
: EmptyNode. instance().modify(shift + SIZE, keyHash, key, value, action);
final boolean removed = exists && child.isEmpty();
final boolean added = !exists && !child.isEmpty();
final int newBitmap = removed ? mask & ~bit : added ? mask | bit : mask;
if (newBitmap == 0) {
return EmptyNode.instance();
} else if (removed) {
if (subNodes.length <= 2 && subNodes[index ^ 1] instanceof LeafNode) {
return (AbstractNode) subNodes[index ^ 1]; // collapse
} else {
return new IndexedNode<>(newBitmap, size - atIndx.size(), remove(subNodes, index));
}
} else if (added) {
if (subNodes.length >= MAX_INDEX_NODE) {
return expand(frag, child, mask, subNodes);
} else {
return new IndexedNode<>(newBitmap, size + child.size(), insert(subNodes, index, child));
}
} else {
if (!exists) {
return this;
} else {
return new IndexedNode<>(newBitmap, size - atIndx.size() + child.size(), update(subNodes, index, child));
}
}
}
private ArrayNode expand(int frag, AbstractNode child, int mask, Object[] subNodes) {
int bit = mask;
int count = 0;
int ptr = 0;
final Object[] arr = new Object[BUCKET_SIZE];
for (int i = 0; i < BUCKET_SIZE; i++) {
if ((bit & 1) != 0) {
arr[i] = subNodes[ptr++];
count++;
} else if (i == frag) {
arr[i] = child;
count++;
} else {
arr[i] = EmptyNode.instance();
}
bit = bit >>> 1;
}
return new ArrayNode<>(count, size + child.size(), arr);
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public int size() {
return size;
}
}
/**
* Representation of a HAMT array node.
*
* @param Key type
* @param Value type
*/
final class ArrayNode extends AbstractNode implements Serializable {
private static final long serialVersionUID = 1L;
private final Object[] subNodes;
private final int count;
private final int size;
ArrayNode(int count, int size, Object[] subNodes) {
this.subNodes = subNodes;
this.count = count;
this.size = size;
}
@SuppressWarnings("unchecked")
@Override
Option lookup(int shift, int keyHash, K key) {
final int frag = hashFragment(shift, keyHash);
final AbstractNode child = (AbstractNode) subNodes[frag];
return child.lookup(shift + SIZE, keyHash, key);
}
@SuppressWarnings("unchecked")
@Override
V lookup(int shift, int keyHash, K key, V defaultValue) {
final int frag = hashFragment(shift, keyHash);
final AbstractNode child = (AbstractNode) subNodes[frag];
return child.lookup(shift + SIZE, keyHash, key, defaultValue);
}
@SuppressWarnings("unchecked")
@Override
AbstractNode modify(int shift, int keyHash, K key, V value, Action action) {
final int frag = hashFragment(shift, keyHash);
final AbstractNode child = (AbstractNode) subNodes[frag];
final AbstractNode newChild = child.modify(shift + SIZE, keyHash, key, value, action);
if (child.isEmpty() && !newChild.isEmpty()) {
return new ArrayNode<>(count + 1, size + newChild.size(), update(subNodes, frag, newChild));
} else if (!child.isEmpty() && newChild.isEmpty()) {
if (count - 1 <= MIN_ARRAY_NODE) {
return pack(frag, subNodes);
} else {
return new ArrayNode<>(count - 1, size - child.size(), update(subNodes, frag, EmptyNode.instance()));
}
} else {
return new ArrayNode<>(count, size - child.size() + newChild.size(), update(subNodes, frag, newChild));
}
}
@SuppressWarnings("unchecked")
private IndexedNode pack(int idx, Object[] elements) {
final Object[] arr = new Object[count - 1];
int bitmap = 0;
int size = 0;
int ptr = 0;
for (int i = 0; i < BUCKET_SIZE; i++) {
final AbstractNode elem = (AbstractNode) elements[i];
if (i != idx && !elem.isEmpty()) {
size += elem.size();
arr[ptr++] = elem;
bitmap = bitmap | (1 << i);
}
}
return new IndexedNode<>(bitmap, size, arr);
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public int size() {
return size;
}
}
}