package com.jwetherell.algorithms.data_structures; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; /** * An interval tree is an ordered tree data structure to hold intervals. * Specifically, it allows one to efficiently find all intervals that overlap * with any given interval or point. * * http://en.wikipedia.org/wiki/Interval_tree * * @author Justin Wetherell */ public class IntervalTree { private Interval root = null; private static final Comparator> START_COMPARATOR = new Comparator>() { /** * {@inheritDoc} */ @Override public int compare(IntervalData arg0, IntervalData arg1) { // Compare start first if (arg0.start < arg1.start) return -1; if (arg1.start < arg0.start) return 1; return 0; } }; private static final Comparator> END_COMPARATOR = new Comparator>() { /** * {@inheritDoc} */ @Override public int compare(IntervalData arg0, IntervalData arg1) { // Compare end first if (arg0.end > arg1.end) return -1; if (arg1.end > arg0.end) return 1; return 0; } }; /** * Create interval tree from list of IntervalData objects; * * @param intervals * is a list of IntervalData objects */ public IntervalTree(List> intervals) { if (intervals.size() <= 0) return; root = createFromList(intervals); } protected static final Interval createFromList(List> intervals) { Interval newInterval = new Interval(); if (intervals.size()==1) { IntervalData middle = intervals.get(0); newInterval.center = ((middle.start + middle.end) / 2); newInterval.add(middle); return newInterval; } int half = intervals.size() / 2; IntervalData middle = intervals.get(half); newInterval.center = ((middle.start + middle.end) / 2); List> leftIntervals = new ArrayList>(); List> rightIntervals = new ArrayList>(); for (IntervalData interval : intervals) { if (interval.end < newInterval.center) { leftIntervals.add(interval); } else if (interval.start > newInterval.center) { rightIntervals.add(interval); } else { newInterval.add(interval); } } if (leftIntervals.size() > 0) newInterval.left = createFromList(leftIntervals); if (rightIntervals.size() > 0) newInterval.right = createFromList(rightIntervals); return newInterval; } /** * Stabbing query * * @param index * to query for. * @return data at index. */ public IntervalData query(long index) { return root.query(index); } /** * Range query * * @param start * of range to query for. * @param end * of range to query for. * @return data for range. */ public IntervalData query(long start, long end) { return root.query(start, end); } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(IntervalTreePrinter.getString(this)); return builder.toString(); } protected static class IntervalTreePrinter { public static String getString(IntervalTree tree) { if (tree.root == null) return "Tree has no nodes."; return getString(tree.root, "", true); } private static String getString(Interval interval, String prefix, boolean isTail) { StringBuilder builder = new StringBuilder(); builder.append(prefix + (isTail ? "└── " : "├── ") + interval.toString() + "\n"); List> children = new ArrayList>(); if (interval.left != null) children.add(interval.left); if (interval.right != null) children.add(interval.right); if (children.size() > 0) { for (int i = 0; i < children.size() - 1; i++) builder.append(getString(children.get(i), prefix + (isTail ? " " : "│ "), false)); if (children.size() > 0) builder.append(getString(children.get(children.size() - 1), prefix + (isTail ? " " : "│ "), true)); } return builder.toString(); } } public static final class Interval { private long center = Long.MIN_VALUE; private Interval left = null; private Interval right = null; private List> overlap = new ArrayList>(); private void add(IntervalData data) { overlap.add(data); } /** * Stabbing query * * @param index * to query for. * @return data at index. */ public IntervalData query(long index) { IntervalData results = null; if (index < center) { // overlap is sorted by start point Collections.sort(overlap,START_COMPARATOR); for (IntervalData data : overlap) { if (data.start > index) break; IntervalData temp = data.query(index); if (results == null && temp != null) results = temp; else if (results != null && temp != null) results.combined(temp); } } else if (index >= center) { // overlap is reverse sorted by end point Collections.sort(overlap,END_COMPARATOR); for (IntervalData data : overlap) { if (data.end < index) break; IntervalData temp = data.query(index); if (results == null && temp != null) results = temp; else if (results != null && temp != null) results.combined(temp); } } if (index < center) { if (left != null) { IntervalData temp = left.query(index); if (results == null && temp != null) results = temp; else if (results != null && temp != null) results.combined(temp); } } else if (index >= center) { if (right != null) { IntervalData temp = right.query(index); if (results == null && temp != null) results = temp; else if (results != null && temp != null) results.combined(temp); } } return results; } /** * Range query * * @param start * of range to query for. * @param end * of range to query for. * @return data for range. */ public IntervalData query(long start, long end) { IntervalData results = null; for (IntervalData data : overlap) { if (data.start > end) break; IntervalData temp = data.query(start, end); if (results == null && temp != null) results = temp; else if (results != null && temp != null) results.combined(temp); } if (left != null && start < center) { IntervalData temp = left.query(start, end); if (temp != null && results == null) results = temp; else if (results != null && temp != null) results.combined(temp); } if (right != null && end >= center) { IntervalData temp = right.query(start, end); if (temp != null && results == null) results = temp; else if (results != null && temp != null) results.combined(temp); } return results; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Center=").append(center); builder.append(" Set=").append(overlap); return builder.toString(); } } /** * Data structure representing an interval. */ public static class IntervalData implements Comparable> { private long start = Long.MIN_VALUE; private long end = Long.MAX_VALUE; private Set set = new HashSet(); /** * Interval data using O as it's unique identifier * * @param object * Object which defines the interval data */ public IntervalData(long index, O object) { this.start = index; this.end = index; this.set.add(object); } /** * Interval data using O as it's unique identifier * * @param object * Object which defines the interval data */ public IntervalData(long start, long end, O object) { this.start = start; this.end = end; this.set.add(object); } /** * Interval data list which should all be unique * * @param list * of interval data objects */ public IntervalData(long start, long end, Set set) { this.start = start; this.end = end; this.set = set; } /** * Get the start of this interval * * @return Start of interval */ public long getStart() { return start; } /** * Get the end of this interval * * @return End of interval */ public long getEnd() { return end; } /** * Get the data set in this interval * * @return Unmodifiable collection of data objects */ public Collection getData() { return Collections.unmodifiableCollection(this.set); } /** * Clear the indices. */ public void clear() { this.start = Long.MIN_VALUE; this.end = Long.MAX_VALUE; this.set.clear(); } /** * Combined this IntervalData with data. * * @param data * to combined with. * @return Data which represents the combination. */ public IntervalData combined(IntervalData data) { if (data.start < this.start) this.start = data.start; if (data.end > this.end) this.end = data.end; this.set.addAll(data.set); return this; } /** * Deep copy of data. * * @return deep copy. */ public IntervalData copy() { Set copy = new HashSet(); copy.addAll(set); return new IntervalData(start, end, copy); } /** * Query inside this data object. * * @param start * of range to query for. * @param end * of range to query for. * @return Data queried for or NULL if it doesn't match the query. */ public IntervalData query(long index) { if (index < this.start || index > this.end) return null; return copy(); } /** * Query inside this data object. * * @param startOfQuery * of range to query for. * @param endOfQuery * of range to query for. * @return Data queried for or NULL if it doesn't match the query. */ public IntervalData query(long startOfQuery, long endOfQuery) { if (endOfQuery < this.start || startOfQuery > this.end) return null; return copy(); } /** * {@inheritDoc} */ @Override public int hashCode() { return 31 * ((int)(this.start + this.end)) + this.set.size(); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (!(obj instanceof IntervalData)) return false; IntervalData data = (IntervalData) obj; if (this.start == data.start && this.end == data.end) { if (this.set.size() != data.set.size()) return false; for (O o : set) { if (!data.set.contains(o)) return false; } return true; } return false; } /** * {@inheritDoc} */ @Override public int compareTo(IntervalData d) { if (this.end < d.end) return -1; if (d.end < this.end) return 1; return 0; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(start).append("->").append(end); builder.append(" set=").append(set); return builder.toString(); } } }