diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 478130f4834da..e687fcbcf16e7 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -1442,13 +1442,13 @@ function _typed_vcat(::Type{T}, V::AbstractVecOrTuple{AbstractVector}) where T for Vk in V n += Int(length(Vk))::Int end - a = similar(V[1], T, n) - pos = 1 - for k=1:Int(length(V))::Int + a = similar(first(V), T, n) + pos = first(axes(a, 1)) + for k = eachindex(V) Vk = V[k] - p1 = pos + Int(length(Vk))::Int - 1 - a[pos:p1] = Vk - pos = p1+1 + n = length(Vk) + copyto!(a, pos, Vk, first(axes(Vk, 1)), n) + pos += n end a end @@ -1459,11 +1459,10 @@ hcat(A::AbstractVecOrMat...) = typed_hcat(promote_eltype(A...), A...) hcat(A::AbstractVecOrMat{T}...) where {T} = typed_hcat(T, A...) function _typed_hcat(::Type{T}, A::AbstractVecOrTuple{AbstractVecOrMat}) where T - nargs = length(A) - nrows = size(A[1], 1) + nrows = size(first(A), 1) ncols = 0 dense = true - for j = 1:nargs + for j = eachindex(A) Aj = A[j] if size(Aj, 1) != nrows throw(ArgumentError("number of rows of each array must match (got $(map(x->size(x,1), A)))")) @@ -1472,17 +1471,17 @@ function _typed_hcat(::Type{T}, A::AbstractVecOrTuple{AbstractVecOrMat}) where T nd = ndims(Aj) ncols += (nd==2 ? size(Aj,2) : 1) end - B = similar(A[1], T, nrows, ncols) - pos = 1 + B = similar(first(A), T, nrows, ncols) + pos = first(axes(B, 1)) if dense - for k=1:nargs + for k=eachindex(A) Ak = A[k] n = length(Ak) copyto!(B, pos, Ak, 1, n) pos += n end else - for k=1:nargs + for k=eachindex(A) Ak = A[k] p1 = pos+(isa(Ak,AbstractMatrix) ? size(Ak, 2) : 1)-1 B[:, pos:p1] = Ak @@ -1496,17 +1495,16 @@ vcat(A::AbstractVecOrMat...) = typed_vcat(promote_eltype(A...), A...) vcat(A::AbstractVecOrMat{T}...) where {T} = typed_vcat(T, A...) function _typed_vcat(::Type{T}, A::AbstractVecOrTuple{AbstractVecOrMat}) where T - nargs = length(A) nrows = sum(a->size(a, 1), A)::Int - ncols = size(A[1], 2) - for j = 2:nargs + ncols = size(first(A), 2) + for j = first(axes(A))[2:end] if size(A[j], 2) != ncols throw(ArgumentError("number of columns of each array must match (got $(map(x->size(x,2), A)))")) end end - B = similar(A[1], T, nrows, ncols) - pos = 1 - for k=1:nargs + B = similar(first(A), T, nrows, ncols) + pos = first(axes(B, 1)) + for k=eachindex(A) Ak = A[k] p1 = pos+size(Ak,1)::Int-1 B[pos:p1, :] = Ak @@ -1589,7 +1587,7 @@ _cat(dims, X...) = cat_t(promote_eltypeof(X...), X...; dims=dims) @inline function _cat_t(dims, ::Type{T}, X...) where {T} catdims = dims2cat(dims) shape = cat_shape(catdims, map(cat_size, X)::Tuple{Vararg{Union{Int,Dims}}})::Dims - A = cat_similar(X[1], T, shape) + A = cat_similar(first(X), T, shape) if count(!iszero, catdims)::Int > 1 fill!(A, zero(T)) end @@ -1604,7 +1602,7 @@ function __cat(A, shape::NTuple{M,Int}, catdims, X...) where M for x in X for i = 1:N if concat[i] - inds[i] = offsets[i] .+ cat_indices(x, i) + inds[i] = offsets[i] .+ parent(cat_indices(x, i)) offsets[i] += cat_size(x, i) else inds[i] = 1:shape[i] diff --git a/stdlib/Distributed/test/splitrange.jl b/stdlib/Distributed/test/splitrange.jl index 7b15593d21eaf..12bfb96650f6b 100644 --- a/stdlib/Distributed/test/splitrange.jl +++ b/stdlib/Distributed/test/splitrange.jl @@ -22,7 +22,7 @@ using Distributed: splitrange @test splitrange(-1, 1, 4) == Array{UnitRange{Int64},1}([-1:-1,0:0,1:1]) const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +isdefined(Main, :OffsetArrays) || @eval Main @everywhere include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) using .Main.OffsetArrays oa = OffsetArray([123, -345], (-2,)) diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 2db1638c8c950..490fba008089f 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -616,6 +616,52 @@ function test_cat(::Type{TestAbstractArray}) @test cat([1], [1], dims=[1, 2]) == I(2) end +module TestOffsetArraysCats + using Test + isdefined(Main, :OffsetArrays) || @eval Main include(joinpath(@__DIR__, "testhelpers", "OffsetArrays.jl")) + using .Main.OffsetArrays + + # `cat`s on OffsetArrays ignore their offsets and treat them as normal list + + # 1d + v1 = collect(1:4) + v2 = collect(5:8) + ov1 = OffsetArray(v1, -1) + ov2 = OffsetArray(v2, 1) + @test hcat(ov1, v1, ov2, v2) == hcat(v1, v1, v2, v2) + @test vcat(ov1, v1, ov2, v2) == vcat(v1, v1, v2, v2) + @test hvcat((2, 2), ov1, v2, v1, ov2) == hvcat((2, 2), v1, v2, v1, v2) + # 37628 + @test reduce(hcat, (v1, v2)) == hcat(v1, v2) + @test reduce(vcat, (v1, v2)) == vcat(v1, v2) + @test reduce(hcat, OffsetVector([1:2, 1:2],10)) == [1 1;2 2] + + # 2d + a1 = reshape(collect(1:6), 2, 3) + a2 = reshape(collect(7:12), 2, 3) + oa1 = OffsetArray(a1, -1, -1) + oa2 = OffsetArray(a2, 1, 1) + @test hcat(oa1, a1, oa2, a2) == hcat(a1, a1, a2, a2) + @test vcat(oa1, a1, oa2, a2) == vcat(a1, a1, a2, a2) + @test hvcat((2, 2), oa1, a2, a1, oa2) == hvcat((2, 2), a1, a2, a1, a2) + + # 3d + a1 = reshape(collect(1:12), 2, 3, 2) + a2 = reshape(collect(13:24), 2, 3, 2) + oa1 = OffsetArray(a1, -1, -1, -1) + oa2 = OffsetArray(a2, 1, 1, 1) + @test hcat(oa1, a1, oa2, a2) == hcat(a1, a1, a2, a2) + @test vcat(oa1, a1, oa2, a2) == vcat(a1, a1, a2, a2) + @test hvcat((2, 2), oa1, a2, a1, oa2) == hvcat((2, 2), a1, a2, a1, a2) + # https://github.com/JuliaArrays/OffsetArrays.jl/issues/63 + form=OffsetArray(reshape(zeros(Int8,0),0,0,2),0:-1,0:-1,0:1) + exp=OffsetArray(reshape(zeros(Int8,0),0,16,2),0:-1,0:15,0:1) + @test size(hcat(form,exp)) == (0, 16, 2) + # 37493 + @test hcat(zeros(2, 1:1, 2), zeros(2, 2:3, 2)) == zeros(2, 3, 2) + @test vcat(zeros(1:1, 2, 2), zeros(2:3, 2, 2)) == zeros(3, 2, 2) +end + function test_ind2sub(::Type{TestAbstractArray}) n = rand(2:5) dims = tuple(rand(1:5, n)...) diff --git a/test/testhelpers/OffsetArrays.jl b/test/testhelpers/OffsetArrays.jl index efebb74ded2d8..63698dbbb1a3a 100644 --- a/test/testhelpers/OffsetArrays.jl +++ b/test/testhelpers/OffsetArrays.jl @@ -5,141 +5,278 @@ # This test file is designed to exercise support for generic indexing, # even though offset arrays aren't implemented in Base. -module OffsetArrays +module OffsetArrays # OffsetArrays@1.0.0 without compat and docstrings -using Base: Indices, IndexCartesian, IndexLinear, tail +using Base: Indices, tail, @propagate_inbounds +@static if !isdefined(Base, :IdentityUnitRange) + const IdentityUnitRange = Base.Slice +else + using Base: IdentityUnitRange +end + +export OffsetArray, OffsetVector + +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)) -export OffsetArray +# 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{T,N,typeof(A)}(A, offsets) -OffsetArray(A::AbstractArray{T,N}, offsets::Vararg{Int,N}) where {T,N} = OffsetArray(A, offsets) +## OffsetArray constructors -OffsetArray{T,N}(::UndefInitializer, inds::Indices{N}) where {T,N} = - OffsetArray{T,N,Array{T,N}}(Array{T,N}(undef, map(length, inds)), map(indsoffset, inds)) -OffsetArray{T}(::UndefInitializer, inds::Indices{N}) where {T,N} = - OffsetArray{T,N}(undef, inds) +offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent) +offset(axparent::AbstractUnitRange, ax::Integer) = 1 - first(axparent) -Base.IndexStyle(::Type{T}) where {T<:OffsetArray} = Base.IndexStyle(parenttype(T)) +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(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) + +# OffsetVector constructors +OffsetVector(A::AbstractVector, offset) = OffsetArray(A, offset) +OffsetVector{T}(init::ArrayInitializer, inds::AbstractUnitRange) where {T} = OffsetArray{T}(init, inds) + +function OffsetArray(A::AbstractArray{T,N}, inds::NTuple{N,AbstractUnitRange}) where {T,N} + 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(offset, axparent, inds)) +end +OffsetArray(A::AbstractArray{T,N}, inds::Vararg{AbstractUnitRange,N}) where {T,N} = + OffsetArray(A, inds) + +# avoid a level of indirection when nesting OffsetArrays +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(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 parenttype(A::OffsetArray) = parenttype(typeof(A)) Base.parent(A::OffsetArray) = A.parent -Base.size(A::OffsetArray) = size(A.parent) -Base.size(A::OffsetArray, d) = size(A.parent, d) Base.eachindex(::IndexCartesian, A::OffsetArray) = CartesianIndices(axes(A)) -Base.eachindex(::IndexLinear, A::OffsetVector) = axes(A, 1) +Base.eachindex(::IndexLinear, A::OffsetVector) = axes(A, 1) -# Implementations of indices and axes1. Since bounds-checking is -# performance-critical and relies on indices, these are usually worth -# optimizing thoroughly. -@inline Base.axes(A::OffsetArray, d) = 1 <= d <= length(A.offsets) ? Base.IdentityUnitRange(axes(parent(A))[d] .+ A.offsets[d]) : Base.IdentityUnitRange(1:1) -@inline Base.axes(A::OffsetArray) = _indices(axes(parent(A)), A.offsets) # would rather use ntuple, but see #15276 -@inline _indices(inds, offsets) = (Base.IdentityUnitRange(inds[1] .+ offsets[1]), _indices(tail(inds), tail(offsets))...) -_indices(::Tuple{}, ::Tuple{}) = () -Base.axes1(A::OffsetArray{T,0}) where {T} = Base.IdentityUnitRange(1:1) # we only need to specialize this one +@inline Base.size(A::OffsetArray) = size(parent(A)) +@inline Base.size(A::OffsetArray, d) = size(parent(A), d) -const OffsetAxis = Union{Integer, UnitRange, Base.IdentityUnitRange{<:UnitRange}, Base.OneTo} -function Base.similar(A::OffsetArray, T::Type, dims::Dims) - B = similar(parent(A), T, dims) +@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)) + return OffsetArray(B, map(offset, axes(B), inds)) end -function Base.similar(A::AbstractArray, T::Type, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) - B = similar(A, T, map(indslength, inds)) - OffsetArray(B, map(indsoffset, inds)) + +Base.reshape(A::AbstractArray, inds::OffsetAxis...) = reshape(A, 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 -Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T<:AbstractArray} = - OffsetArray(T(undef, map(indslength, shape)), map(indsoffset, shape)) +# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return +# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...))) +Base.reshape(A::OffsetArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) = + OffsetArray(reshape(parent(A), map(indexlength, inds)), map(indexoffset, inds)) +# And for non-offset axes, we can just return a reshape of the parent directly +Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = reshape(parent(A), inds) +Base.reshape(A::OffsetArray, inds::Dims) = reshape(parent(A), inds) +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.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) = OffsetArray(reshape(A, map(indslength, inds)), map(indsoffset, inds)) +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(indslength, inds)), map(indsoffset, inds)), v) + fill!(OffsetArray(Array{typeof(v), N}(undef, map(indexlength, inds)), map(indexoffset, inds)), v) Base.zeros(::Type{T}, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {T, N} = - fill!(OffsetArray(Array{T, N}(undef, map(indslength, inds)), map(indsoffset, inds)), zero(T)) + fill!(OffsetArray(Array{T, N}(undef, map(indexlength, inds)), map(indexoffset, inds)), zero(T)) Base.ones(::Type{T}, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {T, N} = - fill!(OffsetArray(Array{T, N}(undef, map(indslength, inds)), map(indsoffset, inds)), one(T)) + fill!(OffsetArray(Array{T, N}(undef, map(indexlength, inds)), map(indexoffset, inds)), one(T)) Base.trues(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = - fill!(OffsetArray(BitArray{N}(undef, map(indslength, inds)), map(indsoffset, inds)), true) + fill!(OffsetArray(BitArray{N}(undef, map(indexlength, inds)), map(indexoffset, inds)), true) Base.falses(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = - fill!(OffsetArray(BitArray{N}(undef, map(indslength, inds)), map(indsoffset, inds)), false) - -@inline function Base.getindex(A::OffsetArray{T,N}, I::Vararg{Int,N}) where {T,N} - checkbounds(A, I...) - @inbounds ret = parent(A)[offset(A.offsets, I)...] - ret -end -# Vectors don't support one-based linear indexing; they always use the offsets -@inline function Base.getindex(A::OffsetVector, i::Int) - checkbounds(A, i) - @inbounds ret = parent(A)[offset(A.offsets, (i,))[1]] - ret -end -# But multidimensional arrays allow one-based linear indexing -@inline function Base.getindex(A::OffsetArray, i::Int) - checkbounds(A, i) - @inbounds ret = parent(A)[i] - ret -end -@inline function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N} - checkbounds(A, I...) - @inbounds parent(A)[offset(A.offsets, I)...] = val + fill!(OffsetArray(BitArray{N}(undef, map(indexlength, inds)), map(indexoffset, inds)), false) + +## 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 + +@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...) + J = @inbounds map(parentindex, axes(A), I) + @inbounds parent(A)[J...] = val val end -@inline function Base.setindex!(A::OffsetVector, val, i::Int) - checkbounds(A, i) - @inbounds parent(A)[offset(A.offsets, (i,))[1]] = val + +@propagate_inbounds function Base.setindex!(A::OffsetVector, val, i::Int) + @boundscheck checkbounds(A, i) + @inbounds parent(A)[parentindex(Base.axes1(A), i)] = val val end -@inline function Base.setindex!(A::OffsetArray, val, i::Int) - checkbounds(A, i) +@propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int) + @boundscheck checkbounds(A, i) @inbounds parent(A)[i] = val val end -@inline function Base.deleteat!(A::OffsetArray, i::Int) - checkbounds(A, i) - @inbounds deleteat!(parent(A), offset(A.offsets, (i,))[1]) -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) -@inline function Base.deleteat!(A::OffsetArray{T,N}, I::Vararg{Int, N}) where {T,N} - checkbounds(A, I...) - @inbounds deleteat!(parent(A), offset(A.offsets, I)...) -end +### Special handling for AbstractRange -@inline function Base.deleteat!(A::OffsetArray, i::UnitRange{Int}) - checkbounds(A, first(i)) - checkbounds(A, last(i)) - first_idx = offset(A.offsets, (first(i),))[1] - last_idx = offset(A.offsets, (last(i),))[1] - @inbounds deleteat!(parent(A), first_idx:last_idx) -end +const OffsetRange{T} = OffsetArray{T,1,<:AbstractRange{T}} +const IIUR = IdentityUnitRange{S} where S<:AbstractUnitRange{T} where T<:Integer + +Base.step(a::OffsetRange) = step(parent(a)) + +Base.getindex(a::OffsetRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets) +Base.getindex(a::OffsetRange, r::AbstractRange) = a.parent[r .- a.offsets[1]] +Base.getindex(a::AbstractRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets) -function Base.push!(a::OffsetArray{T,1}, item) where T - # convert first so we don't grow the array if the assignment won't work - itemT = convert(T, item) - resize!(a, length(a)+1) - a[end] = itemT - return a +@propagate_inbounds Base.getindex(r::UnitRange, s::IIUR) = + OffsetArray(r[s.indices], s) + +@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 = + OffsetArray(r[s.indices], s) +@inline @propagate_inbounds Base.getindex(r::StepRangeLen{T}, s::IIUR) where {T} = + OffsetArray(r[s.indices], s) + +@inline @propagate_inbounds Base.getindex(r::LinRange, s::IIUR) = + OffsetArray(r[s.indices], s) + +function Base.show(io::IO, r::OffsetRange) + show(io, r.parent) + o = r.offsets[1] + print(io, " with indices ", o+1:o+length(r)) end +Base.show(io::IO, ::MIME"text/plain", r::OffsetRange) = show(io, r) + +### Convenience functions ### -# Computing a shifted index (subtracting the offset) -offset(offsets::NTuple{N,Int}, inds::NTuple{N,Int}) where {N} = _offset((), offsets, inds) -_offset(out, ::Tuple{}, ::Tuple{}) = out -@inline _offset(out, offsets, inds) = _offset((out..., inds[1]-offsets[1]), Base.tail(offsets), Base.tail(inds)) +Base.fill(x, inds::Tuple{UnitRange,Vararg{UnitRange}}) = + fill!(OffsetArray{typeof(x)}(undef, inds), x) +@inline Base.fill(x, ind1::UnitRange, inds::UnitRange...) = fill(x, (ind1, inds...)) -indsoffset(r::AbstractRange) = first(r) - 1 -indsoffset(i::Integer) = 0 -indslength(r::AbstractRange) = length(r) -indslength(i::Integer) = i +### Some mutating functions defined only for OffsetVector ### Base.resize!(A::OffsetVector, nl::Integer) = (resize!(A.parent, nl); A) +Base.push!(A::OffsetVector, x...) = (push!(A.parent, x...); A) +Base.pop!(A::OffsetVector) = pop!(A.parent) +Base.empty!(A::OffsetVector) = (empty!(A.parent); A) + +### Low-level utilities ### +indexoffset(r::AbstractRange) = first(r) - 1 +indexoffset(i::Integer) = 0 +indexoffset(i::Colon) = 0 +indexlength(r::AbstractRange) = length(r) +indexlength(i::Integer) = i +indexlength(i::Colon) = Colon() + +function Base.showarg(io::IO, a::OffsetArray, toplevel) + print(io, "OffsetArray(") + Base.showarg(io, parent(a), false) + if ndims(a) > 0 + print(io, ", ") + printindices(io, axes(a)...) + end + print(io, ')') + toplevel && print(io, " with eltype ", eltype(a)) +end +printindices(io::IO, ind1, inds...) = + (print(io, _unslice(ind1), ", "); printindices(io, inds...)) +printindices(io::IO, ind1) = print(io, _unslice(ind1)) +_unslice(x) = x +_unslice(x::IdentityUnitRange) = x.indices + +function no_offset_view(A::AbstractArray) + if Base.has_offset_axes(A) + OffsetArray(A, map(r->1-first(r), axes(A))) + else + A + end end + +no_offset_view(A::OffsetArray) = no_offset_view(parent(A)) + +end # module