/*
* Copyright 2009-2016 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZooDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZooDB. If not, see <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.internal.util;
/**
* 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().
* This conversion is taken from:
* T.Zaeschke, C.Zimmerli, M.C.Norrie: The PH-Tree - A Space-Efficient Storage Structure and
* Multi-Dimensional Index (SIGMOD 2014)
*
* Version 1.0
*
* @author Tilmann Zaeschke
*/
import java.util.Iterator;
import java.util.NoSuchElementException;
public class CritBit64<V> {
private static final int DEPTH = 64;
private Node<V> root;
private long rootKey;
private V rootVal;
private int size;
private static class Node<V> {
V loVal;
V hiVal;
Node<V> lo;
Node<V> 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 CritBit64() {
//private
}
/**
* Create a 1D crit-bit tree with 64 bit key length.
* @return a 1D crit-bit tree
*/
public static <V> CritBit64<V> create() {
return new CritBit64<V>();
}
/**
* Add a key value pair to the tree or replace the value if the key already exists.
* @param key
* @param val
* @return The previous value or {@code null} if there was no previous value
*/
public V put(long key, V val) {
if (size == 0) {
rootKey = key;
rootVal = val;
size++;
return null;
}
if (size == 1) {
Node<V> n2 = createNode(key, val, rootKey, rootVal, 0);
if (n2 == null) {
V prev = rootVal;
rootVal = val;
return prev;
}
root = n2;
rootKey = 0;
rootVal = null;
size++;
return null;
}
Node<V> n = root;
while (true) {
if (n.posFirstBit != n.posDiff) {
//split infix?
int posDiff = compare(key, n.infix);
if (posDiff < n.posDiff && posDiff != -1) {
long subInfix = extractInfix(n.infix, n.posDiff-1);
//new sub-node
Node<V> newSub = new Node<V>(posDiff+1, n.loPost, n.loVal, n.hiPost, n.hiVal,
subInfix, n.posDiff);
newSub.hi = n.hi;
newSub.lo = n.lo;
if (getBit(key, posDiff)) {
n.hi = null;
n.hiPost = key;
n.hiVal = val;
n.lo = newSub;
n.loPost = 0;
n.loVal = null;
} else {
n.hi = newSub;
n.hiPost = 0;
n.hiVal = null;
n.lo = null;
n.loPost = key;
n.loVal = val;
}
n.infix = extractInfix(key, posDiff-1);
n.posDiff = posDiff;
size++;
return null;
}
}
//infix matches, so now we check sub-nodes and postfixes
if (getBit(key, n.posDiff)) {
if (n.hi != null) {
n = n.hi;
continue;
} else {
Node<V> n2 = createNode(key, val, n.hiPost, n.hiVal, n.posDiff + 1);
if (n2 == null) {
V prev = n.hiVal;
n.hiVal = val;
return prev;
}
n.hi = n2;
n.hiPost = 0;
n.hiVal = null;
size++;
return null;
}
} else {
if (n.lo != null) {
n = n.lo;
continue;
} else {
Node<V> n2 = createNode(key, val, n.loPost, n.loVal, n.posDiff + 1);
if (n2 == null) {
V prev = n.loVal;
n.loVal = val;
return prev;
}
n.lo = n2;
n.loPost = 0;
n.loVal = null;
size++;
return null;
}
}
}
}
public void printTree() {
System.out.println("Tree: \n" + toString());
}
public String toString() {
if (root == null) {
if (root == null) {
return "-" + toBinary(rootKey) + " v=" + rootVal;
}
return "- -";
}
Node<V> n = root;
StringBuilder s = new StringBuilder();
printNode(n, s, "", 0);
return s.toString();
}
private void printNode(Node<V> n, StringBuilder s, String level, int currentDepth) {
char NL = '\n';
if (n.posFirstBit != n.posDiff) {
s.append(level + "n: " + currentDepth + "/" + n.posDiff + " " +
toBinary(n.infix) + 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 + " " + toBinary(n.loPost) + " v=" + n.loVal + NL);
}
if (n.hi != null) {
printNode(n.hi, s, level + "-", n.posDiff+1);
} else {
s.append(level + " " + toBinary(n.hiPost) + " v=" + n.hiVal + NL);
}
}
public boolean checkTree() {
if (root == null) {
if (root == null) {
return true;
}
return true;
}
if (root == null) {
System.err.println("root node AND value != null");
return false;
}
return checkNode(root, 0);
}
private boolean checkNode(Node<V> n, int firstBitOfNode) {
//check infix
if (n.posDiff < firstBitOfNode) {
System.err.println("infix with len=0 detected!");
return false;
}
if (n.lo != null) {
if (n.loPost != 0) {
System.err.println("lo: sub-node AND value != 0");
return false;
}
checkNode(n.lo, n.posDiff+1);
}
if (n.hi != null) {
if (n.hiPost != 0) {
System.err.println("hi: sub-node AND value != 0");
return false;
}
checkNode(n.hi, n.posDiff+1);
}
return true;
}
private Node<V> 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, posDiff-1);
if (getBit(k2, posDiff)) {
return new Node<V>(posFirstBit, k1, val1, k2, val2, infix, posDiff);
} else {
return new Node<V>(posFirstBit, k2, val2, k1, val1, infix, posDiff);
}
}
/**
*
* @param v
* @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 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
* @param startPos
* @return True if the infix matches the value or if no infix is defined
*/
private boolean doesInfixMatch(Node<V> n, long v) {
int endPos = n.posDiff-1;
if (endPos >= 0 && v != n.infix) {
long mask = 0x8000000000000000L >>> endPos;
if ((v & mask) != (n.infix & mask)) {
return false;
}
return true;
}
return true;
}
/**
* Compares two values.
* @param v1
* @param v2
* @return Position of the differing bit, or -1 if both values are equal
*/
private static int compare(long v1, long v2) {
int pos = 0;
if (v1 != v2) {
long x = v1 ^ v2;
pos += Long.numberOfLeadingZeros(x);
return pos;
}
return -1;
}
/**
* Get the size of the tree.
* @return the number of keys in the tree
*/
public int size() {
return size;
}
/**
* Check whether a given key exists in the tree.
* @param key
* @return {@code true} if the key exists otherwise {@code false}
*/
public boolean contains(long key) {
if (size == 0) {
return false;
}
if (size == 1) {
int posDiff = compare(key, rootKey);
if (posDiff == -1) {
return true;
}
return false;
}
Node<V> n = root;
while (true) {
if (!doesInfixMatch(n, key)) {
return false;
}
//infix matches, so now we check sub-nodes and postfixes
if (getBit(key, n.posDiff)) {
if (n.hi != null) {
n = n.hi;
continue;
}
return compare(key, n.hiPost) == -1;
} else {
if (n.lo != null) {
n = n.lo;
continue;
}
return compare(key, n.loPost) == -1;
}
}
}
/**
* Get the value for a given key.
* @param key
* @return the values associated with {@code key} or {@code null} if the key does not exist.
*/
public V get(long key) {
if (size == 0) {
return null;
}
if (size == 1) {
int posDiff = compare(key, rootKey);
if (posDiff == -1) {
return rootVal;
}
return null;
}
Node<V> n = root;
while (true) {
if (!doesInfixMatch(n, key)) {
return null;
}
//infix matches, so now we check sub-nodes and postfixes
if (getBit(key, n.posDiff)) {
if (n.hi != null) {
n = n.hi;
continue;
}
if (compare(key, n.hiPost) == -1) {
return n.hiVal;
}
} else {
if (n.lo != null) {
n = n.lo;
continue;
}
if (compare(key, n.loPost) == -1) {
return n.loVal;
}
}
return null;
}
}
/**
* Remove a key and its value
* @param key
* @return The value of the key of {@code null} if the value was not found.
*/
public V remove(long key) {
if (size == 0) {
return null;
}
if (size == 1) {
int posDiff = compare(key, rootKey);
if (posDiff == -1) {
size--;
rootKey = 0;
V prev = rootVal;
rootVal = null;
return prev;
}
return null;
}
Node<V> n = root;
Node<V> parent = null;
boolean isParentHigh = false;
while (true) {
if (!doesInfixMatch(n, key)) {
return null;
}
//infix matches, so now we check sub-nodes and postfixes
if (getBit(key, n.posDiff)) {
if (n.hi != null) {
isParentHigh = true;
parent = n;
n = n.hi;
continue;
} else {
int posDiff = compare(key, n.hiPost);
if (posDiff != -1) {
return null;
}
//match! --> delete node
//a) first recover other values
long newPost = 0;
if (n.lo == null) {
newPost = n.loPost;
}
//b) replace data in parent node
updateParentAfterRemove(parent, newPost, n.loVal, n.lo, isParentHigh, n);
return n.hiVal;
}
} else {
if (n.lo != null) {
isParentHigh = false;
parent = n;
n = n.lo;
continue;
} else {
int posDiff = compare(key, n.loPost);
if (posDiff != -1) {
return null;
}
//match! --> delete node
//a) first recover other values
long newPost = 0;
if (n.hi == null) {
newPost = n.hiPost;
}
//b) replace data in parent node
//for new infixes...
updateParentAfterRemove(parent, newPost, n.hiVal, n.hi, isParentHigh, n);
return n.loVal;
}
}
}
}
private void updateParentAfterRemove(Node<V> parent, long newPost, V newVal,
Node<V> newSub, boolean isParentHigh, Node<V> n) {
if (parent == null) {
rootKey = newPost;
rootVal = newVal;
root = newSub;
} else if (isParentHigh) {
if (newSub == null) {
parent.hiPost = newPost;
parent.hiVal = newVal;
} else {
parent.hiPost = 0;
parent.hiVal = null;
}
parent.hi = newSub;
} else {
if (newSub == null) {
parent.loPost = newPost;
parent.loVal = newVal;
} else {
parent.loPost = 0;
parent.loVal = null;
}
parent.lo = newSub;
}
if (newSub != null) {
newSub.posFirstBit = n.posFirstBit;
newSub.infix = extractInfix(newSub.infix, newSub.posDiff-1);
}
size--;
}
public CBIterator<V> iterator() {
return new CBIterator<V>(this, DEPTH);
}
public static class CBIterator<V> implements Iterator<V> {
private long nextKey = 0;
private V nextValue = null;
private final Node<V>[] 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(CritBit64<V> cb, int DEPTH) {
this.stack = new Node[DEPTH];
this.readHigherNext = new byte[DEPTH]; // default = false
if (cb.size == 0) {
//Tree is empty
return;
}
if (cb.size == 1) {
nextValue = cb.rootVal;
nextKey = cb.rootKey;
return;
}
stack[++stackTop] = cb.root;
findNext();
}
private void findNext() {
while (stackTop >= 0) {
Node<V> 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;
}
@Override
public boolean hasNext() {
return nextValue != 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<V> nextEntry() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Entry<V> ret = new Entry<V>(nextKey, nextValue);
findNext();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public QueryIterator<V> query(long min, long max) {
return new QueryIterator<V>(this, min, max, DEPTH);
}
public static class QueryIterator<V> implements Iterator<V> {
private final long minOrig;
private final long maxOrig;
private long nextKey = 0;
private V nextValue = null;
private final Node<V>[] 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 QueryIterator(CritBit64<V> cb, long minOrig, long maxOrig, int DEPTH) {
this.stack = new Node[DEPTH];
this.readHigherNext = new byte[DEPTH]; // default = false
this.minOrig = minOrig;
this.maxOrig = maxOrig;
if (cb.size == 0) {
//Tree is empty
return;
}
if (cb.size == 1) {
checkMatchFullIntoNextVal(cb.rootKey, cb.rootVal);
return;
}
Node<V> n = cb.root;
if (!checkMatch(n.infix, n.posDiff)) {
return;
}
stack[++stackTop] = cb.root;
findNext();
}
private void findNext() {
while (stackTop >= 0) {
Node<V> n = stack[stackTop];
//check lower
if (readHigherNext[stackTop] == READ_LOWER) {
readHigherNext[stackTop] = READ_UPPER;
//TODO use bit directly to check validity
long valTemp = setBit(n.infix, n.posDiff, false);
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;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//check upper
if (readHigherNext[stackTop] == READ_UPPER) {
readHigherNext[stackTop] = RETURN_TO_PARENT;
long valTemp = setBit(n.infix, n.posDiff, true);
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;
readHigherNext[stackTop] = READ_LOWER;
continue;
}
}
}
//proceed to move up a level
--stackTop;
}
//Finished
nextValue = null;
nextKey = 0;
}
/**
* 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 = 64 - 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 nextValue != 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<V> nextEntry() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Entry<V> ret = new Entry<V>(nextKey, nextValue);
findNext();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public static class Entry<V> {
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;
}
}
public static long setBit(long ba, int posBit, boolean b) {
if (b) {
return ba | (0x8000000000000000L >>> posBit);
} else {
return ba & (~(0x8000000000000000L >>> posBit));
}
}
/**
* @param posBit Counts from left to right!!!
*/
public static boolean getBit(long l, int posBit) {
//last 6 bit [0..63]
return (l & (0x8000000000000000L >>> posBit)) != 0;
}
public static String toBinary(long l) {
final int DEPTH = 64;
StringBuilder sb = new StringBuilder();
//long mask = DEPTH < 64 ? (1<<(DEPTH-1)) : 0x8000000000000000L;
for (int i = 0; i < DEPTH; i++) {
long mask = (1l << (long)(DEPTH-i-1));
if ((l & mask) != 0) { sb.append("1"); } else { sb.append("0"); }
if ((i+1)%8==0 && (i+1)<DEPTH) sb.append('.');
mask >>>= 1;
}
return sb.toString();
}
}