diff --git a/.travis.yml b/.travis.yml index 71e2a6b..c623d1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,13 @@ language: julia os: - linux julia: - - release + - 1.1 + - 1.0 - nightly notifications: email: false script: - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia --check-bounds=yes -e 'Pkg.clone(pwd()); Pkg.build("PiecewiseIncreasingRanges"); Pkg.test("PiecewiseIncreasingRanges"; coverage=true)' + - julia --check-bounds=yes -e 'if VERSION >= v"0.7.0-" using Pkg; end; Pkg.clone(pwd()); Pkg.build("PiecewiseIncreasingRanges"); Pkg.test("PiecewiseIncreasingRanges"; coverage=true)' after_success: - - if [ $TRAVIS_JULIA_VERSION = "nightly" ]; then julia -e 'cd(Pkg.dir("PiecewiseIncreasingRanges")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())'; fi + - if [ $TRAVIS_JULIA_VERSION = "nightly" ]; then julia -e 'if VERSION >= v"0.7.0-" using Pkg; end; import PiecewiseIncreasingRanges; cd(joinpath(dirname(pathof(PiecewiseIncreasingRanges)),"..")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())'; fi diff --git a/REQUIRE b/REQUIRE index df77a41..d1987a4 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,2 +1,2 @@ -julia 0.3 +julia 1.1.1 Compat diff --git a/src/PiecewiseIncreasingRanges.jl b/src/PiecewiseIncreasingRanges.jl index 1935d9a..d6cecfa 100644 --- a/src/PiecewiseIncreasingRanges.jl +++ b/src/PiecewiseIncreasingRanges.jl @@ -3,14 +3,14 @@ export PiecewiseIncreasingRange, NoNearestSampleError, findnearest, resample using Compat, Base.Order -constructrange{T<:UnitRange}(::Type{T}, start, step, stop) = T(start, stop) -constructrange{T<:StepRange}(::Type{T}, start, step, stop) = T(start, step, stop) +constructrange(::Type{T}, start, step, stop) where {T<:UnitRange} = T(start, stop) +constructrange(::Type{T}, start, step, stop) where {T<:StepRange} = T(start, step, stop) -function combine_ranges{R<:Range}(ranges::Vector{R}, firstrg::R, firstrgidx::Int) +function combine_ranges(ranges::Vector{R}, firstrg::R, firstrgidx::Int) where {R<:AbstractRange} newranges = R[] offsets = Int[1] - step(firstrg) < 0 && throw(ArgumentError("ranges must be strictly monotonically increasing")) + step(firstrg) < zero(eltype(R)) && throw(ArgumentError("ranges must be strictly monotonically increasing")) curstart = first(firstrg) curstop = last(firstrg) curstep = step(firstrg) @@ -23,7 +23,7 @@ function combine_ranges{R<:Range}(ranges::Vector{R}, firstrg::R, firstrgidx::Int # Can extend current range curstop = last(newrg) else - if first(newrg) <= curstop || step(newrg) < 0 + if first(newrg) <= curstop || step(newrg) < zero(eltype(R)) throw(ArgumentError("ranges must be strictly monotonically increasing")) end @@ -42,11 +42,11 @@ function combine_ranges{R<:Range}(ranges::Vector{R}, firstrg::R, firstrgidx::Int (newranges, offsets) end -function combine_ranges{R<:FloatRange}(ranges::Vector{R}, firstrg::R, firstrgidx::Int) +function combine_ranges(ranges::Vector{R}, firstrg::R, firstrgidx::Int) where {R<:StepRangeLen} newranges = R[] offsets = Int[1] - if signbit(firstrg.step) != signbit(firstrg.divisor) + if step(firstrg) <= zero(eltype(R)) throw(ArgumentError("ranges must be strictly monotonically increasing")) end currg = firstrg @@ -55,10 +55,11 @@ function combine_ranges{R<:FloatRange}(ranges::Vector{R}, firstrg::R, firstrgidx newrg = ranges[i] isempty(newrg) && continue - if step(newrg) == step(currg) && newrg.start*currg.divisor == (currg.start+currg.step*currg.len)*newrg.divisor - currg = FloatRange(currg.start, currg.step, currg.len + newrg.len, currg.divisor) + if step(newrg) == step(currg) && first(newrg) == last(currg) + step(currg) + currg = R(currg.ref, step(currg), + length(currg) + length(newrg),currg.offset) else - if first(newrg) <= last(currg) || signbit(newrg.step) != signbit(newrg.divisor) + if first(newrg) <= last(currg) || step(newrg) <= zero(eltype(R)) throw(ArgumentError("ranges must be strictly monotonically increasing")) end @@ -73,40 +74,36 @@ function combine_ranges{R<:FloatRange}(ranges::Vector{R}, firstrg::R, firstrgidx (newranges, offsets) end -immutable PiecewiseIncreasingRange{T,R<:Range,S} <: AbstractVector{T} +struct PiecewiseIncreasingRange{T,R<:AbstractRange,S} <: AbstractVector{T} ranges::Vector{R} offsets::Vector{Int} divisor::S - function PiecewiseIncreasingRange(ranges::AbstractVector, divisor) + function PiecewiseIncreasingRange{T,R,S}(ranges::AbstractVector{R}, divisor::S) where {T,R<:AbstractRange,S} ranges = convert(Vector{R}, ranges) - isempty(ranges) && return new(ranges, Int[]) + isempty(ranges) && return new{T,R,S}(ranges, Int[]) # Find first non-empty range - firstrg = ranges[1] - j = 0 - for j = 1:length(ranges) - firstrg = ranges[j] - !isempty(firstrg) && break - end - isempty(firstrg) && return new(R[], Int[], divisor) + j = findfirst(!isempty,ranges) + isnothing(j) && return new{T,R,S}(R[], Int[], divisor) + firstrg = ranges[j] newranges, offsets = combine_ranges(ranges, firstrg, j+1) - new(newranges, offsets, divisor) + new{T,R,S}(newranges, offsets, divisor) end end -PiecewiseIncreasingRange{R<:Range}(ranges::Vector{R}, divisor) = PiecewiseIncreasingRange{typeof(inv(one(eltype(R)))),R,typeof(divisor)}(ranges, divisor) -PiecewiseIncreasingRange{R<:Range}(ranges::Vector{R}) = PiecewiseIncreasingRange{eltype(R),R,@compat(Void)}(ranges, nothing) +PiecewiseIncreasingRange(ranges::Vector{R}, divisor) where {R<:AbstractRange} = PiecewiseIncreasingRange{typeof(inv(one(eltype(R)))),R,typeof(divisor)}(ranges, divisor) +PiecewiseIncreasingRange(ranges::Vector{R}) where {R<:AbstractRange} = PiecewiseIncreasingRange{eltype(R),R,@compat(Nothing)}(ranges, nothing) -Base.convert{T,R<:Range,S}(::Type{PiecewiseIncreasingRange{T,R,S}}, x::PiecewiseIncreasingRange{T,R,S}) = x -Base.convert{T,R,S}(::Type{PiecewiseIncreasingRange{T,R,S}}, x::PiecewiseIncreasingRange) = +Base.convert(::Type{PiecewiseIncreasingRange{T,R,S}}, x::PiecewiseIncreasingRange{T,R,S}) where {T,R<:AbstractRange,S} = x +Base.convert(::Type{PiecewiseIncreasingRange{T,R,S}}, x::PiecewiseIncreasingRange) where {T,R,S} = PiecewiseIncreasingRange{T,R,S}(x.ranges, x.divisor) # Avoid applying the divisor if it is one, to get types right -divide_divisor{T,R}(r::PiecewiseIncreasingRange{T,R,@compat(Void)}, x) = x -multiply_divisor{T,R}(r::PiecewiseIncreasingRange{T,R,@compat(Void)}, x) = x -divide_divisor{T,R,S}(r::PiecewiseIncreasingRange{T,R,S}, x) = x/r.divisor -multiply_divisor{T,R,S}(r::PiecewiseIncreasingRange{T,R,S}, x) = x*r.divisor +divide_divisor(r::PiecewiseIncreasingRange{T,R,@compat(Nothing)}, x) where {T,R} = x +multiply_divisor(r::PiecewiseIncreasingRange{T,R,@compat(Nothing)}, x) where {T,R} = x +divide_divisor(r::PiecewiseIncreasingRange{T,R,S}, x) where {T,R,S} = x/r.divisor +multiply_divisor(r::PiecewiseIncreasingRange{T,R,S}, x) where {T,R,S} = x*r.divisor function Base.size(r::PiecewiseIncreasingRange) isempty(r.ranges) && return (0,) @@ -118,7 +115,7 @@ function Base.getindex(r::PiecewiseIncreasingRange, i::Integer) divide_divisor(r, r.ranges[rgidx][i-r.offsets[rgidx]+1]) end -function Base.getindex{T,R,S}(r::PiecewiseIncreasingRange{T,R,S}, x::Range{Int}) +function Base.getindex(r::PiecewiseIncreasingRange{T,R,S}, x::AbstractRange{Int}) where {T,R,S} isempty(x) && return PiecewiseIncreasingRange{T,R,S}(R[], Int[], r.divisor) (first(x) >= 1 && last(x) <= length(r)) || throw(BoundsError()) @@ -127,48 +124,48 @@ function Base.getindex{T,R,S}(r::PiecewiseIncreasingRange{T,R,S}, x::Range{Int}) firstrgidx = searchsortedlast(r.offsets, first(x), Forward) lastrgidx = searchsortedlast(r.offsets, last(x), Forward) - newrgs = Array(R, lastrgidx-firstrgidx+1) + newrgs = Array{R}(undef, lastrgidx-firstrgidx+1) for irange = firstrgidx:lastrgidx elmax = min(last(x)-r.offsets[irange]+1, length(r.ranges[irange])) newrg = newrgs[irange-firstrgidx+1] = r.ranges[irange][first(x)-r.offsets[irange]+1:elmax] x = x[length(newrg)+1:end] end - PiecewiseIncreasingRange(newrgs, r.divisor) + PiecewiseIncreasingRange{T,R,S}(newrgs, r.divisor) end -function resample{T,R,S}(r::PiecewiseIncreasingRange{T,R,S}, ratio::Rational{Int}) +function resample(r::PiecewiseIncreasingRange{T,R,S}, ratio::Rational{Int}) where {T,R,S} excess = zero(eltype(R)) curpt = 0 newrgs = R[] for i = 1:length(r.ranges) - endpt = last(r.ranges[i])*num(ratio) - newrg = first(r.ranges[i])*num(ratio)+excess*step(r.ranges[i]):step(r.ranges[i])*den(ratio):endpt + endpt = last(r.ranges[i])*numerator(ratio) + newrg = first(r.ranges[i])*numerator(ratio)+excess*step(r.ranges[i]):step(r.ranges[i])*denominator(ratio):endpt curpt += length(newrg) push!(newrgs, newrg) if i != length(r.ranges) # Need to linearly interpolate between endpoint and next range - # nextind is the next index in r (= 1 + curpt/ratio) times num(ratio) - nextind = num(ratio) + curpt*den(ratio) - if nextind < r.offsets[i+1]*num(ratio) + # nextind is the next index in r (= 1 + curpt/ratio) times numerator(ratio) + nextind = numerator(ratio) + curpt*denominator(ratio) + if nextind < r.offsets[i+1]*numerator(ratio) # Need to interpolate between this point and the next - weight = mod(nextind, num(ratio)) - interpt = last(r.ranges[i])*(num(ratio)-weight) + first(r.ranges[i+1])*weight - interpstep = (first(r.ranges[i+1]) - last(r.ranges[i]))*den(ratio) - newrg = interpt:interpstep:first(r.ranges[i+1])*num(ratio) + weight = mod(nextind, numerator(ratio)) + interpt = last(r.ranges[i])*(numerator(ratio)-weight) + first(r.ranges[i+1])*weight + interpstep = (first(r.ranges[i+1]) - last(r.ranges[i]))*denominator(ratio) + newrg = interpt:interpstep:first(r.ranges[i+1])*numerator(ratio) push!(newrgs, newrg) curpt += length(newrg) - nextind += length(newrg)*den(ratio) + nextind += length(newrg)*denominator(ratio) end - excess = nextind - r.offsets[i+1]*num(ratio) + excess = nextind - r.offsets[i+1]*numerator(ratio) end end - PiecewiseIncreasingRange(newrgs, multiply_divisor(r, num(ratio))) + PiecewiseIncreasingRange(newrgs, multiply_divisor(r, numerator(ratio))) end # searchsortedfirst, searchsortedlast -immutable PiecewiseIncreasingRangeFirstOrdering <: Ordering end +struct PiecewiseIncreasingRangeFirstOrdering <: Ordering end Base.Order.lt(o::PiecewiseIncreasingRangeFirstOrdering, a, b) = isless(first(a), first(b)) -immutable PiecewiseIncreasingRangeLastOrdering <: Ordering end +struct PiecewiseIncreasingRangeLastOrdering <: Ordering end Base.Order.lt(o::PiecewiseIncreasingRangeLastOrdering, a, b) = isless(last(a), last(b)) function Base.searchsortedfirst(r::PiecewiseIncreasingRange, x) @@ -189,7 +186,7 @@ function Base.searchsortedlast(r::PiecewiseIncreasingRange, x) searchsortedlast(r.ranges[rgidx], xd, Forward) + r.offsets[rgidx] - 1 end -immutable NoNearestSampleError <: Exception end +struct NoNearestSampleError <: Exception end function findnearest(r::PiecewiseIncreasingRange, x, within_half_step::Bool=false) isempty(r.ranges) && throw(NoNearestSampleError()) diff --git a/test/REQUIRE b/test/REQUIRE index 2ff40b8..af430fb 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -1 +1 @@ -Grid \ No newline at end of file +Interpolations \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 5f42b9d..dfd62d8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using PiecewiseIncreasingRanges, Base.Test, Grid +using PiecewiseIncreasingRanges, Test, Interpolations function test(rgs, divisor...) vcrgs = vcat(rgs...) @@ -7,11 +7,11 @@ function test(rgs, divisor...) @test length(rg.ranges) == 4 @test vcrgs == rg - yi = InterpGrid(convert(Vector{Float64}, vcrgs), BCnil, InterpLinear) - @test_approx_eq resample(rg, 3//7) yi[1:7//3:length(yi)] - @test_approx_eq resample(rg, 7//3) yi[1:3//7:length(yi)] - @test_approx_eq resample(rg, 5//9) yi[1:9//5:length(yi)] - @test_approx_eq resample(rg, 61//3) yi[1:3//61:length(yi)] + yi = LinearInterpolation(1:length(vcrgs),convert(Vector{Float64}, vcrgs)) + @test resample(rg, 3//7) ≈ yi(1:7//3:length(yi)) + @test resample(rg, 7//3) ≈ yi(1:3//7:length(yi)) + @test resample(rg, 5//9) ≈ yi(1:9//5:length(yi)) + @test resample(rg, 61//3) ≈ yi(1:3//61:length(yi)) for i = 1:length(rg) @test searchsortedfirst(rg, rg[i]) == i @@ -49,7 +49,7 @@ end test(StepRange{Int,Int}[0:4:40, 41:1:80, 82:2:112, 114:2:120, 136:4:144], 8) test(StepRange{Rational{Int},Rational{Int}}[0:1//2:5, 5+1//8:1//8:10, 10+1//4:1//4:14, 14+1//4:1//4:15, 17:1//2:18]) -test(FloatRange{Float64}[0:1//2:5., 5+1//8:1//8:10., 10+1//4:1//4:14., 14+1//4:1//4:15., 17:1//2:18.]) +test(StepRangeLen{Float64}[0:1//2:5., 5+1//8:1//8:10., 10+1//4:1//4:14., 14+1//4:1//4:15., 17:1//2:18.]) # Empty test rg = PiecewiseIncreasingRange(StepRange{Rational{Int},Rational{Int}}[]) @@ -63,9 +63,9 @@ rg = PiecewiseIncreasingRange(UnitRange{Int}[0:-1]) @test_throws ArgumentError PiecewiseIncreasingRange(StepRange{Rational{Int},Rational{Int}}[0:1//2:1, 3//4:1//2:4]) @test_throws ArgumentError PiecewiseIncreasingRange(StepRange{Rational{Int},Rational{Int}}[0:-1//2:-1, 10:1//2:8]) @test_throws ArgumentError PiecewiseIncreasingRange(StepRange{Rational{Int},Rational{Int}}[0:1//2:1, 10:-1//2:8]) -@test_throws ArgumentError PiecewiseIncreasingRange(FloatRange{Float64}[0:0.5:1, 0.75:0.5:4]) -@test_throws ArgumentError PiecewiseIncreasingRange(FloatRange{Float64}[0:-0.5:-1, 10:0.5:8]) -@test_throws ArgumentError PiecewiseIncreasingRange(FloatRange{Float64}[0:0.5:1, 10:-0.5:8]) +@test_throws ArgumentError PiecewiseIncreasingRange(StepRangeLen{Float64}[0:0.5:1, 0.75:0.5:4]) +@test_throws ArgumentError PiecewiseIncreasingRange(StepRangeLen{Float64}[0:-0.5:-1, 10:0.5:8]) +@test_throws ArgumentError PiecewiseIncreasingRange(StepRangeLen{Float64}[0:0.5:1, 10:-0.5:8]) # Test with empty ranges interspersed with non-empty ranges rg = PiecewiseIncreasingRange(UnitRange{Int}[0:-1, 1:0, 0:-1, 1:3, 0:-1, 5:10])