org.dishevelled.bio.range.tree.CenteredRangeTree.java Source code

Java tutorial

Introduction

Here is the source code for org.dishevelled.bio.range.tree.CenteredRangeTree.java

Source

/*
    
dsh-bio-range  Guava ranges for genomics.
Copyright (c) 2013-2019 held jointly by the individual authors.
    
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.fsf.org/licensing/licenses/lgpl.html
> http://www.opensource.org/licenses/lgpl-license.php
    
*/
package org.dishevelled.bio.range.tree;

import static com.google.common.base.Preconditions.checkNotNull;

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.dishevelled.bio.range.Ranges;

/**
 * Centered range tree.
 *
 * @param <C> range endpoint type
 * @author  Michael Heuer
 */
public final class CenteredRangeTree<C extends Comparable> extends AbstractRangeTree<C> {
    /** 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<Range<C>> ranges) {
        checkNotNull(ranges);
        size = Iterables.size(ranges);
        root = createNode(ranges);
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Iterable<Range<C>> intersect(final Range<C> range) {
        checkNotNull(range);
        List<Range<C>> result = Lists.newLinkedList();
        Set<Node> 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<Range<C>> ranges) {
        Range<C> span = Iterables.getFirst(ranges, null);
        if (span == null) {
            return null;
        }
        for (Range<C> 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<Range<C>> left = Lists.newArrayList();
        List<Range<C>> right = Lists.newArrayList();
        List<Range<C>> overlap = Lists.newArrayList();
        for (Range<C> 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<C> query, final Node node, final List<Range<C>> result,
            final Set<Node> 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<C> 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<C> 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<Range<C>> overlapByLowerEndpoint;

        /** List of overlapping ranges ordered by upper endpoint. */
        private final List<Range<C>> 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<Range<C>> overlap) {
            this.center = center;
            this.left = left;
            this.right = right;
            overlapByLowerEndpoint = Lists.newArrayList(overlap);
            overlapByUpperEndpoint = Lists.newArrayList(overlap);
            Ordering<Range<C>> orderingByLowerEndpoint = Ranges.orderingByLowerEndpoint();
            Ordering<Range<C>> reverseOrderingByUpperEndpoint = Ranges.reverseOrderingByUpperEndpoint();
            overlapByLowerEndpoint.sort(orderingByLowerEndpoint);
            overlapByUpperEndpoint.sort(reverseOrderingByUpperEndpoint);
        }

        /**
         * Return the center.
         *
         * @return the center
         */
        C center() {
            return center;
        }

        /**
         * Return the left node, if any.
         *
         * @return the left node or <code>null</code> if no such node exists
         */
        Node left() {
            return left;
        }

        /**
         * Return the right node, if any.
         *
         * @return the right node or <code>null</code> 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<Range<C>> overlapByLowerEndpoint() {
            return overlapByLowerEndpoint;
        }

        /**
         * Return the list of overlapping ranges ordered by upper endpoint.
         *
         * @return the list of overlapping ranges ordered by upper endpoint
         */
        List<Range<C>> overlapByUpperEndpoint() {
            return overlapByUpperEndpoint;
        }
    }

    /**
     * Create and return a new range tree from the specified ranges.
     *
     * @param <C> range endpoint type
     * @param ranges ranges, must not be null
     * @return a new range tree from the specified ranges
     */
    public static <C extends Comparable> RangeTree<C> create(final Iterable<Range<C>> ranges) {
        return new CenteredRangeTree<C>(ranges);
    }
}