diff --git a/Project.toml b/Project.toml index fc3ce99..382e9c0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "IntervalSets" uuid = "8197267c-284f-5f27-9208-e0e47529a953" -version = "0.7.3" +version = "0.8.0" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" diff --git a/README.md b/README.md index 82c6912..967d9d9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ You can construct `ClosedInterval`s in a variety of ways: julia> using IntervalSets julia> ClosedInterval{Float64}(1,3) -1.0..3.0 +1..3 julia> 0.5..2.5 0.5..2.5 @@ -44,7 +44,7 @@ julia> 1.5±1 Similarly, you can construct `OpenInterval`s and `Interval{:open,:closed}`s, and `Interval{:closed,:open}`: ```julia julia> OpenInterval{Float64}(1,3) -1.0..3.0 (open) +1..3 (open) julia> OpenInterval(0.5..2.5) 0.5..2.5 (open) diff --git a/src/IntervalSets.jl b/src/IntervalSets.jl index 3e67a1b..670bb64 100644 --- a/src/IntervalSets.jl +++ b/src/IntervalSets.jl @@ -18,10 +18,20 @@ export AbstractInterval, Interval, OpenInterval, ClosedInterval, searchsorted_interval """ -A subtype of `Domain{T}` represents a subset of type `T`, that provides `in`. +A subtype of `Domain{T}` represents a set that provides `in`. `T` is a type suitable for representing elements in the domain. """ abstract type Domain{T} end +""" + eltype(::Domain{T}) + eltype(::Type{<:Domain{T}}) + +Return `T`. The `eltype`, `T`, of a `Domain` is the type that best represents elements of the domain according to the criteria chosen by the programmer who created the domain. + +Note: Objects of other types may be in the domain (as determined by the `in` function) and there may not be a unique object of type `T` for each mathematical element in the domain (e.g. a real interval may be represented by a `Domain{Float64}`, but there there are not unique `Float64`s for each real number in the interval). +""" +Base.eltype(::Type{<:Domain{T}}) where T = T + Base.IteratorSize(::Type{<:Domain}) = Base.SizeUnknown() Base.isdisjoint(a::Domain, b::Domain) = isempty(a ∩ b) @@ -64,9 +74,6 @@ isclosedset(d::AbstractInterval) = isleftclosed(d) && isrightclosed(d) "Is the interval open?" isopenset(d::AbstractInterval) = isleftopen(d) && isrightopen(d) -eltype(::Type{AbstractInterval{T}}) where {T} = T -@pure eltype(::Type{I}) where {I<:AbstractInterval} = eltype(supertype(I)) - convert(::Type{AbstractInterval}, i::AbstractInterval) = i convert(::Type{AbstractInterval{T}}, i::AbstractInterval{T}) where T = i @@ -74,10 +81,10 @@ convert(::Type{AbstractInterval{T}}, i::AbstractInterval{T}) where T = i ordered(a::T, b::T) where {T} = ifelse(a < b, (a, b), (b, a)) ordered(a, b) = ordered(promote(a, b)...) -checked_conversion(::Type{T}, a, b) where {T} = _checked_conversion(T, convert(T, a), convert(T, b)) -_checked_conversion(::Type{T}, a::T, b::T) where {T} = a, b -_checked_conversion(::Type{Any}, a, b) = throw(ArgumentError("$a and $b promoted to type Any")) -_checked_conversion(::Type{T}, a, b) where {T} = throw(ArgumentError("$a and $b are not both of type $T")) +default_interval_eltype(left, right) = default_interval_eltype(typeof(left), typeof(right)) +default_interval_eltype(TL::Type, TR::Type) = default_interval_eltype(promote_type(TL, TR)) +default_interval_eltype(T::Type) = T +default_interval_eltype(T::Type{<:Number}) = float(T) function infimum(d::AbstractInterval{T}) where T a = leftendpoint(d) diff --git a/src/interval.jl b/src/interval.jl index feefbea..ccc7dfd 100644 --- a/src/interval.jl +++ b/src/interval.jl @@ -6,29 +6,33 @@ is an interval set containg `x` such that 3. `left ≤ x < right` if `L == :closed` and `R == :open`, or 4. `left < x < right` if `L == R == :open` """ -struct Interval{L,R,T} <: TypedEndpointsInterval{L,R,T} - left::T - right::T - - Interval{L,R,T}(l, r) where {L,R,T} = ((a, b) = checked_conversion(T, l, r); new{L,R,T}(a, b)) +struct Interval{L,R,T,TL,TR} <: TypedEndpointsInterval{L,R,T} + left::TL + right::TR end - """ A `ClosedInterval(left, right)` is an interval set that includes both its upper and lower bounds. In mathematical notation, the constructed range is `[left, right]`. """ -const ClosedInterval{T} = Interval{:closed,:closed,T} +const ClosedInterval{T,TL,TR} = Interval{:closed,:closed,T,TL,TR} """ An `TypedEndpointsInterval{:open,:open}(left, right)` is an interval set that includes both its upper and lower bounds. In mathematical notation, the constructed range is `(left, right)`. """ -const OpenInterval{T} = Interval{:open,:open,T} +const OpenInterval{T,TL,TR} = Interval{:open,:open,T,TL,TR} Interval{L,R,T}(i::AbstractInterval) where {L,R,T} = Interval{L,R,T}(endpoints(i)...) -Interval{L,R}(left, right) where {L,R} = Interval{L,R}(promote(left,right)...) -Interval{L,R}(left::T, right::T) where {L,R,T} = Interval{L,R,T}(left, right) +Interval{L,R,T,TL,TR}(i::AbstractInterval) where {L,R,T,TL,TR} = Interval{L,R,T,TL,TR}(endpoints(i)...) +Interval{L,R,T}(l, r) where {L,R,T} = Interval{L,R,T,typeof(l),typeof(r)}(l, r) +function Interval{L,R}(left, right) where {L,R} + T = default_interval_eltype(left, right) + if T == Any + error("Endpoints ($left, $right) of Interval were incompatible (inferred eltype was Any).") + end + Interval{L,R,T}(left, right) +end Interval(left, right) = ClosedInterval(left, right) @@ -94,10 +98,15 @@ Construct a ClosedInterval `iv` spanning the region from ±(x, y) = ClosedInterval(x - y, x + y) ±(x::CartesianIndex, y::CartesianIndex) = ClosedInterval(x-y, x+y) -show(io::IO, I::ClosedInterval) = print(io, leftendpoint(I), "..", rightendpoint(I)) -show(io::IO, I::OpenInterval) = print(io, leftendpoint(I), "..", rightendpoint(I), " (open)") -show(io::IO, I::Interval{:open,:closed}) = print(io, leftendpoint(I), "..", rightendpoint(I), " (open–closed)") -show(io::IO, I::Interval{:closed,:open}) = print(io, leftendpoint(I), "..", rightendpoint(I), " (closed–open)") +function show(io::IO, I::ClosedInterval) + print(io, leftendpoint(I), "..", rightendpoint(I)) + if eltype(I) != default_interval_eltype(leftendpoint(I), rightendpoint(I)) + print(io, " (", eltype(I), ")") + end +end +show(io::IO, I::OpenInterval) = print(io, leftendpoint(I), "..", rightendpoint(I), " (", eltype(I), ", open)") +show(io::IO, I::Interval{:open,:closed}) = print(io, leftendpoint(I), "..", rightendpoint(I), " (", eltype(I), ", open–closed)") +show(io::IO, I::Interval{:closed,:open}) = print(io, leftendpoint(I), "..", rightendpoint(I), " (", eltype(I), ", closed–open)") # The following are not typestable for mixed endpoint types _left_intersect_type(::Type{Val{:open}}, ::Type{Val{L2}}, a1, a2) where L2 = a1 < a2 ? (a2,L2) : (a1,:open) @@ -159,18 +168,18 @@ function _union(A::TypedEndpointsInterval{L1,R1}, B::TypedEndpointsInterval{L2,R end # random sampling from interval -Random.gentype(::Type{Interval{L,R,T}}) where {L,R,T} = float(T) +Random.gentype(::Type{Interval{L,R,T}}) where {L,R,T} = T function Random.rand(rng::AbstractRNG, i::Random.SamplerTrivial{<:TypedEndpointsInterval{:closed, :closed, T}}) where T<:Real _i = i[] isempty(_i) && throw(ArgumentError("The interval should be non-empty.")) a,b = endpoints(_i) t = rand(rng, float(T)) # technically this samples from [0, 1), but we still allow it with TypedEndpointsInterval{:closed, :closed} for convenience - return clamp(t*a+(1-t)*b, _i) + return convert(T, clamp(t*a+(1-t)*b, _i)) end ClosedInterval{T}(i::AbstractUnitRange{I}) where {T,I<:Integer} = ClosedInterval{T}(minimum(i), maximum(i)) ClosedInterval(i::AbstractUnitRange{I}) where {I<:Integer} = ClosedInterval{I}(minimum(i), maximum(i)) -Base.promote_rule(::Type{Interval{L,R,T1}}, ::Type{Interval{L,R,T2}}) where {L,R,T1,T2} = Interval{L,R,promote_type(T1, T2)} +Base.promote_rule(::Type{Interval{L,R,T1,TL1,TR1}}, ::Type{Interval{L,R,T2,TL2,TR2}}) where {L,R,T1,T2,TL1,TR1,TL2,TR2} = Interval{L,R,promote_type(T1, T2),promote_type(TL1,TL2),promote_type(TR1,TR2)} float(i::Interval{L, R, T}) where {L,R,T} = Interval{L, R, float(T)}(endpoints(i)...) diff --git a/test/runtests.jl b/test/runtests.jl index 20fac18..dbb1419 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,30 +31,33 @@ struct IncompleteInterval <: AbstractInterval{Int} end @testset "Basic Closed Sets" begin @test_throws ErrorException :a .. "b" - @test_throws ErrorException 1 .. missing - @test_throws ErrorException 1u"m" .. 2u"s" + @test ismissing(2 in 1 .. missing) + @test !(0 in 1 .. missing) + @test_throws Exception 1u"m" in 1u"m" .. 2u"s" I = 0..3 - @test I === ClosedInterval(0,3) === ClosedInterval{Int}(0,3) === + @test I === ClosedInterval(0,3) === ClosedInterval{Float64}(0,3) === Interval(0,3) @test string(I) == "0..3" - @test @inferred(UnitRange(I)) === 0:3 - @test @inferred(range(I)) === 0:3 - @test @inferred(UnitRange{Int16}(I)) === Int16(0):Int16(3) - @test @inferred(ClosedInterval(0:3)) === I - @test @inferred(ClosedInterval{Float64}(0:3)) === 0.0..3.0 - @test @inferred(ClosedInterval(Base.OneTo(3))) === 1..3 + + II = ClosedInterval{Int}(0,3) + @test @inferred(UnitRange(II)) === 0:3 + @test @inferred(range(II)) === 0:3 + @test @inferred(UnitRange{Int16}(II)) === Int16(0):Int16(3) + @test @inferred(ClosedInterval(0:3)) === II + @test @inferred(ClosedInterval{Float64}(0:3)) === 0..3 + @test @inferred(ClosedInterval(Base.OneTo(3))) === ClosedInterval{Int}(1,3) J = 3..2 K = 5..4 L = 3 ± 2 M = @inferred(ClosedInterval(2, 5.0)) - @test string(M) == "2.0..5.0" + @test string(M) == "2..5.0" N = @inferred(ClosedInterval(UInt8(255), 300)) x, y = CartesianIndex(1, 2, 3, 4), CartesianIndex(1, 2, 3, 4) O = @inferred x±y @test O == ClosedInterval(x-y, x+y) - @test eltype(I) == Int + @test eltype(I) == Float64 @test eltype(M) == Float64 @test !isempty(I) @@ -67,10 +70,10 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test isequal(I, I) @test isequal(J, K) - @test typeof(leftendpoint(M)) == typeof(rightendpoint(M)) && typeof(leftendpoint(M)) == Float64 - @test typeof(leftendpoint(N)) == typeof(rightendpoint(N)) && typeof(leftendpoint(N)) == Int - @test @inferred(endpoints(M)) === (2.0,5.0) - @test @inferred(endpoints(N)) === (255,300) + @test typeof(leftendpoint(M)) == Int && typeof(rightendpoint(M)) == Float64 + @test typeof(leftendpoint(N)) == UInt8 && typeof(rightendpoint(N)) == Int + @test @inferred(endpoints(M)) === (2,5.0) + @test @inferred(endpoints(N)) === (UInt8(255),300) @test maximum(I) === 3 @test minimum(I) === 0 @@ -157,7 +160,7 @@ struct IncompleteInterval <: AbstractInterval{Int} end @inferred(convert(Domain{Float64}, I)) === @inferred(ClosedInterval{Float64}(I)) === @inferred(convert(TypedEndpointsInterval{:closed,:closed,Float64},I)) === - 0.0..3.0 + 0..3 @test @inferred(convert(ClosedInterval, I)) === @inferred(convert(Interval, I)) === @inferred(ClosedInterval(I)) === @@ -165,34 +168,34 @@ struct IncompleteInterval <: AbstractInterval{Int} end @inferred(convert(AbstractInterval, I)) === @inferred(convert(Domain, I)) === @inferred(convert(TypedEndpointsInterval{:closed,:closed}, I)) === - @inferred(convert(TypedEndpointsInterval{:closed,:closed,Int}, I)) === - @inferred(convert(ClosedInterval{Int}, I)) === I + @inferred(convert(TypedEndpointsInterval{:closed,:closed,Float64}, I)) === + @inferred(convert(ClosedInterval{Float64}, I)) === I @test_throws InexactError convert(OpenInterval, I) @test_throws InexactError convert(Interval{:open,:closed}, I) @test_throws InexactError convert(Interval{:closed,:open}, I) - @test !(convert(ClosedInterval{Float64}, I) === 0..3) - @test ClosedInterval{Float64}(1,3) === 1.0..3.0 + @test !(convert(ClosedInterval{Int}, I) === 0..3) + @test ClosedInterval{Float64, Float64, Float64}(1,3) === 1.0..3.0 @test ClosedInterval(0.5..2.5) === 0.5..2.5 - @test ClosedInterval{Int}(1.0..3.0) === 1..3 + @test ClosedInterval{Float64,Int,Int}(1.0..3.0) === 1..3 J = OpenInterval(I) @test_throws InexactError convert(ClosedInterval, J) @test @inferred(convert(OpenInterval{Float64}, J)) === @inferred(convert(AbstractInterval{Float64}, J)) === @inferred(convert(Domain{Float64}, J)) === - @inferred(OpenInterval{Float64}(J)) === OpenInterval(0.0..3.0) + @inferred(OpenInterval{Float64}(J)) === OpenInterval(0..3) @test @inferred(convert(OpenInterval, J)) === @inferred(convert(Interval, J)) === @inferred(convert(AbstractInterval, J)) === @inferred(convert(Domain, J)) === @inferred(OpenInterval(J)) === - @inferred(OpenInterval{Int}(J)) === - @inferred(convert(OpenInterval{Int},J)) === OpenInterval(J) + @inferred(OpenInterval{Float64}(J)) === + @inferred(convert(OpenInterval{Float64},J)) === OpenInterval(J) J = Interval{:open,:closed}(I) @test_throws InexactError convert(Interval{:closed,:open}, J) @test @inferred(convert(Interval{:open,:closed,Float64}, J)) === @inferred(convert(AbstractInterval{Float64}, J)) === @inferred(convert(Domain{Float64}, J)) === - @inferred(Interval{:open,:closed,Float64}(J)) === Interval{:open,:closed}(0.0..3.0) + @inferred(Interval{:open,:closed,Float64}(J)) === Interval{:open,:closed}(0..3) @test @inferred(convert(Interval{:open,:closed}, J)) === @inferred(convert(Interval, J)) === @inferred(convert(AbstractInterval, J)) === @@ -203,18 +206,16 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test @inferred(convert(Interval{:closed,:open,Float64}, J)) === @inferred(convert(AbstractInterval{Float64}, J)) === @inferred(convert(Domain{Float64}, J)) === - @inferred(Interval{:closed,:open,Float64}(J)) === Interval{:closed,:open}(0.0..3.0) + @inferred(Interval{:closed,:open,Float64}(J)) === Interval{:closed,:open}(0..3) @test @inferred(convert(Interval{:closed,:open}, J)) === @inferred(convert(Interval, J)) === @inferred(convert(AbstractInterval, J)) === @inferred(convert(Domain, J)) === @inferred(Interval{:closed,:open}(J)) === Interval{:closed,:open}(J) - @test 1.0..2.0 === 1.0..2 === 1..2.0 === ClosedInterval{Float64}(1..2) === - Interval(1.0,2.0) + @test 1.0..2.0 === ClosedInterval{Float64}(1.0..2.0) === Interval(1.0,2.0) - @test promote_type(Interval{:closed,:open,Float64}, Interval{:closed,:open,Int}) === - Interval{:closed,:open,Float64} + @test promote_type(Interval{:closed,:open,Float64,Float64,Int}, Interval{:closed,:open,Int,Int,Int}) === Interval{:closed,:open,Float64,Float64,Int} end @@ -612,8 +613,8 @@ struct IncompleteInterval <: AbstractInterval{Int} end Interval{:closed,:open}(0..1) # - different interval types - @test (1..2) ∩ OpenInterval(0.5, 1.5) ≡ Interval{:closed, :open}(1, 1.5) - @test (1..2) ∪ OpenInterval(0.5, 1.5) ≡ Interval{:open, :closed}(0.5, 2) + @test (1..2) ∩ OpenInterval(0.5, 1.5) == Interval{:closed, :open}(1, 1.5) + @test (1..2) ∪ OpenInterval(0.5, 1.5) == Interval{:open, :closed}(0.5, 2) end end @@ -638,7 +639,7 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test isrightclosed(I) @test !isrightopen(I) @test ClosedInterval(I) === convert(ClosedInterval, I) === - ClosedInterval{Int}(I) === convert(ClosedInterval{Int}, I) === + ClosedInterval{Float64}(I) === convert(ClosedInterval{Float64}, I) === convert(Interval, I) === Interval(I) === 0..1 @test_throws InexactError convert(OpenInterval, I) I = MyUnitInterval(false,false) @@ -647,7 +648,7 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test !isleftclosed(I) @test !isrightclosed(I) @test OpenInterval(I) === convert(OpenInterval, I) === - OpenInterval{Int}(I) === convert(OpenInterval{Int}, I) === + OpenInterval{Float64}(I) === convert(OpenInterval{Float64}, I) === convert(Interval, I) === Interval(I) === OpenInterval(0..1) I = MyUnitInterval(false,true) @test leftendpoint(I) == 0 @@ -655,7 +656,7 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test isleftclosed(I) == false @test isrightclosed(I) == true @test Interval{:open,:closed}(I) === convert(Interval{:open,:closed}, I) === - Interval{:open,:closed,Int}(I) === convert(Interval{:open,:closed,Int}, I) === + Interval{:open,:closed,Float64}(I) === convert(Interval{:open,:closed,Float64}, I) === convert(Interval, I) === Interval(I) === Interval{:open,:closed}(0..1) I = MyUnitInterval(true,false) @test leftendpoint(I) == 0 @@ -663,7 +664,7 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test isleftclosed(I) == true @test isrightclosed(I) == false @test Interval{:closed,:open}(I) === convert(Interval{:closed,:open}, I) === - Interval{:closed,:open,Int}(I) === convert(Interval{:closed,:open,Int}, I) === + Interval{:closed,:open,Float64}(I) === convert(Interval{:closed,:open,Float64}, I) === convert(Interval, I) === Interval(I) === Interval{:closed,:open}(0..1) @test convert(AbstractInterval, I) === convert(AbstractInterval{Int}, I) === I end @@ -675,7 +676,7 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test isleftclosed(I) == true @test isrightclosed(I) == true @test ClosedInterval(I) === convert(ClosedInterval, I) === - ClosedInterval{Int}(I) === convert(ClosedInterval{Int}, I) === + ClosedInterval{Float64}(I) === convert(ClosedInterval{Float64}, I) === convert(Interval, I) === Interval(I) === 0..1 @test_throws InexactError convert(OpenInterval, I) @test I ∩ I === 0..1 @@ -735,10 +736,12 @@ struct IncompleteInterval <: AbstractInterval{Int} end end @testset "OneTo" begin - @test_throws ArgumentError Base.OneTo{Int}(0..5) - @test_throws ArgumentError Base.OneTo(0..5) - @test Base.OneTo(1..5) == Base.OneTo{Int}(1..5) == Base.OneTo(5) - @test Base.Slice(1..5) == Base.Slice{UnitRange{Int}}(1..5) == Base.Slice(1:5) + @test_throws MethodError Base.OneTo{Int}(0..5) + @test_throws MethodError Base.OneTo(0..5) + @test_throws ArgumentError Base.OneTo{Int}(ClosedInterval{Int}(0..5)) + @test_throws ArgumentError Base.OneTo(ClosedInterval{Int}(0..5)) + @test Base.OneTo(ClosedInterval{Int}(1..5)) == Base.OneTo{Int}(ClosedInterval{Int}(1..5)) == Base.OneTo(5) + @test Base.Slice(ClosedInterval{Int}(1..5)) == Base.Slice{UnitRange{Int}}(ClosedInterval{Int}(1..5)) == Base.Slice(1:5) end @testset "range" begin @@ -772,12 +775,12 @@ struct IncompleteInterval <: AbstractInterval{Int} end end @testset "rand" begin - @test rand(1..2) isa Float64 - @test rand(1..2.) isa Float64 - @test rand(1..big(2)) isa BigFloat - @test rand(1..(3//2)) isa Float64 - @test rand(Int32(1)..Int32(2)) isa Float64 - @test rand(Float32(1)..Float32(2)) isa Float32 + @test rand(1..2)::Float64 in 1..2 + @test rand(1..2.)::Float64 in 1..2 + @test rand(1..big(2))::BigFloat in 1..2 + @test rand(1..(3//2))::Float64 in 1..3/2 + @test rand(Int32(1)..Int32(2))::Float64 in 1..2 + @test rand(Float32(1)..Float32(2))::Float32 in 1..2 @test_throws ArgumentError rand(2..1) i1 = 1..2 @@ -816,15 +819,15 @@ struct IncompleteInterval <: AbstractInterval{Int} end end @testset "float" begin - i1 = 1..2 + i1 = ClosedInterval{Int}(1..2) @test i1 isa ClosedInterval{Int} @test float(i1) isa ClosedInterval{Float64} @test float(i1) == i1 - i2 = big(1)..2 + i2 = ClosedInterval{BigInt}(big(1)..2) @test i2 isa ClosedInterval{BigInt} @test float(i2) isa ClosedInterval{BigFloat} @test float(i2) == i2 - i3 = OpenInterval(1,2) + i3 = OpenInterval{Int}(1,2) @test i3 isa OpenInterval{Int} @test float(i3) isa OpenInterval{Float64} @test float(i3) == i3