From 528249dd247908b236093c06424301f1c01c3ff3 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 8 May 2016 07:03:07 -0500 Subject: [PATCH 1/2] Add `indices` to support indexing with arbitrary offsets --- base/abstractarray.jl | 53 ++++++++++++++++++++++++--------------- base/deprecated.jl | 9 +++++++ base/exports.jl | 1 + base/multidimensional.jl | 11 +++++--- doc/manual/arrays.rst | 4 ++- doc/manual/interfaces.rst | 41 ++++++++++++++++-------------- doc/stdlib/arrays.rst | 12 +++++++++ test/abstractarray.jl | 49 +++++++++++++++++++++++++++++++++--- 8 files changed, 132 insertions(+), 48 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 8a2b5dff32c2f2..dca4d96a561450 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -21,6 +21,18 @@ end size{T,n}(t::AbstractArray{T,n}, d) = d <= n ? size(t)[d] : 1 size(x, d1::Integer, d2::Integer, dx::Integer...) = tuple(size(x, d1), size(x, d2, dx...)...) +""" + indices(A, d) + +Returns the valid range of indices for array `A` along dimension `d`. +""" +indices(A::AbstractArray, d) = 1:size(A,d) +""" + indices(A) + +Returns the tuple of valid indices for array `A`. +""" +indices{T,N}(A::AbstractArray{T,N}) = ntuple(d->indices(A, d), Val{N}) eltype{T}(::Type{AbstractArray{T}}) = T eltype{T,n}(::Type{AbstractArray{T,n}}) = T elsize{T}(::AbstractArray{T}) = sizeof(T) @@ -97,36 +109,36 @@ linearindexing(::LinearIndexing, ::LinearIndexing) = LinearSlow() end # check along a single dimension -checkbounds(::Type{Bool}, sz::Integer, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))")) -checkbounds(::Type{Bool}, sz::Integer, i::Real) = 1 <= i <= sz -checkbounds(::Type{Bool}, sz::Integer, ::Colon) = true -function checkbounds(::Type{Bool}, sz::Integer, r::Range) +checkbounds(::Type{Bool}, inds::UnitRange, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))")) +checkbounds(::Type{Bool}, inds::UnitRange, i::Real) = first(inds) <= i <= last(inds) +checkbounds(::Type{Bool}, inds::UnitRange, ::Colon) = true +function checkbounds(::Type{Bool}, inds::UnitRange, r::Range) @_propagate_inbounds_meta - isempty(r) | (checkbounds(Bool, sz, first(r)) & checkbounds(Bool, sz, last(r))) + isempty(r) | (checkbounds(Bool, inds, first(r)) & checkbounds(Bool, inds, last(r))) end -checkbounds{N}(::Type{Bool}, sz::Integer, I::AbstractArray{Bool,N}) = N == 1 && length(I) == sz -function checkbounds(::Type{Bool}, sz::Integer, I::AbstractArray) +checkbounds{N}(::Type{Bool}, indx::UnitRange, I::AbstractArray{Bool,N}) = N == 1 && indx == indices(I,1) +function checkbounds(::Type{Bool}, inds::UnitRange, I::AbstractArray) @_inline_meta b = true for i in I - b &= checkbounds(Bool, sz, i) + b &= checkbounds(Bool, inds, i) end b end # check all dimensions -function checkbounds{N,T}(::Type{Bool}, sz::NTuple{N,Integer}, I1::T, I...) +function checkbounds{N,T}(::Type{Bool}, inds::NTuple{N,UnitRange}, I1::T, I...) @_inline_meta - checkbounds(Bool, sz[1], I1) & checkbounds(Bool, tail(sz), I...) + checkbounds(Bool, inds[1], I1) & checkbounds(Bool, tail(inds), I...) end -checkbounds{T<:Integer}(::Type{Bool}, sz::Tuple{T}, I1) = (@_inline_meta; checkbounds(Bool, sz[1], I1)) -checkbounds{N}(::Type{Bool}, sz::NTuple{N,Integer}, I1) = (@_inline_meta; checkbounds(Bool, prod(sz), I1)) -checkbounds{N}(::Type{Bool}, sz::NTuple{N,Integer}) = (@_inline_meta; checkbounds(Bool, sz, 1)) # for a[] +checkbounds(::Type{Bool}, inds::Tuple{UnitRange}, I1) = (@_inline_meta; checkbounds(Bool, inds[1], I1)) +checkbounds{N}(::Type{Bool}, inds::NTuple{N,UnitRange}, I1) = (@_inline_meta; checkbounds(Bool, 1:prod(map(length, inds)), I1)) # TODO: eliminate (partial linear indexing) +checkbounds{N}(::Type{Bool}, inds::NTuple{N,UnitRange}) = (@_inline_meta; checkbounds(Bool, inds, 1)) # for a[] -checkbounds(::Type{Bool}, sz::Tuple{}, i) = (@_inline_meta; checkbounds(Bool, 1, i)) -function checkbounds(::Type{Bool}, sz::Tuple{}, i, I...) +checkbounds(::Type{Bool}, inds::Tuple{}, i) = (@_inline_meta; checkbounds(Bool, 1:1, i)) +function checkbounds(::Type{Bool}, inds::Tuple{}, i, I...) @_inline_meta - checkbounds(Bool, 1, i) & checkbounds(Bool, (), I...) + checkbounds(Bool, 1:1, i) & checkbounds(Bool, (), I...) end # Prevent allocation of a GC frame by hiding the BoundsError in a noinline function throw_boundserror(A, I) = (@_noinline_meta; throw(BoundsError(A, I))) @@ -136,12 +148,13 @@ checkbounds(A::AbstractArray, I...) = (@_inline_meta; _internal_checkbounds(A, I # The internal function is named _internal_checkbounds since there had been a # _checkbounds previously that meant something different. _internal_checkbounds(A::AbstractArray) = true -_internal_checkbounds(A::AbstractArray, I::AbstractArray{Bool}) = size(A) == size(I) || throw_boundserror(A, I) +_internal_checkbounds(A::AbstractArray, I::AbstractArray{Bool}) = indices(A) == indices(I) || throw_boundserror(A, I) +_internal_checkbounds(A::AbstractVector, I::AbstractVector{Bool}) = indices(A) == indices(I) || throw_boundserror(A, I) _internal_checkbounds(A::AbstractArray, I::AbstractVector{Bool}) = length(A) == length(I) || throw_boundserror(A, I) function _internal_checkbounds(A::AbstractArray, I1, I...) # having I1 seems important for good codegen @_inline_meta - checkbounds(Bool, size(A), I1, I...) || throw_boundserror(A, (I1, I...)) + checkbounds(Bool, indices(A), I1, I...) || throw_boundserror(A, (I1, I...)) end # See also specializations in multidimensional @@ -565,9 +578,9 @@ end typealias RangeVecIntList{A<:AbstractVector{Int}} Union{Tuple{Vararg{Union{Range, AbstractVector{Int}}}}, AbstractVector{UnitRange{Int}}, AbstractVector{Range{Int}}, AbstractVector{A}} -get(A::AbstractArray, i::Integer, default) = checkbounds(Bool, length(A), i) ? A[i] : default +get(A::AbstractArray, i::Integer, default) = checkbounds(Bool, indices(A), i) ? A[i] : default get(A::AbstractArray, I::Tuple{}, default) = similar(A, typeof(default), 0) -get(A::AbstractArray, I::Dims, default) = checkbounds(Bool, size(A), I...) ? A[I...] : default +get(A::AbstractArray, I::Dims, default) = checkbounds(Bool, indices(A), I...) ? A[I...] : default function get!{T}(X::AbstractArray{T}, A::AbstractArray, I::Union{Range, AbstractVector{Int}}, default::T) ind = findin(I, 1:length(A)) diff --git a/base/deprecated.jl b/base/deprecated.jl index f2f8afdb05f5af..cd12f64a062726 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1151,6 +1151,15 @@ end isequal(x::Char, y::Integer) = false isequal(x::Integer, y::Char) = false +function checkbounds(::Type{Bool}, sz::Integer, i) + depwarn("checkbounds(Bool, size(A, d), i) is deprecated, use checkbounds(Bool, indices(A, d), i).", :checkbounds) + checkbounds(Bool, 1:sz, i) +end +function checkbounds{N,T}(::Type{Bool}, sz::NTuple{N,Integer}, I1::T, I...) + depwarn("checkbounds(Bool, size(A), I...) is deprecated, use checkbounds(Bool, indices(A), I...).", :checkbounds) + checkbounds(Bool, map(s->1:s, sz), I1, I...) +end + # During the 0.5 development cycle, do not add any deprecations below this line # To be deprecated in 0.6 diff --git a/base/exports.jl b/base/exports.jl index b1cba3ce808955..a9569dc3c892ea 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -537,6 +537,7 @@ export hvcat, ind2sub, indexin, + indices, indmax, indmin, invperm, diff --git a/base/multidimensional.jl b/base/multidimensional.jl index f050701da89ff8..44cd2b0ddbd3a1 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -199,7 +199,7 @@ using .IteratorsMD for N = 1:5 args = [:($(Symbol(:I, d))) for d = 1:N] targs = [:($(Symbol(:I, d))::Union{Colon,Number,AbstractArray}) for d = 1:N] # prevent co-opting the CartesianIndex version - exs = [:(checkbounds(Bool, size(A, $d), $(args[d]))) for d = 1:N] + exs = [:(checkbounds(Bool, indices(A, $d), $(args[d]))) for d = 1:N] cbexpr = exs[1] for d = 2:N cbexpr = :($(exs[d]) & $cbexpr) @@ -223,11 +223,14 @@ _internal_checkbounds(A::AbstractVector, I::AbstractVector{Bool}) = length(A) == @inline function checkbounds(::Type{Bool}, ::Tuple{}, I1::CartesianIndex) checkbounds(Bool, (), I1.I...) end -@inline function checkbounds(::Type{Bool}, sz::Tuple{}, I1::CartesianIndex, I...) +@inline function checkbounds(::Type{Bool}, inds::Tuple{}, I1::CartesianIndex, I...) checkbounds(Bool, (), I1.I..., I...) end -@inline function checkbounds(::Type{Bool}, sz::Dims, I1::CartesianIndex, I...) - checkbounds(Bool, sz, I1.I..., I...) +@inline function checkbounds(::Type{Bool}, inds::Tuple{UnitRange}, I1::CartesianIndex) + checkbounds(Bool, inds, I1.I..., I...) +end +@inline function checkbounds{N}(::Type{Bool}, inds::NTuple{N,UnitRange}, I1::CartesianIndex, I...) + checkbounds(Bool, inds, I1.I..., I...) end # Recursively compute the lengths of a list of indices, without dropping scalars diff --git a/doc/manual/arrays.rst b/doc/manual/arrays.rst index ee85263e9a273b..07e67fd4c8e32f 100644 --- a/doc/manual/arrays.rst +++ b/doc/manual/arrays.rst @@ -50,7 +50,9 @@ Function Description :func:`length(A) ` the number of elements in ``A`` :func:`ndims(A) ` the number of dimensions of ``A`` :func:`size(A) ` a tuple containing the dimensions of ``A`` -:func:`size(A,n) ` the size of ``A`` in a particular dimension +:func:`size(A,n) ` the size of ``A`` along a particular dimension +:func:`indices(A) ` a tuple containing the valid indices of ``A`` +:func:`indices(A,n) ` a range expressing the valid indices along dimension ``n`` :func:`eachindex(A) ` an efficient iterator for visiting each position in ``A`` :func:`stride(A,k) ` the stride (linear index distance between adjacent elements) along dimension ``k`` :func:`strides(A) ` a tuple of the strides in each dimension diff --git a/doc/manual/interfaces.rst b/doc/manual/interfaces.rst index 58c9f318fdc891..b327a67faddb17 100644 --- a/doc/manual/interfaces.rst +++ b/doc/manual/interfaces.rst @@ -147,25 +147,28 @@ While this is starting to support more of the :ref:`indexing operations supporte Abstract Arrays --------------- -========================================================== ============================================ ======================================================================================= -Methods to implement Brief description -========================================================== ============================================ ======================================================================================= -:func:`size(A) ` Returns a tuple containing the dimensions of A -:func:`Base.linearindexing(Type) ` Returns either ``Base.LinearFast()`` or ``Base.LinearSlow()``. See the description below. -:func:`getindex(A, i::Int) ` (if ``LinearFast``) Linear scalar indexing -:func:`getindex(A, i1::Int, ..., iN::Int) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing -:func:`setindex!(A, v, i::Int) ` (if ``LinearFast``) Scalar indexed assignment -:func:`setindex!(A, v, i1::Int, ..., iN::Int) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment -**Optional methods** **Default definition** **Brief description** -:func:`getindex(A, I...) ` defined in terms of scalar :func:`getindex` :ref:`Multidimensional and nonscalar indexing ` -:func:`setindex!(A, I...) ` defined in terms of scalar :func:`setindex!` :ref:`Multidimensional and nonscalar indexed assignment ` -:func:`start`/:func:`next`/:func:`done` defined in terms of scalar :func:`getindex` Iteration -:func:`length(A) ` ``prod(size(A))`` Number of elements -:func:`similar(A) ` ``similar(A, eltype(A), size(A))`` Return a mutable array with the same shape and element type -:func:`similar(A, ::Type{S}) ` ``similar(A, S, size(A))`` Return a mutable array with the same shape and the specified element type -:func:`similar(A, dims::NTuple{Int}) ` ``similar(A, eltype(A), dims)`` Return a mutable array with the same element type and the specified dimensions -:func:`similar(A, ::Type{S}, dims::NTuple{Int}) ` ``Array(S, dims)`` Return a mutable array with the specified element type and dimensions -========================================================== ============================================ ======================================================================================= +===================================================================== ============================================ ======================================================================================= +Methods to implement Brief description +===================================================================== ============================================ ======================================================================================= +:func:`size(A) ` Returns a tuple containing the dimensions of ``A`` +:func:`getindex(A, i::Int) ` (if ``LinearFast``) Linear scalar indexing +:func:`getindex(A, i1::Int, ..., iN::Int) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing +:func:`setindex!(A, v, i::Int) ` (if ``LinearFast``) Scalar indexed assignment +:func:`setindex!(A, v, i1::Int, ..., iN::Int) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment +**Optional methods** **Default definition** **Brief description** +:func:`Base.linearindexing(Type) ` ``Base.LinearSlow()`` Returns either ``Base.LinearFast()`` or ``Base.LinearSlow()``. See the description below. +:func:`indices(A, d) ` ``1:size(A, d)`` Returns the range of valid indices along dimension ``d`` +:func:`getindex(A, I...) ` defined in terms of scalar :func:`getindex` :ref:`Multidimensional and nonscalar indexing ` +:func:`setindex!(A, I...) ` defined in terms of scalar :func:`setindex!` :ref:`Multidimensional and nonscalar indexed assignment ` +:func:`start`/:func:`next`/:func:`done` defined in terms of scalar :func:`getindex` Iteration +:func:`length(A) ` ``prod(size(A))`` Number of elements +:func:`similar(A) ` ``similar(A, eltype(A), indices(A))`` Return a mutable array with the same shape and element type +:func:`similar(A, ::Type{S}) ` ``similar(A, S, indices(A))`` Return a mutable array with the same shape and the specified element type +:func:`similar(A, inds::NTuple{UnitRange{Int}}) ` ``similar(A, eltype(A), inds)`` Return a mutable array with the same element type and the specified indices +:func:`similar(A, dims::NTuple{Int}) ` ``similar(A, eltype(A), dims)`` Return a mutable array with the same element type and size `dims` +:func:`similar(A, ::Type{S}, inds::NTuple{UnitRange{Int}}) ` ``Array(S, map(length, inds))`` Return a mutable array with the specified element type and indices +:func:`similar(A, ::Type{S}, dims::NTuple{Int}) ` ``Array(S, dims)`` Return a mutable array with the specified element type and size +===================================================================== ============================================ ======================================================================================= If a type is defined as a subtype of ``AbstractArray``, it inherits a very large set of rich behaviors including iteration and multidimensional indexing built on top of single-element access. See the :ref:`arrays manual page ` and :ref:`standard library section ` for more supported methods. diff --git a/doc/stdlib/arrays.rst b/doc/stdlib/arrays.rst index 45f1140a3b6f52..ff8e18414cd137 100644 --- a/doc/stdlib/arrays.rst +++ b/doc/stdlib/arrays.rst @@ -31,6 +31,18 @@ Basic functions julia> size(A,3,2) (4,3) +.. function:: indices(A) + + .. Docstring generated from Julia source + + Returns the tuple of valid indices for array ``A``\ . + +.. function:: indices(A, d) + + .. Docstring generated from Julia source + + Returns the valid range of indices for array ``A`` along dimension ``d``\ . + .. function:: iseltype(A,T) .. Docstring generated from Julia source diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 946bc03e9bcc12..fcbedb4a75cc11 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -254,12 +254,12 @@ end function test_in_bounds(::Type{TestAbstractArray}) n = rand(2:5) - dims = tuple(rand(2:5, n)...) - len = prod(dims) + inds = ntuple(d->1:rand(2:5), n) + len = prod(map(length, inds)) for i in 1:len - @test checkbounds(Bool, dims, i) == true + @test checkbounds(Bool, inds, i) == true end - @test checkbounds(Bool, dims, len + 1) == false + @test checkbounds(Bool, inds, len + 1) == false end type UnimplementedFastArray{T, N} <: AbstractArray{T, N} end @@ -520,3 +520,44 @@ A = TSlowNIndexes(rand(2,2)) @test_throws ErrorException A[1] @test A[1,1] == A.data[1] @test first(A) == A.data[1] + +# OffsetArrays (arrays with indexing that doesn't start at 1) + +module OAs + +immutable OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N} + parent::AA + offsets::NTuple{N,Int} +end + +OffsetArray{T,N}(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) = OffsetArray{T,N,typeof(A)}(A, offsets) + +Base.parent(A::OffsetArray) = A.parent +Base.size(A::OffsetArray) = size(parent(A)) +Base.indices(A::OffsetArray, d) = (1:size(parent(A),d))+A.offsets[d] +Base.eachindex(A::OffsetArray) = CartesianRange(indices(A)) +Base.summary(A::OffsetArray) = string(typeof(A))*" with indices "*string(indices(A)) + +@inline function Base.getindex{T,N}(A::OffsetArray{T,N}, I::Vararg{Int,N}) + @boundscheck checkbounds(A, I...) + @inbounds ret = parent(A)[offset(A.offsets, I)...] + ret +end +@inline function Base.setindex!{T,N}(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) + @boundscheck checkbounds(A, I...) + @inbounds parent(A)[offset(A.offsets, I)...] = val + val +end + +offset{N}(offsets::NTuple{N,Int}, inds::NTuple{N,Int}) = _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)) + +end + +A = OAs.OffsetArray([1 3; 2 4], (-1,2)) +@test A[0,3] == 1 +@test A[1,3] == 2 +@test A[0,4] == 3 +@test A[1,4] == 4 +@test_throws BoundsError A[1,1] From 7f3981fd90890d0540836448022fa78818673344 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 11 May 2016 06:14:37 -0500 Subject: [PATCH 2/2] Force indices to inline Kind of surprising this doesn't happen automatically --- base/abstractarray.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index dca4d96a561450..50428816f4b57c 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -26,13 +26,19 @@ size(x, d1::Integer, d2::Integer, dx::Integer...) = tuple(size(x, d1), size(x, d Returns the valid range of indices for array `A` along dimension `d`. """ -indices(A::AbstractArray, d) = 1:size(A,d) +function indices(A::AbstractArray, d) + @_inline_meta + 1:size(A,d) +end """ indices(A) Returns the tuple of valid indices for array `A`. """ -indices{T,N}(A::AbstractArray{T,N}) = ntuple(d->indices(A, d), Val{N}) +function indices{T,N}(A::AbstractArray{T,N}) + @_inline_meta + ntuple(d->indices(A, d), Val{N}) +end eltype{T}(::Type{AbstractArray{T}}) = T eltype{T,n}(::Type{AbstractArray{T,n}}) = T elsize{T}(::AbstractArray{T}) = sizeof(T)