org.zoodb.index.critbit.CritBit64 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of phtree Show documentation
Show all versions of phtree Show documentation
The PH-Tree is a multi-dimensional index
/*
* Copyright 2009-2017 Tilmann Zaeschke. All rights reserved.
*
* This file is part of TinSpin.
*
* 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 org.zoodb.index.critbit;
/**
* CritBit64 is a 1D crit-bit tree with 64bit key length.
*
* In order to store floating point values, please convert them to 'long' with
* BitTools.toSortableLong(...), also when supplying query parameters.
* Extracted values can be converted back with BitTools.toDouble() or toFloat().
*
* Version 1.3.4
* - Merged with CB64COW
* - Implemented resetable iterators
*
* Version 1.3.2
* - Improved mask checking in QueryWithMask
*
* Version 1.3.1
* - Fixed issue #3 where iterators won't work with 'null' as values.
*
* Version 1.3
* - Removed separate field for prefix
*
* Version 1.2.2
* - simplified updateParentAfterRemove()
* - removed Node.posFirstBit
* - Moved test to tst folder
*
* Version 1.2.1
* - Replaced compare() with '==' where possible
* - Simplified compare(), doesInfixMatch(), get(), contains()
* - Replaced setBit() with set0()/set1()
*
* Version 1.1:
* - added queryWithMask()
*
* Version 1.0
* - Initial release
*
* @author Tilmann Zaeschke
*/
import java.util.Iterator;
import java.util.NoSuchElementException;
public class CritBit64 implements Iterable {
protected static final int DEPTH = 64;
protected AtomicInfo info = new AtomicInfo<>();
protected static class AtomicInfo {
protected Node root;
//the following contains either the actual key or the prefix for the sub-node
protected long rootKey;
protected V rootVal;
protected int size;
protected AtomicInfo copy() {
AtomicInfo c = new AtomicInfo<>();
c.root = this.root;
c.rootKey = this.rootKey;
c.rootVal = this.rootVal;
c.size = this.size;
return c;
}
}
protected static class Node {
//TODO replace posDiff with posMask
// --> Possibly easier to calculate (non-log?)
// --> Similarly powerful....
//TODO merge loPost & loVal!
V loVal;
V hiVal;
Node lo;
Node hi;
//the following contain either the actual key or the prefix for sub-nodes
long loPost;
long hiPost;
byte posDiff;
Node(long loPost, V loVal, long hiPost, V hiVal, int posDiff) {
this.loPost = loPost;
this.loVal = loVal;
this.hiPost = hiPost;
this.hiVal = hiVal;
this.posDiff = (byte) posDiff;
}
Node(Node original) {
this.loVal = original.loVal;
this.hiVal = original.hiVal;
this.lo = original.lo;
this.hi = original.hi;
this.loPost = original.loPost;
this.hiPost = original.hiPost;
this.posDiff = original.posDiff;
}
}
protected CritBit64() {
//private
}
/**
* Create a 1D crit-bit tree with 64 bit key length.
* @return a 1D crit-bit tree
* @param value type
*/
public static CritBit64 create() {
return new CritBit64();
}
/**
* Add a key value pair to the tree or replace the value if the key already exists.
* @param key key
* @param val value
* @return The previous value or {@code null} if there was no previous value
*/
public V put(long key, V val) {
if (info.size == 0) {
info.rootKey = key;
info.rootVal = val;
info.size++;
return null;
}
if (info.size == 1) {
Node n2 = createNode(key, val, info.rootKey, info.rootVal);
if (n2 == null) {
V prev = info.rootVal;
info.rootVal = val;
return prev;
}
info.root = n2;
info.rootKey = extractPrefix(key, n2.posDiff-1);
info.rootVal = null;
info.size++;
return null;
}
Node n = info.root;
int parentPosDiff = -1;
long prefix = info.rootKey;
Node parent = null;
boolean isCurrentChildLo = false;
while (true) {
if (parentPosDiff+1 != n.posDiff) {
//split in prefix?
int posDiff = compare(key, prefix);
if (posDiff < n.posDiff && posDiff != -1) {
Node newSub;
long subPrefix = extractPrefix(prefix, posDiff-1);
if (BitTools.getBit(key, posDiff)) {
newSub = new Node(prefix, null, key, val, posDiff);
newSub.lo = n;
} else {
newSub = new Node(key, val, prefix, null, posDiff);
newSub.hi = n;
}
if (parent == null) {
info.rootKey = subPrefix;
info.root = newSub;
} else if (isCurrentChildLo) {
parent.loPost = subPrefix;
parent.lo = newSub;
} else {
parent.hiPost = subPrefix;
parent.hi = newSub;
}
info.size++;
return null;
}
}
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
prefix = n.hiPost;
parent = n;
n = n.hi;
isCurrentChildLo = false;
} else {
Node n2 = createNode(key, val, n.hiPost, n.hiVal);
if (n2 == null) {
V prev = n.hiVal;
n.hiVal = val;
return prev;
}
n.hi = n2;
n.hiPost = extractPrefix(key, n2.posDiff-1);
n.hiVal = null;
info.size++;
return null;
}
} else {
if (n.lo != null) {
prefix = n.loPost;
parent = n;
n = n.lo;
isCurrentChildLo = true;
} else {
Node n2 = createNode(key, val, n.loPost, n.loVal);
if (n2 == null) {
V prev = n.loVal;
n.loVal = val;
return prev;
}
n.lo = n2;
n.loPost = extractPrefix(key, n2.posDiff-1);
n.loVal = null;
info.size++;
return null;
}
}
parentPosDiff = n.posDiff;
}
}
public void printTree() {
System.out.println("Tree: \n" + toString());
}
@Override
public String toString() {
if (info.size == 0) {
return "- -";
}
if (info.root == null) {
return "-" + BitTools.toBinary(info.rootKey, 64) + " v=" + info.rootVal;
}
Node n = info.root;
StringBuilder s = new StringBuilder();
printNode(n, s, "", 0, info.rootKey);
return s.toString();
}
private void printNode(Node n, StringBuilder s, String level, int currentDepth, long infix) {
char NL = '\n';
if (currentDepth != n.posDiff) {
s.append(level + "n: " + currentDepth + "/" + n.posDiff + " " +
BitTools.toBinary(infix, 64) + NL);
} else {
s.append(level + "n: " + currentDepth + "/" + n.posDiff + " i=0" + NL);
}
if (n.lo != null) {
printNode(n.lo, s, level + "-", n.posDiff+1, n.loPost);
} else {
s.append(level + " " + BitTools.toBinary(n.loPost, 64) + " v=" + n.loVal + NL);
}
if (n.hi != null) {
printNode(n.hi, s, level + "-", n.posDiff+1, n.hiPost);
} else {
s.append(level + " " + BitTools.toBinary(n.hiPost,64) + " v=" + n.hiVal + NL);
}
}
public boolean checkTree() {
if (info.root == null) {
if (info.size > 1) {
System.err.println("root node = null AND size = " + info.size);
return false;
}
return true;
}
return checkNode(info.root, 0, info.rootKey);
}
private boolean checkNode(Node n, int firstBitOfNode, long prefix) {
//check prefix
if (n.posDiff < firstBitOfNode) {
System.err.println("prefix with len=0 detected!");
return false;
}
if (n.lo != null) {
if (!doesPrefixMatch(n.posDiff-1, n.loPost, prefix)) {
System.err.println("lo: prefix mismatch");
return false;
}
checkNode(n.lo, n.posDiff+1, n.loPost);
}
if (n.hi != null) {
if (!doesPrefixMatch(n.posDiff-1, n.hiPost, prefix)) {
System.err.println("hi: prefix mismatch");
return false;
}
checkNode(n.hi, n.posDiff+1, n.hiPost);
}
return true;
}
private Node createNode(long k1, V val1, long k2, V val2) {
int posDiff = compare(k1, k2);
if (posDiff == -1) {
return null;
}
if (BitTools.getBit(k2, posDiff)) {
return new Node(k1, val1, k2, val2, posDiff);
} else {
return new Node(k2, val2, k1, val1, posDiff);
}
}
/**
*
* @param v key
* @param endPos last bit of prefix, counting starts with 0 for 1st bit
* @return The prefix.
*/
private static long extractPrefix(long v, int endPos) {
long inf = v;
//avoid shifting by 64 bit which means 0 shifting in Java!
if (endPos < 63) {
inf &= ~((-1L) >>> (1+endPos)); // & 0x3f == %64
}
return inf;
}
/**
*
* @param v key
* @param startPos start position
* @return True if the prefix matches the value or if no prefix is defined
*/
private boolean doesPrefixMatch(int posDiff, long v, long prefix) {
if (posDiff > 0) {
return (v ^ prefix) >>> (64-posDiff) == 0;
}
return true;
}
/**
* Compares two values.
* @param v1 key 1
* @param v2 key 2
* @return Position of the differing bit, or -1 if both values are equal
*/
private static int compare(long v1, long v2) {
return (v1 == v2) ? -1 : Long.numberOfLeadingZeros(v1 ^ v2);
}
/**
* Get the size of the tree.
* @return the number of keys in the tree
*/
public int size() {
return info.size;
}
/**
* Check whether a given key exists in the tree.
* @param key key
* @return {@code true} if the key exists otherwise {@code false}
*/
public boolean contains(long key) {
if (info.size == 0) {
return false;
}
if (info.size == 1) {
return key == info.rootKey;
}
Node n = info.root;
long prefix = info.rootKey;
while (doesPrefixMatch(n.posDiff, key, prefix)) {
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
prefix = n.hiPost;
n = n.hi;
continue;
}
return key == n.hiPost;
} else {
if (n.lo != null) {
prefix = n.loPost;
n = n.lo;
continue;
}
return key == n.loPost;
}
}
return false;
}
/**
* Get the value for a given key.
* @param key key
* @return the values associated with {@code key} or {@code null} if the key does not exist.
*/
public V get(long key) {
if (info.size == 0) {
return null;
}
if (info.size == 1) {
return (key == info.rootKey) ? info.rootVal : null;
}
Node n = info.root;
long prefix = info.rootKey;
while (doesPrefixMatch(n.posDiff, key, prefix)) {
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
prefix = n.hiPost;
n = n.hi;
continue;
}
return (key == n.hiPost) ? n.hiVal : null;
} else {
if (n.lo != null) {
prefix = n.loPost;
n = n.lo;
continue;
}
return (key == n.loPost) ? n.loVal : null;
}
}
return null;
}
/**
* Remove a key and its value
* @param key key
* @return The value of the key of {@code null} if the value was not found.
*/
public V remove(long key) {
if (info.size == 0) {
return null;
}
if (info.size == 1) {
if (key == info.rootKey) {
info.size--;
info.rootKey = 0;
V prev = info.rootVal;
info.rootVal = null;
return prev;
}
return null;
}
Node n = info.root;
Node parent = null;
boolean isParentHigh = false;
long prefix = info.rootKey;
while (doesPrefixMatch(n.posDiff, key, prefix)) {
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
isParentHigh = true;
prefix = n.hiPost;
parent = n;
n = n.hi;
continue;
} else {
if (key != n.hiPost) {
return null;
}
//match! --> delete node
//replace data in parent node
updateParentAfterRemove(parent, n.loPost, n.loVal, n.lo, isParentHigh);
return n.hiVal;
}
} else {
if (n.lo != null) {
isParentHigh = false;
prefix = n.loPost;
parent = n;
n = n.lo;
continue;
} else {
if (key != n.loPost) {
return null;
}
//match! --> delete node
//replace data in parent node
//for new prefixes...
updateParentAfterRemove(parent, n.hiPost, n.hiVal, n.hi, isParentHigh);
return n.loVal;
}
}
}
return null;
}
private void updateParentAfterRemove(Node parent, long newPost, V newVal,
Node newSub, boolean isParentHigh) {
newPost = (newSub == null) ? newPost : extractPrefix(newPost, newSub.posDiff-1);
if (parent == null) {
info.rootKey = newPost;
info.rootVal = newVal;
info.root = newSub;
} else if (isParentHigh) {
parent.hiPost = newPost;
parent.hiVal = newVal;
parent.hi = newSub;
} else {
parent.loPost = newPost;
parent.loVal = newVal;
parent.lo = newSub;
}
info.size--;
}
@Override
public CBIterator iterator() {
return new CBIterator().reset(this);
}
public static class CBIterator implements Iterator {
private long nextKey = 0;
private V nextValue = null;
private boolean hasNext = true;
private final Node[] stack;
//0==read_lower; 1==read_upper; 2==go_to_parent
private static final byte READ_LOWER = 0;
private static final byte READ_UPPER = 1;
private static final byte RETURN_TO_PARENT = 2;
private final byte[] readHigherNext;
private int stackTop = -1;
@SuppressWarnings("unchecked")
public CBIterator() {
this.stack = new Node[DEPTH];
this.readHigherNext = new byte[DEPTH]; // default = false
}
public CBIterator reset(CritBit64 cb) {
stackTop = -1;
hasNext = true;
readHigherNext[0] = READ_LOWER;
AtomicInfo info = cb.info;
if (info.size == 0) {
//Tree is empty
hasNext = false;
return this;
}
if (info.size == 1) {
nextValue = info.rootVal;
nextKey = info.rootKey;
return this;
}
stack[++stackTop] = info.root;
findNext();
return this;
}
private void findNext() {
while (stackTop >= 0) {
Node n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
if (n.lo == null) {
nextValue = n.loVal;
nextKey = n.loPost;
return;
} else {
stack[++stackTop] = n.lo;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
if (n.hi == null) {
nextValue = n.hiVal;
nextKey = n.hiPost;
--stackTop;
return;
//proceed to move up a level
} else {
stack[++stackTop] = n.hi;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextValue = null;
nextKey = 0;
hasNext = false;
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
V ret = nextValue;
findNext();
return ret;
}
public long nextKey() {
if (!hasNext()) {
throw new NoSuchElementException();
}
long ret = nextKey;
findNext();
return ret;
}
public Entry nextEntry() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Entry ret = new Entry(nextKey, nextValue);
findNext();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Queries the tree for entries with {@code min<=key<=max}.
* @param min minimum key
* @param max maximum key
* @return An iterator over the matching entries.
*/
public QueryIterator query(long min, long max) {
return new QueryIterator().reset(this, min, max);
}
public static class QueryIterator implements Iterator {
private long minOrig;
private long maxOrig;
private long nextKey = 0;
private V nextValue = null;
private boolean hasNext = true;
private final Node[] stack;
//0==read_lower; 1==read_upper; 2==go_to_parent
private static final byte READ_LOWER = 0;
private static final byte READ_UPPER = 1;
private static final byte RETURN_TO_PARENT = 2;
private final byte[] readHigherNext;
private final long[] prefixes;
private int stackTop = -1;
@SuppressWarnings("unchecked")
public QueryIterator() {
this.stack = new Node[DEPTH];
this.readHigherNext = new byte[DEPTH]; // default = false
this.prefixes = new long[DEPTH];
}
public QueryIterator reset(CritBit64 cb, long minOrig, long maxOrig) {
stackTop = -1;
hasNext = true;
readHigherNext[0] = READ_LOWER;
AtomicInfo info = cb.info;
this.minOrig = minOrig;
this.maxOrig = maxOrig;
if (info.size == 0) {
//Tree is empty
hasNext = false;
return this;
}
if (info.size == 1) {
hasNext = checkMatchFullIntoNextVal(info.rootKey, info.rootVal);
return this;
}
Node n = info.root;
if (!checkMatch(info.rootKey, n.posDiff-1)) {
hasNext = false;
return this;
}
stack[++stackTop] = info.root;
prefixes[stackTop] = info.rootKey;
findNext();
return this;
}
private void findNext() {
while (stackTop >= 0) {
Node n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
long valTemp = BitTools.set0(prefixes[stackTop], n.posDiff);
if (checkMatch(valTemp, n.posDiff)) {
if (n.lo == null) {
if (checkMatchFullIntoNextVal(n.loPost, n.loVal)) {
return;
}
//proceed to check upper
} else {
stack[++stackTop] = n.lo;
prefixes[stackTop] = n.loPost;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
long valTemp = BitTools.set1(prefixes[stackTop], n.posDiff);
if (checkMatch(valTemp, n.posDiff)) {
if (n.hi == null) {
if (checkMatchFullIntoNextVal(n.hiPost, n.hiVal)) {
--stackTop;
return;
}
//proceed to move up a level
} else {
stack[++stackTop] = n.hi;
prefixes[stackTop] = n.hiPost;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextValue = null;
nextKey = 0;
hasNext = false;
}
/**
* Full comparison on the parameter. Assigns the parameter to 'nextVal' if comparison
* fits.
* @param keyTemplate
* @return Whether we have a match or not
*/
private boolean checkMatchFullIntoNextVal(long keyTemplate, V value) {
if ((minOrig > keyTemplate) || (keyTemplate > maxOrig)) {
return false;
}
nextValue = value;
nextKey = keyTemplate;
return true;
}
private boolean checkMatch(long keyTemplate, int currentDepth) {
int toIgnore = 63 - currentDepth;
long mask = (-1L) << toIgnore;
if ((minOrig & mask) > (keyTemplate & mask)) {
return false;
}
if ((keyTemplate & mask) > (maxOrig & mask)) {
return false;
}
return true;
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
V ret = nextValue;
findNext();
return ret;
}
public long nextKey() {
if (!hasNext()) {
throw new NoSuchElementException();
}
long ret = nextKey;
findNext();
return ret;
}
public Entry nextEntry() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Entry ret = new Entry(nextKey, nextValue);
findNext();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Queries the tree for entries with {@code min<=key<=max}. Unlike the normal query, this
* query also excludes all elements with {@code (key|min)!=key} and {@code (key&max!=max)}.
* See PH-Tree for a discussion.
* @param min minimum key
* @param max maximum key
* @return An iterator over the matching entries.
*/
public QueryIteratorMask queryWithMask(long min, long max) {
return new QueryIteratorMask().reset(this, min, max);
}
public static class QueryIteratorMask implements Iterator {
private long minOrig;
private long maxOrig;
private long nextKey = 0;
private V nextValue = null;
boolean hasNext = true;
private final Node[] stack;
//0==read_lower; 1==read_upper; 2==go_to_parent
private static final byte READ_LOWER = 0;
private static final byte READ_UPPER = 1;
private static final byte RETURN_TO_PARENT = 2;
private final byte[] readHigherNext;
private final long[] prefixes;
private int stackTop = -1;
@SuppressWarnings("unchecked")
public QueryIteratorMask() {
this.stack = new Node[DEPTH];
this.readHigherNext = new byte[DEPTH]; // default = false
this.prefixes = new long[DEPTH];
}
public QueryIteratorMask reset(CritBit64 cb, long minOrig, long maxOrig) {
stackTop = -1;
hasNext = true;
readHigherNext[0] = READ_LOWER;
AtomicInfo info = cb.info;
this.minOrig = minOrig;
this.maxOrig = maxOrig;
if (info.size == 0) {
//Tree is empty
hasNext = false;
return this;
}
if (info.size == 1) {
hasNext = checkMatchFullIntoNextVal(info.rootKey, info.rootVal);
return this;
}
Node n = info.root;
if (!checkMatch(info.rootKey, n.posDiff-1)) {
hasNext = false;
return this;
}
stack[++stackTop] = info.root;
prefixes[stackTop] = info.rootKey;
findNext();
return this;
}
private void findNext() {
while (stackTop >= 0) {
Node n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
long valTemp = BitTools.set0(prefixes[stackTop], n.posDiff);
if (checkMatch(valTemp, n.posDiff)) {
if (n.lo == null) {
if (checkMatchFullIntoNextVal(n.loPost, n.loVal)) {
return;
}
//proceed to check upper
} else {
stack[++stackTop] = n.lo;
prefixes[stackTop] = n.loPost;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
long valTemp = BitTools.set1(prefixes[stackTop], n.posDiff);
if (checkMatch(valTemp, n.posDiff)) {
if (n.hi == null) {
if (checkMatchFullIntoNextVal(n.hiPost, n.hiVal)) {
--stackTop;
return;
}
//proceed to move up a level
} else {
stack[++stackTop] = n.hi;
prefixes[stackTop] = n.hiPost;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextValue = null;
nextKey = 0;
hasNext = false;
}
/**
* Full comparison on the parameter. Assigns the parameter to 'nextVal' if comparison
* fits.
* @param keyTemplate
* @return Whether we have a match or not
*/
private boolean checkMatchFullIntoNextVal(long keyTemplate, V value) {
if (((keyTemplate | minOrig) & maxOrig) != keyTemplate) {
return false;
}
nextValue = value;
nextKey = keyTemplate;
return true;
}
private boolean checkMatch(long keyTemplate, int currentDepth) {
int toIgnore = 63 - currentDepth;
return (((keyTemplate | minOrig) & maxOrig) ^ keyTemplate) >>> toIgnore == 0;
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
V ret = nextValue;
findNext();
return ret;
}
public long nextKey() {
if (!hasNext()) {
throw new NoSuchElementException();
}
long ret = nextKey;
findNext();
return ret;
}
public Entry nextEntry() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Entry ret = new Entry(nextKey, nextValue);
findNext();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public static class Entry {
private final long key;
private final V value;
Entry(long key, V value) {
this.key = key;
this.value = value;
}
public long key() {
return key;
}
public V value() {
return value;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy