Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix union and union! of intervals #74

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions src/interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -279,22 +279,24 @@ end

# There is power in a union.
"""
union(intervals::AbstractVector{<:AbstractInterval})
union(intervals::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, itrs...)

Flattens a vector of overlapping intervals into a new, smaller vector containing only
Flattens sets of overlapping intervals into a new, smaller vector containing only
non-overlapping intervals.
"""
function Base.union(intervals::AbstractVector{<:AbstractInterval})
return union!(convert(Vector{AbstractInterval}, intervals))
function Base.union(
interval::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, intervals...,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do:

Suggested change
interval::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, intervals...,
interval::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}, intervals::Union{AbstractInterval, AbstractVector{<:AbstractInterval}}...,

Avoids a call such as union(Interval(1,2), 3) causing a confusing error

)
return union!(AbstractInterval[interval; intervals...])
end
omus marked this conversation as resolved.
Show resolved Hide resolved

"""
union!(intervals::AbstractVector{<:Union{Interval, AbstractInterval}})
function Base.union!(intervals::AbstractVector{T}) where T <: AbstractInterval
if !(T <: Interval) && T != AbstractInterval
throw(ArgumentError(
"Cannot `union!` array of intervals of type $T as the type may change"
))
end
Copy link
Collaborator

@omus omus Jun 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use dispatch here for future interval types can support their own union! methods

function Base.union!(intervals::AbstractVector{<:AbstractInterval})
    throw(ArgumentError("Cannot `union!` array of intervals of type $(eltype(intervals)) as the type may change"))
end

Base.union!(intervals::AbstractVector{<:Union{AbstractInterval,Interval}) = ...

(I think this works)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the existing definition already allow that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my point here was that this approach in the PR isn't the best for new interval types that want to define their own union! method.


Flattens a vector of overlapping intervals in-place to be a smaller vector containing only
non-overlapping intervals.
"""
function Base.union!(intervals::Union{AbstractVector{<:Interval}, AbstractVector{AbstractInterval}})
sort!(intervals)

i = 2
Expand All @@ -319,6 +321,28 @@ function Base.union!(intervals::Union{AbstractVector{<:Interval}, AbstractVector
return intervals
end

"""
union!(intervals::AbstractVector{<:Union{Interval, AbstractInterval}}, itrs...)

Flattens sets of overlapping intervals in-place into the first argument, making it a smaller
vector containing only non-overlapping intervals.
"""
function union!(
intervals::AbstractVector{<:AbstractInterval},
interval_varargs...
)
# imitates the behaviour of vcat/promote_eltypeof
for interval_vararg in interval_varargs
if interval_vararg isa AbstractInterval
push!(intervals, interval_vararg)
else
append!(intervals, interval_vararg)
end
end

return union!(intervals)
end

"""
superset(intervals::AbstractArray{<:AbstractInterval}) -> Interval

Expand Down
46 changes: 46 additions & 0 deletions test/interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,18 @@
end

@testset "union" begin
a = Interval(-100, -1)
b = Interval(-3, 10)

@test union(a) == AbstractInterval[a]
@test union(b) == AbstractInterval[b]
@test union(a, b) == AbstractInterval[Interval(-100, 10)]
@test union(b, a) == AbstractInterval[Interval(-100, 10)]
@test union([a, b]) == AbstractInterval[Interval(-100, 10)]
@test union([b, a]) == AbstractInterval[Interval(-100, 10)]
@test union!([a, b]) == [Interval(-100, 10)]
@test union!([b, a]) == [Interval(-100, 10)]

intervals = [
Interval(-100, -1, Inclusivity(false, false)),
Interval(-10, -1, Inclusivity(false, false)),
Expand Down Expand Up @@ -572,5 +584,39 @@
Interval(-10, -1, Inclusivity(true, true))
]
@test union(intervals) == [Interval(-100, -1, Inclusivity(false, true))]

# anchored
intervals = [
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 12, 7, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 12, 7, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 13, 7, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 13, 7, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 14, 7, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 14, 7, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 12, 8, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 12, 8, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 13, 8, tz"UTC"), Inclusivity(false, true)),
HourEnding{ZonedDateTime}(ZonedDateTime(2013, 2, 13, 8, tz"UTC"), Inclusivity(false, true)),
]

expected = [
Interval{ZonedDateTime}(ZonedDateTime(2013, 2, 12, 6, tz"UTC"), ZonedDateTime(2013, 2, 12, 8, tz"UTC"), Inclusivity(false, true)),
Interval{ZonedDateTime}(ZonedDateTime(2013, 2, 13, 6, tz"UTC"), ZonedDateTime(2013, 2, 13, 8, tz"UTC"), Inclusivity(false, true)),
Interval{ZonedDateTime}(ZonedDateTime(2013, 2, 14, 6, tz"UTC"), ZonedDateTime(2013, 2, 14, 7, tz"UTC"), Inclusivity(false, true)),
]

@test union(intervals) == expected
@test_throws ArgumentError union!(intervals)

abstract_intervals = convert(Vector{AbstractInterval}, intervals)
@test union(abstract_intervals) == expected
@test length(abstract_intervals) == length(intervals) # test that union doesn't mutate
@test union!(abstract_intervals) == expected
@test abstract_intervals == expected # test that union! does mutate

# varargs
@test union(intervals...) == expected
@test_throws MethodError union!(intervals...)
@test union!(abstract_intervals[1:1], abstract_intervals[2:3], abstract_intervals[4:end]...) == expected
end
end