From 573ebe9e6cdb65d7dd9b28e315ab9be83cbc01c2 Mon Sep 17 00:00:00 2001 From: rofinn Date: Wed, 1 Jun 2022 20:19:22 -0700 Subject: [PATCH 01/29] Introduce an IntervalSet type. --- src/Intervals.jl | 1 + src/deprecated.jl | 4 + src/interval_sets.jl | 100 +++++++++++------- test/comparisons.jl | 247 ++++++++++++++++++++++++++++++++++--------- test/interval.jl | 2 + test/sets.jl | 77 +++++++------- 6 files changed, 304 insertions(+), 127 deletions(-) diff --git a/src/Intervals.jl b/src/Intervals.jl index fe00041a..7d3a939a 100644 --- a/src/Intervals.jl +++ b/src/Intervals.jl @@ -42,6 +42,7 @@ export Bound, Unbounded, AbstractInterval, Interval, + IntervalSet, AnchoredInterval, HourEnding, HourBeginning, diff --git a/src/deprecated.jl b/src/deprecated.jl index e91ae7c4..a549e981 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -195,4 +195,8 @@ function HB(anchor, inc::Inclusivity) return HourBeginning{L,R}(floor(anchor, Hour)) end +@deprecate union(intervals::AbstractVector{<:AbstractInterval}) collect(union(IntervalSet(intervals))) +@deprecate union!(intervals::AbstractVector{<:AbstractInterval}) collect(union!(IntervalSet(intervals))) +@deprecate superset(intervals::AbstractVector{<:AbstractInterval}) superset(IntervalSet(intervals)) + # END Intervals 1.X.Y deprecations diff --git a/src/interval_sets.jl b/src/interval_sets.jl index e0760a7b..f24aedb4 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -1,7 +1,22 @@ ###### Set-related Helpers ##### -const IntervalSet = AbstractVector{<:AbstractInterval} +struct IntervalSet{T<:AbstractInterval} + items::Vector{<:AbstractInterval} +end + +IntervalSet(v::AbstractVector) = IntervalSet{eltype(v)}(v) +IntervalSet(interval::T) where T <: AbstractInterval = IntervalSet{T}([interval]) +IntervalSet(interval::IntervalSet) = interval +IntervalSet(itr) = IntervalSet{eltype(itr)}(collect(itr)) + +Base.copy(intervals::IntervalSet{T}) where T = IntervalSet{T}(copy(intervals.items)) +Base.length(intervals::IntervalSet) = length(intervals.items) +Base.iterate(intervals::IntervalSet, args...) = iterate(intervals.items, args...) +Base.eltype(::IntervalSet{T}) where T = T +Base.:(==)(a::IntervalSet, b::IntervalSet) = a.items == b.items +Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a, b) + const AbstractIntervals = Union{AbstractInterval, IntervalSet} # During merge operations used to compute unions, intersections etc..., @@ -39,9 +54,13 @@ function endpoint_tracking( ) return TrackEachEndpoint() end -endpoint_tracking(a::AbstractVector, b::AbstractVector) = endpoint_tracking(eltype(a), eltype(b)) + +endpoint_tracking(a::IntervalSet, b::IntervalSet) = endpoint_tracking(eltype(a), eltype(b)) endpoint_tracking(a::AbstractInterval, b::AbstractInterval) = endpoint_tracking(typeof(a), typeof(b)) +# TODO: Delete once union deprecation is gone. +endpoint_tracking(a::AbstractVector, b::AbstractVector) = endpoint_tracking(eltype(a), eltype(b)) + # track: run a thunk, but only if we are tracking endpoints dynamically track(fn::Function, ::TrackEachEndpoint, args...) = fn(args...) track(_, tracking::TrackStatically, args...) = tracking @@ -58,11 +77,11 @@ interval_type(::TrackRightOpen{T}) where T = Interval{T, Closed, Open} # all vectors of sets be represented by their endpoints. The functions unbunch # and bunch convert between an interval and an endpoint representation -function unbunch(interval::AbstractInterval, tracking::EndpointTracking; lt=isless) +function unbunch(interval::AbstractInterval, tracking::EndpointTracking; lt=isless) return endpoint_type(tracking)[LeftEndpoint(interval), RightEndpoint(interval)] end unbunch_by_fn(_) = identity -function unbunch(intervals::Union{AbstractIntervals, Base.Iterators.Enumerate{<:AbstractIntervals}}, +function unbunch(intervals::Union{AbstractIntervals, Base.Iterators.Enumerate{<:AbstractIntervals}}, tracking::EndpointTracking; lt=isless) by = unbunch_by_fn(intervals) filtered = Iterators.filter(!isempty ∘ by, intervals) @@ -72,7 +91,7 @@ function unbunch(intervals::Union{AbstractIntervals, Base.Iterators.Enumerate{<: end # support for `unbunch(enumerate(vcat(x)))` (transforming [(i, interval)] -> [(i, endpoint), (i,endpoint)]) unbunch_by_fn(::Base.Iterators.Enumerate) = last -function unbunch((i, interval)::Tuple, tracking; lt=isless) +function unbunch((i, interval)::Tuple, tracking; lt=isless) eltype = Tuple{Int, endpoint_type(tracking)} return eltype[(i, LeftEndpoint(interval)), (i, RightEndpoint(interval))] end @@ -84,19 +103,25 @@ function unbunch(a::AbstractIntervals, b::AbstractIntervals; kwargs...) return a_, b_, tracking end +# TODO: Delete fallback once union deprecation is removed +function unbunch(a::Vector{<:AbstractInterval}, b::Vector{<:AbstractInterval}; kwargs...) + return unbunch(IntervalSet(a), IntervalSet(b); kwargs...) +end + # represent a sequence of endpoints as a sequence of one or more intervals function bunch(endpoints, tracking) @assert iseven(length(endpoints)) - isempty(endpoints) && return interval_type(tracking)[] - return map(Iterators.partition(endpoints, 2)) do pair + isempty(endpoints) && return IntervalSet(interval_type(tracking)[]) + res = map(Iterators.partition(endpoints, 2)) do pair return Interval(pair..., tracking) end + return IntervalSet(res) end Interval(a::Endpoint, b::Endpoint, ::TrackEachEndpoint) = Interval(a, b) Interval(a::Endpoint, b::Endpoint, ::TrackLeftOpen{T}) where T = Interval{T,Open,Closed}(a.endpoint, b.endpoint) Interval(a::Endpoint, b::Endpoint, ::TrackRightOpen{T}) where T = Interval{T,Closed,Open}(a.endpoint, b.endpoint) -# the sentinel endpoint reduces the number of edgecases +# the sentinel endpoint reduces the number of edgecases # we have to deal with when comparing endpoints during a merge # NOTE: it's tempting to replace this with an unbounded endpoint # but if we ever want to support unbounded endpoints in mergesets @@ -108,7 +133,7 @@ function first_endpoint(x) return eltype(x) <: Tuple ? last(first(x)) : first(x) end function last_endpoint(x) - isempty(x) && return SentinelEndpoint() + isempty(x) && return SentinelEndpoint() # if the endpoints are enumerated, eltype will be a tuple return eltype(x) <: Tuple ? last(last(x)) : last(x) end @@ -139,7 +164,7 @@ isleft(::RightEndpoint) = false # will remain unchanged moving left to right along the real-number line until we encounter a # new endpoint. # -# For each endpoint, we determine two things: +# For each endpoint, we determine two things: # 1. whether subsequent points should be included in the merge operation or not (based on # its membership in both `x` and `y`) by using `op` # 2. whether the next step will define a left (start including) or right endpoint (stop @@ -264,35 +289,35 @@ right_endpoint(t, ::TrackRightOpen{T}) where T = RightEndpoint{T,Open}(endpoint( # There is power in a union. """ - union(intervals::AbstractVector{<:AbstractInterval}) + union(intervals::IntervalSets) -Flattens a vector of overlapping intervals into a new, smaller vector containing only -non-overlapping intervals. +Flattens any overlapping intervals within the `IntervalSet` into a new, smaller set +containing only non-overlapping intervals. """ -function Base.union(intervals::AbstractVector{<:AbstractInterval}) - return union!(convert(Vector{AbstractInterval}, intervals)) -end +Base.union(intervals::IntervalSet{<:Interval}) = union!(copy(intervals)) -# allow a concretely typed array for `Interval` objects (as opposed to e.g. anchored intervals -# which may change type during the union process) -function Base.union(intervals::AbstractVector{T}) where T <: Interval - return union!(copy(intervals)) +# In the case where we're dealing with a non-concrete interval type like AnchoredIntervals then simply +# allocate a AbstractInterval vector +function Base.union(intervals::IntervalSet{<:AbstractInterval}) + T = AbstractInterval + return union!(IntervalSet{T}(convert(Vector{T}, intervals.items))) end """ - union!(intervals::AbstractVector{<:AbstractInterval}) + union!(intervals::IntervalSet) Flattens a vector of overlapping intervals in-place to be a smaller vector containing only non-overlapping intervals. """ -function Base.union!(intervals::AbstractVector{<:AbstractInterval}) - sort!(intervals) +function Base.union!(intervals::IntervalSet) + items = intervals.items + sort!(items) i = 2 - n = length(intervals) + n = length(items) while i <= n - prev = intervals[i - 1] - curr = intervals[i] + prev = items[i - 1] + curr = items[i] # If the current and previous intervals don't meet then move along if !overlaps(prev, curr) && !contiguous(prev, curr) @@ -301,8 +326,8 @@ function Base.union!(intervals::AbstractVector{<:AbstractInterval}) # If the two intervals meet then we absorb the current interval into # the previous one. else - intervals[i - 1] = merge(prev, curr) - deleteat!(intervals, i) + items[i - 1] = merge(prev, curr) + deleteat!(items, i) n -= 1 end end @@ -311,13 +336,13 @@ function Base.union!(intervals::AbstractVector{<:AbstractInterval}) end """ - superset(intervals::AbstractArray{<:AbstractInterval}) -> Interval + superset(intervals::IntervalSet) -> Interval Create the smallest single interval which encompasses all of the provided intervals. """ -function superset(intervals::AbstractArray{<:AbstractInterval}) - left = minimum(LeftEndpoint.(intervals)) - right = maximum(RightEndpoint.(intervals)) +function superset(intervals::IntervalSet) + left = minimum(LeftEndpoint.(intervals.items)) + right = maximum(RightEndpoint.(intervals.items)) return Interval(left, right) end @@ -332,14 +357,14 @@ Base.issubset(x::AbstractIntervals, y::AbstractIntervals) = isempty(setdiff(x, y Base.isdisjoint(x::AbstractIntervals, y::AbstractIntervals) = isempty(intersect(x, y)) function Base.issetequal(x::AbstractIntervals, y::AbstractIntervals) - x, y, tracking = unbunch(union(vcat(x)), union(vcat(y))) + x, y, tracking = unbunch(union(IntervalSet(x)), union(IntervalSet(y))) return x == y || all(isempty, bunch(x, tracking)) && all(isempty, bunch(y, tracking)) end # order edges so that closed boundaries are on the outside: e.g. [( )] intersection_order(x::Endpoint) = isleft(x) ? !isclosed(x) : isclosed(x) intersection_isless_fn(::TrackStatically) = isless -function intersection_isless_fn(::TrackEachEndpoint) +function intersection_isless_fn(::TrackEachEndpoint) function (x,y) if isequal(x, y) return isless(intersection_order(x), intersection_order(y)) @@ -351,15 +376,15 @@ end """ find_intersections( - x::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, - y::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, + x::Union{AbstractInterval, IntervalSet}, + y::Union{AbstractInterval, IntervalSet}, ) Returns a `Vector{Vector{Int}}` where the value at index `i` gives the indices to all intervals in `y` that intersect with `x[i]`. """ function find_intersections(x_::AbstractIntervals, y_::AbstractIntervals) - xa, ya = vcat(x_), vcat(y_) + xa, ya = IntervalSet(x_), IntervalSet(y_) tracking = endpoint_tracking(xa, ya) lt = intersection_isless_fn(tracking) x = unbunch(enumerate(xa), tracking; lt) @@ -402,4 +427,3 @@ function find_intersections_helper!(result, x, y, lt) return unique!.(result) end - diff --git a/test/comparisons.jl b/test/comparisons.jl index 1ddcd7ca..decc0ed9 100644 --- a/test/comparisons.jl +++ b/test/comparisons.jl @@ -89,16 +89,24 @@ end # Using a vector of intervals as sets @test union([earlier, later]) == [earlier, later] + @test union(IntervalSet([earlier, later])) == IntervalSet([earlier, later]) # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) @test union([earlier], [later]) == [earlier, later] + @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet([earlier, later]) + @test intersect([earlier], [later]) == [] + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) @test setdiff([earlier], [later]) == expected_xor[1:1] + @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) + @test setdiff([later], [earlier]) == expected_xor[2:2] + @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) @test symdiff([earlier], [later]) == expected_xor + @test symdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor) end end end @@ -168,16 +176,24 @@ end # Using a vector of intervals as sets @test union([earlier, later]) == [earlier, later] + @test union(IntervalSet([earlier, later])) == IntervalSet([earlier, later]) # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) @test union([earlier], [later]) == [earlier, later] + @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet([earlier, later]) + @test intersect([earlier], [later]) == [] + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) @test setdiff([earlier], [later]) == expected_xor[1:1] + @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) + @test setdiff([later], [earlier]) == expected_xor[2:2] + @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) @test symdiff([earlier], [later]) == expected_xor + @test symdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor) end end end @@ -247,20 +263,29 @@ end # Using a vector of intervals as sets @test union([earlier, later]) == [expected_superset] + @test union(IntervalSet([earlier, later])) == IntervalSet([expected_superset]) # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) == [expected_superset] + @test union([earlier], [later]) != [expected_superset] + @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) + @test intersect([earlier], [later]) == [] + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) @test setdiff([earlier], [later]) == expected_xor[1:1] + @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) + @test setdiff([later], [earlier]) == expected_xor[2:2] + @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) - @test symdiff([earlier], [later]) == union(expected_xor) + @test symdiff([earlier], [later]) != union(expected_xor) + @test symdiff(IntervalSet(earlier), IntervalSet(later)) == union(IntervalSet(expected_xor)) end end end + # Compare two intervals which "touch" and the earlier interval includes that point: # Visualization of the finite case: # @@ -326,16 +351,24 @@ end # Using a vector of intervals as sets @test union([earlier, later]) == [expected_superset] + @test union(IntervalSet([earlier, later])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) == [expected_superset] + @test union([earlier], [later]) != [expected_superset] + @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) + @test intersect([earlier], [later]) == [] + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) @test setdiff([earlier], [later]) == expected_xor[1:1] + @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) + @test setdiff([later], [earlier]) == expected_xor[2:2] + @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) - @test symdiff([earlier], [later]) == union(expected_xor) + @test symdiff([earlier], [later]) != union(expected_xor) + @test symdiff(IntervalSet(earlier), IntervalSet(later)) == union(IntervalSet(expected_xor)) end end end @@ -410,16 +443,24 @@ end # Using a vector of intervals as sets @test union([earlier, later]) == [expected_superset] + @test union(IntervalSet([earlier, later])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) == [expected_superset] - @test intersect([earlier], [later]) == [expected_overlap] + @test union([earlier], [later]) != [expected_superset] + @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) - @test setdiff([earlier], [later]) == expected_xor[1:1] - @test setdiff([later], [earlier]) == expected_xor[2:2] + @test intersect([earlier], [later]) != [expected_overlap] + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_overlap) - @test symdiff([earlier], [later]) == expected_xor + @test setdiff([earlier], [later]) != expected_xor[1:1] + @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) + + @test setdiff([later], [earlier]) != expected_xor[2:2] + @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) + + @test symdiff([earlier], [later]) != expected_xor + @test symdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor) end end end @@ -494,16 +535,24 @@ end # Using a vector of intervals as sets @test union([earlier, later]) == [expected_superset] + @test union(IntervalSet([earlier, later])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) == [expected_superset] - @test intersect([earlier], [later]) == [expected_overlap] + @test union([earlier], [later]) != [expected_superset] + @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) - @test setdiff([earlier], [later]) == expected_xor[1:1] - @test setdiff([later], [earlier]) == expected_xor[2:2] + @test intersect([earlier], [later]) != [expected_overlap] + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_overlap) - @test symdiff([earlier], [later]) == expected_xor + @test setdiff([earlier], [later]) != expected_xor[1:1] + @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) + + @test setdiff([later], [earlier]) != expected_xor[2:2] + @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) + + @test symdiff([earlier], [later]) != expected_xor + @test symdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor) end end end @@ -560,16 +609,24 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) @test union([a], [b]) == [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) + @test intersect([a], [b]) == [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) @test setdiff([a], [b]) == [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff([b], [a]) == [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) @test symdiff([a], [b]) == [] + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) end end end @@ -627,18 +684,26 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals # TODO: will have to think carefully about the `expected_` variables # when we allow for unbounded values if isbounded(a) && isbounded(b) - @test union([a], [b]) == [expected_superset] - @test intersect([a], [b]) == [expected_overlap] + @test union([a], [b]) != [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test setdiff([a], [b]) == expected_xor[1:1] - @test setdiff([b], [a]) == [] + @test intersect([a], [b]) != [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) + + @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) - @test symdiff([a], [b]) == expected_xor + @test setdiff([b], [a]) != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + + @test symdiff([a], [b]) != expected_xor + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end end @@ -696,18 +761,26 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals # TODO: will have to think carefully about the `expected_` variables # when we allow for unbounded values if isbounded(a) && isbounded(b) - @test union([a], [b]) == [expected_superset] - @test intersect([a], [b]) == [expected_overlap] + @test union([a], [b]) != [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test setdiff([a], [b]) == expected_xor[1:1] - @test setdiff([b], [a]) == [] + @test intersect([a], [b]) != [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) + + @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) - @test symdiff([a], [b]) == expected_xor + @test setdiff([b], [a]) != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + + @test symdiff([a], [b]) != expected_xor + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end end @@ -769,18 +842,26 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals # TODO: will have to think carefully about the `expected_` variables # when we allow for unbounded values if isbounded(a) && isbounded(b) - @test union([a], [b]) == [expected_superset] - @test intersect([a], [b]) == [expected_overlap] + @test union([a], [b]) != [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test setdiff([a], [b]) == expected_xor - @test setdiff([b], [a]) == [] + @test intersect([a], [b]) != [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test symdiff([a], [b]) == expected_xor + @test setdiff([a], [b]) != expected_xor + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) + + @test setdiff([b], [a]) != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + + @test symdiff([a], [b]) != expected_xor + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end end @@ -840,16 +921,24 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) - @test union([a], [b]) == [expected_superset] - @test intersect([a], [b]) == [expected_overlap] + @test union([a], [b]) != [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test setdiff([a], [b]) == [] - @test setdiff([b], [a]) == expected_xor[1:1] + @test intersect([a], [b]) != [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) + + @test setdiff([a], [b]) != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) - @test symdiff([a], [b]) == expected_xor + @test setdiff([b], [a]) != expected_xor[1:1] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(expected_xor[1:1]) + + @test symdiff([a], [b]) != expected_xor + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end end @@ -909,16 +998,26 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] - @test intersect([a], [b]) == [expected_overlap] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) + @test intersect([a], [b]) != [expected_overlap] + @test_broken intersect(IntervalSet([a, b])) == IntervalSet(expected_overlap) # Internal type issue # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) - @test union([a], [b]) == [expected_superset] + @test union([a], [b]) != [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test setdiff([a], [b]) == [] - @test setdiff([b], [a]) == expected_xor[1:1] + @test intersect([a], [b]) != [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) + + @test setdiff([a], [b]) != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + + @test setdiff([b], [a]) != expected_xor[1:1] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(expected_xor[1:1]) - @test symdiff([a], [b]) == expected_xor + @test symdiff([a], [b]) != expected_xor + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end end @@ -975,16 +1074,24 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) @test union([a], [b]) == [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) + @test intersect([a], [b]) == [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) @test setdiff([a], [b]) == [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff([b], [a]) == [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) @test symdiff([a], [b]) == [] + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) end end end @@ -1042,16 +1149,24 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) @test union([a], [b]) == [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) + @test intersect([a], [b]) == [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) + + @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) - @test setdiff([a], [b]) == expected_xor[1:1] @test setdiff([b], [a]) == [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) @test symdiff([a], [b]) == [] + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) end end end @@ -1109,16 +1224,24 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) @test union([a], [b]) == [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) + @test intersect([a], [b]) == [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) + + @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) - @test setdiff([a], [b]) == expected_xor[1:1] @test setdiff([b], [a]) == [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) @test symdiff([a], [b]) == [] + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) end end end @@ -1174,16 +1297,24 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) @test union([a], [b]) == [expected_superset] + @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) + @test intersect([a], [b]) == [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) @test setdiff([a], [b]) == [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff([b], [a]) == [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) @test symdiff([a], [b]) == [] + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) end end end @@ -1231,14 +1362,20 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] - @test union([a], [b]) == [expected_superset] + @test union([a], [b]) != [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) + + @test intersect([a], [b]) != [expected_overlap] + @test intersect(IntervalSet([a, b])) == IntervalSet(expected_overlap) - @test intersect([a], [b]) == [expected_overlap] + @test setdiff([a], [b]) != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) - @test setdiff([a], [b]) == [] - @test setdiff([b], [a]) == [] + @test setdiff([b], [a]) != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) - @test symdiff([a], [b]) == [] + @test symdiff([a], [b]) != [] + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) end end @@ -1319,16 +1456,24 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) - @test union([a], [b]) == [expected_superset] - @test intersect([a], [b]) == [expected_overlap] + @test union([a], [b]) != [expected_superset] + @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) - @test setdiff([a], [b]) == [] - @test setdiff([b], [a]) == expected_xor[1:2] + @test intersect([a], [b]) != [expected_overlap] + @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) + + @test setdiff([a], [b]) != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + + @test setdiff([b], [a]) != expected_xor[1:2] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(expected_xor[1:2]) - @test symdiff([a], [b]) == expected_xor + @test symdiff([a], [b]) != expected_xor + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end end diff --git a/test/interval.jl b/test/interval.jl index 197a06a9..1ee25d04 100644 --- a/test/interval.jl +++ b/test/interval.jl @@ -765,6 +765,8 @@ Interval{Open, Open}(-10, -1), Interval{Open, Open}(13, 20), ] + @show typeof(intervals) + @test union!(intervals) == expected @test intervals == expected diff --git a/test/sets.jl b/test/sets.jl index 839a38c4..38f31513 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -28,9 +28,11 @@ end area(x::Interval) = last(x) - first(x) # note: `mapreduce` fails here for empty vectors area(x::AbstractVector{<:AbstractInterval{T}}) where T = mapreduce(area, +, x, init=zero(T)) + area(x::IntervalSet) = area(x.items) area(x) = isempty(x) ? 0 : error("Undefined area for object of type $(typeof(x))") myunion(x::Interval) = x myunion(x::AbstractVector{<:Interval}) = union(x) + myunion(x::IntervalSet) = union(x) rand_bound_type(rng) = rand(rng, (Closed, Open)) @@ -43,36 +45,35 @@ end @test issetequal(a, a) @test isdisjoint(setdiff(a, b), b) @test !isdisjoint(a, a) - + intersections = find_intersections(a, b) - a, b = vcat(a), vcat(b) # Always make `a` / `b` vectors # verify that all indices returned in `find_intersections` correspond to sets # in b that overlap with the given set in a @test all(enumerate(intersections)) do (i, x) - isempty(x) || !isempty(intersect(a[i:i], b[x])) + isempty(x) || !isempty(intersect(IntervalSet(a.items[i]), IntervalSet(b.items[x]))) end # verify that all indices not returned in `find_intersections` correspond to # sets in b that do not overlap with the given set in akk @test all(enumerate(intersections)) do (i, x) - isempty(intersect(a[i:i], b[Not(x)])) + isempty(intersect(IntervalSet(a.items[i]), IntervalSet(b.items[Not(x)]))) end end # verify empty interval set @test isempty(union(Interval[])) - + # a few taylored interval sets - a = [Interval(i, i + 3) for i in 1:5:15] - b = a .+ (1:2:5) + a = IntervalSet([Interval(i, i + 3) for i in 1:5:15]) + b = IntervalSet(a.items .+ (1:2:5)) @test all(first.(a) .∈ a) testsets(a, b) - testsets(a[1:1], b) - testsets(a, b[1:1]) + testsets(IntervalSet(first(a)), b) + testsets(a, IntervalSet(first(b))) # verify that `last` need not be ordered - intervals = [Interval(0, 5), Interval(0, 3)] + intervals = IntervalSet([Interval(0, 5), Interval(0, 3)]) @test superset(union(intervals)) == Interval(0, 5) # try out some more involved (random) intervals @@ -82,47 +83,47 @@ end ends = starts .+ rand(rng, 1:10_000, n) offsets = round.(Int, (ends .- starts) .* (2 .* rand(rng, n) .- 1)) - a = Interval.(starts, ends) - b = Interval.(starts .+ offsets, ends .+ offsets) + a = IntervalSet(Interval.(starts, ends)) + b = IntervalSet(Interval.(starts .+ offsets, ends .+ offsets)) @test all(first.(a) .∈ a) testsets(a, b) - testsets(a[1:1], b) - testsets(a, b[1:1]) + testsets(IntervalSet(first(a)), b) + testsets(a, IntervalSet(first(b))) - a = Interval{rand_bound_type(rng), rand_bound_type(rng)}.(starts, ends) - b = Interval{rand_bound_type(rng), rand_bound_type(rng)}.(starts .+ offsets, ends .+ offsets) + a = IntervalSet(Interval{rand_bound_type(rng), rand_bound_type(rng)}.(starts, ends)) + b = IntervalSet(Interval{rand_bound_type(rng), rand_bound_type(rng)}.(starts .+ offsets, ends .+ offsets)) testsets(a, b) - testsets(a[1:1], b) - testsets(a, b[1:1]) + testsets(IntervalSet(first(a)), b) + testsets(a, IntervalSet(first(b))) - a = Interval{Closed, Open}.(starts, ends) - b = Interval{Closed, Open}.(starts .+ offsets, ends .+ offsets) + a = IntervalSet(Interval{Closed, Open}.(starts, ends)) + b = IntervalSet(Interval{Closed, Open}.(starts .+ offsets, ends .+ offsets)) @test Intervals.endpoint_tracking(a, b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(a[1:1], b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(a, b[1:1]) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(IntervalSet(first(a)), b) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(a, IntervalSet(first(b))) isa Intervals.TrackStatically testsets(a, b) - testsets(a[1:1], b) - testsets(a, b[1:1]) + testsets(IntervalSet(first(a)), b) + testsets(a, IntervalSet(first(b))) - a = Interval{Open, Closed}.(starts, ends) - b = Interval{Open, Closed}.(starts .+ offsets, ends .+ offsets) + a = IntervalSet(Interval{Open, Closed}.(starts, ends)) + b = IntervalSet(Interval{Open, Closed}.(starts .+ offsets, ends .+ offsets)) @test Intervals.endpoint_tracking(a, b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(a[1:1], b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(a, b[1:1]) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(IntervalSet(first(a)), b) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(a, IntervalSet(first(b))) isa Intervals.TrackStatically testsets(a, b) - testsets(a[1:1], b) - testsets(a, b[1:1]) + testsets(IntervalSet(first(a)), b) + testsets(a, IntervalSet(first(b))) randint(x::Interval) = Interval{rand_bound_type(rng), rand_bound_type(rng)}(first(x), last(x)) leftint(x::Interval) = Interval{Closed, Open}(first(x), last(x)) rightint(x::Interval) = Interval{Open, Closed}(first(x), last(x)) - a = Interval{Closed, Open}.(starts, ends) - b = Interval{Closed, Open}.(starts .+ offsets, ends .+ offsets) - testsets(a, randint.(b)) - testsets(a[1:1], randint.(b)) - testsets(a, leftint.(b[1:1])) - testsets(a, rightint.(b)) - testsets(a[1:1], rightint.(b)) - testsets(a, rightint.(b[1:1])) + a = IntervalSet(Interval{Closed, Open}.(starts, ends)) + b = IntervalSet(Interval{Closed, Open}.(starts .+ offsets, ends .+ offsets)) + testsets(a, IntervalSet(randint.(b))) + testsets(IntervalSet(first(a)), IntervalSet(randint.(b))) + testsets(a, IntervalSet(leftint.(IntervalSet(first(b))))) + testsets(a, IntervalSet(rightint.(b))) + testsets(IntervalSet(first(a)), IntervalSet(rightint.(b))) + testsets(a, IntervalSet(rightint.(IntervalSet(first(b))))) end From e122bfeaf4e72dafee778bbcf88f09e4f3bd4abc Mon Sep 17 00:00:00 2001 From: rofinn Date: Mon, 6 Jun 2022 15:00:09 -0700 Subject: [PATCH 02/29] Addressing review comments. 1. Made IntervalSet a subtype of AbstractSet - Note about changing internally representation once `union!(Vector{AbstractInterval})` is deprecated 2. Add an extra empty constructor 3. `==` falls back to issetequal 4. Fixed a bug where `union` didn't correctly copy data in the non-concrete case. 5. Updated a bunch of tests to include both the previous (reverted) expectation and the current base fallback behaviour. --- Project.toml | 2 +- src/interval_sets.jl | 18 +++-- test/comparisons.jl | 163 +++++++++++++++++++++++-------------------- test/interval.jl | 1 - 4 files changed, 100 insertions(+), 84 deletions(-) diff --git a/Project.toml b/Project.toml index 310f0476..17b9f7a7 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Intervals" uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5" license = "MIT" authors = ["Invenia Technical Computing"] -version = "1.7.0" +version = "1.8.0" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" diff --git a/src/interval_sets.jl b/src/interval_sets.jl index f24aedb4..93dad4a8 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -1,21 +1,27 @@ ###### Set-related Helpers ##### +""" + IntervalSet{T<:AbstractInterval} <: AbstractSet{T} -struct IntervalSet{T<:AbstractInterval} - items::Vector{<:AbstractInterval} +A set type for performing multi-interval specific operations. +https://en.wikipedia.org/wiki/Interval_arithmetic#Interval_operators +""" +struct IntervalSet{T<:AbstractInterval} <: AbstractSet{T} + # TODO: Use Dict internally once union!(Vector{AbstractInterval}) is fully deprecated + items::Vector{T} end IntervalSet(v::AbstractVector) = IntervalSet{eltype(v)}(v) IntervalSet(interval::T) where T <: AbstractInterval = IntervalSet{T}([interval]) IntervalSet(interval::IntervalSet) = interval IntervalSet(itr) = IntervalSet{eltype(itr)}(collect(itr)) +IntervalSet() = IntervalSet{AbstractInterval}(AbstractInterval[]) Base.copy(intervals::IntervalSet{T}) where T = IntervalSet{T}(copy(intervals.items)) Base.length(intervals::IntervalSet) = length(intervals.items) Base.iterate(intervals::IntervalSet, args...) = iterate(intervals.items, args...) Base.eltype(::IntervalSet{T}) where T = T -Base.:(==)(a::IntervalSet, b::IntervalSet) = a.items == b.items -Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a, b) +Base.:(==)(a::IntervalSet, b::IntervalSet) = issetequal(a, b) const AbstractIntervals = Union{AbstractInterval, IntervalSet} @@ -300,7 +306,9 @@ Base.union(intervals::IntervalSet{<:Interval}) = union!(copy(intervals)) # allocate a AbstractInterval vector function Base.union(intervals::IntervalSet{<:AbstractInterval}) T = AbstractInterval - return union!(IntervalSet{T}(convert(Vector{T}, intervals.items))) + dest = Vector{T}(undef, length(intervals.items)) + copyto!(dest, intervals.items) + return union!(IntervalSet{T}(dest)) end """ diff --git a/test/comparisons.jl b/test/comparisons.jl index decc0ed9..2c8b131d 100644 --- a/test/comparisons.jl +++ b/test/comparisons.jl @@ -97,7 +97,7 @@ end @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet([earlier, later]) @test intersect([earlier], [later]) == [] - @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet() @test setdiff([earlier], [later]) == expected_xor[1:1] @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) @@ -184,7 +184,7 @@ end @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet([earlier, later]) @test intersect([earlier], [later]) == [] - @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet() @test setdiff([earlier], [later]) == expected_xor[1:1] @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) @@ -267,11 +267,11 @@ end # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) != [expected_superset] + @test union([earlier], [later]) == expected_xor != [expected_superset] @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) @test intersect([earlier], [later]) == [] - @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet() @test setdiff([earlier], [later]) == expected_xor[1:1] @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) @@ -279,7 +279,8 @@ end @test setdiff([later], [earlier]) == expected_xor[2:2] @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) - @test symdiff([earlier], [later]) != union(expected_xor) + # TODO: Sometimes expected_xor would get mutated in this call + @test symdiff([earlier], [later]) == expected_xor != union(expected_xor) @test symdiff(IntervalSet(earlier), IntervalSet(later)) == union(IntervalSet(expected_xor)) end end @@ -355,11 +356,11 @@ end # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) != [expected_superset] + @test union([earlier], [later]) == expected_xor != [expected_superset] @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) @test intersect([earlier], [later]) == [] - @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(Interval[]) + @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet() @test setdiff([earlier], [later]) == expected_xor[1:1] @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) @@ -367,7 +368,7 @@ end @test setdiff([later], [earlier]) == expected_xor[2:2] @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) - @test symdiff([earlier], [later]) != union(expected_xor) + @test symdiff([earlier], [later]) == expected_xor != union(expected_xor) @test symdiff(IntervalSet(earlier), IntervalSet(later)) == union(IntervalSet(expected_xor)) end end @@ -447,19 +448,20 @@ end # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [earlier, later] + @test union([earlier], [later]) == [earlier, later] != [expected_superset] @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) - @test intersect([earlier], [later]) != [expected_overlap] + @test intersect([earlier], [later]) == [] != [expected_overlap] @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_overlap) - @test setdiff([earlier], [later]) != expected_xor[1:1] + @test setdiff([earlier], [later]) == [earlier] != expected_xor[1:1] @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) - @test setdiff([later], [earlier]) != expected_xor[2:2] + @test setdiff([later], [earlier]) == [later] != expected_xor[2:2] @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) - @test symdiff([earlier], [later]) != expected_xor + @test symdiff([earlier], [later]) == [earlier, later] @test symdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor) end end @@ -539,19 +541,20 @@ end # TODO: These functions should be compatible with unbounded intervals if isbounded(earlier) && isbounded(later) - @test union([earlier], [later]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [earlier, later] + @test union([earlier], [later]) == [earlier, later] != [expected_superset] @test union(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_superset) - @test intersect([earlier], [later]) != [expected_overlap] + @test intersect([earlier], [later]) == [] != [expected_overlap] @test intersect(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_overlap) - @test setdiff([earlier], [later]) != expected_xor[1:1] + @test setdiff([earlier], [later]) == [earlier] != expected_xor[1:1] @test setdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor[1:1]) - @test setdiff([later], [earlier]) != expected_xor[2:2] + @test setdiff([later], [earlier]) == [later] != expected_xor[2:2] @test setdiff(IntervalSet(later), IntervalSet(earlier)) == IntervalSet(expected_xor[2:2]) - @test symdiff([earlier], [later]) != expected_xor + @test symdiff([earlier], [later]) == [earlier, later] != expected_xor @test symdiff(IntervalSet(earlier), IntervalSet(later)) == IntervalSet(expected_xor) end end @@ -620,13 +623,13 @@ end @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) @test setdiff([a], [b]) == [] - @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() @test setdiff([b], [a]) == [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() @test symdiff([a], [b]) == [] - @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() end end end @@ -690,19 +693,20 @@ end # TODO: will have to think carefully about the `expected_` variables # when we allow for unbounded values if isbounded(a) && isbounded(b) - @test union([a], [b]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [a, b] + @test union([a], [b]) == [a, b] != [expected_superset] @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test intersect([a], [b]) != [expected_overlap] + @test intersect([a], [b]) == [] != [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff([a], [b]) == [a] != expected_xor[1:1] @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) - @test setdiff([b], [a]) != [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff([b], [a]) == [b] != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() - @test symdiff([a], [b]) != expected_xor + @test symdiff([a], [b]) == [a, b] != expected_xor @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end @@ -767,19 +771,20 @@ end # TODO: will have to think carefully about the `expected_` variables # when we allow for unbounded values if isbounded(a) && isbounded(b) - @test union([a], [b]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [a, b] + @test union([a], [b]) == [a, b] != [expected_superset] @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test intersect([a], [b]) != [expected_overlap] + @test intersect([a], [b]) == [] != [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff([a], [b]) == [a] != expected_xor[1:1] @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) - @test setdiff([b], [a]) != [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff([b], [a]) == [b] != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() - @test symdiff([a], [b]) != expected_xor + @test symdiff([a], [b]) == [a, b] != expected_xor @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end @@ -848,19 +853,20 @@ end # TODO: will have to think carefully about the `expected_` variables # when we allow for unbounded values if isbounded(a) && isbounded(b) - @test union([a], [b]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [a, b] + @test union([a], [b]) == [a, b] != [expected_superset] @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test intersect([a], [b]) != [expected_overlap] + @test intersect([a], [b]) == [] != [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != expected_xor + @test setdiff([a], [b]) == [a] != expected_xor @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) - @test setdiff([b], [a]) != [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff([b], [a]) == [b] != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() - @test symdiff([a], [b]) != expected_xor + @test symdiff([a], [b]) == [a, b] != expected_xor @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end @@ -925,19 +931,20 @@ end # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) - @test union([a], [b]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [a, b] + @test union([a], [b]) == [a, b] != [expected_superset] @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test intersect([a], [b]) != [expected_overlap] + @test intersect([a], [b]) == [] != [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != [] - @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff([a], [b]) == [a] != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() - @test setdiff([b], [a]) != expected_xor[1:1] + @test setdiff([b], [a]) == [b] != expected_xor[1:1] @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(expected_xor[1:1]) - @test symdiff([a], [b]) != expected_xor + @test symdiff([a], [b]) == [a, b] != expected_xor @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end @@ -1004,19 +1011,20 @@ end # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) - @test union([a], [b]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [a, b] + @test union([a], [b]) == [a, b] != [expected_superset] @test union(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_superset) - @test intersect([a], [b]) != [expected_overlap] + @test intersect([a], [b]) == [] != [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != [] - @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff([a], [b]) == [a] != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() - @test setdiff([b], [a]) != expected_xor[1:1] + @test setdiff([b], [a]) == [b] != expected_xor[1:1] @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(expected_xor[1:1]) - @test symdiff([a], [b]) != expected_xor + @test symdiff([a], [b]) == [a, b] != expected_xor @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end @@ -1085,13 +1093,13 @@ end @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) @test setdiff([a], [b]) == [] - @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() @test setdiff([b], [a]) == [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() @test symdiff([a], [b]) == [] - @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() end end end @@ -1159,14 +1167,14 @@ end @test intersect([a], [b]) == [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff([a], [b]) == [a] != expected_xor[1:1] @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) @test setdiff([b], [a]) == [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() @test symdiff([a], [b]) == [] - @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() end end end @@ -1234,14 +1242,14 @@ end @test intersect([a], [b]) == [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != expected_xor[1:1] + @test setdiff([a], [b]) == [a] != expected_xor[1:1] @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor[1:1]) @test setdiff([b], [a]) == [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() @test symdiff([a], [b]) == [] - @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() end end end @@ -1308,13 +1316,13 @@ end @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) @test setdiff([a], [b]) == [] - @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() @test setdiff([b], [a]) == [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() @test symdiff([a], [b]) == [] - @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() end end end @@ -1362,20 +1370,20 @@ end # Using a vector of intervals as sets @test union([a, b]) == [expected_superset] - @test union([a], [b]) != [expected_superset] + @test union([a], [b]) == [a, b] != [expected_superset] @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) - @test intersect([a], [b]) != [expected_overlap] + @test intersect([a], [b]) == [] != [expected_overlap] @test intersect(IntervalSet([a, b])) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != [] - @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff([a], [b]) == [a] != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() - @test setdiff([b], [a]) != [] - @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(Interval[]) + @test setdiff([b], [a]) == [b] != [] + @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet() - @test symdiff([a], [b]) != [] - @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test symdiff([a], [b]) == [a, b] != [] + @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() end end @@ -1460,19 +1468,20 @@ end # TODO: These functions should be compatible with unbounded intervals if isbounded(a) && isbounded(b) - @test union([a], [b]) != [expected_superset] + # NOTE: expected_xor may have different bounds than [a, b] + @test union([a], [b]) == [a, b] != [expected_superset] @test union(IntervalSet([a, b])) == IntervalSet(expected_superset) - @test intersect([a], [b]) != [expected_overlap] + @test intersect([a], [b]) == [] != [expected_overlap] @test intersect(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_overlap) - @test setdiff([a], [b]) != [] - @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(Interval[]) + @test setdiff([a], [b]) == [a] != [] + @test setdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet() - @test setdiff([b], [a]) != expected_xor[1:2] + @test setdiff([b], [a]) == [b] != expected_xor[1:2] @test setdiff(IntervalSet(b), IntervalSet(a)) == IntervalSet(expected_xor[1:2]) - @test symdiff([a], [b]) != expected_xor + @test symdiff([a], [b]) == [a, b] != expected_xor @test symdiff(IntervalSet(a), IntervalSet(b)) == IntervalSet(expected_xor) end end diff --git a/test/interval.jl b/test/interval.jl index 1ee25d04..b85814d6 100644 --- a/test/interval.jl +++ b/test/interval.jl @@ -765,7 +765,6 @@ Interval{Open, Open}(-10, -1), Interval{Open, Open}(13, 20), ] - @show typeof(intervals) @test union!(intervals) == expected @test intervals == expected From a68029963e6ea063f4c5708bc84e7d3b1b4f1eaf Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 14:21:30 +0000 Subject: [PATCH 03/29] some small adgjuetments --- src/interval_sets.jl | 6 ++++++ test/sets.jl | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index f24aedb4..a585bdd0 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -1,6 +1,12 @@ ###### Set-related Helpers ##### +""" + IntervalSet(x::AbstractVector{<:AbstractInterval}) + +Interpret an array of intervals as a set of points: the union of all points in the +intervals. +""" struct IntervalSet{T<:AbstractInterval} items::Vector{<:AbstractInterval} end diff --git a/test/sets.jl b/test/sets.jl index 38f31513..1889175c 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -36,6 +36,10 @@ end rand_bound_type(rng) = rand(rng, (Closed, Open)) + # verify case where we interpret array as a set of intervals (rather than a set of + # points) + @test intersect([1..2, 2..3, 3..4, 4..5], [2..3, 3..4]) == [2..3, 3..4] + function testsets(a, b) @test area(a ∪ b) ≤ area(myunion(a)) + area(myunion(b)) @test area(setdiff(a, b)) ≤ area(myunion(a)) From 707c7bf6bb058b5a2ae72436cd8c339a5b5f0e5e Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 16:11:38 +0000 Subject: [PATCH 04/29] update docs --- docs/src/index.md | 2 ++ src/interval_sets.jl | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index 4fc13736..9c86ba9d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -268,6 +268,7 @@ In the plot, inclusive boundaries are marked with a vertical bar, whereas exclus ```@docs Interval AnchoredInterval +IntervalSet HourEnding HourBeginning HE @@ -291,4 +292,5 @@ Base.parse(::Type{Interval{T}}, ::AbstractString) where T union union! superset +Intervals.find_intersections ``` diff --git a/src/interval_sets.jl b/src/interval_sets.jl index a585bdd0..9bdee144 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -5,7 +5,45 @@ IntervalSet(x::AbstractVector{<:AbstractInterval}) Interpret an array of intervals as a set of points: the union of all points in the -intervals. +intervals. Set operations over intervals sets will return an IntervalSet containing the +fewest number of intervals that can be used to represent the resulting point set. + +## Examples + +```jldoctest +julia> Array(union(IntervalSet([1..5]), IntervalSet[3..8])) +1-element Vector{Interval{Int64, Closed, Closed}}: + Interval{Int64, Closed, Closed}(1, 8) + +julia> Array(intersect(IntervalSet([1..5]), IntervalSet([3..8]))) +1-element Vector{Interval{Int64, Closed, Closed}}: + Interval{Int64, Closed, Closed}(3, 5) + +julia> Array(symdiff(IntervalSet([1..5]), IntervalSet([3..8]))) +2-element Vector{Interval{Int64}}: + Interval{Int64, Closed, Open}(1, 3) + Interval{Int64, Open, Closed}(5, 8) + +julia> Array(union(IntervalSet([1..2, 2..5]), IntervalSet([6..7]))) +2-element Vector{Interval{Int64, Closed, Closed}}: + Interval{Int64, Closed, Closed}(1, 5) + Interval{Int64, Closed, Closed}(6, 7) + +julia> Array(union(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) +2-element Vector{Interval{Int64, Closed, Closed}}: + Interval{Int64, Closed, Closed}(1, 10) + Interval{Int64, Closed, Closed}(12, 14) + +julia> Array(intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) +2-element Vector{Interval{Int64, Closed, Closed}}: + Interval{Int64, Closed, Closed}(4, 5) + Interval{Int64, Closed, Closed}(8, 9) + +julia> Array(setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) +2-element Vector{Interval{Int64}}: + Interval{Int64, Closed, Open}(1, 4) + Interval{Int64, Open, Closed}(9, 10) +``` """ struct IntervalSet{T<:AbstractInterval} items::Vector{<:AbstractInterval} @@ -22,6 +60,7 @@ Base.iterate(intervals::IntervalSet, args...) = iterate(intervals.items, args... Base.eltype(::IntervalSet{T}) where T = T Base.:(==)(a::IntervalSet, b::IntervalSet) = a.items == b.items Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a, b) +Base.Array(intervals::IntervalSet) = intervals.items const AbstractIntervals = Union{AbstractInterval, IntervalSet} From abaddf2689eb03ed3e11b7ab27f220f133b4cac2 Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 16:16:31 +0000 Subject: [PATCH 05/29] proofreading --- src/interval_sets.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 9bdee144..f8c1e22a 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -5,8 +5,9 @@ IntervalSet(x::AbstractVector{<:AbstractInterval}) Interpret an array of intervals as a set of points: the union of all points in the -intervals. Set operations over intervals sets will return an IntervalSet containing the +intervals. Set operations over them will return an IntervalSet containing the fewest number of intervals that can be used to represent the resulting point set. +Unbounded intervals are not supported. ## Examples From 8231eeb44b7d68628a02da83cbcbff1369eca3fc Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 16:18:08 +0000 Subject: [PATCH 06/29] add set link --- src/interval_sets.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index f8c1e22a..029ac1aa 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -9,6 +9,8 @@ intervals. Set operations over them will return an IntervalSet containing the fewest number of intervals that can be used to represent the resulting point set. Unbounded intervals are not supported. +see also: https://en.wikipedia.org/wiki/Interval_arithmetic#Interval_operators + ## Examples ```jldoctest From 20b0c20dadab4cad227644f20d5cc09d84250445 Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 16:57:20 +0000 Subject: [PATCH 07/29] revise doc tests --- src/interval_sets.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 16e161ea..4f659a49 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -13,20 +13,21 @@ see also: https://en.wikipedia.org/wiki/Interval_arithmetic#Interval_operators ## Examples ```jldoctest -julia> Array(union(IntervalSet([1..5]), IntervalSet[3..8])) +julia> using Intervals +julia> Array(union(IntervalSet(1..5), IntervalSet(3..8))) 1-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(1, 8) -julia> Array(intersect(IntervalSet([1..5]), IntervalSet([3..8]))) +julia> Array(intersect(IntervalSet(1..5), IntervalSet(3..8))) 1-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(3, 5) -julia> Array(symdiff(IntervalSet([1..5]), IntervalSet([3..8]))) +julia> Array(symdiff(IntervalSet(1..5), IntervalSet(3..8))) 2-element Vector{Interval{Int64}}: Interval{Int64, Closed, Open}(1, 3) Interval{Int64, Open, Closed}(5, 8) -julia> Array(union(IntervalSet([1..2, 2..5]), IntervalSet([6..7]))) +julia> Array(union(IntervalSet([1..2, 2..5]), IntervalSet(6..7))) 2-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(1, 5) Interval{Int64, Closed, Closed}(6, 7) From 9af3a28b5609cc837284dd4b1fd69ba1f98dec1a Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 18:30:50 +0000 Subject: [PATCH 08/29] support other array types for an interval set --- src/interval_sets.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 4f659a49..34abf81c 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -48,8 +48,8 @@ julia> Array(setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) Interval{Int64, Open, Closed}(9, 10) ``` """ -struct IntervalSet{T<:AbstractInterval} - items::Vector{<:AbstractInterval} +struct IntervalSet{A <: AbstractVector{<:AbstractInterval}} + items::A end IntervalSet(v::AbstractVector) = IntervalSet{eltype(v)}(v) From f4386edc77157042a62dbbc59ec05960e4509046 Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 18:34:33 +0000 Subject: [PATCH 09/29] remove superfluous intervalset conversion --- src/interval_sets.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 34abf81c..64d0195d 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -435,7 +435,7 @@ Returns a `Vector{Vector{Int}}` where the value at index `i` gives the indices t intervals in `y` that intersect with `x[i]`. """ function find_intersections(x_::AbstractIntervals, y_::AbstractIntervals) - xa, ya = IntervalSet(x_), IntervalSet(y_) + xa, ya = vcat(x_), vcat(y_) tracking = endpoint_tracking(xa, ya) lt = intersection_isless_fn(tracking) x = unbunch(enumerate(xa), tracking; lt) From df2d9db522f04f141de42d305dbe37674b03cb9e Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 21:18:10 +0000 Subject: [PATCH 10/29] fix type signature --- src/interval_sets.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 64d0195d..59e635f0 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -434,7 +434,7 @@ end Returns a `Vector{Vector{Int}}` where the value at index `i` gives the indices to all intervals in `y` that intersect with `x[i]`. """ -function find_intersections(x_::AbstractIntervals, y_::AbstractIntervals) +function find_intersections(x_::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, y_::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}) xa, ya = vcat(x_), vcat(y_) tracking = endpoint_tracking(xa, ya) lt = intersection_isless_fn(tracking) From 3503f7404eadf5e5338b276c0494904e99891192 Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 21:18:42 +0000 Subject: [PATCH 11/29] fix test --- test/sets.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sets.jl b/test/sets.jl index 1889175c..a10a5afd 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -50,7 +50,7 @@ end @test isdisjoint(setdiff(a, b), b) @test !isdisjoint(a, a) - intersections = find_intersections(a, b) + intersections = find_intersections(Array(a), Array(b)) # verify that all indices returned in `find_intersections` correspond to sets # in b that overlap with the given set in a From fc5dea1b95a27525ae79b61e0594a71e0f2a1f24 Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 21:31:55 +0000 Subject: [PATCH 12/29] fix some type issues --- src/interval_sets.jl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 59e635f0..cdaf8ff6 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -66,7 +66,11 @@ Base.:(==)(a::IntervalSet, b::IntervalSet) = a.items == b.items Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a, b) Base.Array(intervals::IntervalSet) = intervals.items -const AbstractIntervals = Union{AbstractInterval, IntervalSet} +# currently (to avoid breaking changes) new methods for `Base` +# accept `IntervalSet` objects and Interval singletons. +const AbstractIntervalSets = Union{AbstractInterval, IntervalSet} +# internal methods need to operate on both sets and arrays of intervals +const AbstractIntervals = Union{AbstractIntervalSets, AbstractArray{<:AbstractInterval}} # During merge operations used to compute unions, intersections etc..., # endpoint types can change (from left to right, and from open to closed, @@ -152,11 +156,6 @@ function unbunch(a::AbstractIntervals, b::AbstractIntervals; kwargs...) return a_, b_, tracking end -# TODO: Delete fallback once union deprecation is removed -function unbunch(a::Vector{<:AbstractInterval}, b::Vector{<:AbstractInterval}; kwargs...) - return unbunch(IntervalSet(a), IntervalSet(b); kwargs...) -end - # represent a sequence of endpoints as a sequence of one or more intervals function bunch(endpoints, tracking) @assert iseven(length(endpoints)) @@ -404,10 +403,10 @@ Base.intersect(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && Base.union(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx || iny, x, y) Base.setdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && !iny, x, y) Base.symdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx ⊻ iny, x, y) -Base.issubset(x::AbstractIntervals, y::AbstractIntervals) = isempty(setdiff(x, y)) -Base.isdisjoint(x::AbstractIntervals, y::AbstractIntervals) = isempty(intersect(x, y)) +Base.issubset(x::AbstractIntervalSets, y::AbstractIntervalSets) = isempty(setdiff(x, y)) +Base.isdisjoint(x::AbstractIntervalSets, y::AbstractIntervalSets) = isempty(intersect(x, y)) -function Base.issetequal(x::AbstractIntervals, y::AbstractIntervals) +function Base.issetequal(x::AbstractIntervalSets, y::AbstractIntervalSets) x, y, tracking = unbunch(union(IntervalSet(x)), union(IntervalSet(y))) return x == y || all(isempty, bunch(x, tracking)) && all(isempty, bunch(y, tracking)) end From bcffe822c6dea8b525f805d39d1fcc93c730a181 Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 22:08:16 +0000 Subject: [PATCH 13/29] fix bug in interval set construction --- src/interval_sets.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index cdaf8ff6..9cac769f 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -1,7 +1,7 @@ ###### Set-related Helpers ##### """ - IntervalSet{T<:AbstractInterval} <: AbstractSet{T} + IntervalSet{T<:AbstractInterval} An 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 @@ -48,7 +48,7 @@ julia> Array(setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) Interval{Int64, Open, Closed}(9, 10) ``` """ -struct IntervalSet{A <: AbstractVector{<:AbstractInterval}} +struct IntervalSet{I <: AbstractInterval, A <: AbstractVector{I}} items::A end @@ -58,7 +58,7 @@ IntervalSet(interval::IntervalSet) = interval IntervalSet(itr) = IntervalSet{eltype(itr)}(collect(itr)) IntervalSet() = IntervalSet{AbstractInterval}(AbstractInterval[]) -Base.copy(intervals::IntervalSet{T}) where T = IntervalSet{T}(copy(intervals.items)) +Base.copy(intervals::IntervalSet{T, A}) where {T, A} = IntervalSet{T, A}(copy(intervals.items)) Base.length(intervals::IntervalSet) = length(intervals.items) Base.iterate(intervals::IntervalSet, args...) = iterate(intervals.items, args...) Base.eltype(::IntervalSet{T}) where T = T @@ -110,8 +110,6 @@ end endpoint_tracking(a::IntervalSet, b::IntervalSet) = endpoint_tracking(eltype(a), eltype(b)) endpoint_tracking(a::AbstractInterval, b::AbstractInterval) = endpoint_tracking(typeof(a), typeof(b)) - -# TODO: Delete once union deprecation is gone. endpoint_tracking(a::AbstractVector, b::AbstractVector) = endpoint_tracking(eltype(a), eltype(b)) # track: run a thunk, but only if we are tracking endpoints dynamically From 7f8017d025ada26ace1ed8fdb15a000b0e9d69b9 Mon Sep 17 00:00:00 2001 From: David F Little Date: Fri, 17 Jun 2022 22:31:08 +0000 Subject: [PATCH 14/29] type cleanup --- src/interval_sets.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 9cac769f..3e844c05 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -52,11 +52,11 @@ struct IntervalSet{I <: AbstractInterval, A <: AbstractVector{I}} items::A end -IntervalSet(v::AbstractVector) = IntervalSet{eltype(v)}(v) -IntervalSet(interval::T) where T <: AbstractInterval = IntervalSet{T}([interval]) +IntervalSet{T}(intervals) where T <: AbstractInterval = IntervalSet{T, Vector{T}}(intervals) +IntervalSet(interval::T) where T <: AbstractInterval = IntervalSet{T, Vector{T}}([interval]) IntervalSet(interval::IntervalSet) = interval IntervalSet(itr) = IntervalSet{eltype(itr)}(collect(itr)) -IntervalSet() = IntervalSet{AbstractInterval}(AbstractInterval[]) +IntervalSet() = IntervalSet(AbstractInterval[]) Base.copy(intervals::IntervalSet{T, A}) where {T, A} = IntervalSet{T, A}(copy(intervals.items)) Base.length(intervals::IntervalSet) = length(intervals.items) From e077615398bb9bec4e880b3b726337a3d65c49e1 Mon Sep 17 00:00:00 2001 From: David F Little Date: Tue, 21 Jun 2022 13:26:47 +0000 Subject: [PATCH 15/29] removed array-based type alias --- src/interval_sets.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 3e844c05..8de76950 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -68,9 +68,7 @@ Base.Array(intervals::IntervalSet) = intervals.items # currently (to avoid breaking changes) new methods for `Base` # accept `IntervalSet` objects and Interval singletons. -const AbstractIntervalSets = Union{AbstractInterval, IntervalSet} -# internal methods need to operate on both sets and arrays of intervals -const AbstractIntervals = Union{AbstractIntervalSets, AbstractArray{<:AbstractInterval}} +const AbstractIntervals = Union{AbstractInterval, IntervalSet} # During merge operations used to compute unions, intersections etc..., # endpoint types can change (from left to right, and from open to closed, @@ -132,7 +130,8 @@ function unbunch(interval::AbstractInterval, tracking::EndpointTracking; lt=isle return endpoint_type(tracking)[LeftEndpoint(interval), RightEndpoint(interval)] end unbunch_by_fn(_) = identity -function unbunch(intervals::Union{AbstractIntervals, Base.Iterators.Enumerate{<:AbstractIntervals}}, +function unbunch(intervals::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals, + Base.Iterators.Enumerate{<:AbstractIntervals}}, tracking::EndpointTracking; lt=isless) by = unbunch_by_fn(intervals) filtered = Iterators.filter(!isempty ∘ by, intervals) @@ -147,7 +146,8 @@ function unbunch((i, interval)::Tuple, tracking; lt=isless) return eltype[(i, LeftEndpoint(interval)), (i, RightEndpoint(interval))] end -function unbunch(a::AbstractIntervals, b::AbstractIntervals; kwargs...) +function unbunch(a::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals, + b::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals}; kwargs...) tracking = endpoint_tracking(a, b) a_ = unbunch(a, tracking; kwargs...) b_ = unbunch(b, tracking; kwargs...) @@ -424,8 +424,8 @@ end """ find_intersections( - x::Union{AbstractInterval, IntervalSet}, - y::Union{AbstractInterval, IntervalSet}, + x::AbstractVector{<:AbstractInterval}, + y::AbstractVector{<:AbstractInterval} ) Returns a `Vector{Vector{Int}}` where the value at index `i` gives the indices to all From 3ec49101b563ae3b148d76ec95339e84177ecb69 Mon Sep 17 00:00:00 2001 From: David F Little Date: Tue, 21 Jun 2022 13:27:05 +0000 Subject: [PATCH 16/29] fixed typo --- src/interval_sets.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 8de76950..d0c42d06 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -146,7 +146,7 @@ function unbunch((i, interval)::Tuple, tracking; lt=isless) return eltype[(i, LeftEndpoint(interval)), (i, RightEndpoint(interval))] end -function unbunch(a::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals, +function unbunch(a::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals}, b::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals}; kwargs...) tracking = endpoint_tracking(a, b) a_ = unbunch(a, tracking; kwargs...) From fe6bb9163c0d75fb2be2891a01235069b0e89284 Mon Sep 17 00:00:00 2001 From: David F Little Date: Tue, 21 Jun 2022 13:41:34 +0000 Subject: [PATCH 17/29] fixed missing set alias --- src/interval_sets.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index d0c42d06..35e03dd6 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -401,10 +401,10 @@ Base.intersect(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && Base.union(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx || iny, x, y) Base.setdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && !iny, x, y) Base.symdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx ⊻ iny, x, y) -Base.issubset(x::AbstractIntervalSets, y::AbstractIntervalSets) = isempty(setdiff(x, y)) -Base.isdisjoint(x::AbstractIntervalSets, y::AbstractIntervalSets) = isempty(intersect(x, y)) +Base.issubset(x::AbstractIntervals, y::AbstractIntervals) = isempty(setdiff(x, y)) +Base.isdisjoint(x::AbstractIntervals, y::AbstractIntervals) = isempty(intersect(x, y)) -function Base.issetequal(x::AbstractIntervalSets, y::AbstractIntervalSets) +function Base.issetequal(x::AbstractIntervals, y::AbstractIntervals) x, y, tracking = unbunch(union(IntervalSet(x)), union(IntervalSet(y))) return x == y || all(isempty, bunch(x, tracking)) && all(isempty, bunch(y, tracking)) end From f8a9a908d5657485f9454bd0f5e15fcd7ab98838 Mon Sep 17 00:00:00 2001 From: David F Little Date: Tue, 21 Jun 2022 17:05:09 +0000 Subject: [PATCH 18/29] fix method signature --- src/interval_sets.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 35e03dd6..0d08c4c1 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -131,7 +131,8 @@ function unbunch(interval::AbstractInterval, tracking::EndpointTracking; lt=isle end unbunch_by_fn(_) = identity function unbunch(intervals::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals, - Base.Iterators.Enumerate{<:AbstractIntervals}}, + Base.Iterators.Enumerate{<:Union{AbstractIntervals, + AbstractVector{<:AbstractInterval}}}}, tracking::EndpointTracking; lt=isless) by = unbunch_by_fn(intervals) filtered = Iterators.filter(!isempty ∘ by, intervals) From 525e59fc4a98fb4e6d6438f314288f09983dc9c6 Mon Sep 17 00:00:00 2001 From: David F Little Date: Tue, 21 Jun 2022 17:53:56 +0000 Subject: [PATCH 19/29] changed `Array` to `convert(Array` --- src/interval_sets.jl | 16 ++++++++-------- test/sets.jl | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 0d08c4c1..8ea4ade8 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -14,35 +14,35 @@ see also: https://en.wikipedia.org/wiki/Interval_arithmetic#Interval_operators ```jldoctest julia> using Intervals -julia> Array(union(IntervalSet(1..5), IntervalSet(3..8))) +julia> convert(Array, union(IntervalSet(1..5), IntervalSet(3..8))) 1-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(1, 8) -julia> Array(intersect(IntervalSet(1..5), IntervalSet(3..8))) +julia> convert(Array, intersect(IntervalSet(1..5), IntervalSet(3..8))) 1-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(3, 5) -julia> Array(symdiff(IntervalSet(1..5), IntervalSet(3..8))) +julia> convert(Array, symdiff(IntervalSet(1..5), IntervalSet(3..8))) 2-element Vector{Interval{Int64}}: Interval{Int64, Closed, Open}(1, 3) Interval{Int64, Open, Closed}(5, 8) -julia> Array(union(IntervalSet([1..2, 2..5]), IntervalSet(6..7))) +julia> convert(Array, union(IntervalSet([1..2, 2..5]), IntervalSet(6..7))) 2-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(1, 5) Interval{Int64, Closed, Closed}(6, 7) -julia> Array(union(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) +julia> convert(Array, union(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) 2-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(1, 10) Interval{Int64, Closed, Closed}(12, 14) -julia> Array(intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) +julia> convert(Array, intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) 2-element Vector{Interval{Int64, Closed, Closed}}: Interval{Int64, Closed, Closed}(4, 5) Interval{Int64, Closed, Closed}(8, 9) -julia> Array(setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) +julia> convert(Array, setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) 2-element Vector{Interval{Int64}}: Interval{Int64, Closed, Open}(1, 4) Interval{Int64, Open, Closed}(9, 10) @@ -64,7 +64,7 @@ Base.iterate(intervals::IntervalSet, args...) = iterate(intervals.items, args... Base.eltype(::IntervalSet{T}) where T = T Base.:(==)(a::IntervalSet, b::IntervalSet) = a.items == b.items Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a, b) -Base.Array(intervals::IntervalSet) = intervals.items +Base.convert(::Type{T}, intervals::IntervalSet) where T <: AbstractArray = convert(T, intervals.items) # currently (to avoid breaking changes) new methods for `Base` # accept `IntervalSet` objects and Interval singletons. diff --git a/test/sets.jl b/test/sets.jl index a10a5afd..51c503a3 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -50,7 +50,7 @@ end @test isdisjoint(setdiff(a, b), b) @test !isdisjoint(a, a) - intersections = find_intersections(Array(a), Array(b)) + intersections = find_intersections(convert(Array, a), convert(Array, b)) # verify that all indices returned in `find_intersections` correspond to sets # in b that overlap with the given set in a From 5453749b3b7d381d0a544c16611f8a3f27ab77c6 Mon Sep 17 00:00:00 2001 From: David F Little Date: Wed, 22 Jun 2022 14:25:05 +0000 Subject: [PATCH 20/29] revisions based on review --- src/interval_sets.jl | 94 ++++++++++++++++++++++---------------------- test/sets.jl | 14 ++++++- 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 8ea4ade8..48401662 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -12,59 +12,54 @@ see also: https://en.wikipedia.org/wiki/Interval_arithmetic#Interval_operators ## Examples -```jldoctest -julia> using Intervals -julia> convert(Array, union(IntervalSet(1..5), IntervalSet(3..8))) -1-element Vector{Interval{Int64, Closed, Closed}}: - Interval{Int64, Closed, Closed}(1, 8) - -julia> convert(Array, intersect(IntervalSet(1..5), IntervalSet(3..8))) -1-element Vector{Interval{Int64, Closed, Closed}}: - Interval{Int64, Closed, Closed}(3, 5) - -julia> convert(Array, symdiff(IntervalSet(1..5), IntervalSet(3..8))) -2-element Vector{Interval{Int64}}: - Interval{Int64, Closed, Open}(1, 3) - Interval{Int64, Open, Closed}(5, 8) - -julia> convert(Array, union(IntervalSet([1..2, 2..5]), IntervalSet(6..7))) -2-element Vector{Interval{Int64, Closed, Closed}}: - Interval{Int64, Closed, Closed}(1, 5) - Interval{Int64, Closed, Closed}(6, 7) - -julia> convert(Array, union(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) -2-element Vector{Interval{Int64, Closed, Closed}}: - Interval{Int64, Closed, Closed}(1, 10) - Interval{Int64, Closed, Closed}(12, 14) - -julia> convert(Array, intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) -2-element Vector{Interval{Int64, Closed, Closed}}: - Interval{Int64, Closed, Closed}(4, 5) - Interval{Int64, Closed, Closed}(8, 9) - -julia> convert(Array, setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))) -2-element Vector{Interval{Int64}}: - Interval{Int64, Closed, Open}(1, 4) - Interval{Int64, Open, Closed}(9, 10) +```jldoctest; setup = :(using Intervals) +julia> union(IntervalSet(1..5), IntervalSet(3..8)) +[1 .. 8] + +julia> intersect(IntervalSet(1..5), IntervalSet(3..8)) +[3 .. 5] + +julia> symdiff(IntervalSet(1..5), IntervalSet(3..8)) +[1 .. 3) ∪ (5 .. 8] + +julia> union(IntervalSet([1..2, 2..5]), IntervalSet(6..7)) +[1 .. 5] ∪ [6 .. 7] + +julia> union(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) +[1 .. 10] ∪ [12 .. 14] + +julia> intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) +[4 .. 5] ∪ [8 .. 9] + +julia> setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) +[1 .. 4) ∪ (9 .. 10] ``` """ -struct IntervalSet{I <: AbstractInterval, A <: AbstractVector{I}} - items::A +struct IntervalSet{T <: AbstractInterval} + items::Vector{T} end -IntervalSet{T}(intervals) where T <: AbstractInterval = IntervalSet{T, Vector{T}}(intervals) -IntervalSet(interval::T) where T <: AbstractInterval = IntervalSet{T, Vector{T}}([interval]) +IntervalSet(interval::T) where T <: AbstractInterval = IntervalSet{T}([interval]) IntervalSet(interval::IntervalSet) = interval IntervalSet(itr) = IntervalSet{eltype(itr)}(collect(itr)) IntervalSet() = IntervalSet(AbstractInterval[]) -Base.copy(intervals::IntervalSet{T, A}) where {T, A} = IntervalSet{T, A}(copy(intervals.items)) +Base.copy(intervals::IntervalSet{T}) where {T} = IntervalSet{T}(copy(intervals.items)) Base.length(intervals::IntervalSet) = length(intervals.items) Base.iterate(intervals::IntervalSet, args...) = iterate(intervals.items, args...) Base.eltype(::IntervalSet{T}) where T = T -Base.:(==)(a::IntervalSet, b::IntervalSet) = a.items == b.items -Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a, b) +Base.:(==)(a::IntervalSet, b::IntervalSet) = issetequal(a, b) +Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a.items, b.items) Base.convert(::Type{T}, intervals::IntervalSet) where T <: AbstractArray = convert(T, intervals.items) +function Base.show(io::IO, ::MIME"text/plain", x::IntervalSet) + intervals = union(x) + iocompact = IOContext(io, :compact => true) + for interval in intervals.items[1:(end-1)] + show(iocompact, MIME"text/plain"(), interval) + print(io, " ∪ ") + end + show(iocompact, MIME"text/plain"(), intervals.items[end]) +end # currently (to avoid breaking changes) new methods for `Base` # accept `IntervalSet` objects and Interval singletons. @@ -410,6 +405,9 @@ function Base.issetequal(x::AbstractIntervals, y::AbstractIntervals) return x == y || all(isempty, bunch(x, tracking)) && all(isempty, bunch(y, tracking)) end +# when `x` is number-like object (Number, Date, Time, etc...): +Base.in(x, y::IntervalSet) = any(Base.Fix1(in, x), y) + # order edges so that closed boundaries are on the outside: e.g. [( )] intersection_order(x::Endpoint) = isleft(x) ? !isclosed(x) : isclosed(x) intersection_isless_fn(::TrackStatically) = isless @@ -432,15 +430,15 @@ end Returns a `Vector{Vector{Int}}` where the value at index `i` gives the indices to all intervals in `y` that intersect with `x[i]`. """ -function find_intersections(x_::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, y_::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}) - xa, ya = vcat(x_), vcat(y_) - tracking = endpoint_tracking(xa, ya) +find_intersections(x, y) = find_intersections(vcat(x), vcat(y)) +function find_intersections(x::AbstractVector{<:AbstractInterval}, y::AbstractVector{<:AbstractInterval}) + tracking = endpoint_tracking(x, y) lt = intersection_isless_fn(tracking) - x = unbunch(enumerate(xa), tracking; lt) - y = unbunch(enumerate(ya), tracking; lt) - result = [Vector{Int}() for _ in 1:length(xa)] + x_endpoints = unbunch(enumerate(x), tracking; lt) + y_endpoints = unbunch(enumerate(y), tracking; lt) + result = [Vector{Int}() for _ in 1:length(x)] - return find_intersections_helper!(result, x, y, lt) + return find_intersections_helper!(result, x_endpoints, y_endpoints, lt) end function find_intersections_helper!(result, x, y, lt) diff --git a/test/sets.jl b/test/sets.jl index 51c503a3..600de81a 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -36,10 +36,20 @@ end rand_bound_type(rng) = rand(rng, (Closed, Open)) - # verify case where we interpret array as a set of intervals (rather than a set of - # points) + # verify case where we interpret array as a set of elements (rather than an + # interval-bound point set) @test intersect([1..2, 2..3, 3..4, 4..5], [2..3, 3..4]) == [2..3, 3..4] + # verify that elements are in / subsets of interval sets + @test 2 ∈ IntervalSet([1..3, 5..10]) + @test 0 ∉ IntervalSet([1..3, 5..10]) + @test 4 ∉ IntervalSet([1..3, 5..10]) + @test 11 ∉ IntervalSet([1..3, 5..10]) + @test issubset(2, IntervalSet([1..3, 5..10])) + @test !issubset(0, IntervalSet([1..3, 5..10])) + @test !issubset(4, IntervalSet([1..3, 5..10])) + @test !issubset(11, IntervalSet([1..3, 5..10])) + function testsets(a, b) @test area(a ∪ b) ≤ area(myunion(a)) + area(myunion(b)) @test area(setdiff(a, b)) ≤ area(myunion(a)) From c170ffcf956f4060a5b86a25db317f471b31ee5a Mon Sep 17 00:00:00 2001 From: David F Little Date: Thu, 23 Jun 2022 16:09:15 +0000 Subject: [PATCH 21/29] revised show method --- src/interval_sets.jl | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 48401662..a2ebb04c 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -14,25 +14,37 @@ see also: https://en.wikipedia.org/wiki/Interval_arithmetic#Interval_operators ```jldoctest; setup = :(using Intervals) 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)) -[1 .. 3) ∪ (5 .. 8] +2-interval IntervalSet{Interval{Int64}}: +[1 .. 3) +(5 .. 8] julia> union(IntervalSet([1..2, 2..5]), IntervalSet(6..7)) -[1 .. 5] ∪ [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])) -[1 .. 10] ∪ [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])) -[4 .. 5] ∪ [8 .. 9] +2-interval IntervalSet{Interval{Int64, Closed, Closed}}: +[4 .. 5] +[8 .. 9] julia> setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) -[1 .. 4) ∪ (9 .. 10] +2-interval IntervalSet{Interval{Int64}}: +[1 .. 4) +(9 .. 10] ``` """ struct IntervalSet{T <: AbstractInterval} @@ -54,11 +66,14 @@ Base.convert(::Type{T}, intervals::IntervalSet) where T <: AbstractArray = conve function Base.show(io::IO, ::MIME"text/plain", x::IntervalSet) intervals = union(x) iocompact = IOContext(io, :compact => true) + print(io, "$(length(intervals))-interval ") + show(io, MIME"text/plain"(), typeof(x)) + println(io, ":") for interval in intervals.items[1:(end-1)] show(iocompact, MIME"text/plain"(), interval) - print(io, " ∪ ") + println(io, "") end - show(iocompact, MIME"text/plain"(), intervals.items[end]) + isempty(intervals) || show(iocompact, MIME"text/plain"(), intervals.items[end]) end # currently (to avoid breaking changes) new methods for `Base` From 99b5c6e58abe399ac3a16bf9e26d9a747d42e2f1 Mon Sep 17 00:00:00 2001 From: David F Little Date: Thu, 23 Jun 2022 16:35:33 +0000 Subject: [PATCH 22/29] handle display size --- src/interval_sets.jl | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index a2ebb04c..7aef1153 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -63,17 +63,32 @@ Base.eltype(::IntervalSet{T}) where T = T Base.:(==)(a::IntervalSet, b::IntervalSet) = issetequal(a, b) Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a.items, b.items) Base.convert(::Type{T}, intervals::IntervalSet) where T <: AbstractArray = convert(T, intervals.items) -function Base.show(io::IO, ::MIME"text/plain", x::IntervalSet) +function Base.show(io::Base.AbstractPipe, ::MIME"text/plain", x::IntervalSet) intervals = union(x) iocompact = IOContext(io, :compact => true) print(io, "$(length(intervals))-interval ") show(io, MIME"text/plain"(), typeof(x)) println(io, ":") - for interval in intervals.items[1:(end-1)] - show(iocompact, MIME"text/plain"(), interval) - println(io, "") + nrows = displaysize(io)[1] + half = fld(nrows, 2) - 2 + if nrows ≥ length(intervals.items) && half > 1 + for interval in intervals.items[1:(end-1)] + show(iocompact, MIME"text/plain"(), interval) + println(io, "") + end + isempty(intervals) || show(iocompact, MIME"text/plain"(), intervals.items[end]) + else + for interval in intervals.items[1:half] + show(iocompact, MIME"text/plain"(), interval) + println(io, "") + end + println(io, "⋮") + for interval in intervals.items[(end-half+1):end-1] + show(iocompact, MIME"text/plain"(), interval) + println(io, "") + end + show(iocompact, MIME"text/plain"(), intervals.items[end]) end - isempty(intervals) || show(iocompact, MIME"text/plain"(), intervals.items[end]) end # currently (to avoid breaking changes) new methods for `Base` From a011e00b270b69906a3a8ec02f7cbb4d0dc3d2e4 Mon Sep 17 00:00:00 2001 From: rofinn Date: Thu, 23 Jun 2022 15:32:13 -0700 Subject: [PATCH 23/29] Fixed doctests for Julia 1.6 and made the IntervalSet non-iterable. --- src/deprecated.jl | 4 ++-- src/interval_sets.jl | 36 ++++++++++++++++++++++-------------- test/sets.jl | 44 ++++++++++++++++++++++---------------------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/deprecated.jl b/src/deprecated.jl index a549e981..e6c370b5 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -195,8 +195,8 @@ function HB(anchor, inc::Inclusivity) return HourBeginning{L,R}(floor(anchor, Hour)) end -@deprecate union(intervals::AbstractVector{<:AbstractInterval}) collect(union(IntervalSet(intervals))) -@deprecate union!(intervals::AbstractVector{<:AbstractInterval}) collect(union!(IntervalSet(intervals))) +@deprecate union(intervals::AbstractVector{<:AbstractInterval}) convert(Vector, union(IntervalSet(intervals))) +@deprecate union!(intervals::AbstractVector{<:AbstractInterval}) convert(Vector, union!(IntervalSet(intervals))) @deprecate superset(intervals::AbstractVector{<:AbstractInterval}) superset(IntervalSet(intervals)) # END Intervals 1.X.Y deprecations diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 7aef1153..0ca7f389 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -22,7 +22,7 @@ julia> intersect(IntervalSet(1..5), IntervalSet(3..8)) [3 .. 5] julia> symdiff(IntervalSet(1..5), IntervalSet(3..8)) -2-interval IntervalSet{Interval{Int64}}: +2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}: [1 .. 3) (5 .. 8] @@ -42,7 +42,7 @@ julia> intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) [8 .. 9] julia> setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) -2-interval IntervalSet{Interval{Int64}}: +2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}: [1 .. 4) (9 .. 10] ``` @@ -57,21 +57,21 @@ IntervalSet(itr) = IntervalSet{eltype(itr)}(collect(itr)) IntervalSet() = IntervalSet(AbstractInterval[]) Base.copy(intervals::IntervalSet{T}) where {T} = IntervalSet{T}(copy(intervals.items)) -Base.length(intervals::IntervalSet) = length(intervals.items) -Base.iterate(intervals::IntervalSet, args...) = iterate(intervals.items, args...) Base.eltype(::IntervalSet{T}) where T = T +Base.isempty(intervals::IntervalSet) = isempty(intervals.items) || all(isempty, intervals.items) Base.:(==)(a::IntervalSet, b::IntervalSet) = issetequal(a, b) Base.isequal(a::IntervalSet, b::IntervalSet) = isequal(a.items, b.items) Base.convert(::Type{T}, intervals::IntervalSet) where T <: AbstractArray = convert(T, intervals.items) -function Base.show(io::Base.AbstractPipe, ::MIME"text/plain", x::IntervalSet) +function Base.show(io::Base.AbstractPipe, ::MIME"text/plain", x::IntervalSet) intervals = union(x) + n = length(intervals.items) iocompact = IOContext(io, :compact => true) - print(io, "$(length(intervals))-interval ") + print(io, "$n-interval ") show(io, MIME"text/plain"(), typeof(x)) println(io, ":") nrows = displaysize(io)[1] half = fld(nrows, 2) - 2 - if nrows ≥ length(intervals.items) && half > 1 + if nrows ≥ n && half > 1 for interval in intervals.items[1:(end-1)] show(iocompact, MIME"text/plain"(), interval) println(io, "") @@ -154,11 +154,18 @@ interval_type(::TrackRightOpen{T}) where T = Interval{T, Closed, Open} function unbunch(interval::AbstractInterval, tracking::EndpointTracking; lt=isless) return endpoint_type(tracking)[LeftEndpoint(interval), RightEndpoint(interval)] end +function unbunch(intervals::IntervalSet, tracking::EndpointTracking; kwargs...) + return unbunch(convert(Vector, intervals), tracking; kwargs...) +end unbunch_by_fn(_) = identity -function unbunch(intervals::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals, - Base.Iterators.Enumerate{<:Union{AbstractIntervals, - AbstractVector{<:AbstractInterval}}}}, - tracking::EndpointTracking; lt=isless) +function unbunch( + intervals::Union{ + AbstractVector{<:AbstractInterval}, + Base.Iterators.Enumerate{<:Union{AbstractIntervals, AbstractVector{<:AbstractInterval}}} + }, + tracking::EndpointTracking; + lt=isless, +) by = unbunch_by_fn(intervals) filtered = Iterators.filter(!isempty ∘ by, intervals) isempty(filtered) && return endpoint_type(tracking)[] @@ -172,7 +179,7 @@ function unbunch((i, interval)::Tuple, tracking; lt=isless) return eltype[(i, LeftEndpoint(interval)), (i, RightEndpoint(interval))] end -function unbunch(a::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals}, +function unbunch(a::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals}, b::Union{AbstractVector{<:AbstractInterval}, AbstractIntervals}; kwargs...) tracking = endpoint_tracking(a, b) a_ = unbunch(a, tracking; kwargs...) @@ -427,16 +434,17 @@ Base.intersect(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && Base.union(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx || iny, x, y) Base.setdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && !iny, x, y) Base.symdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx ⊻ iny, x, y) +Base.issubset(x::T, y::IntervalSet{<:AbstractInterval{T}}) where {T} = any(i -> x in i, y.items) Base.issubset(x::AbstractIntervals, y::AbstractIntervals) = isempty(setdiff(x, y)) Base.isdisjoint(x::AbstractIntervals, y::AbstractIntervals) = isempty(intersect(x, y)) function Base.issetequal(x::AbstractIntervals, y::AbstractIntervals) x, y, tracking = unbunch(union(IntervalSet(x)), union(IntervalSet(y))) - return x == y || all(isempty, bunch(x, tracking)) && all(isempty, bunch(y, tracking)) + return x == y || isempty(bunch(x, tracking)) && isempty(bunch(y, tracking)) end # when `x` is number-like object (Number, Date, Time, etc...): -Base.in(x, y::IntervalSet) = any(Base.Fix1(in, x), y) +Base.in(x, y::IntervalSet) = any(Base.Fix1(in, x), y.items) # order edges so that closed boundaries are on the outside: e.g. [( )] intersection_order(x::Endpoint) = isleft(x) ? !isclosed(x) : isclosed(x) diff --git a/test/sets.jl b/test/sets.jl index 600de81a..38ac987d 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -81,10 +81,10 @@ end # a few taylored interval sets a = IntervalSet([Interval(i, i + 3) for i in 1:5:15]) b = IntervalSet(a.items .+ (1:2:5)) - @test all(first.(a) .∈ a) + @test all(x -> first(x) ∈ a, a.items) testsets(a, b) - testsets(IntervalSet(first(a)), b) - testsets(a, IntervalSet(first(b))) + testsets(IntervalSet(a.items[1]), b) + testsets(a, IntervalSet(b.items[1])) # verify that `last` need not be ordered intervals = IntervalSet([Interval(0, 5), Interval(0, 3)]) @@ -99,34 +99,34 @@ end a = IntervalSet(Interval.(starts, ends)) b = IntervalSet(Interval.(starts .+ offsets, ends .+ offsets)) - @test all(first.(a) .∈ a) + @test all(x -> first(x) ∈ a, a.items) testsets(a, b) - testsets(IntervalSet(first(a)), b) - testsets(a, IntervalSet(first(b))) + testsets(IntervalSet(first(a.items)), b) + testsets(a, IntervalSet(first(b.items))) a = IntervalSet(Interval{rand_bound_type(rng), rand_bound_type(rng)}.(starts, ends)) b = IntervalSet(Interval{rand_bound_type(rng), rand_bound_type(rng)}.(starts .+ offsets, ends .+ offsets)) testsets(a, b) - testsets(IntervalSet(first(a)), b) - testsets(a, IntervalSet(first(b))) + testsets(IntervalSet(first(a.items)), b) + testsets(a, IntervalSet(first(b.items))) a = IntervalSet(Interval{Closed, Open}.(starts, ends)) b = IntervalSet(Interval{Closed, Open}.(starts .+ offsets, ends .+ offsets)) @test Intervals.endpoint_tracking(a, b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(IntervalSet(first(a)), b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(a, IntervalSet(first(b))) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(IntervalSet(first(a.items)), b) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(a, IntervalSet(first(b.items))) isa Intervals.TrackStatically testsets(a, b) - testsets(IntervalSet(first(a)), b) - testsets(a, IntervalSet(first(b))) + testsets(IntervalSet(first(a.items)), b) + testsets(a, IntervalSet(first(b.items))) a = IntervalSet(Interval{Open, Closed}.(starts, ends)) b = IntervalSet(Interval{Open, Closed}.(starts .+ offsets, ends .+ offsets)) @test Intervals.endpoint_tracking(a, b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(IntervalSet(first(a)), b) isa Intervals.TrackStatically - @test Intervals.endpoint_tracking(a, IntervalSet(first(b))) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(IntervalSet(first(a.items)), b) isa Intervals.TrackStatically + @test Intervals.endpoint_tracking(a, IntervalSet(first(b.items))) isa Intervals.TrackStatically testsets(a, b) - testsets(IntervalSet(first(a)), b) - testsets(a, IntervalSet(first(b))) + testsets(IntervalSet(first(a.items)), b) + testsets(a, IntervalSet(first(b.items))) randint(x::Interval) = Interval{rand_bound_type(rng), rand_bound_type(rng)}(first(x), last(x)) leftint(x::Interval) = Interval{Closed, Open}(first(x), last(x)) @@ -134,10 +134,10 @@ end a = IntervalSet(Interval{Closed, Open}.(starts, ends)) b = IntervalSet(Interval{Closed, Open}.(starts .+ offsets, ends .+ offsets)) - testsets(a, IntervalSet(randint.(b))) - testsets(IntervalSet(first(a)), IntervalSet(randint.(b))) - testsets(a, IntervalSet(leftint.(IntervalSet(first(b))))) - testsets(a, IntervalSet(rightint.(b))) - testsets(IntervalSet(first(a)), IntervalSet(rightint.(b))) - testsets(a, IntervalSet(rightint.(IntervalSet(first(b))))) + testsets(a, IntervalSet(randint.(b.items))) + testsets(IntervalSet(first(a.items)), IntervalSet(randint.(b.items))) + testsets(a, IntervalSet(leftint.(first(b.items)))) + testsets(a, IntervalSet(rightint.(b.items))) + testsets(IntervalSet(first(a.items)), IntervalSet(rightint.(b.items))) + testsets(a, IntervalSet(rightint.(first(b.items)))) end From 1a5713286c080f0a35c5b9db11bc55bbb4cb2bb4 Mon Sep 17 00:00:00 2001 From: rofinn Date: Thu, 23 Jun 2022 15:36:53 -0700 Subject: [PATCH 24/29] Build documentation on 1.6 for now. --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index dcec41d6..13a4973f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -79,7 +79,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 with: - version: '1' + version: '1.6' - run: | julia --project=docs -e ' using Pkg From ca36a293cf54538a881ac391c52f5ea45ab2ba98 Mon Sep 17 00:00:00 2001 From: rofinn Date: Fri, 24 Jun 2022 11:23:05 -0700 Subject: [PATCH 25/29] Added a few more issubset signatures that don't work because we're falling back to the base `setdiff`. --- test/sets.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/sets.jl b/test/sets.jl index 38ac987d..8dd0f077 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -49,6 +49,11 @@ end @test !issubset(0, IntervalSet([1..3, 5..10])) @test !issubset(4, IntervalSet([1..3, 5..10])) @test !issubset(11, IntervalSet([1..3, 5..10])) + @test issubset(2, IntervalSet([1.0 .. 3.0, 5.0 .. 10.0])) + @test issubset(2 .. 4, IntervalSet([1 .. 5, 7 .. 9])) + @test !issubset(2 .. 4, IntervalSet([1 ..3, 5 .. 10])) + @test issubset(IntervalSet([1 .. 3, 5 .. 10]), 1 .. 20) + @test !issubset(IntervalSet([1 .. 3, 5 .. 10]), 1 .. 9) function testsets(a, b) @test area(a ∪ b) ≤ area(myunion(a)) + area(myunion(b)) From 40aa82ba72d1e0f4012f3822937332402441abbc Mon Sep 17 00:00:00 2001 From: rofinn Date: Fri, 24 Jun 2022 11:47:32 -0700 Subject: [PATCH 26/29] Fix isssubset signatures. --- src/interval_sets.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 0ca7f389..06b23952 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -434,10 +434,13 @@ Base.intersect(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && Base.union(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx || iny, x, y) Base.setdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && !iny, x, y) Base.symdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx ⊻ iny, x, y) -Base.issubset(x::T, y::IntervalSet{<:AbstractInterval{T}}) where {T} = any(i -> x in i, y.items) -Base.issubset(x::AbstractIntervals, y::AbstractIntervals) = isempty(setdiff(x, y)) Base.isdisjoint(x::AbstractIntervals, y::AbstractIntervals) = isempty(intersect(x, y)) +Base.issubset(x, y::IntervalSet) = x in y +Base.issubset(x::AbstractInterval, y::IntervalSet) = any(Base.Fix1(issubset, x), y.items) +Base.issubset(x::IntervalSet, y::AbstractInterval) = all(Base.Fix2(issubset, y), x.items) +Base.issubset(x::IntervalSet, y::IntervalSet) = isempty(setdiff(x, y)) + function Base.issetequal(x::AbstractIntervals, y::AbstractIntervals) x, y, tracking = unbunch(union(IntervalSet(x)), union(IntervalSet(y))) return x == y || isempty(bunch(x, tracking)) && isempty(bunch(y, tracking)) From 212469e5343051e799ec9e18fc78f1219c7f7c5b Mon Sep 17 00:00:00 2001 From: rofinn Date: Fri, 24 Jun 2022 14:34:06 -0700 Subject: [PATCH 27/29] More set operation tests for mismatched Interval and IntervalSet arguments. --- test/sets.jl | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/sets.jl b/test/sets.jl index 8dd0f077..e28e24fc 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -50,10 +50,6 @@ end @test !issubset(4, IntervalSet([1..3, 5..10])) @test !issubset(11, IntervalSet([1..3, 5..10])) @test issubset(2, IntervalSet([1.0 .. 3.0, 5.0 .. 10.0])) - @test issubset(2 .. 4, IntervalSet([1 .. 5, 7 .. 9])) - @test !issubset(2 .. 4, IntervalSet([1 ..3, 5 .. 10])) - @test issubset(IntervalSet([1 .. 3, 5 .. 10]), 1 .. 20) - @test !issubset(IntervalSet([1 .. 3, 5 .. 10]), 1 .. 9) function testsets(a, b) @test area(a ∪ b) ≤ area(myunion(a)) + area(myunion(b)) @@ -78,6 +74,24 @@ end @test all(enumerate(intersections)) do (i, x) isempty(intersect(IntervalSet(a.items[i]), IntervalSet(b.items[Not(x)]))) end + + # Test f(interval, intervalset) and f(intervalset, interval) methods work the same + # as the regular f(intervalset, intervalset) methods + if a isa IntervalSet && b isa IntervalSet + for f in (intersect, union, setdiff, symdiff, isdisjoint, issubset) + # We'll just use the first and last entries as sample intervals for these + # tests rather than an exhausting search + x = first(a.items) + @test f(x, b) == f(IntervalSet([x]), b) + x = last(a.items) + @test f(x, b) == f(IntervalSet([x]), b) + + y = first(b.items) + @test f(a, y) == f(a, IntervalSet([y])) + y = last(b.items) + @test f(a, y) == f(a, IntervalSet([y])) + end + end end # verify empty interval set From 376484a20a19f0719052dba03132dcfe6b9df16b Mon Sep 17 00:00:00 2001 From: rofinn Date: Fri, 24 Jun 2022 14:47:53 -0700 Subject: [PATCH 28/29] Fix interval set operation which were incorrectly falling back to base. --- src/interval_sets.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 06b23952..3bfb4e85 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -434,6 +434,7 @@ Base.intersect(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && Base.union(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx || iny, x, y) Base.setdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && !iny, x, y) Base.symdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx ⊻ iny, x, y) + Base.isdisjoint(x::AbstractIntervals, y::AbstractIntervals) = isempty(intersect(x, y)) Base.issubset(x, y::IntervalSet) = x in y @@ -441,6 +442,12 @@ Base.issubset(x::AbstractInterval, y::IntervalSet) = any(Base.Fix1(issubset, x), Base.issubset(x::IntervalSet, y::AbstractInterval) = all(Base.Fix2(issubset, y), x.items) Base.issubset(x::IntervalSet, y::IntervalSet) = isempty(setdiff(x, y)) +# Add methods where just 1 argument is an Interval +for f in (:intersect, :union, :setdiff, :symdiff) + @eval Base.$f(x::AbstractInterval, y::IntervalSet) = $f(IntervalSet([x]), y) + @eval Base.$f(x::IntervalSet, y::AbstractInterval) = $f(x, IntervalSet([y])) +end + function Base.issetequal(x::AbstractIntervals, y::AbstractIntervals) x, y, tracking = unbunch(union(IntervalSet(x)), union(IntervalSet(y))) return x == y || isempty(bunch(x, tracking)) && isempty(bunch(y, tracking)) From e891f305483365dd01da1153eb6fe2ca954fe209 Mon Sep 17 00:00:00 2001 From: Rory Finnegan Date: Thu, 30 Jun 2022 13:15:01 -0500 Subject: [PATCH 29/29] Apply suggestions from code review Co-authored-by: mattBrzezinski --- src/interval_sets.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/interval_sets.jl b/src/interval_sets.jl index 3bfb4e85..16211696 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -3,7 +3,7 @@ """ IntervalSet{T<:AbstractInterval} -An set of points represented by a sequence of intervals. Set operations over interval sets +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 using the iteration API or by passing the set to `Array`. @@ -434,7 +434,6 @@ Base.intersect(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && Base.union(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx || iny, x, y) Base.setdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx && !iny, x, y) Base.symdiff(x::IntervalSet, y::IntervalSet) = mergesets((inx, iny) -> inx ⊻ iny, x, y) - Base.isdisjoint(x::AbstractIntervals, y::AbstractIntervals) = isempty(intersect(x, y)) Base.issubset(x, y::IntervalSet) = x in y