
org.zoodb.index.critbit.CritBit Maven / Gradle / Ivy
/*
* 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;
/**
* CritBit is a multi-dimensional OR arbitrary length crit-bit tree.
*
* Cribit trees are very space efficient due to prefix-sharing and suitable for
* multi-dimensional data with low dimensionality (e.g. less than 10 dimensions or so).
* They are also stable, that means unlike kD-trees or quadtrees they do not require
* rebalancing, this makes update performance much more predictable.
*
* There is 1 1D-version and a kD-version (kD: k-dimensional).
* The 1D version supports keys with arbitrary length (e.g. 256bit), the kD-version
* supports k-dimensional keys with a maximum length of 64 bit per dimension.
*
* Both tree versions use internally the same methods, except for the range queries.
* For range queries, the 1D version interprets the parameters as one minimum and one
* maximum value. For kD queries, the parameters are interpreted as arrays of
* minimum and maximum values (i.e. the low left and upper right
* corner of a query (hyper-)rectangle).
*
* All method ending with 'KD' are for k-dimensional use of the tree, all other methods are for
* 1-dimensional use. Exceptions are the size(), printTree() and similar methods, which work for
* all dimensions.
*
* 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.5
* - Fixed rare problem with postfix creation. This solves a problem
* with kd-queries and slightly reduces memory consumption.
*
* Version 1.3.2
* - Added QueryIterator.reset()
*
* Version 1.3.1
* - Fixed issue #3 where iterators won't work with 'null' as values.
*
* Version 1.2.2
* - Moved tests to tst folder
*
* Version 1.2.1
* - Replaced compare() with isEqual() where possible
* - Simplified compare(), doesInfixMatch()
* - Removed unused arguments
*
* Version 1.2
* - Added iterator() to iterate over all entries
*
* Version 1.1
* - Slight performance improvements in mergeLong() and readAndSplit()
*
* Version 1.0
* - Initial release
*
* @author Tilmann Zaeschke
*/
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class CritBit implements CritBit1D, CritBitKD {
private final int DEPTH;
private final int DIM;
private Node root;
private long[] rootKey;
private V rootVal;
private int size;
private static final int SINGLE_DIM = -1;
private static final int BITS_LOG_64 = 6;
private static final int BITS_MASK_6 = ~((-1) << BITS_LOG_64);
private static class Node {
//TODO store both post in one array
//TODO reduce space usage by using same reference for lo and loPost/loVal
//TODO --> use only one ref for hi/low each
//TODO ? put other fields into long[]????
V loVal;
V hiVal;
Node lo;
Node hi;
long[] loPost;
long[] hiPost;
long[] infix;
int posFirstBit;
int posDiff;
Node(int posFirstBit, long[] loPost, V loVal, long[] hiPost, V hiVal,
long[] infix, int posDiff) {
this.loPost = loPost;
this.loVal = loVal;
this.hiPost = hiPost;
this.hiVal = hiVal;
this.infix = infix;
this.posFirstBit = posFirstBit;
this.posDiff = posDiff;
}
}
private CritBit(int depth, int dim) {
this.DEPTH = depth;
//we deliberately allow dim=1 here
this.DIM = dim;
}
/**
* Create a 1D crit-bit tree with arbitrary key length.
* @param width The number of bits per value
* @return a 1D crit-bit tree
* @param value type
*/
public static CritBit1D create1D(int width) {
if (width < 1) {
throw new IllegalArgumentException("Illegal bit width: " + width);
}
// SINGLE_DIM ensures that DIM is never used in this case.
return new CritBit(width, SINGLE_DIM);
}
/**
* Create a kD crit-bit tree with maximum 64bit key length.
*
* @param width The number of bits per value
* @param dim The number of dimensions
* @return k-dimensional tree
* @param value type
*/
public static CritBitKD createKD(int width, int dim) {
if (width < 1 || width > 64) {
throw new IllegalArgumentException("Illegal bit width: " + width);
}
if (dim < 1) {
throw new IllegalArgumentException("Illegal dimension count: " + dim);
}
return new CritBit(width, dim);
}
/**
* 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
*/
@Override
public V put(long[] key, V val) {
checkDim0();
return putNoCheck(key, val);
}
private V putNoCheck(long[] key, V val) {
if (root == null) {
if (rootKey == null) {
rootKey = new long[key.length];
System.arraycopy(key, 0, rootKey, 0, key.length);
rootVal = val;
} else {
Node n2 = createNode(key, val, rootKey, rootVal, 0);
if (n2 == null) {
V prev = rootVal;
rootVal = val;
return prev;
}
root = n2;
rootKey = null;
rootVal = null;
}
size++;
return null;
}
Node n = root;
long[] currentPrefix = new long[key.length];
while (true) {
readInfix(n, currentPrefix);
if (n.infix != null) {
//split infix?
int posDiff = compare(key, currentPrefix);
if (posDiff < n.posDiff && posDiff != -1) {
long[] subInfix = extractInfix(currentPrefix, posDiff+1, n.posDiff-1);
//new sub-node
Node newSub = new Node(posDiff+1, n.loPost, n.loVal, n.hiPost, n.hiVal,
subInfix, n.posDiff);
newSub.hi = n.hi;
newSub.lo = n.lo;
if (BitTools.getAndCopyBit(key, posDiff, currentPrefix)) {
n.hi = null;
n.hiPost = createPostFix(key, posDiff);
n.hiVal = val;
n.lo = newSub;
n.loPost = null;
n.loVal = null;
} else {
n.hi = newSub;
n.hiPost = null;
n.hiVal = null;
n.lo = null;
n.loPost = createPostFix(key, posDiff);
n.loVal = val;
}
n.infix = extractInfix(currentPrefix, n.posFirstBit, posDiff-1);
n.posDiff = posDiff;
size++;
return null;
}
}
//infix matches, so now we check sub-nodes and postfixes
if (BitTools.getAndCopyBit(key, n.posDiff, currentPrefix)) {
if (n.hi != null) {
n = n.hi;
continue;
} else {
readPostFix(n.hiPost, currentPrefix);
Node n2 = createNode(key, val, currentPrefix, n.hiVal, n.posDiff + 1);
if (n2 == null) {
V prev = n.hiVal;
n.hiVal = val;
return prev;
}
n.hi = n2;
n.hiPost = null;
n.hiVal = null;
size++;
return null;
}
} else {
if (n.lo != null) {
n = n.lo;
continue;
} else {
readPostFix(n.loPost, currentPrefix);
Node n2 = createNode(key, val, currentPrefix, n.loVal, n.posDiff + 1);
if (n2 == null) {
V prev = n.loVal;
n.loVal = val;
return prev;
}
n.lo = n2;
n.loPost = null;
n.loVal = null;
size++;
return null;
}
}
}
}
private void checkDim0() {
if (DIM != SINGLE_DIM) {
throw new IllegalStateException("Please use ___KD() methods for k-dimensional data.");
}
}
@Override
public void printTree() {
System.out.println("Tree: \n" + toString());
}
@Override
public String toString() {
if (root == null) {
if (rootKey != null) {
return "-" + BitTools.toBinary(rootKey, 64) + " v=" + rootVal;
}
return "- -";
}
Node n = root;
StringBuilder s = new StringBuilder();
printNode(n, s, "", 0);
return s.toString();
}
private void printNode(Node n, StringBuilder s, String level, int currentDepth) {
char NL = '\n';
if (n.infix != null) {
s.append(level + "n: " + currentDepth + "/" + n.posDiff + " " +
BitTools.toBinary(n.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);
} 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);
} else {
s.append(level + " " + BitTools.toBinary(n.hiPost,64) + " v=" + n.hiVal + NL);
}
}
public boolean checkTree() {
if (root == null) {
if (rootKey != null) {
return true;
}
return true;
}
if (rootKey != null) {
System.err.println("root node AND value != null");
return false;
}
return checkNode(root, 0);
}
private boolean checkNode(Node n, int firstBitOfNode) {
//check infix
if (n.posDiff == firstBitOfNode && n.infix != null) {
System.err.println("infix with len=0 detected!");
return false;
}
if (n.posFirstBit != firstBitOfNode) {
System.err.println("infix inconsistency detected!");
return false;
}
if (n.lo != null) {
if (n.loPost != null) {
System.err.println("lo: sub-node AND key != null");
return false;
}
checkNode(n.lo, n.posDiff+1);
} else {
if (n.loPost == null) {
System.err.println("lo: sub-node AND key == null");
return false;
}
}
if (n.hi != null) {
if (n.hiPost != null) {
System.err.println("hi: sub-node AND key != null");
return false;
}
checkNode(n.hi, n.posDiff+1);
} else {
if (n.hiPost == null) {
System.err.println("hi: sub-node AND key == null");
return false;
}
}
return true;
}
/**
* Creates a postfix starting at posDiff+1.
* @param val
* @param posDiff
* @return the postfix.
*/
private long[] createPostFix(long[] val, int posDiff) {
int preLen = (posDiff+1) >>> 6;
long[] p = new long[val.length - preLen];
System.arraycopy(val, preLen, p, 0, p.length);
return p;
}
private static void readPostFix(long[] postVal, long[] currentPrefix) {
int preLen = currentPrefix.length - postVal.length;
System.arraycopy(postVal, 0, currentPrefix, preLen, postVal.length);
}
private Node createNode(long[] k1, V val1, long[] k2, V val2, int posFirstBit) {
int posDiff = compare(k1, k2);
if (posDiff == -1) {
return null;
}
long[] infix = extractInfix(k1, posFirstBit, posDiff-1);
long[] p1 = createPostFix(k1, posDiff);
long[] p2 = createPostFix(k2, posDiff);
//if (isABitwiseSmallerB(v1, v2)) {
if (BitTools.getBit(k2, posDiff)) {
return new Node(posFirstBit, p1, val1, p2, val2, infix, posDiff);
} else {
return new Node(posFirstBit, p2, val2, p1, val1, infix, posDiff);
}
}
/**
*
* @param n node
* @param currentPrefix prefix
* @param value type
*/
protected static void readInfix(Node n, long[] currentPrefix) {
if (n.infix == null) {
return;
}
int dst = n.posFirstBit >>> 6;
System.arraycopy(n.infix, 0, currentPrefix, dst, n.infix.length);
}
/**
*
* @param v key
* @param startPos first bit of infix, counting starts with 0 for 1st bit
* @param endPos last bit of infix
* @return The infix PLUS leading bits before the infix that belong in the same 'long'.
*/
private static long[] extractInfix(long[] v, int startPos, int endPos) {
if (endPos < startPos) {
//no infix (LEN = 0)
return null;
}
//TODO In half of the cases we could avoid one 'long' by shifting the bits such that there
// are less then 64 unused bits
int start = startPos >>> 6;
int end = endPos >>> 6;
long[] inf = new long[end-start+1];
//System.out.println("s/e/l/sp/ep=" + start + "/" + end + "/" + inf.length + "/" + startPos + "/" + endPos);
//System.out.println("vl/s/il=" + v.length + "/" + start + "/" + inf.length);
System.arraycopy(v, start, inf, 0, inf.length);
//avoid shifting by64 bit which means 0 shifting in Java!
if ((endPos & 0x3F) < 63) {
inf[inf.length-1] &= ~((-1L) >>> (1+(endPos & 0x3F))); // & 0x3f == %64
}
return inf;
}
/**
*
* @param v key
* @param startPos start position
* @return True if the infix matches the value or if no infix is defined
*/
private boolean doesInfixMatch(Node n, long[] v, long[] currentVal) {
if (n.infix == null) {
return true;
}
int start = n.posFirstBit >>> 6;
int end = (n.posDiff-1) >>> 6;
for (int i = start; i < end; i++) {
if (v[i] != currentVal[i]) {
return false;
}
}
//last element
int shift = 63 - ((n.posDiff-1) & 0x3f);
return (v[end] ^ currentVal[end]) >>> shift == 0;
}
/**
* 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) {
for (int i = 0; i < v1.length; i++) {
if (v1[i] != v2[i]) {
return (i*64) + Long.numberOfLeadingZeros(v1[i] ^ v2[i]);
}
}
return -1;
}
/**
* Compares two values.
* @param v1 key 1
* @param v2 key 2
* @return {@code true} iff both values are equal
*/
private static boolean isEqual(long[] v1, long[] v2) {
for (int i = 0; i < v1.length; i++) {
if (v1[i] != v2[i]) {
return false;
}
}
return true;
}
/**
* Get the size of the tree.
* @return the number of keys in the tree
*/
@Override
public int size() {
return size;
}
/**
* Check whether a given key exists in the tree.
* @param key key
* @return {@code true} if the key exists otherwise {@code false}
*/
@Override
public boolean contains(long[] key) {
checkDim0();
return containsNoCheck(key);
}
private boolean containsNoCheck(long[] val) {
if (root == null) {
if (rootKey != null) {
if (isEqual(val, rootKey)) {
return true;
}
}
return false;
}
Node n = root;
long[] currentPrefix = new long[val.length];
while (true) {
readInfix(n, currentPrefix);
if (!doesInfixMatch(n, val, currentPrefix)) {
return false;
}
//infix matches, so now we check sub-nodes and postfixes
if (BitTools.getAndCopyBit(val, n.posDiff, currentPrefix)) {
if (n.hi != null) {
n = n.hi;
continue;
}
readPostFix(n.hiPost, currentPrefix);
} else {
if (n.lo != null) {
n = n.lo;
continue;
}
readPostFix(n.loPost, currentPrefix);
}
return isEqual(val, currentPrefix);
}
}
/**
* 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.
*/
@Override
public V get(long[] key) {
checkDim0();
return getNoCheck(key);
}
private V getNoCheck(long[] key) {
if (root == null) {
if (rootKey != null) {
if (isEqual(key, rootKey)) {
return rootVal;
}
}
return null;
}
Node n = root;
long[] currentPrefix = new long[key.length];
while (true) {
readInfix(n, currentPrefix);
if (!doesInfixMatch(n, key, currentPrefix)) {
return null;
}
//infix matches, so now we check sub-nodes and postfixes
if (BitTools.getAndCopyBit(key, n.posDiff, currentPrefix)) {
if (n.hi != null) {
n = n.hi;
continue;
}
readPostFix(n.hiPost, currentPrefix);
if (isEqual(key, currentPrefix)) {
return n.hiVal;
}
} else {
if (n.lo != null) {
n = n.lo;
continue;
}
readPostFix(n.loPost, currentPrefix);
if (isEqual(key, currentPrefix)) {
return n.loVal;
}
}
return null;
}
}
private static long[] clone(long[] v) {
long[] r = new long[v.length];
System.arraycopy(v, 0, r, 0, v.length);
return r;
}
/**
* Remove a key and its value
* @param key key
* @return The value of the key of {@code null} if the value was not found.
*/
@Override
public V remove(long[] key) {
checkDim0();
return removeNoCheck(key);
}
private V removeNoCheck(long[] val2) {
if (root == null) {
if (rootKey != null) {
if (isEqual(val2, rootKey)) {
size--;
rootKey = null;
V prev = rootVal;
rootVal = null;
return prev;
}
}
return null;
}
Node n = root;
long[] currentPrefix = new long[val2.length];
Node parent = null;
boolean isParentHigh = false;
while (true) {
readInfix(n, currentPrefix);
if (!doesInfixMatch(n, val2, currentPrefix)) {
return null;
}
//infix matches, so now we check sub-nodes and postfixes
if (BitTools.getAndCopyBit(val2, n.posDiff, currentPrefix)) {
if (n.hi != null) {
isParentHigh = true;
parent = n;
n = n.hi;
continue;
} else {
readPostFix(n.hiPost, currentPrefix);
if (!isEqual(val2, currentPrefix)) {
return null;
}
//match! --> delete node
//a) first recover other values
long[] newPost = null;
if (n.loPost != null) {
readPostFix(n.loPost, currentPrefix);
newPost = currentPrefix;
}
//b) replace data in parent node
BitTools.setBit(currentPrefix, n.posDiff, false);
updateParentAfterRemove(parent, newPost, n.loVal, n.lo, isParentHigh, currentPrefix, n);
return n.hiVal;
}
} else {
if (n.lo != null) {
isParentHigh = false;
parent = n;
n = n.lo;
continue;
} else {
readPostFix(n.loPost, currentPrefix);
if (!isEqual(val2, currentPrefix)) {
return null;
}
//match! --> delete node
//a) first recover other values
long[] newPost = null;
if (n.hiPost != null) {
readPostFix(n.hiPost, currentPrefix);
newPost = currentPrefix;
}
//b) replace data in parent node
//for new infixes...
BitTools.setBit(currentPrefix, n.posDiff, true);
updateParentAfterRemove(parent, newPost, n.hiVal, n.hi, isParentHigh, currentPrefix, n);
return n.loVal;
}
}
}
}
private void updateParentAfterRemove(Node parent, long[] newPost, V newVal,
Node newSub, boolean isParentHigh, long[] currentPrefix, Node n) {
if (newSub != null) {
readInfix(newSub, currentPrefix);
}
if (parent == null) {
rootKey = newPost;
rootVal = newVal;
root = newSub;
} else if (isParentHigh) {
if (newSub == null) {
parent.hiPost = createPostFix(currentPrefix, parent.posDiff);
parent.hiVal = newVal;
} else {
parent.hiPost = null;
parent.hiVal = null;
}
parent.hi = newSub;
} else {
if (newSub == null) {
parent.loPost = createPostFix(currentPrefix, parent.posDiff);
parent.loVal = newVal;
} else {
parent.loPost = null;
parent.loVal = null;
}
parent.lo = newSub;
}
if (newSub != null) {
newSub.posFirstBit = n.posFirstBit;
newSub.infix = extractInfix(currentPrefix, newSub.posFirstBit, newSub.posDiff-1);
}
size--;
}
/**
* Create an iterator over all values, keys or entries.
* @return the iterator
*/
@Override
public FullIterator iterator() {
checkDim0();
return new FullIterator(this, DEPTH);
}
public static class FullIterator implements Iterator {
private final long[] valIntTemplate;
private long[] nextKey = null;
private V nextValue = null;
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 FullIterator(CritBit cb, int DEPTH) {
this.stack = new Node[DEPTH];
this.readHigherNext = new byte[DEPTH]; // default = false
int intArrayLen = (DEPTH+63) >>> 6;
this.valIntTemplate = new long[intArrayLen];
if (cb.rootKey != null) {
readNextVal(cb.rootKey, cb.rootVal);
return;
}
if (cb.root == null) {
//Tree is empty
return;
}
Node n = cb.root;
CritBit.readInfix(n, valIntTemplate);
stack[++stackTop] = cb.root;
findNext();
}
private void findNext() {
while (stackTop >= 0) {
Node n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
//TODO use bit directly to check validity
BitTools.setBit(valIntTemplate, n.posDiff, false);
if (n.loPost != null) {
CritBit.readPostFix(n.loPost, valIntTemplate);
readNextVal(valIntTemplate, n.loVal);
return;
//proceed to check upper
} else {
CritBit.readInfix(n.lo, valIntTemplate);
stack[++stackTop] = n.lo;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
BitTools.setBit(valIntTemplate, n.posDiff, true);
if (n.hiPost != null) {
CritBit.readPostFix(n.hiPost, valIntTemplate);
readNextVal(valIntTemplate, n.hiVal);
--stackTop;
return;
//proceed to move up a level
} else {
CritBit.readInfix(n.hi, valIntTemplate);
stack[++stackTop] = n.hi;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextValue = null;
nextKey = null;
}
/**
* Full comparison on the parameter. Assigns the parameter to 'nextVal' if comparison
* fits.
* @param keyTemplate
* @param value
*/
private void readNextVal(long[] keyTemplate, V value) {
nextValue = value;
nextKey = CritBit.clone(keyTemplate);
}
@Override
public boolean hasNext() {
return nextKey != null;
}
@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();
}
}
@Override
public QueryIterator query(long[] min, long[] max) {
checkDim0();
return new QueryIterator(this, min, max);
}
public static class QueryIterator implements Iterator {
private final CritBit cb;
private final long[] valIntTemplate;
private long[] minOrig;
private long[] maxOrig;
private long[] nextKey = null;
private V nextValue = null;
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;
private final boolean[] loEnclosed, hiEnclosed;
@SuppressWarnings("unchecked")
QueryIterator(CritBit cb, long[] minOrig, long[] maxOrig) {
this.cb = cb;
this.stack = new Node[cb.DEPTH];
this.readHigherNext = new byte[cb.DEPTH]; // default = false
int intArrayLen = (cb.DEPTH+63) >>> 6;
this.valIntTemplate = new long[intArrayLen];
this.loEnclosed = new boolean[intArrayLen];
this.hiEnclosed = new boolean[intArrayLen];
reset(minOrig, maxOrig);
}
public void reset(long[] min, long[] max) {
stackTop = -1;
nextKey = null;
this.minOrig = min;
this.maxOrig = max;
Arrays.fill(readHigherNext, (byte)0);
if (cb.rootKey != null) {
checkMatchIntoNextVal(cb.rootKey, 0, cb.rootVal);
return;
}
if (cb.root == null) {
//Tree is empty
return;
}
Node n = cb.root;
readInfix(n, valIntTemplate);
if (!checkMatch(valIntTemplate, 0, n.posDiff-1)) {
return;
}
stack[++stackTop] = cb.root;
findNext();
}
private void findNext() {
while (stackTop >= 0) {
Node n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
//TODO use bit directly to check validity
BitTools.setBit(valIntTemplate, n.posDiff, false);
if (checkMatch(valIntTemplate, n.posFirstBit, n.posDiff)) {
if (n.loPost != null) {
readPostFix(n.loPost, valIntTemplate);
if (checkMatchIntoNextVal(valIntTemplate, n.posDiff+1, n.loVal)) {
return;
}
//proceed to check upper
} else {
readInfix(n.lo, valIntTemplate);
stack[++stackTop] = n.lo;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
BitTools.setBit(valIntTemplate, n.posDiff, true);
if (checkMatch(valIntTemplate, n.posFirstBit, n.posDiff)) {
if (n.hiPost != null) {
readPostFix(n.hiPost, valIntTemplate);
if (checkMatchIntoNextVal(valIntTemplate, n.posDiff+1, n.hiVal)) {
--stackTop;
return;
}
//proceed to move up a level
} else {
readInfix(n.hi, valIntTemplate);
stack[++stackTop] = n.hi;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextValue = null;
nextKey = null;
}
/**
* Comparison on the post-fix. Assigns the parameter to 'nextVal' if comparison fits.
* @param keyTemplate
* @return Whether we have a match or not
*/
private boolean checkMatchIntoNextVal(long[] keyTemplate, int startBit, V value) {
int iStart = startBit >>> BITS_LOG_64;
//We have to remember this lo/hoMatch stuff starting from i=0 because locally exceeding
//the boundaries is only problematic if the node is not fully enclosed.
boolean loMatch = iStart == 0 ? false : loEnclosed[iStart-1];
boolean hiMatch = iStart == 0 ? false : hiEnclosed[iStart-1];
for (int i = iStart; i < keyTemplate.length; i++) {
if ((!loMatch && minOrig[i] > keyTemplate[i]) ||
(!hiMatch && keyTemplate[i] > maxOrig[i])) {
return false;
}
if (minOrig[i] < keyTemplate[i]) {
loMatch = true;
if (loMatch && hiMatch) {
break;
}
}
if (keyTemplate[i] < maxOrig[i]) {
hiMatch = true;
if (loMatch && hiMatch) {
break;
}
}
}
nextValue = value;
nextKey = CritBit.clone(keyTemplate);
return true;
}
private boolean checkMatch(long[] keyTemplate, int startBit, int currentDepth) {
int i;
int iStart = startBit >>> BITS_LOG_64;
//We have to remember this lo/hoMatch stuff starting from i=0 because locally exceeding
//the boundaries is only problematic if the node is not fully enclosed.
boolean loMatch = iStart == 0 ? false : loEnclosed[iStart-1];
boolean hiMatch = iStart == 0 ? false : hiEnclosed[iStart-1];
for (i = iStart; i < (currentDepth+1) >>> BITS_LOG_64; i++) {
// if (minOrig[i] > valTemplate[i] || valTemplate[i] > maxOrig[i]) {
// return false;
// }
if ((!loMatch && minOrig[i] > keyTemplate[i]) ||
(!hiMatch && keyTemplate[i] > maxOrig[i])) {
return false;
}
if (minOrig[i] < keyTemplate[i]) {
loMatch = true;
if (loMatch && hiMatch) {
break;
}
}
if (keyTemplate[i] < maxOrig[i]) {
hiMatch = true;
if (loMatch && hiMatch) {
break;
}
}
loEnclosed[i] = loMatch;
hiEnclosed[i] = hiMatch;
}
if (loMatch && hiMatch) {
for (; i < (currentDepth+1) >>> BITS_LOG_64; i++) {
loEnclosed[i] = loMatch;
hiEnclosed[i] = hiMatch;
}
return true;
}
int toCheck = (currentDepth+1) & BITS_MASK_6;
if (toCheck != 0) {
long mask = ~((-1L) >>> toCheck);
if (!loMatch && (minOrig[i] & mask) > (keyTemplate[i] & mask)) {
return false;
}
if (!hiMatch && (keyTemplate[i] & mask) > (maxOrig[i] & mask)) {
return false;
}
}
return true;
}
@Override
public boolean hasNext() {
return nextKey != null;
}
@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 QueryIteratorWithMask implements Iterator {
private final CritBit cb;
private final long[] valIntTemplate;
private long[] minOrig;
private long[] maxOrig;
private long[] nextKey = null;
private V nextValue = null;
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;
//This mask remembers whether a certain dimension is fully contained or not
//We have a separate mask for each possible level
private final long[] domMaskLo;
private final long[] domMaskHi;
private final long MAX_MASK;
@SuppressWarnings("unchecked")
public QueryIteratorWithMask(CritBit cb, long[] minOrig, long[] maxOrig, int DIM) {
this.cb = cb;
this.stack = new Node[cb.DEPTH];
this.readHigherNext = new byte[cb.DEPTH]; // default = false
int intArrayLen = (cb.DEPTH+63) >>> 6;
this.valIntTemplate = new long[intArrayLen];
this.domMaskLo = new long[intArrayLen];
this.domMaskHi = new long[intArrayLen];
this.MAX_MASK = ~((-1L) << DIM);
reset(minOrig, maxOrig);
}
public void reset(long[] min, long[] max) {
stackTop = -1;
nextKey = null;
this.minOrig = min;
this.maxOrig = max;
Arrays.fill(readHigherNext, (byte)0);
if (cb.rootKey != null) {
checkMatchIntoNextVal(cb.rootKey, 0, cb.rootVal);
return;
}
if (cb.root == null) {
//Tree is empty
return;
}
Node n = cb.root;
readInfix(n, valIntTemplate);
if (!checkMatch(valIntTemplate, 0, n.posDiff-1)) {
return;
}
stack[++stackTop] = cb.root;
findNext();
}
private void findNext() {
while (stackTop >= 0) {
Node n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
if (checkMatchANdSetSingleBit0(valIntTemplate, n.posDiff)) {
if (n.loPost != null) {
readPostFix(n.loPost, valIntTemplate);
if (checkMatchIntoNextVal(valIntTemplate, n.posDiff+1, n.loVal)) {
return;
}
//proceed to check upper
} else {
readInfix(n.lo, valIntTemplate);
if (checkMatch(valIntTemplate, n.lo.posFirstBit, n.lo.posDiff-1)) {
stack[++stackTop] = n.lo;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
if (checkMatchAndSetSingleBit1(valIntTemplate, n.posDiff)) {
if (n.hiPost != null) {
readPostFix(n.hiPost, valIntTemplate);
if (checkMatchIntoNextVal(valIntTemplate, n.posDiff+1, n.hiVal)) {
--stackTop;
return;
}
//proceed to move up a level
} else {
//TODO checkInfix without extracting it
readInfix(n.hi, valIntTemplate);
//int basePos = (n.hi.posFirstBit >>> BITS_LOG_64) * 64;
//if (checkMatch(n.hi.infix, n.hi.posFirstBit-basePos,
// n.hi.posDiff-1-basePos)) {
if (checkMatch(valIntTemplate, n.hi.posFirstBit, n.hi.posDiff-1)) {
stack[++stackTop] = n.hi;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextValue = null;
nextKey = null;
}
/**
* Comparison on the post-fix. Assigns the parameter to 'nextVal' if comparison fits.
* @param keyTemplate
* @return Whether we have a match or not
*/
private boolean checkMatchIntoNextVal(long[] keyTemplate, int startBit, V value) {
//abort if post-len==0
if (startBit >= keyTemplate.length*64) {
return true;
}
int iStart = startBit >>> BITS_LOG_64;
long diffLo = (iStart == 0) ? 0 : domMaskLo[iStart-1];
long diffHi = (iStart == 0) ? 0 : domMaskHi[iStart-1];
for (int i = iStart; i < keyTemplate.length; i++) {
//calculate all bits that we can ignore during the check below.
//calculate local diff
long localDiffLo = keyTemplate[i] ^ minOrig[i];
long localDiffHi = keyTemplate[i] ^ maxOrig[i];
//calculate global diff by or-ing with 'acceptable' diffs
diffLo |= localDiffLo & ~minOrig[i];
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
//if ((((diffLo | localDiffLo) ^ diffLo) | ((diffHi | localDiffHi) ^ diffHi)) != 0) {
if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
return false;
}
//abort if fully enclosed
if ((diffLo & MAX_MASK) == MAX_MASK && (diffHi & MAX_MASK) == MAX_MASK) {
break;
}
}
nextValue = value;
nextKey = CritBit.clone(keyTemplate);
return true;
}
private boolean checkMatch(long[] keyTemplate, int startBit, int currentDepth) {
if (currentDepth < startBit) {
return true;
}
if (startBit == currentDepth) {
//use matchSingleBit
//TODO remove this? Is it worth it?
return checkMatchSingleBit(BitTools.getBit(keyTemplate, startBit), currentDepth);
}
int i;
int iStart = startBit >>> BITS_LOG_64;
//if min/max encloses keyTemp in any dimension on a given depth, then, for lower depth,
//this dimension doesn't need to be checked anymore.
//Since we can't not-check, we have to ignore any collisions resulting from the checks.
//Or, if possible adjust the check-mask before checking.
//E.g. set hiMask (loMask) to 1 (0) for any dimension that should be ignored.
//That means, the stored mask[] should be set whenever contain inverse masks...
long diffLo = (iStart == 0) ? 0 : domMaskLo[iStart-1];
long diffHi = (iStart == 0) ? 0 : domMaskHi[iStart-1];
for (i = iStart; i < (currentDepth+1) >>> BITS_LOG_64; i++) {
//calculate all bits that we can ignore during the check below.
//calculate local diff
long localDiffLo = keyTemplate[i] ^ minOrig[i];
long localDiffHi = keyTemplate[i] ^ maxOrig[i];
//calculate global diff by or-ing with 'acceptable' diffs
diffLo |= localDiffLo & ~minOrig[i];
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
//if ((((diffLo | localDiffLo) ^ diffLo) | ((diffHi | localDiffHi) ^ diffHi)) != 0) {
if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
return false;
}
domMaskLo[i] = diffLo;
domMaskHi[i] = diffHi;
//abort for long post-fixes, non-IPP data:
if ((diffLo & MAX_MASK) == MAX_MASK && (diffHi & MAX_MASK) == MAX_MASK) {
for (; i < (currentDepth+1) >>> BITS_LOG_64; i++) {
//TODO avoid this by passing an 'enclosed' flag to sub-nodes...
domMaskLo[i] = diffLo;
domMaskHi[i] = diffHi;
}
return true;
}
}
int toCheck = (currentDepth+1) & BITS_MASK_6;
if (toCheck != 0) {
long mask = ~((-1L) >>> toCheck);
long localDiffLo = keyTemplate[i] ^ minOrig[i];
long localDiffHi = keyTemplate[i] ^ maxOrig[i];
diffLo |= localDiffLo & ~minOrig[i];
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
//if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
boolean r = ((((diffLo | localDiffLo) ^ diffLo) | ((diffHi | localDiffHi) ^ diffHi))
& mask) == 0;
if (r) {
domMaskLo[i] = diffLo & mask;
domMaskHi[i] = diffHi & mask;
}
return r;
}
return true;
}
private boolean checkMatchSingleBit(boolean bit, int currentDepth) {
int i = currentDepth >>> BITS_LOG_64;
long diffLo = (i == 0) ? 0 : domMaskLo[i-1];
long diffHi = (i == 0) ? 0 : domMaskHi[i-1];
//setting only one bit is a bit more difficult:
//- we need to include higher bits from domMask[i]
//- we need to set/unset the dom-bit at currentDepth
//- Maybe we need to erase lower bits? -> Probably not
int toCheck = currentDepth & BITS_MASK_6;
long mask = 0x8000000000000000L >>> toCheck;
long keyTemplate = bit ? mask : 0;
long localDiffLo = (keyTemplate ^ minOrig[i]) & mask;
diffLo |= localDiffLo & ~minOrig[i];
long localDiffHi = (keyTemplate ^ maxOrig[i]) & mask;
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
return false;
}
long maskFF00 = ~((0xFFFFFFFFFFFFFFFFL) >>> toCheck);
domMaskLo[i] = diffLo | (domMaskLo[i] & maskFF00);
domMaskHi[i] = diffHi | (domMaskHi[i] & maskFF00);
return true;
}
private boolean checkMatchANdSetSingleBit0(long[] valTemplate, int currentDepth) {
int i = currentDepth >>> BITS_LOG_64;
long diffLo = (i == 0) ? 0 : domMaskLo[i-1];
//setting only one bit is a bit more difficult:
//- we need to include higher bits from domMask[i]
//- we need to set/unset the dom-bit at currentDepth
//- Maybe we need to erase lower bits? -> Probably not
int toCheck = currentDepth & BITS_MASK_6;
long mask = 0x8000000000000000L >>> toCheck;
long localDiffLo = minOrig[i] & mask;
//diffLo |= localDiffLo & ~minOrig[i]; //always |= 0
//find unacceptable diffs by comparing global diff with local diff
if ((diffLo | localDiffLo) != diffLo) {
return false;
}
valTemplate[i] &= ~mask;
long localDiffHi = maxOrig[i] & mask;
long diffHi = (i == 0) ? 0 : domMaskHi[i-1];
diffHi |= localDiffHi & maxOrig[i];
long maskFF00 = ~((0xFFFFFFFFFFFFFFFFL) >>> toCheck);
//0 --> can collide with lo
// --> can result in enclosed/diff with hi
domMaskLo[i] = diffLo | (domMaskLo[i] & maskFF00);
domMaskHi[i] = diffHi | (domMaskHi[i] & maskFF00);
//TODO check full enclosing?
return true;
}
private boolean checkMatchAndSetSingleBit1(long[] keyTemplate, int currentDepth) {
int iStart = currentDepth >>> BITS_LOG_64;
int i = iStart;
//if min/max encloses keyTemp in any dimension on a given depth, then, for lower depth,
//this dimension doesn't need to be checked anymore.
//Since we can't not-check, we have to ignore any collisions resulting from the checks.
//Or, if possible adjust the check-mask before checking.
//E.g. set hiMask (loMask) to 1 (0) for any dimension that should be ignored.
//That means, the stored mask[] should be set whenever contain inverse masks...
long diffHi = (iStart == 0) ? 0 : domMaskHi[iStart-1];
//setting only one bit is a bit more difficult:
//- we need to include higher bits from domMask[i]
//- we need to set/unset the dom-bit at currentDepth
//- Maybe we need to erase lower bits? -> Probably not
int toCheck = currentDepth & BITS_MASK_6;
long mask = 0x8000000000000000L >>> toCheck;
long localDiffHi = (mask ^ maxOrig[i]) & mask;
//diffHi |= localDiffHi & maxOrig[i]; //always |=0
//find unacceptable diffs by comparing global diff with local diff
if ((diffHi | localDiffHi) != diffHi) {
return false;
}
keyTemplate[i] |= mask;
long localDiffLo = (mask ^ minOrig[i]) & mask;
long diffLo = (iStart == 0) ? 0 : domMaskLo[iStart-1];
diffLo |= localDiffLo & ~minOrig[i];
long maskFF00 = ~((0xFFFFFFFFFFFFFFFFL) >>> toCheck);
//1 --> can collide with hi
// --> can result in enclosed/diff with lo
domMaskLo[i] = diffLo | (domMaskLo[i] & maskFF00);
domMaskHi[i] = diffHi | (domMaskHi[i] & maskFF00);
return true;
}
@Override
public boolean hasNext() {
return nextKey != null;
}
@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 CheckEmptyWithMask {
private final CritBit> cb;
private final long[] valIntTemplate;
private long[] minOrig;
private long[] maxOrig;
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;
//This mask remembers whether a certain dimension is fully contained or not
//We have a separate mask for each possible level
private final long[] domMaskLo;
private final long[] domMaskHi;
private final long MAX_MASK;
private boolean ignoreUpper;
private boolean pointFound;
/**
*
* @param cb parent tree
* @param dims dimensions
*/
public CheckEmptyWithMask(CritBit> cb, int dims) {
this.cb = cb;
this.stack = new Node[cb.DEPTH];
this.readHigherNext = new byte[cb.DEPTH]; // default = false
int intArrayLen = (cb.DEPTH+63) >>> 6;
this.valIntTemplate = new long[intArrayLen];
this.domMaskLo = new long[intArrayLen];
this.domMaskHi = new long[intArrayLen];
this.MAX_MASK = ~((-1L) << dims);
}
public boolean isEmpty(long[] min, long[] max, boolean ignoreUpper) {
this.ignoreUpper = ignoreUpper;
this.minOrig = min;
this.maxOrig = max;
Arrays.fill(readHigherNext, (byte)0);
pointFound = false;
if (cb.rootKey != null) {
return !checkMatchIntoNextVal(cb.rootKey, 0);
}
if (cb.root == null) {
//Tree is empty
return true;
}
Node> n = cb.root;
readInfix(n, valIntTemplate);
if (!checkMatch(valIntTemplate, 0, n.posDiff-1)) {
return true;
}
return findNext();
}
private boolean findNext() {
int stackTop = -1;
stack[++stackTop] = cb.root;
//TODO move stack into method
while (stackTop >= 0) {
Node> n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
if (checkMatchANdSetSingleBit0(valIntTemplate, n.posDiff)) {
if (n.loPost != null) {
readPostFix(n.loPost, valIntTemplate);
if (checkMatchIntoNextVal(valIntTemplate, n.posDiff+1)) {
return false;
}
//proceed to check upper
} else {
readInfix(n.lo, valIntTemplate);
if (checkMatch(valIntTemplate, n.lo.posFirstBit, n.lo.posDiff-1)) {
if (pointFound) {
return false;
}
stack[++stackTop] = n.lo;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
if (checkMatchAndSetSingleBit1(valIntTemplate, n.posDiff)) {
if (n.hiPost != null) {
readPostFix(n.hiPost, valIntTemplate);
if (checkMatchIntoNextVal(valIntTemplate, n.posDiff+1)) {
return false;
}
//proceed to move up a level
} else {
//TODO checkInfix without extracting it
readInfix(n.hi, valIntTemplate);
//int basePos = (n.hi.posFirstBit >>> BITS_LOG_64) * 64;
//if (checkMatch(n.hi.infix, n.hi.posFirstBit-basePos,
// n.hi.posDiff-1-basePos)) {
if (checkMatch(valIntTemplate, n.hi.posFirstBit, n.hi.posDiff-1)) {
if (pointFound) {
return false;
}
stack[++stackTop] = n.hi;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
}
//proceed to move up a level
--stackTop;
}
//Finished
return true;
}
/**
* Comparison on the post-fix. Assigns the parameter to 'nextVal' if comparison fits.
* @param keyTemplate
* @return Whether we have a match or not
*/
private boolean checkMatchIntoNextVal(long[] keyTemplate, int startBit) {
int iStart = startBit >>> BITS_LOG_64;
long diffLo = (iStart == 0) ? 0 : domMaskLo[iStart-1];
long diffHi = (iStart == 0) ? 0 : domMaskHi[iStart-1];
if (diffLo == MAX_MASK && diffHi == MAX_MASK) {
//if (true) throw new IllegalStateException(); //TODO remove me??!!?!?
pointFound = true;
return true;
}
//abort if post-len==0
if (startBit >= keyTemplate.length*64) {
if (ignoreUpper) {
//TODO diffHi && MAX_MASK???
if (diffHi == 0 && keyTemplate[keyTemplate.length-1] == maxOrig[maxOrig.length-1]) {
//TODO this can't work, 'i' is OOB.
if (true) throw new IllegalStateException();
//return false;
}
}
return true;
}
for (int i = iStart; i < keyTemplate.length; i++) {
//calculate all bits that we can ignore during the check below.
//calculate local diff
long localDiffLo = keyTemplate[i] ^ minOrig[i];
long localDiffHi = keyTemplate[i] ^ maxOrig[i];
//calculate global diff by or-ing with 'acceptable' diffs
diffLo |= localDiffLo & ~minOrig[i];
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
//if ((((diffLo | localDiffLo) ^ diffLo) | ((diffHi | localDiffHi) ^ diffHi)) != 0) {
if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
return false;
}
//abort if fully enclosed
if (diffLo == MAX_MASK && diffHi == MAX_MASK) {
pointFound = true;
return true;
}
}
if (ignoreUpper) {
if (diffHi == 0 && keyTemplate[keyTemplate.length-1] == maxOrig[maxOrig.length-1]) {
//TODO this can't work, 'i' is OOB.
return false;
}
}
pointFound = true;
return true;
}
private boolean checkMatch(long[] keyTemplate, int startBit, int currentDepth) {
if (currentDepth < startBit) {
return true;
}
if (startBit == currentDepth) {
//use matchSingleBit
//TODO remove this? Is it worth it?
return checkMatchSingleBit(BitTools.getBit(keyTemplate, startBit), currentDepth);
}
int i;
int iStart = startBit >>> BITS_LOG_64;
//if min/max encloses keyTemp in any dimension on a given depth, then, for lower depth,
//this dimension doesn't need to be checked anymore.
//Since we can't not-check, we have to ignore any collisions resulting from the checks.
//Or, if possible adjust the check-mask before checking.
//E.g. set hiMask (loMask) to 1 (0) for any dimension that should be ignored.
//That means, the stored mask[] should be set whenever contain inverse masks...
long diffLo = (iStart == 0) ? 0 : domMaskLo[iStart-1];
long diffHi = (iStart == 0) ? 0 : domMaskHi[iStart-1];
if (diffLo == MAX_MASK && diffHi == MAX_MASK) {
pointFound = true;
return true;
}
for (i = iStart; i < (currentDepth+1) >>> BITS_LOG_64; i++) {
//calculate all bits that we can ignore during the check below.
//calculate local diff
long localDiffLo = keyTemplate[i] ^ minOrig[i];
long localDiffHi = keyTemplate[i] ^ maxOrig[i];
//calculate global diff by or-ing with 'acceptable' diffs
diffLo |= localDiffLo & ~minOrig[i];
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
return false;
}
domMaskLo[i] = diffLo;
domMaskHi[i] = diffHi;
//abort for long post-fixes, non-IPP data:
if (diffLo == MAX_MASK && diffHi == MAX_MASK) {
//this cannot be the upper corner, because it is not enclosed.
pointFound = true;
return true;
}
}
int toCheck = (currentDepth+1) & BITS_MASK_6;
if (toCheck != 0) {
long mask = ~((-1L) >>> toCheck);
long localDiffLo = keyTemplate[i] ^ minOrig[i];
long localDiffHi = keyTemplate[i] ^ maxOrig[i];
diffLo |= localDiffLo & ~minOrig[i];
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
//if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
boolean r = ((((diffLo | localDiffLo) ^ diffLo) | ((diffHi | localDiffHi) ^ diffHi))
& mask) == 0;
if (r) {
//TODO |= ????
domMaskLo[i] = diffLo;// & mask;
domMaskHi[i] = diffHi;// & mask;
}
// if (diffLo == MAX_MASK && diffHi == MAX_MASK) {
// pointFound = true;
// return true;
// }
return r;
}
//TODO abort lways on full enclosure
//TODO don't compare corner if domMask != 0
//pointFound = true;
if (diffLo == MAX_MASK && diffHi == MAX_MASK) {
if (true) throw new IllegalStateException();
pointFound = true;
return true;
}
return true;
}
private boolean checkMatchSingleBit(boolean bit, int currentDepth) {
int i = currentDepth >>> BITS_LOG_64;
long diffLo = (i == 0) ? 0 : domMaskLo[i-1];
long diffHi = (i == 0) ? 0 : domMaskHi[i-1];
//setting only one bit is a bit more difficult:
//- we need to include higher bits from domMask[i]
//- we need to set/unset the dom-bit at currentDepth
//- Maybe we need to erase lower bits? -> Probably not
int toCheck = currentDepth & BITS_MASK_6;
long mask = 0x8000000000000000L >>> toCheck;
long keyTemplate = bit ? mask : 0;
long localDiffLo = (keyTemplate ^ minOrig[i]) & mask;
diffLo |= localDiffLo & ~minOrig[i];
long localDiffHi = (keyTemplate ^ maxOrig[i]) & mask;
diffHi |= localDiffHi & maxOrig[i];
//find unacceptable diffs by comparing global diff with local diff
if ((diffLo | localDiffLo) != diffLo || (diffHi | localDiffHi) != diffHi) {
return false;
}
long maskFF00 = ~((0xFFFFFFFFFFFFFFFFL) >>> toCheck);
domMaskLo[i] = diffLo | (domMaskLo[i] & maskFF00);
domMaskHi[i] = diffHi | (domMaskHi[i] & maskFF00);
//TODO check full enclosing?
return true;
}
private boolean checkMatchANdSetSingleBit0(long[] valTemplate, int currentDepth) {
int i = currentDepth >>> BITS_LOG_64;
long diffLo = (i == 0) ? 0 : domMaskLo[i-1];
//setting only one bit is a bit more difficult:
//- we need to include higher bits from domMask[i]
//- we need to set/unset the dom-bit at currentDepth
//- Maybe we need to erase lower bits? -> Probably not
int toCheck = currentDepth & BITS_MASK_6;
long mask = 0x8000000000000000L >>> toCheck;
long localDiffLo = minOrig[i] & mask;
//diffLo |= localDiffLo & ~minOrig[i]; //always |= 0
//find unacceptable diffs by comparing global diff with local diff
if ((diffLo | localDiffLo) != diffLo) {
return false;
}
valTemplate[i] &= ~mask;
long localDiffHi = maxOrig[i] & mask;
long diffHi = (i == 0) ? 0 : domMaskHi[i-1];
diffHi |= localDiffHi & maxOrig[i];
long maskFF00 = ~((0xFFFFFFFFFFFFFFFFL) >>> toCheck);
//0 --> can collide with lo
// --> can result in enclosed/diff with hi
domMaskLo[i] = diffLo | (domMaskLo[i] & maskFF00);
domMaskHi[i] = diffHi | (domMaskHi[i] & maskFF00);
//TODO check full enclosing here?
//TODO if we do, then stop checking at beginning of other matchXYZ methods
return true;
}
private boolean checkMatchAndSetSingleBit1(long[] keyTemplate, int currentDepth) {
int iStart = currentDepth >>> BITS_LOG_64;
int i = iStart;
//if min/max encloses keyTemp in any dimension on a given depth, then, for lower depth,
//this dimension doesn't need to be checked anymore.
//Since we can't not-check, we have to ignore any collisions resulting from the checks.
//Or, if possible adjust the check-mask before checking.
//E.g. set hiMask (loMask) to 1 (0) for any dimension that should be ignored.
//That means, the stored mask[] should be set whenever contain inverse masks...
long diffHi = (iStart == 0) ? 0 : domMaskHi[iStart-1];
//setting only one bit is a bit more difficult:
//- we need to include higher bits from domMask[i]
//- we need to set/unset the dom-bit at currentDepth
//- Maybe we need to erase lower bits? -> Probably not
int toCheck = currentDepth & BITS_MASK_6;
long mask = 0x8000000000000000L >>> toCheck;
long localDiffHi = (mask ^ maxOrig[i]) & mask;
//diffHi |= localDiffHi & maxOrig[i]; //always |=0
//find unacceptable diffs by comparing global diff with local diff
if ((diffHi | localDiffHi) != diffHi) {
return false;
}
keyTemplate[i] |= mask;
long localDiffLo = (mask ^ minOrig[i]) & mask;
long diffLo = (iStart == 0) ? 0 : domMaskLo[iStart-1];
diffLo |= localDiffLo & ~minOrig[i];
long maskFF00 = ~((0xFFFFFFFFFFFFFFFFL) >>> toCheck);
//1 --> can collide with hi
// --> can result in enclosed/diff with lo
domMaskLo[i] = diffLo | (domMaskLo[i] & maskFF00);
domMaskHi[i] = diffHi | (domMaskHi[i] & maskFF00);
return true;
}
}
/**
* 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
*/
@Override
public V putKD(long[] key, V val) {
checkDIM(key);
long[] vi = BitTools.mergeLong(DEPTH, key);
return putNoCheck(vi, val);
}
/**
* Check whether a given key exists in the tree.
* @param key key
* @return {@code true} if the key exists otherwise {@code false}
*/
@Override
public boolean containsKD(long[] key) {
checkDIM(key);
long[] vi = BitTools.mergeLong(DEPTH, key);
return containsNoCheck(vi);
}
/**
* 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.
*/
@Override
public V getKD(long[] key) {
checkDIM(key);
long[] vi = BitTools.mergeLong(DEPTH, key);
return getNoCheck(vi);
}
/**
* Remove a key and its value
* @param key key
* @return The value of the key of {@code null} if the value was not found.
*/
@Override
public V removeKD(long[] key) {
checkDIM(key);
long[] vi = BitTools.mergeLong(DEPTH, key);
return removeNoCheck(vi);
}
private void checkDIM(long[] key) {
if (key.length != DIM) {
throw new IllegalArgumentException("Dimension mismatch: " + key.length + " vs " + DIM);
}
}
/**
* Performs a k-dimensional query.
* @param min minimum key
* @param max maximum key
* @return Result iterator
*/
@Override
public QueryIteratorKD queryKD(long[] min, long[] max) {
checkDIM(min);
checkDIM(max);
return new QueryIteratorKD(this, min, max, DIM, DEPTH);
}
public static class QueryIteratorKD implements Iterator {
private final long[] keyOrigTemplate;
private final long[] minOrig;
private final long[] maxOrig;
private final int DIM;
private final int DIM_INV_16;
private final int DEPTH;
private final int DEPTH_OFFS;
private V nextValue = null;
private long[] nextKey = null;
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 QueryIteratorKD(CritBit cb, long[] minOrig, long[] maxOrig, int DIM, int DEPTH) {
this.stack = new Node[DIM*DEPTH];
this.readHigherNext = new byte[DIM*DEPTH]; // default = false
this.keyOrigTemplate = new long[DIM];
this.minOrig = minOrig;
this.maxOrig = maxOrig;
this.DIM = DIM;
this.DIM_INV_16 = 1 + ((1<<16)+1)/DIM;
this.DEPTH = DEPTH;
this.DEPTH_OFFS = 64-DEPTH; //the shift local to any Long
if (cb.rootKey != null) {
readPostFixAndSplit(cb.rootKey, 0, keyOrigTemplate);
checkMatchOrigKDFullIntoNextVal(keyOrigTemplate, cb.rootVal);
return;
}
if (cb.root == null) {
//Tree is empty
return;
}
Node n = cb.root;
readAndSplitInfix(n, keyOrigTemplate);
if (n.posDiff > 0 && !checkMatchOrigKD(keyOrigTemplate, n.posDiff-1)) {
return;
}
stack[++stackTop] = cb.root;
findNext();
}
private void findNext() {
while (stackTop >= 0) {
Node n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
//TODO use bit directly to check validity
unsetBitAfterSplit(keyOrigTemplate, n.posDiff);
if (checkMatchOrigKD(keyOrigTemplate, n.posDiff)) {
if (n.loPost != null) {
readPostFixAndSplit(n.loPost, n.posDiff+1, keyOrigTemplate);
if (checkMatchOrigKDFullIntoNextVal(keyOrigTemplate, n.loVal)) {
return;
}
//proceed to check upper
} else {
readAndSplitInfix(n.lo, keyOrigTemplate);
stack[++stackTop] = n.lo;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
setBitAfterSplit(keyOrigTemplate, n.posDiff);
if (checkMatchOrigKD(keyOrigTemplate, n.posDiff)) {
if (n.hiPost != null) {
readPostFixAndSplit(n.hiPost, n.posDiff+1, keyOrigTemplate);
if (checkMatchOrigKDFullIntoNextVal(keyOrigTemplate, n.hiVal)) {
--stackTop;
return;
}
//proceed to move up a level
} else {
readAndSplitInfix(n.hi, keyOrigTemplate);
stack[++stackTop] = n.hi;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextKey = null;
nextValue = null;
}
private void setBitAfterSplit(long[] keyOrigTemplate, int posBitInt) {
int k = posBitInt % DIM;
long maskDst = 0x8000000000000000L >>> (posBitInt / DIM)+DEPTH_OFFS;
keyOrigTemplate[k] |= maskDst;
}
private void unsetBitAfterSplit(long[] keyOrigTemplate, int posBitInt) {
int k = posBitInt % DIM;
long maskDst = 0x8000000000000000L >>> (posBitInt / DIM)+DEPTH_OFFS;
keyOrigTemplate[k] &= ~maskDst;
}
/**
* Full comparison on the parameter. Assigns the parameter to 'nextKey' if comparison
* fits.
* @param keyTemplate
* @return Whether we have a match or not
*/
private boolean checkMatchOrigKDFullIntoNextVal(long[] keyOrigTemplate, V value) {
//TODO optimise: do not check dimensions that can not possibly fail
// --> Track dimensions that could fail.
for (int k = 0; k < DIM; k++) {
if (minOrig[k] > keyOrigTemplate[k] || keyOrigTemplate[k] > maxOrig[k]) {
return false;
}
}
//TODO
//TODO
//TODO
//TODO
//TODO
//TODO
//TODO
//TODO
nextKey = CritBit.clone(keyOrigTemplate);
nextValue = value;
return true;
}
private boolean checkMatchOrigKD(long[] keyOrigTemplate, int currentDepth) {
//TODO no startBit given. Could we use it to avoid unnecessary checking of prefix?
//TODO optimise: do not check dimensions that can not possibly fail
// --> Track dimensions that could fail.
//TODO avoid this! For example track DEPTHs separately for each k in an currentDep[]
int commonBits = (currentDepth+1) / DIM;//getDepthAcrossDims(currentDepth);//currentDepth / DIM;
int openBits = DEPTH-commonBits;
long minMask = (-1L) << openBits; // 0xFF00
long maxMask = ~minMask; // 0x00FF
//We don't need to check the same number of bits in all dimensions.
//--> calc number of dimensions with more bits than others
int kLimit = (currentDepth+1) - DIM*commonBits;
//if all have the same length, we can use a simple loop
if (kLimit == 0) {
for (int k = 0; k < DIM; k++) {
if (minOrig[k] > (keyOrigTemplate[k] | maxMask) // > 0x1212FFFF ? -> exit
|| (keyOrigTemplate[k] & minMask) > maxOrig[k]) { // < 0x12120000 ? -> exit
return false;
}
}
return true;
}
//first check DIMs with fewer bits
for (int k = kLimit; k < DIM; k++) {
if (minOrig[k] > (keyOrigTemplate[k] | maxMask) // > 0x1212FFFF ? -> exit
|| (keyOrigTemplate[k] & minMask) > maxOrig[k]) { // < 0x12120000 ? -> exit
return false;
}
}
//know proceed with one more bit
maxMask >>>= 1;
minMask = ~maxMask;
for (int k = 0; k < kLimit; k++) {
if (minOrig[k] > (keyOrigTemplate[k] | maxMask)
|| (keyOrigTemplate[k] & minMask) > maxOrig[k]) {
return false;
}
}
return true;
}
/**
*
* @param n
* @param infixStart The bit-position of the first infix bits relative to the whole value
* @param currentPrefix
*/
private void readAndSplitInfix(Node n, long[] currentPrefixOrig) {
if (n.infix == null) {
return;
}
readAndSplit(n.infix, n.posFirstBit, n.posDiff, currentPrefixOrig);
}
private void readPostFixAndSplit(long[] postVal, int posFirstBit, long[] currentPrefixOrig) {
int stopBit = DIM*DEPTH;
readAndSplit(postVal, posFirstBit, stopBit, currentPrefixOrig);
}
/**
*
* @param src Interleaved src array
* @param posFirstBit First bit to be transferred
* @param stopBit Stop bit (last bit to be transferred + 1)
* @param dst Non-interleaved destination array
*/
private void readAndSplit(long[] srcVal, int posFirstBit, long stopBit, long[] dstVal) {
long maskSrc = 0x8000000000000000L >>> (posFirstBit & 0x3F);
int k = posFirstBit % DIM;
//long maskDst = 0x8000000000000000L >>> (posFirstBit / DIM)+DEPTH_OFFS;
long maskDst = 0x8000000000000000L >>> (getDepthAcrossDims(posFirstBit)+DEPTH_OFFS);
int src = 0;
for (int i = posFirstBit; i < stopBit; i++) {
if ((srcVal[src] & maskSrc) == 0) {
dstVal[k] &= ~maskDst;
} else {
dstVal[k] |= maskDst;
}
if (++k >= DIM) {
k = 0;
maskDst >>>= 1;
}
maskSrc >>>= 1;
if (maskSrc == 0) {
//overflow for i multiple of 64
src++;
maskSrc = 0x8000000000000000L;
}
}
}
/**
* Calculate the common minimum depth across all dimensions.
* This is equal to {@code floor(posDirstBit/DIM)}.
* @param posFirstBit
* @return depth across dims.
*/
private int getDepthAcrossDims(int posFirstBit) {
int depthAcrossDims = (posFirstBit*DIM_INV_16) >>> 16;
return depthAcrossDims;
}
@Override
public boolean hasNext() {
return nextKey != null;
}
@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 - 2025 Weber Informatics LLC | Privacy Policy