Skip to content

Commit

Permalink
Merge pull request #192 from invenia/revert-191-revert-179-dfl/set-op…
Browse files Browse the repository at this point in the history
…erations

Revert "Revert "Multi-interval set operators""
  • Loading branch information
rofinn authored Jun 30, 2022
2 parents cb4f4d5 + 718ed5a commit a785b56
Show file tree
Hide file tree
Showing 15 changed files with 1,383 additions and 161 deletions.
2 changes: 2 additions & 0 deletions .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
style = "blue"
margin = 92
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/docs/build/
/docs/site/
/Manifest.toml
/test/Manifest.toml
15 changes: 1 addition & 14 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name = "Intervals"
uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5"
license = "MIT"
authors = ["Invenia Technical Computing"]
version = "1.7.1"
version = "1.8.0"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand All @@ -12,19 +12,6 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53"

[compat]
Documenter = "0.23, 0.24, 0.25, 0.26, 0.27"
Infinity = "0.2.3"
RecipesBase = "0.7, 0.8, 1"
TimeZones = "1.7"
julia = "1.6"

[extras]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Infinity = "a303e19e-6eb4-11e9-3b09-cd9505f79100"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92"

[targets]
test = ["Documenter", "ImageMagick", "Infinity", "Plots", "Test", "VisualRegressionTests"]
86 changes: 52 additions & 34 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,38 @@ This package defines:
* [`Open`](@ref), indicating the endpoint value of the interval is not included
* [`Unbounded`](@ref), indicating the endpoint value is effectively infinite

## Example Usage
## Sets

### Intersection
A single interval can be used to represent a contiguous set within a domain but cannot be
used to represent a disjoint set. Due to this restriction all set-based operations that
return an interval will always return a vector of intervals. These operations will combine
any intervals which are overlapping or touching into a single continuous interval and never
return an interval instance which itself is empty.

```jldoctest
julia> a = 1..10
Interval{Int64, Closed, Closed}(1, 10)
```julia
julia> union([1..10], [5..15])
1-element Vector{Interval{Int64, Closed, Closed}}:
Interval{Int64, Closed, Closed}(1, 15)

julia> b = 5..15
Interval{Int64, Closed, Closed}(5, 15)
julia> intersect([1..10], [5..15])
1-element Vector{Interval{Int64, Closed, Closed}}:
Interval{Int64, Closed, Closed}(5, 10)

julia> intersect(a, b)
Interval{Int64, Closed, Closed}(5, 10)
julia> setdiff([1..10], [5..15])
1-element Vector{Interval{Int64, Closed, Open}}:
Interval{Int64, Closed, Open}(1, 5)

julia> symdiff([1..10], [5..15])
2-element Vector{Interval{Int64}}:
Interval{Int64, Closed, Open}(1, 5)
Interval{Int64, Open, Closed}(10, 15)

julia> intersect([1..5], [10..15])
Interval[]
```

## Example Usage

### Bounds

```jldoctest
Expand Down Expand Up @@ -135,31 +152,6 @@ julia> anchor(he)
2013-02-13T01:00:00-06:00
```

### Plotting
`AbstractInterval` subtypes can be plotted with [Plots.jl](https://github.com/JuliaPlots/Plots.jl).


```julia
julia> using Plots

julia> start_dt = DateTime(2017,1,1,0,0,0);

julia> end_dt = DateTime(2017,1,1,10,30,0);

julia> datetimes = start_dt:Hour(1):end_dt
DateTime("2017-01-01T00:00:00"):Hour(1):DateTime("2017-01-01T10:00:00")

julia> intervals = HE.(datetimes);

julia> plot(intervals, 1:11)
```

![Example Plot](assets/HE.png)

In the plot, inclusive boundaries are marked with a vertical bar, whereas exclusive boundaries just end.



### Comparisons

#### Equality
Expand Down Expand Up @@ -247,11 +239,36 @@ julia> round(AnchoredInterval{+0.5}(0.5), on=:right)
AnchoredInterval{0.5, Float64, Closed, Open}(0.5)
```

### Plotting

`AbstractInterval` subtypes can be plotted with [Plots.jl](https://github.com/JuliaPlots/Plots.jl).

```julia
julia> using Plots

julia> start_dt = DateTime(2017,1,1,0,0,0);

julia> end_dt = DateTime(2017,1,1,10,30,0);

julia> datetimes = start_dt:Hour(1):end_dt
DateTime("2017-01-01T00:00:00"):Hour(1):DateTime("2017-01-01T10:00:00")

julia> intervals = HE.(datetimes);

julia> plot(intervals, 1:11)
```

![Example Plot](assets/HE.png)

In the plot, inclusive boundaries are marked with a vertical bar, whereas exclusive boundaries just end.


## API

```@docs
Interval
AnchoredInterval
IntervalSet
HourEnding
HourBeginning
HE
Expand All @@ -275,4 +292,5 @@ Base.parse(::Type{Interval{T}}, ::AbstractString) where T
union
union!
superset
Intervals.find_intersections
```
2 changes: 2 additions & 0 deletions src/Intervals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ bounds_types(x::AbstractInterval{T,L,R}) where {T,L,R} = (L, R)
include("isfinite.jl")
include("endpoint.jl")
include("interval.jl")
include("interval_sets.jl")
include("anchoredinterval.jl")
include("parse.jl")
include("description.jl")
Expand All @@ -41,6 +42,7 @@ export Bound,
Unbounded,
AbstractInterval,
Interval,
IntervalSet,
AnchoredInterval,
HourEnding,
HourBeginning,
Expand Down
4 changes: 4 additions & 0 deletions src/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,8 @@ function HB(anchor, inc::Inclusivity)
return HourBeginning{L,R}(floor(anchor, Hour))
end

@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
3 changes: 2 additions & 1 deletion src/endpoint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const Right = Direction{:Right}()
const Beginning = Left
const Ending = Right

struct Endpoint{T, D, B <: Bound}
abstract type AbstractEndpoint end
struct Endpoint{T, D, B <: Bound} <: AbstractEndpoint
endpoint::T

function Endpoint{T,D,B}(ep::T) where {T, D, B <: Bounded}
Expand Down
54 changes: 0 additions & 54 deletions src/interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -405,60 +405,6 @@ function Base.intersect(a::AbstractInterval{S}, b::AbstractInterval{T}) where {S
return Interval(left, right)
end

# There is power in a union.
"""
union(intervals::AbstractVector{<:AbstractInterval})
Flattens a vector 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))
end

"""
union!(intervals::AbstractVector{<:Union{Interval, AbstractInterval}})
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
n = length(intervals)
while i <= n
prev = intervals[i - 1]
curr = intervals[i]

# If the current and previous intervals don't meet then move along
if !overlaps(prev, curr) && !contiguous(prev, curr)
i = i + 1

# 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)
n -= 1
end
end

return intervals
end

"""
superset(intervals::AbstractArray{<:AbstractInterval}) -> 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))

return Interval(left, right)
end

function Base.merge(a::AbstractInterval, b::AbstractInterval)
if !overlaps(a, b) && !contiguous(a, b)
throw(ArgumentError("$a and $b are neither overlapping or contiguous."))
Expand Down
Loading

2 comments on commit a785b56

@rofinn
Copy link
Member Author

@rofinn rofinn commented on a785b56 Jun 30, 2022

Choose a reason for hiding this comment

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

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/63432

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.8.0 -m "<description of version>" a785b56ce5e0091928fbb152bd2aa4834ada00d4
git push origin v1.8.0

Please sign in to comment.