6

I have a list of intervals with integer values [eg. [1, 4], [10, 19] etc.]. Is there a way to put these intervals into some java collections' container [eg. Set] such that I can call a 'union' function on the container. The 'union' function should give me a list of intervals such that if any 2 inserted intervals overlap then they should be merged in the output. I tried using the Range class in Guava but ended up comparing all the intervals against each other before merging. An elegant approach to this would be really appreciated ! Here is what I have tried based on the response below. The output is [[1, 15], [17, 20]], which is correct. I wanted to know if there is some exiting API which implements something like this.

public static void main(String[] args) {
    // mock data
    List<MyIntRange> rng_lst = new ArrayList<Junk.MyIntRange>();
    rng_lst.add(new MyIntRange(1, 10));
    rng_lst.add(new MyIntRange(5, 15));
    rng_lst.add(new MyIntRange(17, 20));

    // sort intervals by start position
    Collections.sort(rng_lst);

    // merge the intervals which overlap
    List<MyIntRange> res_lst = new ArrayList<Junk.MyIntRange>();
    MyIntRange old_rng = null;
    for (MyIntRange cur_rng : rng_lst) {
        if (old_rng == null) {
            old_rng = cur_rng;
        } else {
            if (old_rng.rng.upperEndpoint() < cur_rng.rng.lowerEndpoint()) {
                // this does not over lap with the next one
                res_lst.add(old_rng);
                old_rng = cur_rng;
            } else {
                // overlap
                old_rng = new MyIntRange(old_rng.rng.lowerEndpoint(),
                        cur_rng.rng.upperEndpoint());
            }
        }
    }
    // add the last range
    res_lst.add(old_rng);

    // done!
    System.out.println(res_lst);
}

// wrapper around Guava's Range to make it comparable based on the
// interval's start
public static class MyIntRange implements Comparable<MyIntRange> {
    Range<Integer> rng;

    public MyIntRange(int start, int end) {
        rng = Ranges.closed(start, end);
    }

    public int compareTo(MyIntRange that) {
        int res = -1;
        if (this.rng.lowerEndpoint() > that.rng.lowerEndpoint()) {
            res = 1;
        }
        return res;
    }

    public String toString() {
        return "[" + rng.lowerEndpoint() + ", " + rng.upperEndpoint() + "]";
    }
}

thanks

user1998031
  • 127
  • 2
  • 6

2 Answers2

7

This is basically exactly what RangeSet does in the just-released Guava 14.0, except it does the merging for you, rather than telling you which ranges could be merged.

Louis Wasserman
  • 172,699
  • 23
  • 307
  • 375
  • that's exactly what I was looking for! Is there a corresponding RangeTree class in Guava which implements an interval tree to answer questions like finding the interval CLOSEST to a point or interval ? – user1998031 Mar 01 '13 at 02:42
  • No, there isn't. You _might_ be able to derive it from `RangeSet` by looking at the `span` of the `subRangeSet` before and after a point, though. – Louis Wasserman Mar 01 '13 at 05:03
3

A basic algorithm:

  1. Sort intervals by start index
  2. Walk through the list. For the ith item:
    1. Compare the end index of i with the start index of the (i+1)st item.
    2. If the end index is greater, set the end index of i to the end index of i+1, and remove the i+1 element. (This merges them.)

Time complexity: O(nlgn), because of the initial sort. (That is, if the removals are done right.)

apnorton
  • 2,360
  • 1
  • 19
  • 32