/*
* The MIT License (MIT)
*
* Copyright (c) 2016 University of California San Diego
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*
*/
package org.broad.igv.util;
/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2015 Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import org.apache.log4j.Logger;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** An implementation of an interval tree, following the explanation.
* from CLR.
*/
public class IntervalTree<T> {
private static Logger logger = Logger.getLogger(IntervalTree.class);
boolean immutable = false;
IntervalTree.Node root;
IntervalTree.Node NIL = IntervalTree.Node.NIL;
public IntervalTree() {
this.root = NIL;
}
public IntervalTree(boolean immutable) {
this.immutable = immutable;
this.root = NIL;
}
public void insert(Interval<T> interval) {
if (immutable) {
throw new java.lang.UnsupportedOperationException("Tree is immutable. Inserts not allowed");
}
IntervalTree.Node node = new IntervalTree.Node(interval);
insert(node);
}
// Returns all matches as a list of Intervals
public List<Interval<T>> findOverlapping(int start, int end) {
Interval searchInterval = new Interval(start, end, 0);
if (root().isNull()) {
return Collections.emptyList();
}
List<Interval<T>> results = new ArrayList();
searchAll(searchInterval, root(), results);
return results;
}
public String toString() {
return root().toString();
}
private List<Interval<T>> searchAll(Interval interval, IntervalTree.Node node, List<Interval<T>> results) {
if (node.interval.overlaps(interval)) {
results.add(node.interval);
}
if (!node.left.isNull() && node.left.max >= interval.getLow()) {
searchAll(interval, node.left, results);
}
if (!node.right.isNull() && node.right.min <= interval.getHigh()) {
searchAll(interval, node.right, results);
}
return results;
}
/**
* Return all intervals in tree.
* TODO: an iterator would be more effecient.
* @return
*/
public List<Interval<T>> getIntervals() {
if (root().isNull()) {
return Collections.emptyList();
}
List<Interval<T>> results = new ArrayList(size());
getAll(root, results);
return results;
}
private List<Interval<T>> getAll(IntervalTree.Node node, List<Interval<T>> results) {
results.add(node.interval);
if (!node.left.isNull()) {
getAll(node.left, results);
}
if (!node.right.isNull()) {
getAll(node.right, results);
}
return results;
}
/**
* Used for testing only.
*
* @param node
* @return
*/
private int getRealMax(IntervalTree.Node node) {
if (node.isNull())
return Integer.MIN_VALUE;
int leftMax = getRealMax(node.left);
int rightMax = getRealMax(node.right);
int nodeHigh = (node.interval).getHigh();
int max1 = (leftMax > rightMax ? leftMax : rightMax);
return (max1 > nodeHigh ? max1 : nodeHigh);
}
/**
* Used for testing only
*
* @param node
* @return
*/
private int getRealMin(IntervalTree.Node node) {
if (node.isNull())
return Integer.MAX_VALUE;
int leftMin = getRealMin(node.left);
int rightMin = getRealMin(node.right);
int nodeLow = (node.interval).getLow();
int min1 = (leftMin < rightMin ? leftMin : rightMin);
return (min1 < nodeLow ? min1 : nodeLow);
}
private void insert(IntervalTree.Node x) {
assert (x != null);
assert (!x.isNull());
treeInsert(x);
x.color = IntervalTree.Node.RED;
while (x != this.root && x.parent.color == IntervalTree.Node.RED) {
if (x.parent == x.parent.parent.left) {
IntervalTree.Node y = x.parent.parent.right;
if (y.color == IntervalTree.Node.RED) {
x.parent.color = IntervalTree.Node.BLACK;
y.color = IntervalTree.Node.BLACK;
x.parent.parent.color = IntervalTree.Node.RED;
x = x.parent.parent;
} else {
if (x == x.parent.right) {
x = x.parent;
this.leftRotate(x);
}
x.parent.color = IntervalTree.Node.BLACK;
x.parent.parent.color = IntervalTree.Node.RED;
this.rightRotate(x.parent.parent);
}
} else {
IntervalTree.Node y = x.parent.parent.left;
if (y.color == IntervalTree.Node.RED) {
x.parent.color = IntervalTree.Node.BLACK;
y.color = IntervalTree.Node.BLACK;
x.parent.parent.color = IntervalTree.Node.RED;
x = x.parent.parent;
} else {
if (x == x.parent.left) {
x = x.parent;
this.rightRotate(x);
}
x.parent.color = IntervalTree.Node.BLACK;
x.parent.parent.color = IntervalTree.Node.RED;
this.leftRotate(x.parent.parent);
}
}
}
this.root.color = IntervalTree.Node.BLACK;
}
private IntervalTree.Node root() {
return this.root;
}
private IntervalTree.Node minimum(IntervalTree.Node node) {
assert (node != null);
assert (!node.isNull());
while (!node.left.isNull()) {
node = node.left;
}
return node;
}
private IntervalTree.Node maximum(IntervalTree.Node node) {
assert (node != null);
assert (!node.isNull());
while (!node.right.isNull()) {
node = node.right;
}
return node;
}
private IntervalTree.Node successor(IntervalTree.Node x) {
assert (x != null);
assert (!x.isNull());
if (!x.right.isNull()) {
return this.minimum(x.right);
}
IntervalTree.Node y = x.parent;
while ((!y.isNull()) && x == y.right) {
x = y;
y = y.parent;
}
return y;
}
private IntervalTree.Node predecessor(IntervalTree.Node x) {
assert (x != null);
assert (!x.isNull());
if (!x.left.isNull()) {
return this.maximum(x.left);
}
IntervalTree.Node y = x.parent;
while ((!y.isNull()) && x == y.left) {
x = y;
y = y.parent;
}
return y;
}
private void leftRotate(IntervalTree.Node x) {
IntervalTree.Node y = x.right;
x.right = y.left;
if (y.left != NIL) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == NIL) {
this.root = y;
} else {
if (x.parent.left == x) {
x.parent.left = y;
} else {
x.parent.right = y;
}
}
y.left = x;
x.parent = y;
applyUpdate(x);
// no need to apply update on y, since it'll y is an ancestor
// of x, and will be touched by applyUpdate().
}
private void rightRotate(IntervalTree.Node x) {
IntervalTree.Node y = x.left;
x.left = y.right;
if (y.right != NIL) {
y.right.parent = x;
}
y.parent = x.parent;
if (x.parent == NIL) {
this.root = y;
} else {
if (x.parent.right == x) {
x.parent.right = y;
} else {
x.parent.left = y;
}
}
y.right = x;
x.parent = y;
applyUpdate(x);
// no need to apply update on y, since it'll y is an ancestor
// of x, and will be touched by applyUpdate().
}
/**
* Note: Does not maintain RB constraints, this is done post insert
*
* @param x
*/
private void treeInsert(IntervalTree.Node x) {
IntervalTree.Node node = this.root;
IntervalTree.Node y = NIL;
while (node != NIL) {
y = node;
if (x.interval.getLow() <= node.interval.getLow()) {
node = node.left;
} else {
node = node.right;
}
}
x.parent = y;
if (y == NIL) {
this.root = x;
x.left = x.right = NIL;
} else {
if (x.interval.getLow() <= y.interval.getLow()) {
y.left = x;
} else {
y.right = x;
}
}
this.applyUpdate(x);
}
// Applies the statistic update on the node and its ancestors.
private void applyUpdate(IntervalTree.Node node) {
while (!node.isNull()) {
this.update(node);
node = node.parent;
}
}
// Note: this method is called millions of times and is optimized for speed, or as optimized as java allows.
private void update(IntervalTree.Node node) {
int nodeMax = node.left.max > node.right.max ? node.left.max : node.right.max;
int intervalHigh = node.interval.high;
node.max = nodeMax > intervalHigh ? nodeMax : intervalHigh;
int nodeMin = node.left.min < node.right.min ? node.left.min : node.right.min;
int intervalLow = node.interval.low;
node.min = nodeMin < intervalLow ? nodeMin : intervalLow;
}
/**
* Returns the number of nodes in the tree.
*/
public int size() {
return _size(this.root);
}
private int _size(IntervalTree.Node node) {
if (node.isNull())
return 0;
return 1 + _size(node.left) + _size(node.right);
}
private boolean allRedNodesFollowConstraints(IntervalTree.Node node) {
if (node.isNull())
return true;
if (node.color == IntervalTree.Node.BLACK) {
return (allRedNodesFollowConstraints(node.left) &&
allRedNodesFollowConstraints(node.right));
}
// At this point, we know we're on a RED node.
return (node.left.color == IntervalTree.Node.BLACK &&
node.right.color == IntervalTree.Node.BLACK &&
allRedNodesFollowConstraints(node.left) &&
allRedNodesFollowConstraints(node.right));
}
// Check that both ends are equally balanced in terms of black height.
private boolean isBalancedBlackHeight(IntervalTree.Node node) {
if (node.isNull())
return true;
return (blackHeight(node.left) == blackHeight(node.right) &&
isBalancedBlackHeight(node.left) &&
isBalancedBlackHeight(node.right));
}
// The black height of a node should be left/right equal.
private int blackHeight(IntervalTree.Node node) {
if (node.isNull())
return 0;
int leftBlackHeight = blackHeight(node.left);
if (node.color == IntervalTree.Node.BLACK) {
return leftBlackHeight + 1;
} else {
return leftBlackHeight;
}
}
/**
* Test code: make sure that the tree has all the properties
* defined by Red Black trees and interval trees
* <p/>
* o. Root is black.
* <p/>
* o. NIL is black.
* <p/>
* o. Red nodes have black children.
* <p/>
* o. Every path from root to leaves contains the same number of
* black nodes.
* <p/>
* o. getMax(node) is the maximum of any interval rooted at that node..
* <p/>
* This code is expensive, and only meant to be used for
* assertions and testing.
*/
public boolean isValid() {
if (this.root.color != IntervalTree.Node.BLACK) {
logger.warn("root color is wrong");
return false;
}
if (NIL.color != IntervalTree.Node.BLACK) {
logger.warn("NIL color is wrong");
return false;
}
if (allRedNodesFollowConstraints(this.root) == false) {
logger.warn("red node doesn't follow constraints");
return false;
}
if (isBalancedBlackHeight(this.root) == false) {
logger.warn("black height unbalanced");
return false;
}
return hasCorrectMaxFields(this.root) &&
hasCorrectMinFields(this.root);
}
private boolean hasCorrectMaxFields(IntervalTree.Node node) {
if (node.isNull())
return true;
return (getRealMax(node) == (node.max) &&
hasCorrectMaxFields(node.left) &&
hasCorrectMaxFields(node.right));
}
private boolean hasCorrectMinFields(IntervalTree.Node node) {
if (node.isNull())
return true;
return (getRealMin(node) == (node.min) &&
hasCorrectMinFields(node.left) &&
hasCorrectMinFields(node.right));
}
static class Node {
public static boolean BLACK = false;
public static boolean RED = true;
Interval interval;
int min;
int max;
IntervalTree.Node left;
IntervalTree.Node right;
// Color and parent are used for inserts. If tree is immutable these are not required (no requirement
// to store these persistently).
boolean color;
IntervalTree.Node parent;
private Node() {
this.max = Integer.MIN_VALUE;
this.min = Integer.MAX_VALUE;
}
public void store(DataOutputStream dos) throws IOException {
dos.writeInt(interval.getLow());
dos.writeInt(interval.getHigh());
dos.writeInt(min);
dos.writeInt(max);
}
public Node(Interval interval) {
this();
this.parent = NIL;
this.left = NIL;
this.right = NIL;
this.interval = interval;
this.color = RED;
}
static IntervalTree.Node NIL;
static {
NIL = new IntervalTree.Node();
NIL.color = BLACK;
NIL.parent = NIL;
NIL.left = NIL;
NIL.right = NIL;
}
public boolean isNull() {
return this == NIL;
}
public String toString() {
if (this == NIL) {
return "nil";
}
/* return
"(" + this.interval + " " + (this.color == RED ? "RED" : "BLACK") +
" (" + this.left.toString() + ", " + this.right.toString() + ")";
*/
StringBuffer buf = new StringBuffer();
_toString(buf);
return buf.toString();
}
public void _toString(StringBuffer buf) {
if (this == NIL) {
buf.append("nil");
return;
}
buf.append(this.interval + " -> " + this.left.interval + ", " + this.right.interval);
buf.append("\n");
this.left._toString(buf);
this.right._toString(buf);
}
}
}