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

Java tutorial

Introduction

Here is the source code for org.dishevelled.bio.range.entrytree.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.entrytree;

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

import java.util.List;
import java.util.Set;

import com.google.common.collect.ComparisonChain;
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
 * @param <V> value type
 * @author  Michael Heuer
 */
public final class CenteredRangeTree<C extends Comparable, V> extends AbstractRangeTree<C, V> {
    /** Cached size. */
    private final int size;

    /** Root node, if any. */
    private final Node root;

    /**
     * Create a new centered range tree with the specified range entries.
     *
     * @param entries range entries, must not be null
     */
    private CenteredRangeTree(final Iterable<Entry<C, V>> entries) {
        checkNotNull(entries);
        size = Iterables.size(entries);
        root = createNode(entries);
    }

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

    @Override
    public Iterable<Entry<C, V>> intersect(final Range<C> range) {
        checkNotNull(range);
        List<Entry<C, V>> result = Lists.newLinkedList();
        Set<Node> visited = Sets.newHashSet();
        depthFirstSearch(range, root, result, visited);
        return result;
    }

    /**
     * Create and return a new node for the specified range entries.
     *
     * @param entries range entries
     * @return a new node for the specified range entries
     */
    private Node createNode(final Iterable<Entry<C, V>> entries) {
        Entry<C, V> first = Iterables.getFirst(entries, null);
        if (first == null) {
            return null;
        }
        Range<C> span = first.getRange();

        for (Entry<C, V> entry : entries) {
            Range<C> range = entry.getRange();
            span = range.span(span);
        }
        if (span.isEmpty()) {
            return null;
        }
        C center = Ranges.center(span);
        List<Entry<C, V>> left = Lists.newArrayList();
        List<Entry<C, V>> right = Lists.newArrayList();
        List<Entry<C, V>> overlap = Lists.newArrayList();

        for (Entry<C, V> entry : entries) {
            Range<C> range = entry.getRange();
            if (Ranges.isLessThan(range, center)) {
                left.add(entry);
            } else if (Ranges.isGreaterThan(range, center)) {
                right.add(entry);
            } else {
                overlap.add(entry);
            }
        }
        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<Entry<C, V>> 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 (Entry<C, V> entry : node.overlapByUpperEndpoint()) {
                Range<C> range = entry.getRange();
                if (Ranges.intersect(range, query)) {
                    result.add(entry);
                }
                if (Ranges.isGreaterThan(query, range.upperEndpoint())) {
                    break;
                }
            }
        } else if (Ranges.isLessThan(query, node.center())) {
            for (Entry<C, V> entry : node.overlapByLowerEndpoint()) {
                Range<C> range = entry.getRange();
                if (Ranges.intersect(range, query)) {
                    result.add(entry);
                }
                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 range entries ordered by lower endpoint. */
        private final List<Entry<C, V>> overlapByLowerEndpoint;

        /** List of overlapping range entries ordered by upper endpoint. */
        private final List<Entry<C, V>> 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<Entry<C, V>> 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();
            Ordering<Entry<C, V>> entryOrderingByLowerEndpoint = new EntryOrdering(orderingByLowerEndpoint);
            Ordering<Entry<C, V>> entryReverseOrderingByUpperEndpoint = new EntryOrdering(
                    reverseOrderingByUpperEndpoint);
            overlapByLowerEndpoint.sort(entryOrderingByLowerEndpoint);
            overlapByUpperEndpoint.sort(entryReverseOrderingByUpperEndpoint);
        }

        /**
         * 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 range entries ordered by lower endpoint.
         *
         * @return the list of overlapping range entries ordered by lower endpoint
         */
        List<Entry<C, V>> overlapByLowerEndpoint() {
            return overlapByLowerEndpoint;
        }

        /**
         * Return the list of overlapping range entries ordered by upper endpoint.
         *
         * @return the list of overlapping range entries ordered by upper endpoint
         */
        List<Entry<C, V>> overlapByUpperEndpoint() {
            return overlapByUpperEndpoint;
        }
    }

    /**
     * All equal ordering.
     */
    private class AllEqualOrdering extends Ordering<V> {
        @Override
        public int compare(final V left, final V right) {
            return 0;
        }
    }

    /**
     * Entry ordering.
     */
    private class EntryOrdering extends Ordering<Entry<C, V>> {
        /** Range ordering. */
        private final Ordering<Range<C>> rangeOrdering;

        /** Value ordering. */
        private final Ordering<V> valueOrdering;

        /**
         * Create a new entry ordering with the specified range ordering.
         *
         * @param rangeOrdering range ordering, must not be null
         */
        private EntryOrdering(final Ordering<Range<C>> rangeOrdering) {
            checkNotNull(rangeOrdering);
            this.rangeOrdering = rangeOrdering;
            this.valueOrdering = new AllEqualOrdering();
        }

        @Override
        public int compare(final Entry<C, V> left, final Entry<C, V> right) {
            return ComparisonChain.start().compare(left.getRange(), right.getRange(), rangeOrdering)
                    .compare(left.getValue(), right.getValue(), valueOrdering).result();
        }
    }

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

    /**
     * Create and return a new range tree from the specified ranges and values.
     *
     * @param <C> range endpoint type
     * @param <V> value type
     * @param ranges ranges, must not be null and must be equal in size to <code>values</code>
     * @param values values, must not be null and must be equal in size to <code>ranges</code>
     * @return a new range tree from the specified ranges and values
     */
    public static <C extends Comparable, V> RangeTree<C, V> create(final List<Range<C>> ranges,
            final List<V> values) {
        checkNotNull(ranges);
        checkNotNull(values);
        checkArgument(ranges.size() == values.size(), "entries and values must be equal size");

        int size = ranges.size();
        List<Entry<C, V>> entries = Lists.newArrayListWithExpectedSize(size);
        for (int i = 0; i < size; i++) {
            entries.add(new RangeEntry<C, V>(ranges.get(i), values.get(i)));
        }
        return new CenteredRangeTree<C, V>(entries);
    }
}