diff --git a/Project.toml b/Project.toml index e858921f..6b23b464 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "OffsetArrays" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "0.11.4" +version = "1.0.0" [compat] julia = "0.7, 1" diff --git a/README.md b/README.md index f03fe0b5..d6b1c9e3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,55 @@ # OffsetArrays.jl +[![Build Status](https://travis-ci.org/JuliaArrays/OffsetArrays.jl.svg?branch=master)](https://travis-ci.org/JuliaArrays/OffsetArrays.jl) +[![codecov.io](http://codecov.io/github/JuliaArrays/OffsetArrays.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaArrays/OffsetArrays.jl?branch=master) +[![PkgEval][pkgeval-img]][pkgeval-url] + OffsetArrays provides Julia users with arrays that have arbitrary indices, similar to those found in some other programming languages like Fortran. +## Usage + +You can construct such arrays as follows: + +```julia +OA = OffsetArray(A, axis1, axis2, ...) +``` + +where you want `OA` to have axes `(axis1, axis2, ...)` and be indexed by values that +fall within these axis ranges. Example: + +```julia +using OffsetArrays +A = reshape(1:15, 3, 5) +println("here is A:") +display(A) +OA = OffsetArray(A, -1:1, 0:4) # OA will have axes (-1:1, 0:4) +println("here is OA:") +display(OA) +@show OA[-1,0] OA[1,4] +``` + +which prints out + +``` +here is A: +3×5 reshape(::UnitRange{Int64}, 3, 5) with eltype Int64: + 1 4 7 10 13 + 2 5 8 11 14 + 3 6 9 12 15 +here is OA: +OffsetArray(reshape(::UnitRange{Int64}, 3, 5), -1:1, 0:4) with eltype Int64 with indices -1:1×0:4: + 1 4 7 10 13 + 2 5 8 11 14 + 3 6 9 12 15 +OA[-1, 0] = 1 +OA[1, 4] = 15 +``` + +OffsetArrays works for arbitrary dimensionality: + ```julia julia> using OffsetArrays @@ -21,7 +66,8 @@ julia> y[-1,-7,-128,-5,-1,-3,-2,-1] += 5 ``` ## Example: Relativistic Notation -Suppose we have a position vector `r = [:x, :y, :z]` which is naturally one-based, ie. `r[1] == :x`, `r[2] == :y`, `r[3] == :z` and we also want to construct a relativistic position vector which includes time as the 0th component. This can be done with OffsetArrays like +Suppose we have a position vector `r = [:x, :y, :z]` which is naturally one-based, ie. `r[1] == :x`, `r[2] == :y`, `r[3] == :z` and we also want to construct a relativistic position vector which includes time as the 0th component. This can be done with OffsetArrays like + ```julia julia> using OffsetArrays @@ -49,7 +95,7 @@ Suppose one wants to represent the Laurent polynomial ``` 6/x + 5 - 2*x + 3*x^2 + x^3 ``` -in julia. The coefficients of this polynomial are a naturally `-1` based list, since the `n`th element of the list +in julia. The coefficients of this polynomial are a naturally `-1` based list, since the `n`th element of the list (counting from `-1`) `6, 5, -2, 3, 1` is the coefficient corresponding to the `n`th power of `x`. This Laurent polynomial can be evaluated at say `x = 2` as follows. ```julia julia> using OffsetArrays @@ -72,4 +118,8 @@ Notice our use of the `eachindex` function which does not assume that the given ## Notes on supporting OffsetArrays -Julia supports generic programming with arrays that doesn't require you to assume that indices start with 1, see the [documentation](http://docs.julialang.org/en/latest/devdocs/offset-arrays/). +There are several "tricks" that make it easier to support arrays with general indexes, see the [documentation](http://docs.julialang.org/en/latest/devdocs/offset-arrays/). + + +[pkgeval-img]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/O/OffsetArrays.svg +[pkgeval-url]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html diff --git a/src/OffsetArrays.jl b/src/OffsetArrays.jl index 503b860c..81c42d89 100644 --- a/src/OffsetArrays.jl +++ b/src/OffsetArrays.jl @@ -1,5 +1,3 @@ -VERSION < v"0.7.0-beta2.199" && __precompile__() - module OffsetArrays using Base: Indices, tail, @propagate_inbounds @@ -11,49 +9,105 @@ end export OffsetArray, OffsetVector +""" + ro = IdOffsetRange(r::AbstractUnitRange, offset) + +Construct an "identity offset range". Numerically, `collect(ro) == collect(r) .+ offset`, +with the additional property that `axes(ro) = (ro,)`, which is where the "identity" comes from. +""" +struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T} + parent::I + offset::T +end +IdOffsetRange(r::AbstractUnitRange{T}, offset::Integer) where T = + IdOffsetRange{T,typeof(r)}(r, convert(T, offset)) + +@inline Base.axes(r::IdOffsetRange) = (Base.axes1(r),) +@inline Base.axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset) +@inline Base.unsafe_indices(r::IdOffsetRange) = (r,) +@inline Base.length(r::IdOffsetRange) = length(r.parent) + +function Base.iterate(r::IdOffsetRange) + ret = iterate(r.parent) + ret === nothing && return nothing + return (ret[1] + r.offset, ret[2]) +end +function Base.iterate(r::IdOffsetRange, i) where T + ret = iterate(r.parent, i) + ret === nothing && return nothing + return (ret[1] + r.offset, ret[2]) +end + +@inline Base.first(r::IdOffsetRange) = first(r.parent) + r.offset +@inline Base.last(r::IdOffsetRange) = last(r.parent) + r.offset + +@propagate_inbounds Base.getindex(r::IdOffsetRange, i::Integer) = r.parent[i - r.offset] + r.offset +@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::AbstractUnitRange{<:Integer}) + return r.parent[s .- r.offset] .+ r.offset +end + +Base.show(io::IO, r::IdOffsetRange) = print(io, first(r), ':', last(r)) + +# Optimizations +@inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset) + +## OffsetArray struct OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N} parent::AA offsets::NTuple{N,Int} end OffsetVector{T,AA<:AbstractArray} = OffsetArray{T,1,AA} -OffsetArray(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) where {T,N} = +## OffsetArray constructors + +offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent) +offset(axparent::AbstractUnitRange, ax::Integer) = 1 - first(axparent) + +function OffsetArray(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) where {T,N} OffsetArray{T,N,typeof(A)}(A, offsets) +end +OffsetArray(A::AbstractArray{T,0}, offsets::Tuple{}) where T = + OffsetArray{T,0,typeof(A)}(A, ()) + OffsetArray(A::AbstractArray{T,N}, offsets::Vararg{Int,N}) where {T,N} = OffsetArray(A, offsets) +OffsetArray(A::AbstractArray{T,0}) where {T} = OffsetArray(A, ()) const ArrayInitializer = Union{UndefInitializer, Missing, Nothing} OffsetArray{T,N}(init::ArrayInitializer, inds::Indices{N}) where {T,N} = - OffsetArray{T,N,Array{T,N}}(Array{T,N}(init, map(indexlength, inds)), map(indexoffset, inds)) + OffsetArray(Array{T,N}(init, map(indexlength, inds)), map(indexoffset, inds)) OffsetArray{T}(init::ArrayInitializer, inds::Indices{N}) where {T,N} = OffsetArray{T,N}(init, inds) OffsetArray{T,N}(init::ArrayInitializer, inds::Vararg{AbstractUnitRange,N}) where {T,N} = OffsetArray{T,N}(init, inds) OffsetArray{T}(init::ArrayInitializer, inds::Vararg{AbstractUnitRange,N}) where {T,N} = OffsetArray{T,N}(init, inds) -OffsetArray(A::AbstractArray{T,0}) where {T} = OffsetArray{T,0,typeof(A)}(A, ()) # OffsetVector constructors OffsetVector(A::AbstractVector, offset) = OffsetArray(A, offset) OffsetVector{T}(init::ArrayInitializer, inds::AbstractUnitRange) where {T} = OffsetArray{T}(init, inds) -# deprecated constructors -using Base: @deprecate - -@deprecate OffsetArray(::Type{T}, inds::Vararg{UnitRange{Int},N}) where {T,N} OffsetArray{T}(undef, inds) -@deprecate OffsetVector(::Type{T}, inds::AbstractUnitRange) where {T} OffsetVector{T}(undef, inds) -@deprecate OffsetArray{T,N}(inds::Indices{N}) where {T,N} OffsetArray{T,N}(undef, inds) -@deprecate OffsetArray{T}(inds::Indices{N}) where {T,N} OffsetArray{T}(undef, inds) -@deprecate OffsetArray{T,N}(inds::Vararg{AbstractUnitRange,N}) where {T,N} OffsetArray{T,N}(undef, inds) -@deprecate OffsetArray{T}(inds::Vararg{AbstractUnitRange,N}) where {T,N} OffsetArray{T}(undef, inds) -@deprecate OffsetVector{T}(inds::AbstractUnitRange) where {T} OffsetVector{T}(undef, inds) - -# The next two are necessary for ambiguity resolution. Really, the -# second method should not be necessary. -OffsetArray(A::AbstractArray{T,0}, inds::Tuple{}) where {T} = OffsetArray{T,0,typeof(A)}(A, ()) -OffsetArray(A::AbstractArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called") +""" + OffsetArray(A, indices...) + +Return an `AbstractArray` that shares element type and size with the first argument, but +used the given `indices`, which are checked for compatible size. + +# Example + +```jldoctest +julia> A = OffsetArray(reshape(1:6, 2, 3), 0:1, -1:1) +OffsetArray(reshape(::UnitRange{Int64}, 2, 3), 0:1, -1:1) with eltype Int64 with indices 0:1×-1:1: + 1 3 5 + 2 4 6 + +julia> A[0, 1] +5 +``` +""" function OffsetArray(A::AbstractArray{T,N}, inds::NTuple{N,AbstractUnitRange}) where {T,N} - lA = map(indexlength, axes(A)) - lI = map(indexlength, inds) + axparent = axes(A) + lA = map(length, axparent) + lI = map(length, inds) lA == lI || throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices")) - OffsetArray(A, map(indexoffset, inds)) + OffsetArray(A, map(offset, axparent, inds)) end OffsetArray(A::AbstractArray{T,N}, inds::Vararg{AbstractUnitRange,N}) where {T,N} = OffsetArray(A, inds) @@ -62,8 +116,8 @@ OffsetArray(A::AbstractArray{T,N}, inds::Vararg{AbstractUnitRange,N}) where {T,N function OffsetArray(A::OffsetArray, inds::NTuple{N,AbstractUnitRange}) where {N} OffsetArray(parent(A), inds) end -OffsetArray(A::OffsetArray{T,0}, inds::Tuple{}) where {T} = OffsetArray{T,0,typeof(A)}(parent(A), ()) -OffsetArray(A::OffsetArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called") +OffsetArray(A::OffsetArray{T,0}, inds::Tuple{}) where {T} = OffsetArray(parent(A), ()) +# OffsetArray(A::OffsetArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called") Base.IndexStyle(::Type{OA}) where {OA<:OffsetArray} = IndexStyle(parenttype(OA)) parenttype(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA @@ -74,36 +128,26 @@ Base.parent(A::OffsetArray) = A.parent Base.eachindex(::IndexCartesian, A::OffsetArray) = CartesianIndices(axes(A)) Base.eachindex(::IndexLinear, A::OffsetVector) = axes(A, 1) -Base.size(A::OffsetArray) = size(parent(A)) -Base.size(A::OffsetArray, d) = size(parent(A), d) - -# Implementations of axes and indices1. Since bounds-checking is -# performance-critical and relies on axes, these are usually worth -# optimizing thoroughly. -@inline Base.axes(A::OffsetArray, d) = - 1 <= d <= length(A.offsets) ? _slice(axes(parent(A))[d], A.offsets[d]) : (1:1) -@inline Base.axes(A::OffsetArray) = - _axes(axes(parent(A)), A.offsets) # would rather use ntuple, but see #15276 -@inline _axes(inds, offsets) = - (_slice(inds[1], offsets[1]), _axes(tail(inds), tail(offsets))...) -_axes(::Tuple{}, ::Tuple{}) = () -Base.axes1(A::OffsetArray{T,0}) where {T} = 1:1 # we only need to specialize this one - -# Avoid the kw-arg on the range(r+x, length=length(r)) call in r .+ x -@inline _slice(r, x) = IdentityUnitRange(Base._range(first(r) + x, nothing, nothing, length(r))) - -const OffsetAxis = Union{Integer, UnitRange, Base.OneTo, IdentityUnitRange, Colon} -function Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T - B = similar(parent(A), T, dims) -end +@inline Base.size(A::OffsetArray) = size(parent(A)) +@inline Base.size(A::OffsetArray, d) = size(parent(A), d) + +@inline Base.axes(A::OffsetArray) = map(IdOffsetRange, axes(parent(A)), A.offsets) +@inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : Base.OneTo(1) +@inline Base.axes1(A::OffsetArray{T,0}) where {T} = Base.OneTo(1) # we only need to specialize this one + +const OffsetAxis = Union{Integer, UnitRange, Base.OneTo, IdentityUnitRange, IdOffsetRange, Colon} +Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T = + similar(parent(A), T, dims) function Base.similar(A::AbstractArray, ::Type{T}, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where T B = similar(A, T, map(indexlength, inds)) - OffsetArray(B, map(indexoffset, inds)) + return OffsetArray(B, map(offset, axes(B), inds)) end Base.reshape(A::AbstractArray, inds::OffsetAxis...) = reshape(A, inds) -Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) = - OffsetArray(reshape(A, map(indexlength, inds)), map(indexoffset, inds)) +function Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) + AR = reshape(A, map(indexlength, inds)) + return OffsetArray(AR, map(offset, axes(AR), inds)) +end # Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return # an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...))) @@ -116,8 +160,10 @@ Base.reshape(A::OffsetArray, ::Colon) = A Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(parent(A), inds) Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = reshape(parent(A), inds) -Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T<:AbstractArray} = - OffsetArray(T(undef, map(indexlength, shape)), map(indexoffset, shape)) +function Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T<:AbstractArray} + P = T(undef, map(indexlength, shape)) + OffsetArray(P, map(offset, axes(P), shape)) +end Base.fill(v, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = fill!(OffsetArray(Array{typeof(v), N}(undef, map(indexlength, inds)), map(indexoffset, inds)), v) @@ -130,32 +176,36 @@ Base.trues(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = Base.falses(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = fill!(OffsetArray(BitArray{N}(undef, map(indexlength, inds)), map(indexoffset, inds)), false) -@inline @propagate_inbounds function Base.getindex(A::OffsetArray{T,N}, I::Vararg{Int,N}) where {T,N} - @boundscheck checkbounds(A, I...) - @inbounds ret = parent(A)[offset(A.offsets, I)...] - ret -end -@inline @propagate_inbounds function Base.getindex(A::OffsetVector, i::Int) - @boundscheck checkbounds(A, i) - @inbounds ret = parent(A)[offset(A.offsets, (i,))[1]] - ret -end -@inline @propagate_inbounds function Base.getindex(A::OffsetArray, i::Int) - @boundscheck checkbounds(A, i) - @inbounds ret = parent(A)[i] - ret +## Indexing + +# Note this gets the index of the parent *array*, not the index of the parent *range* +# Here's how one can think about this: +# Δi = i - first(r) +# i′ = first(r.parent) + Δi +# and one obtains the result below. +parentindex(r::IdOffsetRange, i) = i - r.offset + +@propagate_inbounds function Base.getindex(A::OffsetArray{T,N}, I::Vararg{Int,N}) where {T,N} + J = map(parentindex, axes(A), I) + return parent(A)[J...] end -@inline @propagate_inbounds function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N} + +@propagate_inbounds Base.getindex(A::OffsetVector, i::Int) = parent(A)[parentindex(Base.axes1(A), i)] +@propagate_inbounds Base.getindex(A::OffsetArray, i::Int) = parent(A)[i] + +@propagate_inbounds function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N} @boundscheck checkbounds(A, I...) - @inbounds parent(A)[offset(A.offsets, I)...] = val + J = @inbounds map(parentindex, axes(A), I) + @inbounds parent(A)[J...] = val val end -@inline @propagate_inbounds function Base.setindex!(A::OffsetVector, val, i::Int) + +@propagate_inbounds function Base.setindex!(A::OffsetVector, val, i::Int) @boundscheck checkbounds(A, i) - @inbounds parent(A)[offset(A.offsets, (i,))[1]] = val + @inbounds parent(A)[parentindex(Base.axes1(A), i)] = val val end -@inline @propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int) +@propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int) @boundscheck checkbounds(A, i) @inbounds parent(A)[i] = val val @@ -163,6 +213,7 @@ end # For fast broadcasting: ref https://discourse.julialang.org/t/why-is-there-a-performance-hit-on-broadcasting-with-offsetarrays/32194 Base.dataids(A::OffsetArray) = Base.dataids(parent(A)) +Broadcast.broadcast_unalias(dest::OffsetArray, src::OffsetArray) = parent(dest) === parent(src) ? src : Broadcast.unalias(dest, src) ### Special handling for AbstractRange @@ -175,10 +226,10 @@ Base.getindex(a::OffsetRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offs Base.getindex(a::OffsetRange, r::AbstractRange) = a.parent[r .- a.offsets[1]] Base.getindex(a::AbstractRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets) -@inline @propagate_inbounds Base.getindex(r::UnitRange, s::IIUR) = +@propagate_inbounds Base.getindex(r::UnitRange, s::IIUR) = OffsetArray(r[s.indices], s) -@inline @propagate_inbounds Base.getindex(r::StepRange, s::IIUR) = +@propagate_inbounds Base.getindex(r::StepRange, s::IIUR) = OffsetArray(r[s.indices], s) @inline @propagate_inbounds Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::IIUR) where T = @@ -212,16 +263,6 @@ Base.empty!(A::OffsetVector) = (empty!(A.parent); A) ### Low-level utilities ### -# Computing a shifted index (subtracting the offset) -@inline offset(offsets::NTuple{N,Int}, inds::NTuple{N,Int}) where {N} = - (inds[1]-offsets[1], offset(Base.tail(offsets), Base.tail(inds))...) -offset(::Tuple{}, ::Tuple{}) = () - -# Support trailing 1s -@inline offset(offsets::Tuple{Vararg{Int}}, inds::Tuple{Vararg{Int}}) = - (offset(offsets, Base.front(inds))..., inds[end]) -offset(offsets::Tuple{Vararg{Int}}, inds::Tuple{}) = error("inds cannot be shorter than offsets") - indexoffset(r::AbstractRange) = first(r) - 1 indexoffset(i::Integer) = 0 indexoffset(i::Colon) = 0 @@ -229,8 +270,6 @@ indexlength(r::AbstractRange) = length(r) indexlength(i::Integer) = i indexlength(i::Colon) = Colon() -@eval @deprecate $(Symbol("@unsafe")) $(Symbol("@inbounds")) - function Base.showarg(io::IO, a::OffsetArray, toplevel) print(io, "OffsetArray(") Base.showarg(io, parent(a), false) @@ -352,5 +391,8 @@ Base.searchsortedfirst(v::OffsetArray, x, lo::T, hi::T, o::Base.Ordering) where Base.searchsortedlast(v::OffsetArray, x, lo::T, hi::T, o::Base.Ordering) where T<:Integer = _safe_searchsortedlast(v, x, lo, hi, o) +if VERSION < v"1.1.0-DEV.783" + Base.copyfirst!(dest::OffsetArray, src::OffsetArray) = (maximum!(parent(dest), parent(src)); return dest) +end end # module diff --git a/test/runtests.jl b/test/runtests.jl index a07a6fbf..c6d2e786 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -34,7 +34,12 @@ end @testset "undef, missing, and nothing constructors" begin y = OffsetArray{Float32}(undef, (IdentityUnitRange(-1:1),)) - @test axes(y) === (IdentityUnitRange(-1:1),) + @test axes(y) == (IdentityUnitRange(-1:1),) + @test eltype(y) === Float32 + + y = OffsetArray{Float64}(undef, -1:1, -7:7, -128:512, -5:5, -1:1, -3:3, -2:2, -1:1) + @test axes(y) == (-1:1, -7:7, -128:512, -5:5, -1:1, -3:3, -2:2, -1:1) + @test eltype(y) === Float64 for (T, t) in ((Missing, missing), (Nothing, nothing)) @test !isassigned(OffsetArray{Union{T,Vector{Int}}}(undef, -1:1, -1:1), -1, -1) @@ -44,14 +49,6 @@ end end end -@testset "high dimensionality" begin - y = OffsetArray{Float64}(undef, -1:1, -7:7, -128:512, -5:5, -1:1, -3:3, -2:2, -1:1) - @test axes(y) == (-1:1, -7:7, -128:512, -5:5, -1:1, -3:3, -2:2, -1:1) - y[-1,-7,-128,-5,-1,-3,-2,-1] = 14 - y[-1,-7,-128,-5,-1,-3,-2,-1] += 5 - @test y[-1,-7,-128,-5,-1,-3,-2,-1] == 19 -end - @testset "Offset range construction" begin r = -2:5 y = OffsetArray(r, r) @@ -60,6 +57,13 @@ end @test axes(y) == (r,) end +@testset "Axes supplied to constructor correspond to final result" begin + # Ref https://github.com/JuliaArrays/OffsetArrays.jl/pull/65#issuecomment-457181268 + B = BidirectionalVector([1, 2, 3], -2) + A = OffsetArray(B, -1:1) + @test axes(A) == (IdentityUnitRange(-1:1),) +end + @testset "Traits" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) # IndexLinear @@ -97,6 +101,11 @@ end @test Ac[0,3] == 11 @inbounds Ac[0,3,1] = 12 @test Ac[0,3] == 12 + + y = OffsetArray{Float64}(undef, -1:1, -7:7, -128:512, -5:5, -1:1, -3:3, -2:2, -1:1) + y[-1,-7,-128,-5,-1,-3,-2,-1] = 14 + y[-1,-7,-128,-5,-1,-3,-2,-1] += 5 + @test y[-1,-7,-128,-5,-1,-3,-2,-1] == 19 end @testset "Vector indexing" begin @@ -124,10 +133,10 @@ end r1 = r[0:1] @test r1 === 9:10 r1 = (8:10)[OffsetArray(1:2, -5:-4)] - @test axes(r1) === (IdentityUnitRange(-5:-4),) + @test axes(r1) == (IdentityUnitRange(-5:-4),) @test parent(r1) === 8:9 r1 = OffsetArray(8:10, -1:1)[OffsetArray(0:1, -5:-4)] - @test axes(r1) === (IdentityUnitRange(-5:-4),) + @test axes(r1) == (IdentityUnitRange(-5:-4),) @test parent(r1) === 9:10 end @@ -159,13 +168,13 @@ end @test S[0] == 1 @test S[1] == 2 @test_throws BoundsError S[2] - @test axes(S) === (IdentityUnitRange(0:1),) + @test axes(S) == (IdentityUnitRange(0:1),) S = view(A, 0, :) @test S == OffsetArray([1,3], (A.offsets[2],)) @test S[3] == 1 @test S[4] == 3 @test_throws BoundsError S[1] - @test axes(S) === (IdentityUnitRange(3:4),) + @test axes(S) == (IdentityUnitRange(3:4),) S = view(A, 0:0, 4) @test S == [3] @test S[1] == 3 @@ -184,7 +193,7 @@ end @test S[0,4] == S[3] == 3 @test S[1,4] == S[4] == 4 @test_throws BoundsError S[1,1] - @test axes(S) === IdentityUnitRange.((0:1, 3:4)) + @test axes(S) == IdentityUnitRange.((0:1, 3:4)) end @testset "iteration" begin @@ -259,10 +268,10 @@ end @test axes(B) === (Base.OneTo(3), Base.OneTo(4)) B = similar(A, (-3:3,1:4)) @test isa(B, OffsetArray{Int,2}) - @test axes(B) === IdentityUnitRange.((-3:3, 1:4)) + @test axes(B) == IdentityUnitRange.((-3:3, 1:4)) B = similar(parent(A), (-3:3,1:4)) @test isa(B, OffsetArray{Int,2}) - @test axes(B) === IdentityUnitRange.((-3:3, 1:4)) + @test axes(B) == IdentityUnitRange.((-3:3, 1:4)) @test isa([x for x in [1,2,3]], Vector{Int}) @test similar(Array{Int}, (0:0, 0:0)) isa OffsetArray{Int, 2} @test similar(Array{Int}, (1, 1)) isa Matrix{Int} @@ -307,17 +316,17 @@ end i1 = OffsetArray([2,1], (-5,)) i1 = OffsetArray([2,1], -5) b = A0[i1, 1] - @test axes(b) === (IdentityUnitRange(-4:-3),) + @test axes(b) == (IdentityUnitRange(-4:-3),) @test b[-4] == 2 @test b[-3] == 1 b = A0[1,i1] - @test axes(b) === (IdentityUnitRange(-4:-3),) + @test axes(b) == (IdentityUnitRange(-4:-3),) @test b[-4] == 3 @test b[-3] == 1 v = view(A0, i1, 1) - @test axes(v) === (IdentityUnitRange(-4:-3),) + @test axes(v) == (IdentityUnitRange(-4:-3),) v = view(A0, 1:1, i1) - @test axes(v) === (Base.OneTo(1), IdentityUnitRange(-4:-3)) + @test axes(v) == (Base.OneTo(1), IdentityUnitRange(-4:-3)) for r in (1:10, 1:1:10, StepRangeLen(1, 1, 10), LinRange(1, 10, 10)) for s in (IdentityUnitRange(2:3), OffsetArray(2:3, 2:3)) @@ -517,6 +526,13 @@ struct NegativeArray{T,N,S <: AbstractArray{T,N}} <: AbstractArray{T,N} parent::S end +# Note: this defines the axes-of-the-axes to be OneTo. +# In general this isn't recommended, because +# positionof(A, i, j, ...) == map(getindex, axes(A), (i, j, ...)) +# is quite desirable, and this requires that the axes be "identity" ranges, i.e., +# `r[i] == i`. +# Nevertheless it's useful to test this on a "broken" implementation +# to make sure we still get the right answer. Base.axes(A::NegativeArray) = map(n -> (-n):(-1), size(A.parent)) Base.size(A::NegativeArray) = size(A.parent) @@ -590,10 +606,10 @@ end @test searchsortedlast(o, 1) == -1 @test searchsortedlast(o, 2) == -1 @test searchsortedlast(o, 5) == 2 - @test searchsortedlast(o, 6) == 2 + @test searchsortedlast(o, 6) == 2 @test searchsorted(o, -2) == -1:-2 @test searchsorted(o, 1) == -1:-1 @test searchsorted(o, 2) == 0:-1 @test searchsorted(o, 5) == 2:2 - @test searchsorted(o, 6) == 3:2 -end \ No newline at end of file + @test searchsorted(o, 6) == 3:2 +end