Skip to content

Commit bb67ee7

Browse files
committed
Add indices to support indexing with arbitrary offsets
1 parent bf73102 commit bb67ee7

File tree

8 files changed

+132
-48
lines changed

8 files changed

+132
-48
lines changed

base/abstractarray.jl

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ end
2121

2222
size{T,n}(t::AbstractArray{T,n}, d) = d <= n ? size(t)[d] : 1
2323
size(x, d1::Integer, d2::Integer, dx::Integer...) = tuple(size(x, d1), size(x, d2, dx...)...)
24+
"""
25+
indices(A, d)
26+
27+
Returns the valid range of indices for array `A` along dimension `d`.
28+
"""
29+
indices(A::AbstractArray, d) = 1:size(A,d)
30+
"""
31+
indices(A)
32+
33+
Returns the tuple of valid indices for array `A`.
34+
"""
35+
indices{T,N}(A::AbstractArray{T,N}) = ntuple(d->indices(A, d), Val{N})
2436
eltype{T}(::Type{AbstractArray{T}}) = T
2537
eltype{T,n}(::Type{AbstractArray{T,n}}) = T
2638
elsize{T}(::AbstractArray{T}) = sizeof(T)
@@ -97,36 +109,36 @@ linearindexing(::LinearIndexing, ::LinearIndexing) = LinearSlow()
97109
end
98110

99111
# check along a single dimension
100-
checkbounds(::Type{Bool}, sz::Integer, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))"))
101-
checkbounds(::Type{Bool}, sz::Integer, i::Real) = 1 <= i <= sz
102-
checkbounds(::Type{Bool}, sz::Integer, ::Colon) = true
103-
function checkbounds(::Type{Bool}, sz::Integer, r::Range)
112+
checkbounds(::Type{Bool}, inds::UnitRange, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))"))
113+
checkbounds(::Type{Bool}, inds::UnitRange, i::Real) = first(inds) <= i <= last(inds)
114+
checkbounds(::Type{Bool}, inds::UnitRange, ::Colon) = true
115+
function checkbounds(::Type{Bool}, inds::UnitRange, r::Range)
104116
@_propagate_inbounds_meta
105-
isempty(r) | (checkbounds(Bool, sz, first(r)) & checkbounds(Bool, sz, last(r)))
117+
isempty(r) | (checkbounds(Bool, inds, first(r)) & checkbounds(Bool, inds, last(r)))
106118
end
107-
checkbounds{N}(::Type{Bool}, sz::Integer, I::AbstractArray{Bool,N}) = N == 1 && length(I) == sz
108-
function checkbounds(::Type{Bool}, sz::Integer, I::AbstractArray)
119+
checkbounds{N}(::Type{Bool}, indx::UnitRange, I::AbstractArray{Bool,N}) = N == 1 && indx == indices(I,1)
120+
function checkbounds(::Type{Bool}, inds::UnitRange, I::AbstractArray)
109121
@_inline_meta
110122
b = true
111123
for i in I
112-
b &= checkbounds(Bool, sz, i)
124+
b &= checkbounds(Bool, inds, i)
113125
end
114126
b
115127
end
116128

117129
# check all dimensions
118-
function checkbounds{N,T}(::Type{Bool}, sz::NTuple{N,Integer}, I1::T, I...)
130+
function checkbounds{N,T}(::Type{Bool}, inds::NTuple{N,UnitRange}, I1::T, I...)
119131
@_inline_meta
120-
checkbounds(Bool, sz[1], I1) & checkbounds(Bool, tail(sz), I...)
132+
checkbounds(Bool, inds[1], I1) & checkbounds(Bool, tail(inds), I...)
121133
end
122-
checkbounds{T<:Integer}(::Type{Bool}, sz::Tuple{T}, I1) = (@_inline_meta; checkbounds(Bool, sz[1], I1))
123-
checkbounds{N}(::Type{Bool}, sz::NTuple{N,Integer}, I1) = (@_inline_meta; checkbounds(Bool, prod(sz), I1))
124-
checkbounds{N}(::Type{Bool}, sz::NTuple{N,Integer}) = (@_inline_meta; checkbounds(Bool, sz, 1)) # for a[]
134+
checkbounds(::Type{Bool}, inds::Tuple{UnitRange}, I1) = (@_inline_meta; checkbounds(Bool, inds[1], I1))
135+
checkbounds{N}(::Type{Bool}, inds::NTuple{N,UnitRange}, I1) = (@_inline_meta; checkbounds(Bool, 1:prod(map(length, inds)), I1)) # TODO: eliminate (partial linear indexing)
136+
checkbounds{N}(::Type{Bool}, inds::NTuple{N,UnitRange}) = (@_inline_meta; checkbounds(Bool, inds, 1)) # for a[]
125137

126-
checkbounds(::Type{Bool}, sz::Tuple{}, i) = (@_inline_meta; checkbounds(Bool, 1, i))
127-
function checkbounds(::Type{Bool}, sz::Tuple{}, i, I...)
138+
checkbounds(::Type{Bool}, inds::Tuple{}, i) = (@_inline_meta; checkbounds(Bool, 1:1, i))
139+
function checkbounds(::Type{Bool}, inds::Tuple{}, i, I...)
128140
@_inline_meta
129-
checkbounds(Bool, 1, i) & checkbounds(Bool, (), I...)
141+
checkbounds(Bool, 1:1, i) & checkbounds(Bool, (), I...)
130142
end
131143
# Prevent allocation of a GC frame by hiding the BoundsError in a noinline function
132144
throw_boundserror(A, I) = (@_noinline_meta; throw(BoundsError(A, I)))
@@ -136,12 +148,13 @@ checkbounds(A::AbstractArray, I...) = (@_inline_meta; _internal_checkbounds(A, I
136148
# The internal function is named _internal_checkbounds since there had been a
137149
# _checkbounds previously that meant something different.
138150
_internal_checkbounds(A::AbstractArray) = true
139-
_internal_checkbounds(A::AbstractArray, I::AbstractArray{Bool}) = size(A) == size(I) || throw_boundserror(A, I)
151+
_internal_checkbounds(A::AbstractArray, I::AbstractArray{Bool}) = indices(A) == indices(I) || throw_boundserror(A, I)
152+
_internal_checkbounds(A::AbstractVector, I::AbstractVector{Bool}) = indices(A) == indices(I) || throw_boundserror(A, I)
140153
_internal_checkbounds(A::AbstractArray, I::AbstractVector{Bool}) = length(A) == length(I) || throw_boundserror(A, I)
141154
function _internal_checkbounds(A::AbstractArray, I1, I...)
142155
# having I1 seems important for good codegen
143156
@_inline_meta
144-
checkbounds(Bool, size(A), I1, I...) || throw_boundserror(A, (I1, I...))
157+
checkbounds(Bool, indices(A), I1, I...) || throw_boundserror(A, (I1, I...))
145158
end
146159

147160
# See also specializations in multidimensional
@@ -565,9 +578,9 @@ end
565578

566579
typealias RangeVecIntList{A<:AbstractVector{Int}} Union{Tuple{Vararg{Union{Range, AbstractVector{Int}}}}, AbstractVector{UnitRange{Int}}, AbstractVector{Range{Int}}, AbstractVector{A}}
567580

568-
get(A::AbstractArray, i::Integer, default) = checkbounds(Bool, length(A), i) ? A[i] : default
581+
get(A::AbstractArray, i::Integer, default) = checkbounds(Bool, indices(A), i) ? A[i] : default
569582
get(A::AbstractArray, I::Tuple{}, default) = similar(A, typeof(default), 0)
570-
get(A::AbstractArray, I::Dims, default) = checkbounds(Bool, size(A), I...) ? A[I...] : default
583+
get(A::AbstractArray, I::Dims, default) = checkbounds(Bool, indices(A), I...) ? A[I...] : default
571584

572585
function get!{T}(X::AbstractArray{T}, A::AbstractArray, I::Union{Range, AbstractVector{Int}}, default::T)
573586
ind = findin(I, 1:length(A))

base/deprecated.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,15 @@ end
11311131
isequal(x::Char, y::Integer) = false
11321132
isequal(x::Integer, y::Char) = false
11331133

1134+
function checkbounds(::Type{Bool}, sz::Integer, i)
1135+
depwarn("checkbounds(Bool, size(A, d), i) is deprecated, use checkbounds(Bool, indices(A, d), i).", :checkbounds)
1136+
checkbounds(Bool, 1:sz, i)
1137+
end
1138+
function checkbounds{N,T}(::Type{Bool}, sz::NTuple{N,Integer}, I1::T, I...)
1139+
depwarn("checkbounds(Bool, size(A), I...) is deprecated, use checkbounds(Bool, indices(A), I...).", :checkbounds)
1140+
checkbounds(Bool, map(s->1:s, sz), I1, I...)
1141+
end
1142+
11341143
# During the 0.5 development cycle, do not add any deprecations below this line
11351144
# To be deprecated in 0.6
11361145

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ export
537537
hvcat,
538538
ind2sub,
539539
indexin,
540+
indices,
540541
indmax,
541542
indmin,
542543
invperm,

base/multidimensional.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ using .IteratorsMD
199199
for N = 1:5
200200
args = [:($(Symbol(:I, d))) for d = 1:N]
201201
targs = [:($(Symbol(:I, d))::Union{Colon,Number,AbstractArray}) for d = 1:N] # prevent co-opting the CartesianIndex version
202-
exs = [:(checkbounds(Bool, size(A, $d), $(args[d]))) for d = 1:N]
202+
exs = [:(checkbounds(Bool, indices(A, $d), $(args[d]))) for d = 1:N]
203203
cbexpr = exs[1]
204204
for d = 2:N
205205
cbexpr = :($(exs[d]) & $cbexpr)
@@ -223,11 +223,14 @@ _internal_checkbounds(A::AbstractVector, I::AbstractVector{Bool}) = length(A) ==
223223
@inline function checkbounds(::Type{Bool}, ::Tuple{}, I1::CartesianIndex)
224224
checkbounds(Bool, (), I1.I...)
225225
end
226-
@inline function checkbounds(::Type{Bool}, sz::Tuple{}, I1::CartesianIndex, I...)
226+
@inline function checkbounds(::Type{Bool}, inds::Tuple{}, I1::CartesianIndex, I...)
227227
checkbounds(Bool, (), I1.I..., I...)
228228
end
229-
@inline function checkbounds(::Type{Bool}, sz::Dims, I1::CartesianIndex, I...)
230-
checkbounds(Bool, sz, I1.I..., I...)
229+
@inline function checkbounds(::Type{Bool}, inds::Tuple{UnitRange}, I1::CartesianIndex)
230+
checkbounds(Bool, inds, I1.I..., I...)
231+
end
232+
@inline function checkbounds{N}(::Type{Bool}, inds::NTuple{N,UnitRange}, I1::CartesianIndex, I...)
233+
checkbounds(Bool, inds, I1.I..., I...)
231234
end
232235

233236
# Recursively compute the lengths of a list of indices, without dropping scalars

doc/manual/arrays.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ Function Description
5050
:func:`length(A) <length>` the number of elements in ``A``
5151
:func:`ndims(A) <ndims>` the number of dimensions of ``A``
5252
:func:`size(A) <size>` a tuple containing the dimensions of ``A``
53-
:func:`size(A,n) <size>` the size of ``A`` in a particular dimension
53+
:func:`size(A,n) <size>` the size of ``A`` along a particular dimension
54+
:func:`indices(A) <indices>` a tuple containing the valid indices of ``A``
55+
:func:`indices(A,n) <indices>` a range expressing the valid indices along dimension ``n``
5456
:func:`eachindex(A) <eachindex>` an efficient iterator for visiting each position in ``A``
5557
:func:`stride(A,k) <stride>` the stride (linear index distance between adjacent elements) along dimension ``k``
5658
:func:`strides(A) <strides>` a tuple of the strides in each dimension

doc/manual/interfaces.rst

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -147,25 +147,28 @@ While this is starting to support more of the :ref:`indexing operations supporte
147147
Abstract Arrays
148148
---------------
149149

150-
========================================================== ============================================ =======================================================================================
151-
Methods to implement Brief description
152-
========================================================== ============================================ =======================================================================================
153-
:func:`size(A) <size>` Returns a tuple containing the dimensions of A
154-
:func:`Base.linearindexing(Type) <Base.linearindexing>` Returns either ``Base.LinearFast()`` or ``Base.LinearSlow()``. See the description below.
155-
:func:`getindex(A, i::Int) <getindex>` (if ``LinearFast``) Linear scalar indexing
156-
:func:`getindex(A, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing
157-
:func:`setindex!(A, v, i::Int) <getindex>` (if ``LinearFast``) Scalar indexed assignment
158-
:func:`setindex!(A, v, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment
159-
**Optional methods** **Default definition** **Brief description**
160-
:func:`getindex(A, I...) <getindex>` defined in terms of scalar :func:`getindex` :ref:`Multidimensional and nonscalar indexing <man-array-indexing>`
161-
:func:`setindex!(A, I...) <setindex!>` defined in terms of scalar :func:`setindex!` :ref:`Multidimensional and nonscalar indexed assignment <man-array-indexing>`
162-
:func:`start`/:func:`next`/:func:`done` defined in terms of scalar :func:`getindex` Iteration
163-
:func:`length(A) <length>` ``prod(size(A))`` Number of elements
164-
:func:`similar(A) <similar>` ``similar(A, eltype(A), size(A))`` Return a mutable array with the same shape and element type
165-
:func:`similar(A, ::Type{S}) <similar>` ``similar(A, S, size(A))`` Return a mutable array with the same shape and the specified element type
166-
: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
167-
:func:`similar(A, ::Type{S}, dims::NTuple{Int}) <similar>` ``Array(S, dims)`` Return a mutable array with the specified element type and dimensions
168-
========================================================== ============================================ =======================================================================================
150+
===================================================================== ============================================ =======================================================================================
151+
Methods to implement Brief description
152+
===================================================================== ============================================ =======================================================================================
153+
:func:`size(A) <size>` Returns a tuple containing the dimensions of ``A``
154+
:func:`getindex(A, i::Int) <getindex>` (if ``LinearFast``) Linear scalar indexing
155+
:func:`getindex(A, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing
156+
:func:`setindex!(A, v, i::Int) <getindex>` (if ``LinearFast``) Scalar indexed assignment
157+
:func:`setindex!(A, v, i1::Int, ..., iN::Int) <getindex>` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment
158+
**Optional methods** **Default definition** **Brief description**
159+
:func:`Base.linearindexing(Type) <Base.linearindexing>` ``Base.LinearSlow()`` Returns either ``Base.LinearFast()`` or ``Base.LinearSlow()``. See the description below.
160+
:func:`indices(A, d) <indices>` ``1:size(A, d)`` Returns the range of valid indices along dimension ``d``
161+
:func:`getindex(A, I...) <getindex>` defined in terms of scalar :func:`getindex` :ref:`Multidimensional and nonscalar indexing <man-array-indexing>`
162+
:func:`setindex!(A, I...) <setindex!>` defined in terms of scalar :func:`setindex!` :ref:`Multidimensional and nonscalar indexed assignment <man-array-indexing>`
163+
:func:`start`/:func:`next`/:func:`done` defined in terms of scalar :func:`getindex` Iteration
164+
:func:`length(A) <length>` ``prod(size(A))`` Number of elements
165+
:func:`similar(A) <similar>` ``similar(A, eltype(A), indices(A))`` Return a mutable array with the same shape and element type
166+
:func:`similar(A, ::Type{S}) <similar>` ``similar(A, S, indices(A))`` Return a mutable array with the same shape and the specified element type
167+
: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
168+
:func:`similar(A, dims::NTuple{Int}) <similar>` ``similar(A, eltype(A), dims)`` Return a mutable array with the same element type and size `dims`
169+
: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
170+
:func:`similar(A, ::Type{S}, dims::NTuple{Int}) <similar>` ``Array(S, dims)`` Return a mutable array with the specified element type and size
171+
===================================================================== ============================================ =======================================================================================
169172

170173
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.
171174

doc/stdlib/arrays.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ Basic functions
3131
julia> size(A,3,2)
3232
(4,3)
3333
34+
.. function:: indices(A)
35+
36+
.. Docstring generated from Julia source
37+
38+
Returns the tuple of valid indices for array ``A``\ .
39+
40+
.. function:: indices(A, d)
41+
42+
.. Docstring generated from Julia source
43+
44+
Returns the valid range of indices for array ``A`` along dimension ``d``\ .
45+
3446
.. function:: iseltype(A,T)
3547

3648
.. Docstring generated from Julia source

test/abstractarray.jl

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,12 @@ end
254254

255255
function test_in_bounds(::Type{TestAbstractArray})
256256
n = rand(2:5)
257-
dims = tuple(rand(2:5, n)...)
258-
len = prod(dims)
257+
inds = ntuple(d->1:rand(2:5), n)
258+
len = prod(map(length, inds))
259259
for i in 1:len
260-
@test checkbounds(Bool, dims, i) == true
260+
@test checkbounds(Bool, inds, i) == true
261261
end
262-
@test checkbounds(Bool, dims, len + 1) == false
262+
@test checkbounds(Bool, inds, len + 1) == false
263263
end
264264

265265
type UnimplementedFastArray{T, N} <: AbstractArray{T, N} end
@@ -520,3 +520,44 @@ A = TSlowNIndexes(rand(2,2))
520520
@test_throws ErrorException A[1]
521521
@test A[1,1] == A.data[1]
522522
@test first(A) == A.data[1]
523+
524+
# OffsetArrays (arrays with indexing that doesn't start at 1)
525+
526+
module OAs
527+
528+
immutable OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N}
529+
parent::AA
530+
offsets::NTuple{N,Int}
531+
end
532+
533+
OffsetArray{T,N}(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) = OffsetArray{T,N,typeof(A)}(A, offsets)
534+
535+
Base.parent(A::OffsetArray) = A.parent
536+
Base.size(A::OffsetArray) = size(parent(A))
537+
Base.indices(A::OffsetArray, d) = (1:size(parent(A),d))+A.offsets[d]
538+
Base.eachindex(A::OffsetArray) = CartesianRange(indices(A))
539+
Base.summary(A::OffsetArray) = string(typeof(A))*" with indices "*string(indices(A))
540+
541+
@inline function Base.getindex{T,N}(A::OffsetArray{T,N}, I::Vararg{Int,N})
542+
@boundscheck checkbounds(A, I...)
543+
@inbounds ret = parent(A)[offset(A.offsets, I)...]
544+
ret
545+
end
546+
@inline function Base.setindex!{T,N}(A::OffsetArray{T,N}, val, I::Vararg{Int,N})
547+
@boundscheck checkbounds(A, I...)
548+
@inbounds parent(A)[offset(A.offsets, I)...] = val
549+
val
550+
end
551+
552+
offset{N}(offsets::NTuple{N,Int}, inds::NTuple{N,Int}) = _offset((), offsets, inds)
553+
_offset(out, ::Tuple{}, ::Tuple{}) = out
554+
@inline _offset(out, offsets, inds) = _offset((out..., inds[1]-offsets[1]), Base.tail(offsets), Base.tail(inds))
555+
556+
end
557+
558+
A = OAs.OffsetArray([1 3; 2 4], (-1,2))
559+
@test A[0,3] == 1
560+
@test A[1,3] == 2
561+
@test A[0,4] == 3
562+
@test A[1,4] == 4
563+
@test_throws BoundsError A[1,1]

0 commit comments

Comments
 (0)