Class IntervalSet<T extends Comparable<? super T>>
- Type Parameters:
T
- the type of interval boundaries, must implementComparable
- All Implemented Interfaces:
Iterable<IntervalSet.Interval<T>>
Core Capabilities
IntervalSet efficiently manages collections of intervals with the following key features:
- O(log n) performance - Uses
ConcurrentSkipListMap
for efficient lookups, insertions, and range queries - Thread-safe - Lock-free reads with minimal locking for writes only
- Flexible merging behavior - Configurable auto-merge vs. discrete storage modes
- Intelligent interval splitting - Automatically splits intervals during removal operations
- Rich query API - Comprehensive set of methods for finding, filtering, and navigating intervals
- Type-safe boundaries - Supports precise boundary calculations for 20+ built-in types
Auto-Merge vs. Discrete Modes
The behavior of interval storage is controlled by the autoMerge flag set during construction:
Auto-Merge Mode (default: autoMerge = true
)
Overlapping intervals are automatically merged into larger, non-overlapping intervals:
IntervalSet<Integer> set = new IntervalSet<>(); // autoMerge = true by default
set.add(1, 5);
set.add(3, 8); // Merges with [1,5] to create [1,8]
set.add(10, 15); // Separate interval since no overlap
// Result: [1,8], [10,15]
Discrete Mode (autoMerge = false
)
Intervals are stored separately even if they overlap, useful for audit trails, tracking individual operations, or maintaining historical records:
IntervalSet<Integer> audit = new IntervalSet<>(false); // discrete mode
audit.add(1, 5); // First verification
audit.add(3, 8); // Second verification (overlaps but kept separate)
audit.add(10, 15); // Third verification
// Result: [1,5], [3,8], [10,15] - all intervals preserved for audit purposes
Important: Regardless of storage mode, all query APIs (contains(T)
, intervalContaining(T)
,
navigation methods) work identically - they provide a unified logical view across all stored intervals.
Only the internal storage representation differs.
Primary Client APIs
Basic Operations
add(T, T)
- Add an interval [start, end]remove(T, T)
- Remove an interval, splitting existing ones as neededremoveExact(T, T)
- Remove only exact interval matchesremoveRange(T, T)
- Remove a range, trimming overlapping intervalscontains(T)
- Test if a value falls within any intervalclear()
- Remove all intervals
Query and Navigation
intervalContaining(T)
- Find the interval containing a specific valuenextInterval(T)
- Find the next interval at or after a valuehigherInterval(T)
- Find the next interval strictly after a valuepreviousInterval(T)
- Find the previous interval at or before a valuelowerInterval(T)
- Find the previous interval strictly before a valuefirst()
/last()
- Get the first/last intervals
Bulk Operations and Iteration
asList()
- Get all intervals as an immutable listiterator()
- Iterate intervals in ascending orderdescendingIterator()
- Iterate intervals in descending ordergetIntervalsInRange(T, T)
- Get intervals within a key rangegetIntervalsBefore(T)
- Get intervals before a keygetIntervalsFrom(T)
- Get intervals from a key onwardremoveIntervalsInKeyRange(T, T)
- Bulk removal by key range
Introspection and Utilities
size()
/isEmpty()
- Get count and emptiness statekeySet()
/descendingKeySet()
- Access start keys as NavigableSettotalDuration(java.util.function.BiFunction)
- Compute total duration across intervals
Supported Types
IntervalSet provides intelligent boundary calculation for interval splitting/merging operations across a wide range of types:
- Numeric: Byte, Short, Integer, Long, Float, Double, BigInteger, BigDecimal
- Character: Character (Unicode-aware)
- Temporal: Date, java.sql.Date, Time, Timestamp, Instant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, OffsetDateTime, OffsetTime, Duration
- Custom: Any type implementing Comparable (with manual boundary handling if needed)
Thread Safety
IntervalSet is fully thread-safe with an optimized locking strategy:
- Lock-free reads: All query operations (contains, navigation, iteration) require no locking
- Minimal write locking: Only mutation operations acquire the internal ReentrantLock
- Weakly consistent iteration: Iterators don't throw ConcurrentModificationException
Common Use Cases
Time Range Management
IntervalSet<ZonedDateTime> schedule = new IntervalSet<>();
schedule.add(meeting1Start, meeting1End);
schedule.add(meeting2Start, meeting2End);
if (schedule.contains(proposedMeetingTime)) {
System.out.println("Time conflict detected");
}
Numeric Range Tracking
IntervalSet<Long> processedIds = new IntervalSet<>();
processedIds.add(1000L, 1999L); // First batch
processedIds.add(2000L, 2999L); // Second batch - automatically merges to [1000, 2999]
Duration totalWork = processedIds.totalDuration((start, end) ->
Duration.ofMillis(end - start + 1));
Audit Trail with Discrete Mode
IntervalSet<LocalDate> auditLog = new IntervalSet<>(false); // Keep all entries
auditLog.add(verification1Start, verification1End);
auditLog.add(verification2Start, verification2End); // Overlaps preserved for audit
// Query APIs still work across all intervals
boolean dateVerified = auditLog.contains(targetDate);
Performance Characteristics
Most operations maintain O(log n) complexity regardless of storage mode. However, when
autoMerge = false
(discrete mode), certain query operations degrade to O(n)
because they must search through potentially overlapping intervals:
AutoMerge Mode (autoMerge = true
, default)
- Add: O(log n) - May require merging adjacent intervals
- Remove/RemoveRange: O(log n) - May require splitting intervals
- Contains: O(log n) - Single floor lookup
- IntervalContaining: O(log n) - Single floor lookup
- Navigation: O(log n) - Leverages NavigableMap operations
- Iteration: O(n) - Direct map iteration, no additional overhead
Discrete Mode (autoMerge = false
)
- Add: O(log n) - No merging, direct map insertion
- Remove/RemoveRange: O(n) - Must iterate all intervals to find overlaps
- Contains: O(n) - Must search backwards through overlapping intervals
- IntervalContaining: O(n) - Must search backwards through overlapping intervals
- Navigation: O(log n) - Leverages NavigableMap operations
- Iteration: O(n) - Direct map iteration, no additional overhead
- See Also:
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final class
IntervalSet.Interval<T extends Comparable<? super T>>
Immutable value object representing one interval. -
Constructor Summary
ConstructorsConstructorDescriptionCreates a new IntervalSet with auto-merge enabled (default behavior).IntervalSet
(boolean autoMerge) Creates a new IntervalSet with configurable merge behavior.Creates a new IntervalSet with configurable merge behavior and custom boundary functions.IntervalSet
(IntervalSet<T> other) Copy constructor: creates a deep copy of the given IntervalSet, including mode, intervals, and custom functions. -
Method Summary
Modifier and TypeMethodDescriptionvoid
Add the inclusive interval [start,end].asList()
Unmodifiable snapshot of all intervals (ordered).void
clear()
Remove all stored intervals from the set.boolean
True if the value lies in any closed interval [start,end].Returns an iterator over all intervals in descending order by start key.Returns a set of all start keys in descending order.difference
(IntervalSet<T> other) Returns a new IntervalSet that is the difference of this set minus the other.boolean
first()
Returns the first (lowest key) interval ornull
.getIntervalsBefore
(T toKey) Returns all intervals whose start keys are before the specified key.getIntervalsFrom
(T fromKey) Returns all intervals whose start keys are at or after the specified key.getIntervalsInRange
(T fromKey, T toKey) Returns all intervals whose start keys fall within the specified range [fromKey, toKey].int
hashCode()
higherInterval
(T value) Returns the next interval that starts strictly after the given value, ornull
if none exists.intersection
(IntervalSet<T> other) Returns a new IntervalSet that is the intersection of this set and the other.boolean
intersects
(IntervalSet<T> other) Returns true if this set intersects (overlaps) with the other set.intervalContaining
(T value) Return the interval covering the specifiedvalue
, ornull
if no interval contains it.boolean
isEmpty()
Returnstrue
if this set contains no intervals.iterator()
Returns an iterator over all stored intervals in ascending order by start key.keySet()
Returns a set of all start keys in the interval set.last()
Returns the last (highest key) interval ornull
.lowerInterval
(T value) Returns the previous interval that starts strictly before the given value, ornull
if none exists.nextInterval
(T value) Returns the next interval that contains the given value, or the next interval that starts after the value.previousInterval
(T value) Returns the previous interval that starts at or before the given value, ornull
if none exists.void
Remove the inclusive interval [start,end], splitting existing intervals as needed.boolean
removeExact
(T start, T end) Remove an exact interval [start, end] that matches a stored interval exactly.int
removeIntervalsInKeyRange
(T fromKey, T toKey) Removes all intervals whose start keys fall within the specified range [fromKey, toKey].void
removeRange
(T start, T end) Remove the inclusive range [start, end] from the set, trimming and splitting intervals as necessary.int
size()
Number of stored, non-overlapping intervals.toString()
Compute the total covered duration across all stored intervals using a default mapping.totalDuration
(BiFunction<T, T, Duration> toDuration) Compute the total covered duration across all stored intervals.union
(IntervalSet<T> other) Returns a new IntervalSet that is the union of this set and the other.Methods inherited from class java.lang.Object
clone, finalize, getClass, notify, notifyAll, wait, wait, wait
Methods inherited from interface java.lang.Iterable
forEach, spliterator
-
Constructor Details
-
IntervalSet
public IntervalSet()Creates a new IntervalSet with auto-merge enabled (default behavior). Overlapping intervals will be automatically merged when added. -
IntervalSet
public IntervalSet(boolean autoMerge) Creates a new IntervalSet with configurable merge behavior.- Parameters:
autoMerge
- if true, overlapping intervals are merged automatically; if false, intervals are stored discretely but queries still work across all intervals
-
IntervalSet
Creates a new IntervalSet with configurable merge behavior and custom boundary functions.For custom types not supported by built-in previous/next logic, provide functions to compute the previous and next values. If null, falls back to built-in support. Users must provide these for non-built-in types to enable proper interval splitting during removals.
- Parameters:
autoMerge
- if true, overlapping intervals are merged automatically; if false, intervals are stored discretely but queries still work across all intervalspreviousFunction
- custom function to compute previous value, or null for built-innextFunction
- custom function to compute next value, or null for built-in
-
IntervalSet
Copy constructor: creates a deep copy of the given IntervalSet, including mode, intervals, and custom functions.- Parameters:
other
- the IntervalSet to copy
-
-
Method Details
-
add
Add the inclusive interval [start,end]. Both start and end are inclusive.If autoMerge is true (default), overlapping intervals are merged automatically. When merging, if an interval with the same start key already exists, a union is performed using the maximum end value of both intervals. If autoMerge is false, intervals are stored discretely. If an interval with the same start key already exists, it will be replaced (last-one-wins behavior). Queries still work across all intervals to provide a unified view.
AutoMerge Mode Examples:
- Adding [1,5] then [1,8] results in [1,8] (union of overlapping intervals)
- Adding [1,5] then [3,8] results in [1,8] (overlapping intervals merged)
- Adding [1,5] then [1,3] results in [1,5] (smaller interval absorbed)
Discrete Mode Examples:
- Adding [1,5] then [1,8] results in [1,8] (second interval replaces first)
- Adding [1,5] then [3,8] results in [1,5], [3,8] (both stored separately)
-
remove
Remove the inclusive interval [start,end], splitting existing intervals as needed. Both start and end are treated as inclusive bounds.If autoMerge is true, overlapping intervals are split where needed. If autoMerge is false, overlapping discrete intervals are removed entirely.
-
removeExact
Remove an exact interval [start, end] that matches a stored interval exactly.This operation acts only on a single stored interval whose start and end exactly match the specified values. No other intervals are merged, split, or trimmed as a result of this call. To remove a sub-range or to split existing intervals, use
remove(T, T)
orremoveRange(T, T)
.Both
start
andend
are treated as inclusive bounds. If no matching interval exists, the set remains unchanged. This method is thread-safe: it acquires the internal lock to perform removal under concurrent access but does not affect merging or splitting logic.- Parameters:
start
- the inclusive start key of the interval to remove (must match exactly)end
- the inclusive end key of the interval to remove (must match exactly)- Returns:
true
if an interval with exactly this start and end was found and removed;false
otherwise (no change to the set)
-
removeRange
Remove the inclusive range [start, end] from the set, trimming and splitting intervals as necessary.If autoMerge is true, intervals are trimmed and split as needed. If autoMerge is false, overlapping discrete intervals are removed entirely.
For autoMerge=true, any stored interval that overlaps the removal range:
- If an interval begins before
start
, its right boundary is trimmed tostart
. - If an interval ends after
end
, its left boundary is trimmed toend
. - If an interval fully contains
[start,end]
, it is split into two intervals: one covering[originalStart, start]
and one covering[end, originalEnd]
. - Intervals entirely within
[start,end]
are removed.
This operation is thread-safe: it acquires the internal write lock during mutation.
Performance: O(log n) when
autoMerge = true
, but O(n) whenautoMerge = false
because all intervals must be checked for overlap.- Parameters:
start
- inclusive start of the range to removeend
- inclusive end of the range to remove- Throws:
IllegalArgumentException
- ifend < start
- If an interval begins before
-
contains
True if the value lies in any closed interval [start,end]. Both boundaries are inclusive.Performance: O(log n) when
autoMerge = true
, but O(n) whenautoMerge = false
due to the need to search through potentially overlapping discrete intervals. -
intervalContaining
Return the interval covering the specifiedvalue
, ornull
if no interval contains it.Intervals are closed and inclusive on both ends ([start, end]), so a value v is contained in an interval
if start <= v <= end
. This method performs a lock-free read viaConcurrentSkipListMap.floorEntry(Object)
and does not mutate the underlying set.In discrete mode, if multiple intervals contain the value, returns the one with the largest start key.
Performance: O(log n) when
autoMerge = true
, but O(n) whenautoMerge = false
due to the need to search through potentially overlapping discrete intervals.- Parameters:
value
- the non-null value to locate within stored intervals- Returns:
- an
IntervalSet.Interval
whose start and end bracketvalue
, ornull
if none - Throws:
NullPointerException
- ifvalue
is null
-
asList
Unmodifiable snapshot of all intervals (ordered).This method acquires the read lock to ensure a consistent snapshot of all intervals at the time of invocation.
- Returns:
- an unmodifiable list of all intervals in ascending order by start key
-
first
Returns the first (lowest key) interval ornull
. -
last
Returns the last (highest key) interval ornull
. -
nextInterval
Returns the next interval that contains the given value, or the next interval that starts after the value.If the value falls within an existing interval, that interval is returned. Otherwise, returns the next interval that starts after the value. Uses
NavigableMap.floorEntry(Object)
andNavigableMap.ceilingEntry(Object)
for O(log n) performance.- Parameters:
value
- the value to search from- Returns:
- the interval containing the value or the next interval after it, or
null
if none
-
higherInterval
Returns the next interval that starts strictly after the given value, ornull
if none exists.This method uses
NavigableMap.higherEntry(Object)
for O(log n) performance.- Parameters:
value
- the value to search from (exclusive)- Returns:
- the next interval strictly after the value, or
null
if none
-
previousInterval
Returns the previous interval that starts at or before the given value, ornull
if none exists.This method uses
NavigableMap.floorEntry(Object)
for O(log n) performance. Note: This returns the interval by start key, not necessarily the interval containing the value. UseintervalContaining(T)
to find the interval that actually contains a value.- Parameters:
value
- the value to search from (inclusive)- Returns:
- the previous interval at or before the value, or
null
if none
-
lowerInterval
Returns the previous interval that starts strictly before the given value, ornull
if none exists.This method uses
NavigableMap.lowerEntry(Object)
for O(log n) performance.- Parameters:
value
- the value to search from (exclusive)- Returns:
- the previous interval strictly before the value, or
null
if none
-
getIntervalsInRange
Returns all intervals whose start keys fall within the specified range [fromKey, toKey].This method uses
NavigableMap.subMap(Object, boolean, Object, boolean)
for efficient range queries. The returned list is ordered by start key.- Parameters:
fromKey
- the start of the range (inclusive)toKey
- the end of the range (inclusive)- Returns:
- a list of intervals within the specified range, ordered by start key
- Throws:
IllegalArgumentException
- if fromKey > toKey
-
getIntervalsBefore
Returns all intervals whose start keys are before the specified key.This method uses
NavigableMap.headMap(Object, boolean)
for efficient queries.- Parameters:
toKey
- the upper bound (exclusive)- Returns:
- a list of intervals before the specified key, ordered by start key
-
getIntervalsFrom
Returns all intervals whose start keys are at or after the specified key.This method uses
NavigableMap.tailMap(Object, boolean)
for efficient queries.- Parameters:
fromKey
- the lower bound (inclusive)- Returns:
- a list of intervals at or after the specified key, ordered by start key
-
descendingIterator
Returns an iterator over all intervals in descending order by start key.This method uses
NavigableMap.descendingMap()
for efficient reverse iteration. Like the standard iterator, this is weakly consistent and lock-free.- Returns:
- an iterator over intervals in descending order by start key
-
keySet
Returns a set of all start keys in the interval set.This method uses
NavigableMap.navigableKeySet()
to provide efficient key operations. The returned set supports range operations and is backed by the interval set.- Returns:
- a navigable set of start keys
-
descendingKeySet
Returns a set of all start keys in descending order.This method uses
NavigableMap.descendingKeySet()
for efficient reverse key iteration.- Returns:
- a navigable set of start keys in descending order
-
removeIntervalsInKeyRange
Removes all intervals whose start keys fall within the specified range [fromKey, toKey].This method uses
NavigableMap.subMap(Object, boolean, Object, boolean)
for efficient bulk removal. This is more efficient than callingremoveExact(T, T)
multiple times.- Parameters:
fromKey
- the start of the range (inclusive)toKey
- the end of the range (inclusive)- Returns:
- the number of intervals removed
- Throws:
IllegalArgumentException
- if fromKey > toKey
-
size
public int size()Number of stored, non-overlapping intervals. -
isEmpty
public boolean isEmpty()Returnstrue
if this set contains no intervals.- Returns:
true
if this set contains no intervals
-
clear
public void clear() -
totalDuration
Compute the total covered duration across all stored intervals using a default mapping.This overload uses a default BiFunction based on the type of T: - If T is Temporal (and supports SECONDS unit, e.g., Instant, LocalDateTime, etc.), uses Duration.between(start, end). - If T is Number, computes (end.longValue() - start.longValue() + 1) and maps to Duration.ofNanos(diff) (arbitrary unit). - Otherwise, throws UnsupportedOperationException.
For Temporal types like LocalDate that do not support SECONDS, this will throw DateTimeException. For custom or unsupported types, use the BiFunction overload. For numeric types, the unit (nanos) is arbitrary; use custom BiFunction for specific units.
- Returns:
- the sum of all interval durations
- Throws:
UnsupportedOperationException
- if no default mapping for type TDateTimeException
- if Temporal type does not support Duration.betweenArithmeticException
- if numeric computation overflows long
-
totalDuration
Compute the total covered duration across all stored intervals.The caller provides a
toDuration
function that maps each interval's start and end values to aDuration
. This method sums those Durations over all intervals in key order. For consistency under concurrent modifications, this method captures a snapshot of intervals at the time of invocation.- Parameters:
toDuration
- a function that converts an interval [start, end] to a Duration- Returns:
- the sum of all interval durations
-
iterator
Returns an iterator over all stored intervals in ascending order by start key.This iterator is weakly consistent and lock-free, meaning it reflects live changes to the IntervalSet as they occur during iteration. The iterator does not throw
ConcurrentModificationException
and does not require external synchronization for reading.For strongly consistent iteration that captures a snapshot at call time, use
asList()
.iterator() instead.- Specified by:
iterator
in interfaceIterable<T extends Comparable<? super T>>
- Returns:
- an iterator over the intervals in this set, ordered by start key
-
union
Returns a new IntervalSet that is the union of this set and the other.The result has the same autoMerge mode as this set. In discrete mode, if start keys duplicate, addition may throw IllegalArgumentException.
- Parameters:
other
- the other IntervalSet- Returns:
- a new IntervalSet containing all intervals from both
-
intersection
Returns a new IntervalSet that is the intersection of this set and the other.The result has the same autoMerge mode as this set. Computes overlapping parts of intervals.
- Parameters:
other
- the other IntervalSet- Returns:
- a new IntervalSet containing intersecting intervals
-
difference
Returns a new IntervalSet that is the difference of this set minus the other.Equivalent to removing all intervals from other from this set. The result has the same autoMerge mode as this set.
- Parameters:
other
- the other IntervalSet to subtract- Returns:
- a new IntervalSet with intervals from other removed
-
intersects
Returns true if this set intersects (overlaps) with the other set.- Parameters:
other
- the other IntervalSet- Returns:
- true if there is any overlap between intervals in this and other
-
equals
-
hashCode
public int hashCode() -
toString
-