org.nmdp.ngs.range.tree.CenteredRangeTree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ngs-range Show documentation
Show all versions of ngs-range Show documentation
Guava ranges for genomics.
/*
ngs-range Guava ranges for genomics.
Copyright (c) 2014 National Marrow Donor Program (NMDP)
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 3 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> http://www.gnu.org/licenses/lgpl.html
*/
package org.nmdp.ngs.range.tree;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import org.nmdp.ngs.range.Ranges;
/**
* Centered range tree.
*
* @param range endpoint type
*/
public final class CenteredRangeTree extends AbstractRangeTree {
/** Cached size. */
private final int size;
/** Root node, if any. */
private final Node root;
/**
* Create a new centered range tree with the specified ranges.
*
* @param ranges ranges, must not be null
*/
private CenteredRangeTree(final Iterable> ranges) {
checkNotNull(ranges);
// O(n) hit to cache size
size = Iterables.size(ranges);
root = createNode(ranges);
}
@Override
public int size() {
return size;
}
@Override
public Iterable> intersect(final Range range) {
checkNotNull(range);
List> result = Lists.newLinkedList();
Set visited = Sets.newHashSet();
depthFirstSearch(range, root, result, visited);
return result;
}
/**
* Create and return a new node for the specified ranges.
*
* @param ranges ranges
* @return a new node for the specified ranges
*/
private Node createNode(final Iterable> ranges) {
Range span = Iterables.getFirst(ranges, null);
if (span == null) {
return null;
}
for (Range range : ranges) {
checkNotNull(range, "ranges must not contain null ranges");
span = range.span(span);
}
if (span.isEmpty()) {
return null;
}
C center = Ranges.center(span);
List> left = Lists.newArrayList();
List> right = Lists.newArrayList();
List> overlap = Lists.newArrayList();
for (Range range : ranges) {
if (Ranges.isLessThan(range, center)) {
left.add(range);
}
else if (Ranges.isGreaterThan(range, center)) {
right.add(range);
}
else {
overlap.add(range);
}
}
return new Node(center, createNode(left), createNode(right), overlap);
}
/**
* Depth first search.
*
* @param query query range
* @param node node
* @param result list of matching ranges
* @param visited set of visited nodes
*/
private void depthFirstSearch(final Range query, final Node node, final List> result, final Set visited) {
if (node == null || visited.contains(node) || query.isEmpty()) {
return;
}
if (node.left() != null && Ranges.isLessThan(query, node.center())) {
depthFirstSearch(query, node.left(), result, visited);
}
else if (node.right() != null && Ranges.isGreaterThan(query, node.center())) {
depthFirstSearch(query, node.right(), result, visited);
}
if (Ranges.isGreaterThan(query, node.center())) {
for (Range range : node.overlapByUpperEndpoint()) {
if (Ranges.intersect(range, query)) {
result.add(range);
}
if (Ranges.isGreaterThan(query, range.upperEndpoint())) {
break;
}
}
}
else if (Ranges.isLessThan(query, node.center())) {
for (Range range : node.overlapByLowerEndpoint()) {
if (Ranges.intersect(range, query)) {
result.add(range);
}
if (Ranges.isLessThan(query, range.lowerEndpoint())) {
break;
}
}
}
else {
result.addAll(node.overlapByLowerEndpoint());
}
visited.add(node);
}
/**
* Node.
*/
private class Node {
/** Center. */
private final C center;
/** Left node, if any. */
private final Node left;
/** Right node, if any. */
private final Node right;
/** List of overlapping ranges ordered by lower endpoint. */
private final List> overlapByLowerEndpoint;
/** List of overlapping ranges ordered by upper endpoint. */
private final List> overlapByUpperEndpoint;
/**
* Create a new node.
*
* @param center center
* @param left left node, if any
* @param right right node, if any
* @param overlap list of overlapping nodes
*/
Node(final C center, final Node left, final Node right, final List> overlap) {
this.center = center;
this.left = left;
this.right = right;
overlapByLowerEndpoint = Lists.newArrayList(overlap);
overlapByUpperEndpoint = Lists.newArrayList(overlap);
Ordering> orderingByLowerEndpoint = Ranges.orderingByLowerEndpoint();
Ordering> reverseOrderingByUpperEndpoint = Ranges.reverseOrderingByUpperEndpoint();
Collections.sort(overlapByLowerEndpoint, orderingByLowerEndpoint);
Collections.sort(overlapByUpperEndpoint, reverseOrderingByUpperEndpoint);
}
/**
* Return the center.
*
* @return the center
*/
C center() {
return center;
}
/**
* Return the left node, if any.
*
* @return the left node or null
if no such node exists
*/
Node left() {
return left;
}
/**
* Return the right node, if any.
*
* @return the right node or null
if no such node exists
*/
Node right() {
return right;
}
/**
* Return the list of overlapping ranges ordered by lower endpoint.
*
* @return the list of overlapping ranges ordered by lower endpoint
*/
List> overlapByLowerEndpoint() {
return overlapByLowerEndpoint;
}
/**
* Return the list of overlapping ranges ordered by upper endpoint.
*
* @return the list of overlapping ranges ordered by upper endpoint
*/
List> overlapByUpperEndpoint() {
return overlapByUpperEndpoint;
}
}
/**
* Create and return a new range tree from the specified ranges.
*
* @param range endpoint type
* @param ranges ranges, must not be null
* @return a new range tree from the specified ranges
*/
public static RangeTree create(final Iterable> ranges) {
return new CenteredRangeTree(ranges);
}
}