package fj.data.hamt; import fj.Equal; import fj.F2; import fj.Hash; import fj.Ord; import fj.P2; import fj.Show; import fj.data.List; import fj.data.Option; import fj.data.Seq; import fj.data.Stream; import static fj.P.p; import static fj.data.Option.none; import static fj.data.Option.some; import static fj.data.hamt.BitSet.longBitSet; /** * 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. * * @author Mark Perry * * Based on "Ideal Hash Trees" by Phil Bagwell, available from * http://lampwww.epfl.ch/papers/idealhashtrees.pdf */ public final class HashArrayMappedTrie<K, V> { private final Seq<Node<K, V>> seq; private final BitSet bitSet; private final Hash<K> hash; private final Equal<K> equal; public static final int BITS_IN_INDEX = 5; public static final int SIZE = (int) StrictMath.pow(2, BITS_IN_INDEX); public static final int MIN_INDEX = 0; public static final int MAX_INDEX = SIZE - 1; /** * Creates an empty trie for the bitset, sequence of nodes, equal and hash. * * @param bs - The set of bits to indicate which of the SIZE nodes in the sequence are used. * @param s - The sequence of HAMT nodes - either a HAMT or a key-value pair. * @param e - Equality instance for keys. * @param h - Hash instance for keys. */ private HashArrayMappedTrie(final BitSet bs, final Seq<Node<K, V>> s, final Equal<K> e, final Hash<K> h) { bitSet = bs; seq = s; hash = h; equal = e; } /** * Creates an empty trie. */ public static <K, V> HashArrayMappedTrie<K, V> empty(final Equal<K> e, final Hash<K> h) { return new HashArrayMappedTrie<>(BitSet.empty(), Seq.empty(), e, h); } /** * Create and empty trie keyed by integer. */ public static <V> HashArrayMappedTrie<Integer, V> emptyKeyInteger() { return empty(Equal.intEqual, Hash.intHash); } /** * Returns if the trie is empty. */ public boolean isEmpty() { return bitSet.isEmpty(); } /** * Static constructor for a HAMT instance. */ private static <K, V> HashArrayMappedTrie<K, V> hamt(final BitSet bs, final Seq<Node<K, V>> s, final Equal<K> e, final Hash<K> h) { return new HashArrayMappedTrie<>(bs, s, e, h); } /** * Returns an optional value for the given key k. */ public Option<V> find(final K k) { return find(k, MIN_INDEX, MIN_INDEX + BITS_IN_INDEX); } /** * Returns an optional value for the given key k for those nodes between * lowIndex (inclusive) and highIndex (exclusive). */ public Option<V> find(final K k, final int lowIndex, final int highIndex) { BitSet bs1 = longBitSet(hash.hash(k)).range(lowIndex, highIndex); int i = (int) bs1.longValue(); boolean b = bitSet.isSet(i); final int index = bitSet.bitsToRight(i); if (!b) { return none(); } else { final Node<K, V> oldNode = seq.index(index); return oldNode.match( n -> equal.eq(n._1(), k) ? some(n._2()) : none(), hamt -> hamt.find(k, lowIndex + BITS_IN_INDEX, highIndex + BITS_IN_INDEX) ); } } /** * Adds the key-value pair (k, v) to the trie. */ public HashArrayMappedTrie<K, V> set(final K k, final V v) { return set(k, v, MIN_INDEX, MIN_INDEX + BITS_IN_INDEX); } /** * Adds the product of key-value (k, v) pairs to the trie. */ public HashArrayMappedTrie<K, V> set(final List<P2<K, V>> list) { return list.foldLeft(h -> p -> h.set(p._1(), p._2()), this); } /** * Sets the key-value pair (k, v) for the bit range lowIndex (inclusive) to highIndex (exclusive). */ private HashArrayMappedTrie<K, V> set(final K k, final V v, final int lowIndex, final int highIndex) { final BitSet bs1 = longBitSet(hash.hash(k)).range(lowIndex, highIndex); final int i = (int) bs1.longValue(); final boolean b = bitSet.isSet(i); final int index = bitSet.bitsToRight(i); if (!b) { // append new node final Node<K, V> sn1 = Node.p2Node(p(k, v)); return hamt(bitSet.set(i), seq.insert(index, sn1), equal, hash); } else { final Node<K, V> oldNode = seq.index(index); final Node<K, V> newNode = oldNode.match(n -> { if (equal.eq(n._1(), k)) { return Node.p2Node(p(k, v)); } else { final HashArrayMappedTrie<K, V> e = HashArrayMappedTrie.empty(equal, hash); final HashArrayMappedTrie<K, V> h1 = e.set(n._1(), n._2(), lowIndex + BITS_IN_INDEX, highIndex + BITS_IN_INDEX); final HashArrayMappedTrie<K, V> h2 = h1.set(k, v, lowIndex + BITS_IN_INDEX, highIndex + BITS_IN_INDEX); return Node.hamtNode(h2); } }, hamt -> Node.hamtNode(hamt.set(k, v, lowIndex + BITS_IN_INDEX, highIndex + BITS_IN_INDEX)) ); return hamt(bitSet, seq.update(index, newNode), equal, hash); } } /** * Returns a stream of key-value pairs. */ public Stream<P2<K, V>> toStream() { return seq.toStream().bind(Node::toStream); } /** * Returns the list of key-value pairs, ordered by key. */ public List<P2<K, V>> toList(Ord<K> o) { return toStream().sort(Ord.p2Ord1(o)).toList(); } /** * Returns a list of key-value pairs. */ public List<P2<K, V>> toList() { return toStream().toList(); } @Override public String toString() { return Show.hamtShow(Show.<K>anyShow(), Show.<V>anyShow()).showS(this); } /** * Performs a left-fold reduction across this trie. */ public <B> B foldLeftOnNode(F2<B, Node<K, V>, B> f, B b) { return seq.foldLeft(f, b); } /** * Performs a left-fold reduction across this trie. */ public <B> B foldLeft(F2<B, P2<K, V>, B> f, F2<B, HashArrayMappedTrie<K, V>, B> g, B b) { return foldLeftOnNode((acc, n) -> n.match(p -> f.f(acc, p), h -> g.f(acc, h)), b); } /** * Performs a left-fold reduction across this trie. */ public <B> B foldLeft(F2<B, P2<K, V>, B> f, B b) { return foldLeftOnNode((acc, n) -> n.match(p -> f.f(acc, p), h -> h.foldLeft(f, acc)), b); } public BitSet getBitSet() { return bitSet; } public Seq<Node<K, V>> getSeq() { return seq; } /** * Returns the number of elements in the trie. */ public int length() { return seq.foldLeft( (acc, node) -> node.match(p2 -> acc + 1, hamt -> acc + hamt.length()), 0 ); } }