All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nmdp.ngs.range.tree.CenteredRangeTree Maven / Gradle / Ivy

The newest version!
/*

    ngs-range  Guava ranges for genomics.
    Copyright (c) 2014-2015 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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy