Intervals
This package defines:
AbstractInterval, along with its subtypes:Interval{T,L,R}, which represents a non-iterable range between two endpoints of typeTwith left/right bounds types respectively beingLandRAnchoredInterval{P,T,L,R}, which represents a non-iterable range defined by a single valueanchor::Tand the value typePwhich represents the span of the range. Left/right bounds types are specifed byLandRrespectivelyHourEnding, a type alias forAnchoredInterval{Hour(-1)}HourBeginning, a type alias forAnchoredInterval{Hour(1)}HEandHB, pseudoconstructors forHourEndingandHourBeginningthat 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 Sets 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))
trueDisplay
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:00Comparisons
Equality
Two AbstractIntervals 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
trueLess 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
trueRounding
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) -> HourEndingHE is a pseudoconstructor for HourEnding that rounds the anchor provided up to the nearest hour.
Intervals.HB — FunctionHB(anchor) -> HourBeginningHB is a pseudoconstructor for HourBeginning that rounds the anchor provided down to the nearest hour.
Intervals.Bound — TypeBound <: AnyAbstract type representing all possible endpoint classifications (e.g. open, closed, unbounded).
Intervals.Bounded — TypeBounded <: BoundAbstract type indicating that the endpoint of an interval is not unbounded (e.g. open or closed).
Intervals.Closed — TypeClosed <: Bounded <: BoundType indicating that the endpoint of an interval is closed (the endpoint value is included in the interval).
Intervals.Open — TypeOpen <: Bounded <: BoundType indicating that the endpoint of an interval is open (the endpoint value is not included in the interval).
Intervals.Unbounded — TypeUnbounded <: BoundType 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) -> AnyThe 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) : infinityWhere infinity is a variable representing the value you wish to use to represent an unbounded, or infinite, span.
Intervals.isclosed — Functionisclosed(interval) -> BoolIs a closed-interval: includes both of its endpoints.
Base.isopen — Functionisopen(interval) -> BoolIs an open-interval: excludes both of its endpoints.
Intervals.isunbounded — Functionisunbounded(interval) -> BoolIs an unbounded-interval: unbounded at both ends.
Intervals.isbounded — Functionisbounded(interval) -> BoolIs 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 eitherClosedorOpen.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-valuefor more details.right-type: Must be either "]" or ")" which indicates if the right-endpoint of the interval is eitherClosedorOpen.
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) -> BoolLess-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
trueIntervals.:≫ — Function≫(a::AbstractInterval, b::AbstractInterval) -> Bool
greater_than_disjoint(a::AbstractInterval, b::AbstractInterval) -> BoolGreater-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
trueBase.:== — Function==(a::Endpoint, b::Endpoint) -> BoolDetermine 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 != LeftEndpointBase.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) -> IntervalCreate 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].