Skip to content

Commit

Permalink
Add indices to support indexing with arbitrary offsets
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy committed May 9, 2016
1 parent bf73102 commit bb67ee7
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 48 deletions.
53 changes: 33 additions & 20 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)))
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand Down
9 changes: 9 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,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

Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ export
hvcat,
ind2sub,
indexin,
indices,
indmax,
indmin,
invperm,
Expand Down
11 changes: 7 additions & 4 deletions base/multidimensional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion doc/manual/arrays.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ Function Description
:func:`length(A) <length>` the number of elements in ``A``
:func:`ndims(A) <ndims>` the number of dimensions of ``A``
:func:`size(A) <size>` a tuple containing the dimensions of ``A``
:func:`size(A,n) <size>` the size of ``A`` in a particular dimension
:func:`size(A,n) <size>` the size of ``A`` along a particular dimension
:func:`indices(A) <indices>` a tuple containing the valid indices of ``A``
:func:`indices(A,n) <indices>` a range expressing the valid indices along dimension ``n``
:func:`eachindex(A) <eachindex>` an efficient iterator for visiting each position in ``A``
:func:`stride(A,k) <stride>` the stride (linear index distance between adjacent elements) along dimension ``k``
:func:`strides(A) <strides>` a tuple of the strides in each dimension
Expand Down
41 changes: 22 additions & 19 deletions doc/manual/interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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) <size>` Returns a tuple containing the dimensions of A
:func:`Base.linearindexing(Type) <Base.linearindexing>` Returns either ``Base.LinearFast()`` or ``Base.LinearSlow()``. See the description below.
:func:`getindex(A, i::Int) <getindex>` (if ``LinearFast``) Linear scalar indexing
:func:`getindex(A, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing
:func:`setindex!(A, v, i::Int) <getindex>` (if ``LinearFast``) Scalar indexed assignment
:func:`setindex!(A, v, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment
**Optional methods** **Default definition** **Brief description**
:func:`getindex(A, I...) <getindex>` defined in terms of scalar :func:`getindex` :ref:`Multidimensional and nonscalar indexing <man-array-indexing>`
:func:`setindex!(A, I...) <setindex!>` defined in terms of scalar :func:`setindex!` :ref:`Multidimensional and nonscalar indexed assignment <man-array-indexing>`
:func:`start`/:func:`next`/:func:`done` defined in terms of scalar :func:`getindex` Iteration
:func:`length(A) <length>` ``prod(size(A))`` Number of elements
:func:`similar(A) <similar>` ``similar(A, eltype(A), size(A))`` Return a mutable array with the same shape and element type
:func:`similar(A, ::Type{S}) <similar>` ``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>` ``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}) <similar>` ``Array(S, dims)`` Return a mutable array with the specified element type and dimensions
========================================================== ============================================ =======================================================================================
===================================================================== ============================================ =======================================================================================
Methods to implement Brief description
===================================================================== ============================================ =======================================================================================
:func:`size(A) <size>` Returns a tuple containing the dimensions of ``A``
:func:`getindex(A, i::Int) <getindex>` (if ``LinearFast``) Linear scalar indexing
:func:`getindex(A, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing
:func:`setindex!(A, v, i::Int) <getindex>` (if ``LinearFast``) Scalar indexed assignment
:func:`setindex!(A, v, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment
**Optional methods** **Default definition** **Brief description**
:func:`Base.linearindexing(Type) <Base.linearindexing>` ``Base.LinearSlow()`` Returns either ``Base.LinearFast()`` or ``Base.LinearSlow()``. See the description below.
:func:`indices(A, d) <indices>` ``1:size(A, d)`` Returns the range of valid indices along dimension ``d``
:func:`getindex(A, I...) <getindex>` defined in terms of scalar :func:`getindex` :ref:`Multidimensional and nonscalar indexing <man-array-indexing>`
:func:`setindex!(A, I...) <setindex!>` defined in terms of scalar :func:`setindex!` :ref:`Multidimensional and nonscalar indexed assignment <man-array-indexing>`
:func:`start`/:func:`next`/:func:`done` defined in terms of scalar :func:`getindex` Iteration
:func:`length(A) <length>` ``prod(size(A))`` Number of elements
:func:`similar(A) <similar>` ``similar(A, eltype(A), indices(A))`` Return a mutable array with the same shape and element type
:func:`similar(A, ::Type{S}) <similar>` ``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>` ``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>` ``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}}) <similar>` ``Array(S, map(length, inds))`` Return a mutable array with the specified element type and indices
:func:`similar(A, ::Type{S}, dims::NTuple{Int}) <similar>` ``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 <man-arrays>` and :ref:`standard library section <stdlib-arrays>` for more supported methods.

Expand Down
12 changes: 12 additions & 0 deletions doc/stdlib/arrays.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 45 additions & 4 deletions test/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]

0 comments on commit bb67ee7

Please sign in to comment.