net.sf.saxon.ma.trie.ImmutableHashTrieMap Maven / Gradle / Ivy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.ma.trie;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
// Original author: Michael Froh (published on Github). Released under MPL 2.0
// by Saxonica Limited with permission from the author
public abstract class ImmutableHashTrieMap
implements ImmutableMap, Iterable> {
private static final ImmutableHashTrieMap EMPTY_NODE = new EmptyHashNode();
public static ImmutableHashTrieMap empty() {
return EMPTY_NODE;
}
private static int getBucket(int shift, K key) {
return key.hashCode() >> shift & MASK;
}
@Override
public ImmutableHashTrieMap put(K key, V value) {
return put(0, key, value);
}
@Override
public ImmutableHashTrieMap remove(K key) {
return remove(0, key);
}
@Override
public V get(K key) {
return get(0, key);
}
/*
* In the methods below, "shift" denotes how far (in bits) through the
* hash code we are currently looking. At each level, we add "bits",
* defined below, to shift.
*/
private static final int BITS = 5;
private static final int FANOUT = 1 << BITS;
private static final int MASK = FANOUT - 1;
abstract ImmutableHashTrieMap put(int shift, K key,
V value);
abstract ImmutableHashTrieMap remove(int shift, K key);
abstract V get(int shift, K key);
abstract boolean isArrayNode();
/**
* Implementation for an empty map
* @param the key type
* @param the value type
*/
private static class EmptyHashNode
extends ImmutableHashTrieMap {
@Override
public ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
return new EntryHashNode<>(key, value);
}
@Override
public ImmutableHashTrieMap remove(final int shift,
final K key) {
return this;
}
@Override
public boolean isArrayNode() {
return false;
}
@Override
public V get(final int shift, final K key) {
return null;
}
@Override
public Iterator> iterator() {
return Collections.emptyIterator();
}
}
/**
* Implementation for a single-entry map
* @param the key type
* @param the value type
*/
private static class EntryHashNode
extends ImmutableHashTrieMap {
private final K key;
private final V value;
private EntryHashNode(final K key,
final V value) {
this.key = key;
this.value = value;
}
@Override
public ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
if (this.key.equals(key)) {
// Overwriting this entry
return new EntryHashNode<>(key, value);
} else if (this.key.hashCode() == key.hashCode()) {
// This is a collision. Return a new ListHashNode.
return new ListHashNode<>(new TrieKVP<>(this.key, this.value),
new TrieKVP<>(key, value));
}
// Split this node into an ArrayHashNode with this and the new value
// as entries.
return newArrayHashNode(shift, this.key.hashCode(), this,
key.hashCode(), new EntryHashNode(key, value));
}
@Override
public ImmutableHashTrieMap remove(final int shift,
final K key) {
if (this.key.equals(key)) {
return empty();
}
return this;
}
@Override
public boolean isArrayNode() {
return false;
}
@Override
public V get(final int shift, final K key) {
if (this.key.equals(key)) {
return value;
}
return null;
}
@Override
public Iterator> iterator() {
return Collections.singleton(new TrieKVP<>(key, value)).iterator();
}
}
/**
* Implementation for a set of entries where the keys of all entries share the same hash code
* @param the key type
* @param the value type
*/
private static class ListHashNode extends ImmutableHashTrieMap {
private final ImmutableList> entries;
public ListHashNode(TrieKVP entry1,
TrieKVP entry2) {
// These entries must collide
assert entry1.key.hashCode() == entry2.key.hashCode();
ImmutableList> newList = ImmutableList.empty();
entries = newList.prepend(entry1).prepend(entry2);
}
private ListHashNode(final ImmutableList> entries) {
// Size should be at least 2
assert !entries.isEmpty();
assert !entries.tail().isEmpty();
this.entries = entries;
}
@Override
public ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
if (entries.head().key.hashCode() != key.hashCode()) {
return newArrayHashNode(shift,
entries.head().key.hashCode(),
this,
key.hashCode(),
new EntryHashNode<>(
key, value));
}
ImmutableList> newList = ImmutableList.empty();
boolean found = false;
for (TrieKVP entry : entries) {
if (entry.key.equals(key)) {
// Node replacement
newList = newList.prepend(new TrieKVP<>(key, value));
found = true;
} else {
newList = newList.prepend(entry);
}
}
if (!found) {
// Adding a new entry
newList = newList.prepend(new TrieKVP<>(key, value));
}
return new ListHashNode<>(newList);
}
@Override
public ImmutableHashTrieMap remove(final int shift,
final K key) {
ImmutableList> newList = ImmutableList.empty();
int size = 0;
for (TrieKVP entry : entries) {
if (!entry.key.equals(key)) {
newList = newList.prepend(entry);
size++;
}
}
if (size == 1) {
TrieKVP entry = newList.head();
return new EntryHashNode<>(entry.key, entry.value);
}
return new ListHashNode<>(newList);
}
@Override
public boolean isArrayNode() {
return false;
}
@Override
public V get(final int shift, final K key) {
for (TrieKVP entry : entries) {
if (entry.key.equals(key)) {
return entry.value;
}
}
return null;
}
@Override
public Iterator> iterator() {
return new Iterator>() {
private ImmutableList> curList =
ListHashNode.this.entries;
@Override
public boolean hasNext() {
return !curList.isEmpty();
}
@Override
public TrieKVP next() {
TrieKVP retVal = curList.head();
curList = curList.tail();
return retVal;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Create a new node combining two existing nodes
* @param shift the shift to be applied to the hash code at this level in the tree
* @param hash1 the hash code of the first node
* @param subNode1 the first node
* @param hash2 the hash code of the second node
* @param subNode2 the second node
* @param the key type
* @param the value type
* @return the new node
*/
private static ImmutableHashTrieMap
newArrayHashNode(int shift,
int hash1,
ImmutableHashTrieMap subNode1,
int hash2,
ImmutableHashTrieMap subNode2) {
int curShift = shift;
int h1 = hash1 >> shift & MASK;
int h2 = hash2 >> shift & MASK;
List buckets = new LinkedList<>();
while (h1 == h2) {
buckets.add(0, h1);
curShift += BITS;
h1 = hash1 >> curShift & MASK;
h2 = hash2 >> curShift & MASK;
}
ImmutableHashTrieMap newNode =
new BranchedArrayHashNode(h1, subNode1, h2, subNode2);
for (Integer bucket : buckets) {
newNode = new SingletonArrayHashNode(bucket, newNode);
}
return newNode;
}
private static abstract class ArrayHashNode
extends ImmutableHashTrieMap {
@Override
boolean isArrayNode() {
return true;
}
}
private static class BranchedArrayHashNode
extends ArrayHashNode {
private final ImmutableHashTrieMap[] subnodes;
private final int size;
public BranchedArrayHashNode(int h1,
ImmutableHashTrieMap subNode1,
int h2,
ImmutableHashTrieMap subNode2) {
assert h1 != h2;
size = 2;
subnodes = new ImmutableHashTrieMap[FANOUT];
for (int i = 0; i < FANOUT; i++) {
if (i == h1) {
subnodes[i] = subNode1;
} else if (i == h2) {
subnodes[i] = subNode2;
} else {
subnodes[i] = EMPTY_NODE;
}
}
}
public BranchedArrayHashNode(int size,
final ImmutableHashTrieMap[] subnodes) {
assert subnodes.length == FANOUT;
this.size = size;
this.subnodes = subnodes;
}
@Override
public ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
final int bucket = getBucket(shift, key);
ImmutableHashTrieMap[] newNodes = new ImmutableHashTrieMap[FANOUT];
System.arraycopy(subnodes, 0, newNodes, 0, FANOUT);
final int newSize =
newNodes[bucket] == EMPTY_NODE ? size + 1 : size;
newNodes[bucket] = newNodes[bucket].put(shift + BITS,
key, value);
return new BranchedArrayHashNode(newSize, newNodes);
}
@Override
public ImmutableHashTrieMap remove(final int shift,
final K key) {
final int bucket = getBucket(shift, key);
if (subnodes[bucket] == EMPTY_NODE) {
return this;
}
ImmutableHashTrieMap[] newNodes = new ImmutableHashTrieMap[FANOUT];
System.arraycopy(subnodes, 0, newNodes, 0, FANOUT);
newNodes[bucket] = newNodes[bucket].remove(shift + BITS,
key);
final int newSize =
newNodes[bucket] == EMPTY_NODE ? size - 1 : size;
if (newSize == 1) {
int orphanedBucket = -1;
for (int i = 0; i < FANOUT; i++) {
if (newNodes[i] != EMPTY_NODE) {
orphanedBucket = i;
break;
}
}
ImmutableHashTrieMap orphanedEntry =
subnodes[orphanedBucket];
if (orphanedEntry.isArrayNode()) {
return new SingletonArrayHashNode<>(orphanedBucket, orphanedEntry);
}
return orphanedEntry;
}
return new BranchedArrayHashNode<>(newSize, newNodes);
}
@Override
public V get(final int shift, final K key) {
final int bucket = getBucket(shift, key);
return subnodes[bucket].get(shift + BITS, key);
}
@Override
public Iterator> iterator() {
return new Iterator>() {
private int bucket = 0;
private Iterator> childIterator =
subnodes[0].iterator();
@Override
public boolean hasNext() {
if (childIterator.hasNext()) {
return true;
}
bucket++;
while (bucket < FANOUT) {
childIterator = subnodes[bucket].iterator();
if (childIterator.hasNext()) {
return true;
}
bucket++;
}
return false;
}
@Override
public TrieKVP next() {
return childIterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
private static class SingletonArrayHashNode extends
ArrayHashNode {
private final int bucket;
private final ImmutableHashTrieMap subnode;
private SingletonArrayHashNode(final int bucket,
final ImmutableHashTrieMap subnode) {
assert subnode instanceof ArrayHashNode;
this.bucket = bucket;
this.subnode = subnode;
}
@Override
ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
final int bucket = getBucket(shift, key);
if (bucket == this.bucket) {
return new SingletonArrayHashNode<>(bucket,
subnode.put(shift + BITS, key, value));
}
return new BranchedArrayHashNode<>(this.bucket, subnode,
bucket, new EntryHashNode<>(key, value));
}
@Override
ImmutableHashTrieMap remove(final int shift, final K key) {
final int bucket = getBucket(shift, key);
if (bucket == this.bucket) {
ImmutableHashTrieMap newNode =
subnode.remove(shift + BITS, key);
if (!newNode.isArrayNode()) {
return newNode;
}
return new SingletonArrayHashNode<>(bucket, newNode);
}
return this;
}
@Override
V get(final int shift, final K key) {
final int bucket = getBucket(shift, key);
if (bucket == this.bucket) {
return subnode.get(shift + BITS, key);
}
return null;
}
@Override
public Iterator> iterator() {
return subnode.iterator();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy