/* * The MIT License * * Copyright (c) 2009 The 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. */ package htsjdk.samtools.util; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; /** * A Red-Black tree with intervals for keys. * Not thread-safe, and cannot be made so. * * 7/24/2008: This was copied from the tedUtils package. * IMPORTANT!!! It has been modified to use the Reseq way of * handling coordinates (end-inclusive). * * @author tsharpe */ public class IntervalTree<V> implements Iterable<IntervalTree.Node<V>> { /** * Return the number of intervals in the tree. * @return The number of intervals. */ public int size() { return mRoot == null ? 0 : mRoot.getSize(); } /** * Remove all entries. */ public void clear() { mRoot = null; } /** * Put a new interval into the tree (or update the value associated with an existing interval). * If the interval is novel, the special sentinel value is returned. * @param start The interval's start. * @param end The interval's end. * @param value The associated value. * @return The old value associated with that interval, or the sentinel. */ @SuppressWarnings("null") public V put( final int start, final int end, final V value ) { if ( start > end ) throw new IllegalArgumentException("Start cannot exceed end."); V result = mSentinel; if ( mRoot == null ) { mRoot = new Node<V>(start,end,value); } else { Node<V> parent = null; Node<V> node = mRoot; int cmpVal = 0; while ( node != null ) { parent = node; // last non-null node cmpVal = node.compare(start,end); if ( cmpVal == 0 ) { break; } node = cmpVal < 0 ? node.getLeft() : node.getRight(); } if ( cmpVal == 0 ) { result = parent.setValue(value); } else { if ( cmpVal < 0 ) { mRoot = parent.insertLeft(start,end,value,mRoot); } else { mRoot = parent.insertRight(start,end,value,mRoot); } } } return result; } /** * Remove an interval from the tree. If the interval does not exist in the tree the * special sentinel value is returned. * @param start The interval's start. * @param end The interval's end. * @return The value associated with that interval, or the sentinel. */ public V remove( final int start, final int end ) { V result = mSentinel; Node<V> node = mRoot; while ( node != null ) { final int cmpVal = node.compare(start,end); if ( cmpVal == 0 ) { result = node.getValue(); mRoot = node.remove(mRoot); break; } node = cmpVal < 0 ? node.getLeft() : node.getRight(); } return result; } /** * Find an interval. * @param start The interval's start. * @param end The interval's end. * @return The Node that represents that interval, or null. */ public Node<V> find( final int start, final int end ) { Node<V> node = mRoot; while ( node != null ) { final int cmpVal = node.compare(start,end); if ( cmpVal == 0 ) { break; } node = cmpVal < 0 ? node.getLeft() : node.getRight(); } return node; } /** * Find the nth interval in the tree. * @param idx The rank of the interval sought (from 0 to size()-1). * @return The Node that represents the nth interval. */ public Node<V> findByIndex( final int idx ) { return Node.findByRank(mRoot,idx+1); } /** * Find the rank of the specified interval. If the specified interval is not in the * tree, then -1 is returned. * @param start The interval's start. * @param end The interval's end. * @return The rank of that interval, or -1. */ public int getIndex( final int start, final int end ) { return Node.getRank(mRoot,start,end) - 1; } /** * Find the least interval in the tree. * @return The earliest interval, or null if the tree is empty. */ public Node<V> min() { Node<V> result = null; Node<V> node = mRoot; while ( node != null ) { result = node; node = node.getLeft(); } return result; } /** * Find the earliest interval in the tree greater than or equal to the specified interval. * @param start The interval's start. * @param end The interval's end. * @return The earliest >= interval, or null if there is none. */ @SuppressWarnings("null") public Node<V> min( final int start, final int end ) { Node<V> result = null; Node<V> node = mRoot; int cmpVal = 0; while ( node != null ) { result = node; cmpVal = node.compare(start,end); if ( cmpVal == 0 ) { break; } node = cmpVal < 0 ? node.getLeft() : node.getRight(); } if ( cmpVal > 0 ) { result = result.getNext(); } return result; } /** * Find the earliest interval in the tree that overlaps the specified interval. * @param start The interval's start. * @param end The interval's end. * @return The earliest overlapping interval, or null if there is none. */ public Node<V> minOverlapper( final int start, final int end ) { Node<V> result = null; Node<V> node = mRoot; if ( node != null && node.getMaxEnd() >= start ) { while ( true ) { if ( node.getStart() <= end && start <= node.getEnd() ) { // this node overlaps. there might be a lesser overlapper down the left sub-tree. // no need to consider the right sub-tree: even if there's an overlapper, if won't be minimal result = node; node = node.getLeft(); if ( node == null || node.getMaxEnd() < start ) break; // no left sub-tree or all nodes end too early } else { // no overlap. if there might be a left sub-tree overlapper, consider the left sub-tree. final Node<V> left = node.getLeft(); if ( left != null && left.getMaxEnd() >= start ) { node = left; } else { // left sub-tree cannot contain an overlapper. consider the right sub-tree. if ( node.getStart() > end ) break; // everything in the right sub-tree is past the end of the query interval node = node.getRight(); if ( node == null || node.getMaxEnd() < start ) break; // no right sub-tree or all nodes end too early } } } } return result; } /** * Find the greatest interval in the tree. * @return The latest interval, or null if the tree is empty. */ public Node<V> max() { Node<V> result = null; Node<V> node = mRoot; while ( node != null ) { result = node; node = node.getRight(); } return result; } /** * Find the latest interval in the tree less than or equal to the specified interval. * @param start The interval's start. * @param end The interval's end. * @return The latest >= interval, or null if there is none. */ @SuppressWarnings("null") public Node<V> max( final int start, final int end ) { Node<V> result = null; Node<V> node = mRoot; int cmpVal = 0; while ( node != null ) { result = node; cmpVal = node.compare(start,end); if ( cmpVal == 0 ) { break; } node = cmpVal < 0 ? node.getLeft() : node.getRight(); } if ( cmpVal < 0 ) { result = result.getPrev(); } return result; } /** * Return an iterator over the entire tree. * @return An iterator. */ public Iterator<Node<V>> iterator() { return new FwdIterator(min()); } /** * Return an iterator over all intervals greater than or equal to the specified interval. * @param start The interval's start. * @param end The interval's end. * @return An iterator. */ public Iterator<Node<V>> iterator( final int start, final int end ) { return new FwdIterator(min(start,end)); } /** * Return an iterator over all intervals overlapping the specified range. * @param start The range start. * @param end The range end. * @return An iterator. */ public Iterator<Node<V>> overlappers( final int start, final int end ) { return new OverlapIterator(start,end); } /** * Return an iterator over the entire tree that returns intervals in reverse order. * @return An iterator. */ public Iterator<Node<V>> reverseIterator() { return new RevIterator(max()); } /** * Return an iterator over all intervals less than or equal to the specified interval, in reverse order. * @param start The interval's start. * @param end The interval's end. * @return An iterator. */ public Iterator<Node<V>> reverseIterator( final int start, final int end ) { return new RevIterator(max(start,end)); } /** * Get the special sentinel value that will be used to signal novelty when putting a new interval * into the tree, or to signal "not found" when removing an interval. This is null by default. * @return The sentinel value. */ public V getSentinel() { return mSentinel; } /** * Set the special sentinel value that will be used to signal novelty when putting a new interval * into the tree, or to signal "not found" when removing an interval. * @param sentinel The new sentinel value. * @return The old sentinel value. */ public V setSentinel( final V sentinel ) { final V result = mSentinel; mSentinel = sentinel; return result; } /** * This method is only for debugging. * It verifies whether the tree is internally consistent with respect to the mMaxEnd cached on each node. * @throws IllegalStateException If an inconsistency is detected. */ public void checkMaxEnds() { if (mRoot != null) mRoot.checkMaxEnd(); } /** * This method draws a nested picture of the tree on System.out. * Useful for debugging. */ public void printTree() { if (mRoot != null) mRoot.printNode(); } void removeNode( final Node<V> node ) { mRoot = node.remove(mRoot); } private Node<V> mRoot; private V mSentinel; public static class Node<V1> { // bit-wise definitions from which the other constants are composed public static final int HAS_LESSER_PART = 1; public static final int HAS_OVERLAPPING_PART = 2; public static final int HAS_GREATER_PART = 4; public static final int IS_ADJACENT_AND_EMPTY = 0; public static final int IS_STRICTLY_LESS = HAS_LESSER_PART; // 1 public static final int IS_SUBSET = HAS_OVERLAPPING_PART; // 2 public static final int IS_LEFT_OVERHANGING_OVERLAPPER = HAS_LESSER_PART | HAS_OVERLAPPING_PART; // 3 public static final int IS_STRICTLY_GREATER = HAS_GREATER_PART; // 4 // there is no value that equals 5, since that would imply overhanging on left and right without overlapping public static final int IS_RIGHT_OVERHANGING_OVERLAPPER = HAS_GREATER_PART | HAS_OVERLAPPING_PART; // 6 public static final int IS_SUPERSET = HAS_LESSER_PART | HAS_OVERLAPPING_PART | HAS_GREATER_PART; // 7 Node( final int start, final int end, final V1 value ) { mStart = start; mEnd = end; mValue = value; mSize = 1; mMaxEnd = mEnd; mIsBlack = true; } Node( final Node<V1> parent, final int start, final int end, final V1 value ) { mParent = parent; mStart = start; mEnd = end; mValue = value; mMaxEnd = mEnd; mSize = 1; } public int getStart() { return mStart; } public int getEnd() { return mEnd; } public int getLength() { return mEnd - mStart; } public int getRelationship( final Node<V1> interval ) { int result = 0; if ( mStart < interval.getStart() ) result = HAS_LESSER_PART; if ( mEnd > interval.getEnd() ) result |= HAS_GREATER_PART; if ( mStart < interval.getEnd() && interval.getStart() < mEnd ) result |= HAS_OVERLAPPING_PART; return result; } public boolean isAdjacent( final Node<V1> interval ) { return mStart == interval.getEnd() || mEnd == interval.getStart(); } public V1 getValue() { return mValue; } public V1 setValue( final V1 value ) { final V1 result = mValue; mValue = value; return result; } int getSize() { return mSize; } int getMaxEnd() { return mMaxEnd; } Node<V1> getLeft() { return mLeft; } Node<V1> insertLeft( final int start, final int end, final V1 value, final Node<V1> root ) { mLeft = new Node<V1>(this,start,end,value); return insertFixup(mLeft,root); } Node<V1> getRight() { return mRight; } Node<V1> insertRight( final int start, final int end, final V1 value, final Node<V1> root ) { mRight = new Node<V1>(this,start,end,value); return insertFixup(mRight,root); } Node<V1> getNext() { Node<V1> result; if ( mRight != null ) { result = mRight; while ( result.mLeft != null ) { result = result.mLeft; } } else { Node<V1> node = this; result = mParent; while ( result != null && node == result.mRight ) { node = result; result = result.mParent; } } return result; } Node<V1> getPrev() { Node<V1> result; if ( mLeft != null ) { result = mLeft; while ( result.mRight != null ) { result = result.mRight; } } else { Node<V1> node = this; result = mParent; while ( result != null && node == result.mLeft ) { node = result; result = result.mParent; } } return result; } boolean wasRemoved() { return mSize == 0; } Node<V1> remove( Node<V1> root ) { if ( mSize == 0 ) { throw new IllegalStateException("Entry was already removed."); } if ( mLeft == null ) { if ( mRight == null ) { // no children if ( mParent == null ) { root = null; } else if ( mParent.mLeft == this ) { mParent.mLeft = null; fixup(mParent); if ( mIsBlack ) root = removeFixup(mParent,null,root); } else { mParent.mRight = null; fixup(mParent); if ( mIsBlack ) root = removeFixup(mParent,null,root); } } else { // single child on right root = spliceOut(mRight,root); } } else if ( mRight == null ) { // single child on left root = spliceOut(mLeft,root); } else { // two children final Node<V1> next = getNext(); root = next.remove(root); // put next into tree in same position as this, effectively removing this if ( (next.mParent = mParent) == null ) root = next; else if ( mParent.mLeft == this ) mParent.mLeft = next; else mParent.mRight = next; if ( (next.mLeft = mLeft) != null ) { mLeft.mParent = next; } if ( (next.mRight = mRight) != null ) { mRight.mParent = next; } next.mIsBlack = mIsBlack; next.mSize = mSize; // PIC-123 fix fixup(next); } mSize = 0; return root; } // backwards comparison! compares start+end to this. int compare( final int start, final int end ) { int result = 0; if ( start > mStart ) result = 1; else if ( start < mStart ) result = -1; else if ( end > mEnd ) result = 1; else if ( end < mEnd ) result = -1; return result; } @SuppressWarnings("null") static <V1> Node<V1> getNextOverlapper( Node<V1> node, final int start, final int end ) { do { Node<V1> nextNode = node.mRight; if ( nextNode != null && nextNode.mMaxEnd >= start ) { node = nextNode; while ( (nextNode = node.mLeft) != null && nextNode.mMaxEnd >= start ) node = nextNode; } else { nextNode = node; while ( (node = nextNode.mParent) != null && node.mRight == nextNode ) nextNode = node; } if ( node != null && node.mStart > end ) node = null; } while ( node != null && !(node.mStart <= end && start <= node.mEnd) ); return node; } static <V1> Node<V1> findByRank( Node<V1> node, int rank ) { while ( node != null ) { final int nodeRank = node.getRank(); if ( rank == nodeRank ) break; if ( rank < nodeRank ) { node = node.mLeft; } else { node = node.mRight; rank -= nodeRank; } } return node; } static <V1> int getRank( Node<V1> node, final int start, final int end ) { int rank = 0; while ( node != null ) { final int cmpVal = node.compare(start,end); if ( cmpVal < 0 ) { node = node.mLeft; } else { rank += node.getRank(); if ( cmpVal == 0 ) return rank; // EARLY RETURN!!! node = node.mRight; } } return 0; } private int getRank() { int result = 1; if ( mLeft != null ) result = mLeft.mSize + 1; return result; } private Node<V1> spliceOut( final Node<V1> child, Node<V1> root ) { if ( (child.mParent = mParent) == null ) { root = child; child.mIsBlack = true; } else { if ( mParent.mLeft == this ) mParent.mLeft = child; else mParent.mRight = child; fixup(mParent); if ( mIsBlack ) root = removeFixup(mParent,child,root); } return root; } private Node<V1> rotateLeft( Node<V1> root ) { final Node<V1> child = mRight; final int childSize = child.mSize; child.mSize = mSize; mSize -= childSize; if ( (mRight = child.mLeft) != null ) { mRight.mParent = this; mSize += mRight.mSize; } if ( (child.mParent = mParent) == null ) root = child; else if ( this == mParent.mLeft ) mParent.mLeft = child; else mParent.mRight = child; child.mLeft = this; mParent = child; setMaxEnd(); child.setMaxEnd(); return root; } private Node<V1> rotateRight( Node<V1> root ) { final Node<V1> child = mLeft; final int childSize = child.mSize; child.mSize = mSize; mSize -= childSize; if ( (mLeft = child.mRight) != null ) { mLeft.mParent = this; mSize += mLeft.mSize; } if ( (child.mParent = mParent) == null ) root = child; else if ( this == mParent.mLeft ) mParent.mLeft = child; else mParent.mRight = child; child.mRight = this; mParent = child; setMaxEnd(); child.setMaxEnd(); return root; } private void setMaxEnd() { mMaxEnd = mEnd; if ( mLeft != null ) mMaxEnd = Math.max(mMaxEnd,mLeft.mMaxEnd); if ( mRight != null ) mMaxEnd = Math.max(mMaxEnd,mRight.mMaxEnd); } private static <V1> void fixup( Node<V1> node ) { do { node.mSize = 1; node.mMaxEnd = node.mEnd; if ( node.mLeft != null ) { node.mSize += node.mLeft.mSize; node.mMaxEnd = Math.max(node.mMaxEnd,node.mLeft.mMaxEnd); } if ( node.mRight != null ) { node.mSize += node.mRight.mSize; node.mMaxEnd = Math.max(node.mMaxEnd,node.mRight.mMaxEnd); } } while ( (node = node.mParent) != null ); } private static <V1> Node<V1> insertFixup( Node<V1> daughter, Node<V1> root ) { Node<V1> mom = daughter.mParent; fixup(mom); while( mom != null && !mom.mIsBlack ) { final Node<V1> gramma = mom.mParent; Node<V1> auntie = gramma.mLeft; if ( auntie == mom ) { auntie = gramma.mRight; if ( auntie != null && !auntie.mIsBlack ) { mom.mIsBlack = true; auntie.mIsBlack = true; gramma.mIsBlack = false; daughter = gramma; } else { if ( daughter == mom.mRight ) { root = mom.rotateLeft(root); mom = daughter; } mom.mIsBlack = true; gramma.mIsBlack = false; root = gramma.rotateRight(root); break; } } else { if ( auntie != null && !auntie.mIsBlack ) { mom.mIsBlack = true; auntie.mIsBlack = true; gramma.mIsBlack = false; daughter = gramma; } else { if ( daughter == mom.mLeft ) { root = mom.rotateRight(root); mom = daughter; } mom.mIsBlack = true; gramma.mIsBlack = false; root = gramma.rotateLeft(root); break; } } mom = daughter.mParent; } root.mIsBlack = true; return root; } private static <V1> Node<V1> removeFixup( Node<V1> parent, Node<V1> node, Node<V1> root ) { do { if ( node == parent.mLeft ) { Node<V1> sister = parent.mRight; if ( !sister.mIsBlack ) { sister.mIsBlack = true; parent.mIsBlack = false; root = parent.rotateLeft(root); sister = parent.mRight; } if ( (sister.mLeft == null || sister.mLeft.mIsBlack) && (sister.mRight == null || sister.mRight.mIsBlack) ) { sister.mIsBlack = false; node = parent; } else { if ( sister.mRight == null || sister.mRight.mIsBlack ) { sister.mLeft.mIsBlack = true; sister.mIsBlack = false; root = sister.rotateRight(root); sister = parent.mRight; } sister.mIsBlack = parent.mIsBlack; parent.mIsBlack = true; sister.mRight.mIsBlack = true; root = parent.rotateLeft(root); node = root; } } else { Node<V1> sister = parent.mLeft; if ( !sister.mIsBlack ) { sister.mIsBlack = true; parent.mIsBlack = false; root = parent.rotateRight(root); sister = parent.mLeft; } if ( (sister.mLeft == null || sister.mLeft.mIsBlack) && (sister.mRight == null || sister.mRight.mIsBlack) ) { sister.mIsBlack = false; node = parent; } else { if ( sister.mLeft == null || sister.mLeft.mIsBlack ) { sister.mRight.mIsBlack = true; sister.mIsBlack = false; root = sister.rotateLeft(root); sister = parent.mLeft; } sister.mIsBlack = parent.mIsBlack; parent.mIsBlack = true; sister.mLeft.mIsBlack = true; root = parent.rotateRight(root); node = root; } } parent = node.mParent; } while ( parent != null && node.mIsBlack ); node.mIsBlack = true; return root; } public void checkMaxEnd() { if (mMaxEnd != calcMaxEnd()) { throw new IllegalStateException("Max end mismatch " + mMaxEnd + " vs " + calcMaxEnd() + ": " + this); } if (mLeft != null) mLeft.checkMaxEnd(); if (mRight != null) mRight.checkMaxEnd(); } private int calcMaxEnd() { int end = mEnd; if (mLeft != null) end = Math.max(end, mLeft.mMaxEnd); if (mRight != null) end = Math.max(end, mRight.mMaxEnd); return end; } public void printNode() { this.printNodeInternal("", "root: "); } private void printNodeInternal(final String padding, final String tag) { System.out.println(padding + tag + " " + this); if (mLeft != null) mLeft.printNodeInternal(padding + " ", "left: "); if (mRight != null) mRight.printNodeInternal(padding + " ", "right:"); } public String toString() { return "Node(" + mStart + "," + mEnd + "," + mValue + "," + mSize + "," + mMaxEnd + "," + mIsBlack + ")"; } private Node<V1> mParent; private Node<V1> mLeft; private Node<V1> mRight; private final int mStart; private final int mEnd; private V1 mValue; private int mSize; private int mMaxEnd; private boolean mIsBlack; } public class FwdIterator implements Iterator<Node<V>> { public FwdIterator( final Node<V> node ) { mNext = node; } public boolean hasNext() { return mNext != null; } public Node<V> next() { if ( mNext == null ) { throw new NoSuchElementException("No next element."); } if ( mNext.wasRemoved() ) { mNext = min(mNext.getStart(),mNext.getEnd()); if ( mNext == null ) throw new ConcurrentModificationException("Current element was removed, and there are no more elements."); } mLast = mNext; mNext = mNext.getNext(); return mLast; } public void remove() { if ( mLast == null ) { throw new IllegalStateException("No entry to remove."); } removeNode(mLast); mLast = null; } private Node<V> mNext; private Node<V> mLast; } public class RevIterator implements Iterator<Node<V>> { public RevIterator( final Node<V> node ) { mNext = node; } public boolean hasNext() { return mNext != null; } public Node<V> next() { if ( mNext == null ) throw new NoSuchElementException("No next element."); if ( mNext.wasRemoved() ) { mNext = max(mNext.getStart(),mNext.getEnd()); if ( mNext == null ) throw new ConcurrentModificationException("Current element was removed, and there are no more elements."); } mLast = mNext; mNext = mNext.getPrev(); return mLast; } public void remove() { if ( mLast == null ) { throw new IllegalStateException("No entry to remove."); } removeNode(mLast); mLast = null; } private Node<V> mNext; private Node<V> mLast; } public class OverlapIterator implements Iterator<Node<V>> { public OverlapIterator( final int start, final int end ) { mNext = minOverlapper(start,end); mStart = start; mEnd = end; } public boolean hasNext() { return mNext != null; } public Node<V> next() { if ( mNext == null ) { throw new NoSuchElementException("No next element."); } if ( mNext.wasRemoved() ) { throw new ConcurrentModificationException("Current element was removed."); } mLast = mNext; mNext = Node.getNextOverlapper(mNext,mStart,mEnd); return mLast; } public void remove() { if ( mLast == null ) { throw new IllegalStateException("No entry to remove."); } removeNode(mLast); mLast = null; } private Node<V> mNext; private Node<V> mLast; private final int mStart; private final int mEnd; } public static class ValuesIterator<V1> implements Iterator<V1> { public ValuesIterator( final Iterator<Node<V1>> itr ) { mItr = itr; } public boolean hasNext() { return mItr.hasNext(); } public V1 next() { return mItr.next().getValue(); } public void remove() { mItr.remove(); } private final Iterator<Node<V1>> mItr; } }