Intervals
This package defines:
AbstractInterval
, along with its subtypes:Interval{T,L,R}
, which represents a non-iterable range between two endpoints of typeT
with left/right bounds types respectively beingL
andR
AnchoredInterval{P,T,L,R}
, which represents a non-iterable range defined by a single valueanchor::T
and the value typeP
which represents the span of the range. Left/right bounds types are specifed byL
andR
respectivelyHourEnding
, a type alias forAnchoredInterval{Hour(-1)}
HourBeginning
, a type alias forAnchoredInterval{Hour(1)}
HE
andHB
, pseudoconstructors forHourEnding
andHourBeginning
that round the anchor up (HE
) or down (HB
) to the nearest hour
Bound
, abstract type for all possible bounds type classifications:
Sets
A single interval can be used to represent a contiguous set within a domain but cannot be used to represent a disjoint set. For general purpose set operations you need to use the IntervalSet
type.
Intervals.IntervalSet
— TypeIntervalSet{T<:AbstractInterval}
A set of points represented by a sequence of intervals. Set operations over interval sets return a new IntervalSet, with the fewest number of intervals possible. Unbounded intervals are not supported. The individual intervals in the set can be accessed by calling convert(Array, interval_set)
.
see also: https://en.wikipedia.org/wiki/Intervalarithmetic#Intervaloperators
Examples
julia> union(IntervalSet(1..5), IntervalSet(3..8))
1-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[1 .. 8]
julia> intersect(IntervalSet(1..5), IntervalSet(3..8))
1-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[3 .. 5]
julia> symdiff(IntervalSet(1..5), IntervalSet(3..8))
2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}:
[1 .. 3)
(5 .. 8]
julia> union(IntervalSet([1..2, 2..5]), IntervalSet(6..7))
2-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[1 .. 5]
[6 .. 7]
julia> union(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))
2-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[1 .. 10]
[12 .. 14]
julia> intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))
2-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[4 .. 5]
[8 .. 9]
julia> setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))
2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}:
[1 .. 4)
(9 .. 10]
If you wish to instead treat each interval as an element of a set, you can operate over vectors or Set
s of intervals.
For example:
julia> intersect([1..2, 2..3, 3..4, 4..5], [2..3, 3..4])
2-element Vector{Interval{Int64, Closed, Closed}}:
Interval{Int64, Closed, Closed}(2, 3)
Interval{Int64, Closed, Closed}(3, 4)
Example Usage
Bounds
julia> a = Interval{Closed, Closed}(1, 10)
Interval{Int64, Closed, Closed}(1, 10)
julia> b = Interval{Open, Open}(5, 15)
Interval{Int64, Open, Open}(5, 15)
julia> 5 in a
true
julia> 5 in b
false
julia> intersect(a, b)
Interval{Int64, Open, Closed}(5, 10)
julia> c = Interval(15, 20)
Interval{Int64, Closed, Closed}(15, 20)
julia> isempty(intersect(b, c))
true
Display
julia> a = Interval('a', 'z')
Interval{Char, Closed, Closed}('a', 'z')
julia> string(a)
"[a .. z]"
julia> using Dates
julia> b = Interval{Closed, Open}(Date(2013), Date(2016))
Interval{Date, Closed, Open}(Date("2013-01-01"), Date("2016-01-01"))
julia> string(b)
"[2013-01-01 .. 2016-01-01)"
julia> c = HourEnding(DateTime(2016, 8, 11))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T00:00:00"))
julia> string(c)
"(2016-08-10 HE24]"
HourEnding
and HE
julia> using TimeZones, Dates
julia> unrounded = HourEnding(ZonedDateTime(2013, 2, 13, 0, 30, tz"America/Winnipeg"))
HourEnding{ZonedDateTime, Open, Closed}(ZonedDateTime(2013, 2, 13, 0, 30, tz"America/Winnipeg"))
julia> he = HE(ZonedDateTime(2013, 2, 13, 0, 30, tz"America/Winnipeg"))
HourEnding{ZonedDateTime, Open, Closed}(ZonedDateTime(2013, 2, 13, 1, tz"America/Winnipeg"))
julia> he + Hour(1)
HourEnding{ZonedDateTime, Open, Closed}(ZonedDateTime(2013, 2, 13, 2, tz"America/Winnipeg"))
julia> foreach(println, he:he + Day(1))
(2013-02-13 HE01-06:00]
(2013-02-13 HE02-06:00]
(2013-02-13 HE03-06:00]
(2013-02-13 HE04-06:00]
(2013-02-13 HE05-06:00]
(2013-02-13 HE06-06:00]
(2013-02-13 HE07-06:00]
(2013-02-13 HE08-06:00]
(2013-02-13 HE09-06:00]
(2013-02-13 HE10-06:00]
(2013-02-13 HE11-06:00]
(2013-02-13 HE12-06:00]
(2013-02-13 HE13-06:00]
(2013-02-13 HE14-06:00]
(2013-02-13 HE15-06:00]
(2013-02-13 HE16-06:00]
(2013-02-13 HE17-06:00]
(2013-02-13 HE18-06:00]
(2013-02-13 HE19-06:00]
(2013-02-13 HE20-06:00]
(2013-02-13 HE21-06:00]
(2013-02-13 HE22-06:00]
(2013-02-13 HE23-06:00]
(2013-02-13 HE24-06:00]
(2013-02-14 HE01-06:00]
julia> anchor(he)
2013-02-13T01:00:00-06:00
Comparisons
Equality
Two AbstractInterval
s are considered equal if they have identical left and right endpoints (taking bounds into account):
julia> a = Interval{Closed, Open}(DateTime(2013, 2, 13), DateTime(2013, 2, 13, 1))
Interval{DateTime, Closed, Open}(DateTime("2013-02-13T00:00:00"), DateTime("2013-02-13T01:00:00"))
julia> b = Interval{Open, Closed}(DateTime(2013, 2, 13), DateTime(2013, 2, 13, 1))
Interval{DateTime, Open, Closed}(DateTime("2013-02-13T00:00:00"), DateTime("2013-02-13T01:00:00"))
julia> c = HourEnding(DateTime(2013, 2, 13, 1))
HourEnding{DateTime, Open, Closed}(DateTime("2013-02-13T01:00:00"))
julia> a == b
false
julia> b == c
true
Less Than
When determining whether one AbstractInterval
is less than (or greater than) another, two sets of comparison operators are available: <
/>
and ≪
/≫
.
The standard <
and >
operators (which are not explicitly defined, but are derived from isless
) simply compare the leftmost endpoint of the intervals, and are used for things like sort
, min
, max
, etc.
The ≪
and ≫
operators (the Unicode symbols for "much less than" and "much greater than", accessible from the REPL with \ll
and \gg
, respectively) are used in this context to mean "less/greater than and disjoint"; they will verify that there is no overlap between the intervals.
julia> 0..10 < 10..20
true
julia> 0..10 ≪ 10..20
false
julia> 0..10 ≪ 11..20
true
Rounding
Interval rounding maintains the original span of the interval, shifting it according to whichever endpoint is specified as the one to use for rounding. The operations floor
, ceil
, and round
are supported, as long as the on
keyword is supplied to specify which endpoint should be used for rounding. Valid options are :left
, :right
, or :anchor
if dealing with anchored intervals.
julia> floor(Interval(0.0, 1.0), on=:left)
Interval{Float64, Closed, Closed}(0.0, 1.0)
julia> floor(Interval(0.5, 1.0), on=:left)
Interval{Float64, Closed, Closed}(0.0, 0.5)
julia> floor(Interval(0.5, 1.5), on=:right)
Interval{Float64, Closed, Closed}(0.0, 1.0)
Anchored intervals default to rounding using the anchor point.
julia> round(AnchoredInterval{-0.5}(1.0))
AnchoredInterval{-0.5, Float64, Open, Closed}(1.0)
julia> round(AnchoredInterval{+0.5}(0.5))
AnchoredInterval{0.5, Float64, Closed, Open}(0.0)
julia> round(AnchoredInterval{+0.5}(0.5), on=:anchor)
AnchoredInterval{0.5, Float64, Closed, Open}(0.0)
julia> round(AnchoredInterval{+0.5}(0.5), on=:left)
AnchoredInterval{0.5, Float64, Closed, Open}(0.0)
julia> round(AnchoredInterval{+0.5}(0.5), on=:right)
AnchoredInterval{0.5, Float64, Closed, Open}(0.5)
Plotting
AbstractInterval
subtypes can be plotted with Plots.jl.
julia> using Plots
julia> start_dt = DateTime(2017,1,1,0,0,0);
julia> end_dt = DateTime(2017,1,1,10,30,0);
julia> datetimes = start_dt:Hour(1):end_dt
DateTime("2017-01-01T00:00:00"):Hour(1):DateTime("2017-01-01T10:00:00")
julia> intervals = HE.(datetimes);
julia> plot(intervals, 1:11)
In the plot, inclusive boundaries are marked with a vertical bar, whereas exclusive boundaries just end.
API
Intervals.Interval
— TypeInterval{T, L <: Bound, R <: Bound}
An Interval
represents a non-iterable range or span of values (non-iterable because, unlike a StepRange
, no step is defined).
An Interval
can be closed (both first
and last
are included in the interval), open (neither first
nor last
are included), or half-open. This openness is defined by the bounds information which is stored as the type parameters L
and R
.
Example
julia> interval = Interval{Closed,Open}(0, 100)
Interval{Int64,Closed,Open}}(0, 100)
julia> 0 in interval
true
julia> 50 in interval
true
julia> 100 in interval
false
julia> intersect(Interval{Open,Open}(0, 25), Interval{Closed,Closed}(20, 50)
Interval{Int64,Closed,Open}(20, 25)
Infix Constructor: ..
A closed Interval
can be constructed with the ..
infix constructor:
julia> Dates.today() - Dates.Week(1) .. Dates.today()
Interval{Date,Closed,Closed}(2018-01-24, 2018-01-31)
See also: AnchoredInterval
Intervals.AnchoredInterval
— TypeAnchoredInterval{P,T,L,R}
AnchoredInterval
is a subtype of AbstractInterval
that represents a non-iterable range or span of values defined not by two endpoints but instead by a single anchor
point and the value type P
which represents the size of the range. When P
is positive, the anchor
represents the lesser endpoint (the beginning of the range); when P
is negative, the anchor
represents the greater endpoint (the end of the range).
The interval represented by an AnchoredInterval
value may be closed (both endpoints are included in the interval), open (neither endpoint is included), or half-open. This openness is defined by the bounds types L
and R
, which defaults to half-open (with the lesser endpoint included for positive values of P
and the greater endpoint included for negative values).
Why?
AnchoredIntervals
are most useful in cases where a single value is used to stand in for a range of values. This happens most often with dates and times, where "HE15" is often used as shorthand for (14:00..15:00].
To this end, HourEnding
is a type alias for AnchoredInterval{Hour(-1)}
. Similarly, HourBeginning
is a type alias for AnchoredInterval{Hour(1)}
.
Rounding
While the user may expect an HourEnding
or HourBeginning
value to be anchored to a specific hour, the constructor makes no guarantees that the anchor provided is rounded:
julia> HourEnding(DateTime(2016, 8, 11, 2, 30))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T02:30:00"))
The HE
and HB
pseudoconstructors round the input up or down to the nearest hour, as appropriate:
julia> HE(DateTime(2016, 8, 11, 2, 30))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T03:00:00"))
julia> HB(DateTime(2016, 8, 11, 2, 30))
HourBeginning{DateTime, Closed, Open}(DateTime("2016-08-11T02:00:00"))
Example
julia> AnchoredInterval{Hour(-1)}(DateTime(2016, 8, 11, 12))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T12:00:00"))
julia> AnchoredInterval{Day(1)}(DateTime(2016, 8, 11))
AnchoredInterval{Day(1), DateTime, Closed, Open}(DateTime("2016-08-11T00:00:00"))
julia> AnchoredInterval{Minute(5),Closed,Closed}(DateTime(2016, 8, 11, 12, 30))
AnchoredInterval{Minute(5), DateTime, Closed, Closed}(DateTime("2016-08-11T12:30:00"))
Intervals.HourEnding
— TypeHourEnding{T<:TimeType, L, R} <: AbstractInterval{T}
A type alias for AnchoredInterval{Hour(-1), T}
which is used to denote a 1-hour period of time which ends at a time instant (of type T
).
When constructing an instance of HourEnding{T}
the resulting interval will right-closed (of type HourEnding{T,Open,Closed}
).
Intervals.HourBeginning
— TypeHourBeginning{T<:TimeType, L, R} <: AbstractInterval{T}
A type alias for AnchoredInterval{Hour(1), T}
which is used to denote a 1-hour period of time which begins at a time instant (of type T
).
When constructing an instance of HourBeginning{T}
the resulting interval will left-closed (of type HourBeginning{T,Closed,Open}
).
Intervals.HE
— FunctionHE(anchor) -> HourEnding
HE
is a pseudoconstructor for HourEnding
that rounds the anchor provided up to the nearest hour.
Intervals.HB
— FunctionHB(anchor) -> HourBeginning
HB
is a pseudoconstructor for HourBeginning
that rounds the anchor provided down to the nearest hour.
Intervals.Bound
— TypeBound <: Any
Abstract type representing all possible endpoint classifications (e.g. open, closed, unbounded).
Intervals.Bounded
— TypeBounded <: Bound
Abstract type indicating that the endpoint of an interval is not unbounded (e.g. open or closed).
Intervals.Closed
— TypeClosed <: Bounded <: Bound
Type indicating that the endpoint of an interval is closed (the endpoint value is included in the interval).
Intervals.Open
— TypeOpen <: Bounded <: Bound
Type indicating that the endpoint of an interval is open (the endpoint value is not included in the interval).
Intervals.Unbounded
— TypeUnbounded <: Bound
Type indicating that the endpoint of an interval is unbounded (the endpoint value is effectively infinite).
Base.first
— Functionfirst(interval::AbstractInterval{T}) -> Union{T,Nothing}
The value of the lower endpoint. When the lower endpoint is unbounded nothing
will be returned.
Base.last
— Functionlast(interval::AbstractInterval{T}) -> Union{T,Nothing}
The value of the upper endpoint. When the upper endpoint is unbounded nothing
will be returned.
Intervals.span
— Functionspan(interval::AbstractInterval) -> Any
The delta between the upper and lower endpoints. For bounded intervals returns a non-negative value while intervals with any unbounded endpoints will throw an ArgumentError
.
To avoid having to capture the exception use the pattern:
Intervals.isbounded(interval) ? span(interval) : infinity
Where infinity
is a variable representing the value you wish to use to represent an unbounded, or infinite, span.
Intervals.isclosed
— Functionisclosed(interval) -> Bool
Is a closed-interval: includes both of its endpoints.
Base.isopen
— Functionisopen(interval) -> Bool
Is an open-interval: excludes both of its endpoints.
Intervals.isunbounded
— Functionisunbounded(interval) -> Bool
Is an unbounded-interval: unbounded at both ends.
Intervals.isbounded
— Functionisbounded(interval) -> Bool
Is a bounded-interval: either open, closed, left-closed/right-open, or left-open/right-closed.
Note using !isbounded
is commonly used to determine if any end of the interval is unbounded.
Base.parse
— Methodparse(::Type{Interval{T}}, str; element_parser=parse) -> Interval{T}
Parse a string of the form <left-type><left-value><delim><right-value><right-type>
(e.g. [1 .. 2)
) as an Interval{T}
. The format above is interpreted as:
left-type
: Must be either "[" or "(" which indicates if the left-endpoint of the interval is eitherClosed
orOpen
.left-value
: Specifies the value of the left-endpoint which will be parsed as the typeT
. If the value string has a length of zero then the left-endpoint will be specified asUnbounded
. If the value string contains the delimiter (see below) then you may double-quote the value string to avoid any ambiguity.delim
: Must be either ".." or "," which indicates the delimiter separating the left/right endpoint values.right-value
: Specifies the value of the right-endpoint. Seeleft-value
for more details.right-type
: Must be either "]" or ")" which indicates if the right-endpoint of the interval is eitherClosed
orOpen
.
The element_parser
keyword allows a custom parser to be used when parsing the left/right values. The function is expected to take two arguments: Type{T}
and AbstractString
. This is useful for supplying additional arguments/keywords, alternative parser functions, or for types that do not define parse
(e.g. String
).
Intervals.:≪
— Function≪(a::AbstractInterval, b::AbstractInterval) -> Bool
less_than_disjoint(a::AbstractInterval, b::AbstractInterval) -> Bool
Less-than-and-disjoint comparison operator. Returns true
if a
is less than b
and they are disjoint (they do not overlap).
julia> 0..10 ≪ 10..20
false
julia> 0..10 ≪ 11..20
true
Intervals.:≫
— Function≫(a::AbstractInterval, b::AbstractInterval) -> Bool
greater_than_disjoint(a::AbstractInterval, b::AbstractInterval) -> Bool
Greater-than-and-disjoint comparison operator. Returns true
if a
is greater than b
and they are disjoint (they do not overlap).
julia> 10..20 ≫ 0..10
false
julia> 11..20 ≫ 0..10
true
Base.:==
— Function==(a::Endpoint, b::Endpoint) -> Bool
Determine if two endpoints are equal. When both endpoints are left or right then the points and inclusiveness must be the same.
Checking the equality of left-endpoint and a right-endpoint is slightly more difficult. A left-endpoint and a right-endpoint are only equal when they use the same point and are both included. Note that left/right endpoints which are both not included are not equal as the left-endpoint contains values below that point while the right-endpoint only contains values that are above that point.
Visualizing two contiguous intervals can assist in understanding this logic:
[x..y][y..z] -> RightEndpoint == LeftEndpoint
[x..y)[y..z] -> RightEndpoint != LeftEndpoint
[x..y](y..z] -> RightEndpoint != LeftEndpoint
[x..y)(y..z] -> RightEndpoint != LeftEndpoint
Base.union
— Functionunion(intervals::IntervalSets)
Flattens any overlapping intervals within the IntervalSet
into a new, smaller set containing only non-overlapping intervals.
Base.union!
— Functionunion!(intervals::IntervalSet)
Flattens a vector of overlapping intervals in-place to be a smaller vector containing only non-overlapping intervals.
Intervals.superset
— Functionsuperset(intervals::IntervalSet) -> Interval
Create the smallest single interval which encompasses all of the provided intervals.
Intervals.find_intersections
— Functionfind_intersections(
x::AbstractVector{<:AbstractInterval},
y::AbstractVector{<:AbstractInterval}
)
Returns a Vector{Vector{Int}}
where the value at index i
gives the indices to all intervals in y
that intersect with x[i]
.