I'm sure that there's a more elegant way to do it (using higher-order functions), but how about this:
Basically, make an Interval into a type of its own, then add some functions to that type which check for overlaps and allows merging intervals. Then some basic iteration over the input data and merge intervals that can be merged...
Types in Swift are very useful.
//
// main.swift
// intervals
//
// Created by Diggory Laycock on 25/02/2026.
//
print("Intervals: Hello, World!")
/// An interval with a start and end.
struct Interval: Comparable, CustomDebugStringConvertible {
let start: Int
let end: Int
var debugDescription: String {
"Interval(\(start) <--> \(end))"
}
// Comparable
static func <(lhs: Interval, rhs: Interval) -> Bool {
if lhs.start == rhs.start {
return lhs.end < rhs.end
}
return lhs.start < rhs.start
}
/// Does another interval overlap this one?
func overlaps(another: Interval) -> Bool {
var overlaps = true
if (another.start < start && another.end < start) { overlaps = false }
if (another.end > end && another.start > end) { overlaps = false }
// print("\(self) overlaps \(another) == \(overlaps)")
return overlaps
}
/// Make a new interval covering both intervals if they overlap. Otherwise do nothing
func mergeIfPossible(another: Interval) -> Interval? {
if !overlaps(another: another) {
return nil
}
return Interval(start: min(self.start, another.start), end: max(self.end, another.end))
}
}
let sampleData = [
Interval(start: 30, end: 33),
Interval(start: 27, end: 30),
Interval(start: 1, end: 3),
Interval(start: 2, end: 6),
Interval(start: 8, end: 10),
Interval(start: 15, end: 18),
Interval(start: 20, end: 25),
Interval(start: 21, end: 23),
Interval(start: 22, end: 26),
]
let sortedIntervals = sampleData.sorted()
print("sortedIntervals:")
print(sortedIntervals)
// Merge overlapping intervals
var unmergedIntervals = sortedIntervals
var mergedIntervals = [Interval]()
var done = false
while !done {
if unmergedIntervals.count == 0 {
print("no more unmerged to process")
done = true
continue
}
let candidate = unmergedIntervals.first!
if let lastMerged = mergedIntervals.last {
if let mergeResult = lastMerged.mergeIfPossible(another: candidate) {
// Overlapping, can be merged
// print("We can merge \(candidate) and \(lastMerged) to become: \(mergeResult)")
mergedIntervals.removeLast()
mergedIntervals.append(mergeResult)
} else {
// Not overlapping
mergedIntervals.append(candidate)
}
} else {
// This is the first to merge
mergedIntervals.append(candidate)
}
// We are done with this element
unmergedIntervals.removeFirst()
}
print("Merged:")
print(mergedIntervals)
Outputs:
Intervals: Hello, World!
sortedIntervals:
[Interval(1 <--> 3), Interval(2 <--> 6), Interval(8 <--> 10), Interval(15 <--> 18), Interval(20 <--> 25), Interval(21 <--> 23), Interval(22 <--> 26), Interval(27 <--> 30), Interval(30 <--> 33)]
no more unmerged to process
Merged:
[Interval(1 <--> 6), Interval(8 <--> 10), Interval(15 <--> 18), Interval(20 <--> 26), Interval(27 <--> 33)]
Program ended with exit code: 0