From 573ebe9e6cdb65d7dd9b28e315ab9be83cbc01c2 Mon Sep 17 00:00:00 2001 From: rofinn Date: Wed, 1 Jun 2022 20:19:22 -0700 Subject: [PATCH] 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