1. Overview
In this tutorial, we’ll look at taking a Collection of intervals and merging any that overlap.
2. Understanding the Problem
First, let’s define what an interval is. We’ll describe it as a pair of numbers where the second is greater than the first. This could also be dates, but we’ll stick with integers here. So for example, an interval could be 2 -> 7, or 100 -> 200.
We can create a simple Java class to represent an interval. We’ll have start as our first number and end as our second and higher number:
class Interval {
int start;
int end;
// Usual constructor, getters, setters and equals functions
}
Now, we want to take a Collection of Intervals and merge any that overlap. We can define overlapping intervals as anywhere the first interval ends after the second interval starts. For example, if we take two intervals 2 -> 10 and 5 -> 15 we can see that they overlap and the result of merging them would be 2 -> 15.
3. Algorithm
To perform the merge we’ll need code following this pattern:
List<Interval> doMerge(List<Interval> intervals) {
// Sort the intervals based on the start
intervals.sort((one, two) -> one.start - two.start);
// Create somewhere to put the merged list, start it off with the earliest starting interval
ArrayList<Interval> merged = new ArrayList<>();
merged.add(intervals.get(0));
// Loop over each interval and merge if start is before the end of the
// previous interval
intervals.forEach(interval -> {
if (merged.get(merged.size() - 1).end > interval.start) {
merged.get(merged.size() - 1).setEnd(interval.end);
} else {
merged.add(interval);
}
});
return merged;
}
To start we’ve defined a method that takes a List of Intervals and returns the same but sorted. Of course, there are other types of Collections our Intervals could be stored in, but we’ll use a List today.
The first step in the processing we need to take is to sort our Collection of Intervals. We’ve sorted based on the Interval’s start so that when we loop over them each Interval is followed by the one most likely to overlap with it.
We can use List.sort() to do this assuming our Collection is a List. However, if we had a different kind of Collection we’d have to use something else. List.sort() takes a Comparator and sorts in place, so we don’t need to reassign our variable. For our Comparator, we can subtract the start of the second Interval from the start of the first. This results in higher starts being placed after lower starts in our Collection.
Next, we need somewhere to put the results of our merging process. We’ve created an ArrayList called merged for this purpose. We can also get it started by putting in the first interval as we know it has the earliest start.
Now for the interesting part. We need to loop over every Interval and do one of two things. If the Interval starts before the end of the previous Interval, we merge them by setting the end of the previous Interval to the end of the current Interval. Alternatively, if the Interval starts after the end of the previous Interval we simply add it to the Collection.
Finally, we returned the list of merged Intervals.
4. Practical Example
Let’s see that method in action with a test. We can define four Intervals we want to merge:
ArrayList<Interval> intervals = new ArrayList<>(Arrays.asList(
new Interval(2, 5),
new Interval(13, 20),
new Interval(11, 15),
new Interval(1, 3)
));
After merging we’d expect the result to be only two Intervals:
ArrayList<Interval> intervalsMerged = new ArrayList<>(Arrays.asList(
new Interval(1, 5),
new Interval(11, 20)
));
We can now use the method we created earlier and confirm it works as expected:
@Test
void givenIntervals_whenMerging_thenReturnMergedIntervals() {
MergeOverlappingIntervals merger = new MergeOverlappingIntervals();
ArrayList<Interval> result = (ArrayList<Interval>) merger.doMerge(intervals);
assertArrayEquals(intervalsMerged.toArray(), result.toArray());
}
5. Conclusion
In this article, we learned how to merge overlapping intervals in a Collection in Java. We saw that the process of merging the intervals is fairly simple. First, we have to know how to properly sort them, by start value. Then we just have to understand that we need to merge them when one interval starts before the previous one ends.
As always, the full code for the examples is available over on GitHub.