Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make CartesianRange an AbstractArray and deprecate sub2ind and ind2sub #25113

Merged
merged 3 commits into from
Dec 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,14 @@ Library improvements
defined, linear-algebra function `transpose`. Similarly,
`permutedims(v::AbstractVector)` will create a row matrix ([#24839]).

* `CartesianRange` changes ([#24715]):
- Inherits from `AbstractArray`, and linear indexing can be used to provide
linear-to-cartesian conversion ([#24715])
- It has a new constructor taking an array

* The type `LinearIndices` has been added, providing conversion from
cartesian incices to linear indices using the normal indexing operation. ([#24715])

Compiler/Runtime improvements
-----------------------------

Expand Down Expand Up @@ -792,6 +800,10 @@ Deprecated or removed
and `unsafe_get`/`get` can be dropped or replaced with `coalesce`.
`NullException` has been removed.

* `CartesianRange` has been renamed `CartesianIndices` ([#24715]).

* `sub2ind` and `ind2sub` are deprecated in favor of using `CartesianIndices` and `LinearIndices` ([#24715]).

Command-line option changes
---------------------------

Expand Down Expand Up @@ -1768,6 +1780,7 @@ Command-line option changes
[#24413]: https://github.com/JuliaLang/julia/issues/24413
[#24653]: https://github.com/JuliaLang/julia/issues/24653
[#24714]: https://github.com/JuliaLang/julia/issues/24714
[#24715]: https://github.com/JuliaLang/julia/issues/24715
[#24869]: https://github.com/JuliaLang/julia/issues/24869
[#25021]: https://github.com/JuliaLang/julia/issues/25021
[#25088]: https://github.com/JuliaLang/julia/issues/25088
151 changes: 45 additions & 106 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ julia> extrema(b)
linearindices(A::AbstractArray) = (@_inline_meta; OneTo(_length(A)))
linearindices(A::AbstractVector) = (@_inline_meta; indices1(A))

keys(a::AbstractArray) = CartesianRange(axes(a))
keys(a::AbstractArray) = CartesianIndices(axes(a))
keys(a::AbstractVector) = linearindices(a)

prevind(::AbstractArray, i::Integer) = Int(i)-1
Expand Down Expand Up @@ -308,9 +308,8 @@ type:
The default is `IndexCartesian()`.

Julia's internal indexing machinery will automatically (and invisibly)
convert all indexing operations into the preferred style using
[`sub2ind`](@ref) or [`ind2sub`](@ref). This allows users to access
elements of your array using any indexing style, even when explicit
convert all indexing operations into the preferred style. This allows users
to access elements of your array using any indexing style, even when explicit
methods have not been provided.

If you define both styles of indexing for your `AbstractArray`, this
Expand Down Expand Up @@ -774,7 +773,7 @@ zero(x::AbstractArray{T}) where {T} = fill!(similar(x), zero(T))
# Allows fast iteration by default for both IndexLinear and IndexCartesian arrays

# While the definitions for IndexLinear are all simple enough to inline on their
# own, IndexCartesian's CartesianRange is more complicated and requires explicit
# own, IndexCartesian's CartesianIndices is more complicated and requires explicit
# inlining.
start(A::AbstractArray) = (@_inline_meta; itr = eachindex(A); (itr, start(itr)))
next(A::AbstractArray, i) = (@_propagate_inbounds_meta; (idx, s) = next(i[1], i[2]); (A[idx], (i[1], s)))
Expand Down Expand Up @@ -825,7 +824,7 @@ A[iter] = 0

If you supply more than one `AbstractArray` argument, `eachindex` will create an
iterable object that is fast for all arguments (a `UnitRange`
if all inputs have fast linear indexing, a [`CartesianRange`](@ref)
if all inputs have fast linear indexing, a [`CartesianIndices`](@ref)
otherwise).
If the arrays have different sizes and/or dimensionalities, `eachindex` will return an
iterable that spans the largest range along each dimension.
Expand Down Expand Up @@ -959,7 +958,7 @@ end
_to_linear_index(A::AbstractArray, i::Int) = i
_to_linear_index(A::AbstractVector, i::Int, I::Int...) = i
_to_linear_index(A::AbstractArray) = 1
_to_linear_index(A::AbstractArray, I::Int...) = (@_inline_meta; sub2ind(A, I...))
_to_linear_index(A::AbstractArray, I::Int...) = (@_inline_meta; _sub2ind(A, I...))

## IndexCartesian Scalar indexing: Canonical method is full dimensionality of Ints
function _getindex(::IndexCartesian, A::AbstractArray, I::Vararg{Int,M}) where M
Expand Down Expand Up @@ -993,8 +992,8 @@ _to_subscript_indices(A, J::Tuple, Jrem::Tuple) = J # already bounds-checked, sa
_to_subscript_indices(A::AbstractArray{T,N}, I::Vararg{Int,N}) where {T,N} = I
_remaining_size(::Tuple{Any}, t::Tuple) = t
_remaining_size(h::Tuple, t::Tuple) = (@_inline_meta; _remaining_size(tail(h), tail(t)))
_unsafe_ind2sub(::Tuple{}, i) = () # ind2sub may throw(BoundsError()) in this case
_unsafe_ind2sub(sz, i) = (@_inline_meta; ind2sub(sz, i))
_unsafe_ind2sub(::Tuple{}, i) = () # _ind2sub may throw(BoundsError()) in this case
_unsafe_ind2sub(sz, i) = (@_inline_meta; _ind2sub(sz, i))

## Setindex! is defined similarly. We first dispatch to an internal _setindex!
# function that allows dispatch on array storage
Expand Down Expand Up @@ -1581,116 +1580,67 @@ function (==)(A::AbstractArray, B::AbstractArray)
return anymissing ? missing : true
end

# sub2ind and ind2sub
# _sub2ind and _ind2sub
# fallbacks
function sub2ind(A::AbstractArray, I...)
function _sub2ind(A::AbstractArray, I...)
@_inline_meta
sub2ind(axes(A), I...)
_sub2ind(axes(A), I...)
end

"""
ind2sub(a, index) -> subscripts

Return a tuple of subscripts into array `a` corresponding to the linear index `index`.

# Examples
```jldoctest
julia> A = ones(5,6,7);

julia> ind2sub(A,35)
(5, 1, 2)

julia> ind2sub(A,70)
(5, 2, 3)
```
"""
function ind2sub(A::AbstractArray, ind)
function _ind2sub(A::AbstractArray, ind)
@_inline_meta
ind2sub(axes(A), ind)
_ind2sub(axes(A), ind)
end

# 0-dimensional arrays and indexing with []
sub2ind(::Tuple{}) = 1
sub2ind(::DimsInteger) = 1
sub2ind(::Indices) = 1
sub2ind(::Tuple{}, I::Integer...) = (@_inline_meta; _sub2ind((), 1, 1, I...))
# Generic cases

"""
sub2ind(dims, i, j, k...) -> index

The inverse of [`ind2sub`](@ref), return the linear index corresponding to the provided subscripts.

# Examples
```jldoctest
julia> sub2ind((5,6,7),1,2,3)
66
_sub2ind(::Tuple{}) = 1
_sub2ind(::DimsInteger) = 1
_sub2ind(::Indices) = 1
_sub2ind(::Tuple{}, I::Integer...) = (@_inline_meta; _sub2ind_recurse((), 1, 1, I...))

julia> sub2ind((5,6,7),1,6,3)
86
```
"""
sub2ind(dims::DimsInteger, I::Integer...) = (@_inline_meta; _sub2ind(dims, 1, 1, I...))
sub2ind(inds::Indices, I::Integer...) = (@_inline_meta; _sub2ind(inds, 1, 1, I...))
# Generic cases
_sub2ind(dims::DimsInteger, I::Integer...) = (@_inline_meta; _sub2ind_recurse(dims, 1, 1, I...))
_sub2ind(inds::Indices, I::Integer...) = (@_inline_meta; _sub2ind_recurse(inds, 1, 1, I...))
# In 1d, there's a question of whether we're doing cartesian indexing
# or linear indexing. Support only the former.
sub2ind(inds::Indices{1}, I::Integer...) =
_sub2ind(inds::Indices{1}, I::Integer...) =
throw(ArgumentError("Linear indexing is not defined for one-dimensional arrays"))
sub2ind(inds::Tuple{OneTo}, I::Integer...) = (@_inline_meta; _sub2ind(inds, 1, 1, I...)) # only OneTo is safe
sub2ind(inds::Tuple{OneTo}, i::Integer) = i
_sub2ind(inds::Tuple{OneTo}, I::Integer...) = (@_inline_meta; _sub2ind_recurse(inds, 1, 1, I...)) # only OneTo is safe
_sub2ind(inds::Tuple{OneTo}, i::Integer) = i

_sub2ind(::Any, L, ind) = ind
function _sub2ind(::Tuple{}, L, ind, i::Integer, I::Integer...)
_sub2ind_recurse(::Any, L, ind) = ind
function _sub2ind_recurse(::Tuple{}, L, ind, i::Integer, I::Integer...)
@_inline_meta
_sub2ind((), L, ind+(i-1)*L, I...)
_sub2ind_recurse((), L, ind+(i-1)*L, I...)
end
function _sub2ind(inds, L, ind, i::Integer, I::Integer...)
function _sub2ind_recurse(inds, L, ind, i::Integer, I::Integer...)
@_inline_meta
r1 = inds[1]
_sub2ind(tail(inds), nextL(L, r1), ind+offsetin(i, r1)*L, I...)
_sub2ind_recurse(tail(inds), nextL(L, r1), ind+offsetin(i, r1)*L, I...)
end

nextL(L, l::Integer) = L*l
nextL(L, r::AbstractUnitRange) = L*unsafe_length(r)
offsetin(i, l::Integer) = i-1
offsetin(i, r::AbstractUnitRange) = i-first(r)

ind2sub(::Tuple{}, ind::Integer) = (@_inline_meta; ind == 1 ? () : throw(BoundsError()))

"""
ind2sub(dims, index) -> subscripts

Return a tuple of subscripts into an array with dimensions `dims`,
corresponding to the linear index `index`.

# Examples
```jldoctest
julia> ind2sub((3,4),2)
(2, 1)

julia> ind2sub((3,4),3)
(3, 1)

julia> ind2sub((3,4),4)
(1, 2)
```
"""
ind2sub(dims::DimsInteger, ind::Integer) = (@_inline_meta; _ind2sub(dims, ind-1))
ind2sub(inds::Indices, ind::Integer) = (@_inline_meta; _ind2sub(inds, ind-1))
ind2sub(inds::Indices{1}, ind::Integer) =
_ind2sub(::Tuple{}, ind::Integer) = (@_inline_meta; ind == 1 ? () : throw(BoundsError()))
_ind2sub(dims::DimsInteger, ind::Integer) = (@_inline_meta; _ind2sub_recurse(dims, ind-1))
_ind2sub(inds::Indices, ind::Integer) = (@_inline_meta; _ind2sub_recurse(inds, ind-1))
_ind2sub(inds::Indices{1}, ind::Integer) =
throw(ArgumentError("Linear indexing is not defined for one-dimensional arrays"))
ind2sub(inds::Tuple{OneTo}, ind::Integer) = (ind,)
_ind2sub(inds::Tuple{OneTo}, ind::Integer) = (ind,)

_ind2sub(::Tuple{}, ind) = (ind+1,)
function _ind2sub(indslast::NTuple{1}, ind)
_ind2sub_recurse(::Tuple{}, ind) = (ind+1,)
function _ind2sub_recurse(indslast::NTuple{1}, ind)
@_inline_meta
(_lookup(ind, indslast[1]),)
end
function _ind2sub(inds, ind)
function _ind2sub_recurse(inds, ind)
@_inline_meta
r1 = inds[1]
indnext, f, l = _div(ind, r1)
(ind-l*indnext+f, _ind2sub(tail(inds), indnext)...)
(ind-l*indnext+f, _ind2sub_recurse(tail(inds), indnext)...)
end

_lookup(ind, d::Integer) = ind+1
Expand All @@ -1699,12 +1649,12 @@ _div(ind, d::Integer) = div(ind, d), 1, d
_div(ind, r::AbstractUnitRange) = (d = unsafe_length(r); (div(ind, d), first(r), d))

# Vectorized forms
function sub2ind(inds::Indices{1}, I1::AbstractVector{T}, I::AbstractVector{T}...) where T<:Integer
function _sub2ind(inds::Indices{1}, I1::AbstractVector{T}, I::AbstractVector{T}...) where T<:Integer
throw(ArgumentError("Linear indexing is not defined for one-dimensional arrays"))
end
sub2ind(inds::Tuple{OneTo}, I1::AbstractVector{T}, I::AbstractVector{T}...) where {T<:Integer} =
_sub2ind(inds::Tuple{OneTo}, I1::AbstractVector{T}, I::AbstractVector{T}...) where {T<:Integer} =
_sub2ind_vecs(inds, I1, I...)
sub2ind(inds::Union{DimsInteger,Indices}, I1::AbstractVector{T}, I::AbstractVector{T}...) where {T<:Integer} =
_sub2ind(inds::Union{DimsInteger,Indices}, I1::AbstractVector{T}, I::AbstractVector{T}...) where {T<:Integer} =
_sub2ind_vecs(inds, I1, I...)
function _sub2ind_vecs(inds, I::AbstractVector...)
I1 = I[1]
Expand All @@ -1720,39 +1670,28 @@ end
function _sub2ind!(Iout, inds, Iinds, I)
@_noinline_meta
for i in Iinds
# Iout[i] = sub2ind(inds, map(Ij -> Ij[i], I)...)
# Iout[i] = _sub2ind(inds, map(Ij -> Ij[i], I)...)
Iout[i] = sub2ind_vec(inds, i, I)
end
Iout
end

sub2ind_vec(inds, i, I) = (@_inline_meta; sub2ind(inds, _sub2ind_vec(i, I...)...))
sub2ind_vec(inds, i, I) = (@_inline_meta; _sub2ind(inds, _sub2ind_vec(i, I...)...))
_sub2ind_vec(i, I1, I...) = (@_inline_meta; (I1[i], _sub2ind_vec(i, I...)...))
_sub2ind_vec(i) = ()

function ind2sub(inds::Union{DimsInteger{N},Indices{N}}, ind::AbstractVector{<:Integer}) where N
function _ind2sub(inds::Union{DimsInteger{N},Indices{N}}, ind::AbstractVector{<:Integer}) where N
M = length(ind)
t = ntuple(n->similar(ind),Val(N))
for (i,idx) in pairs(IndexLinear(), ind)
sub = ind2sub(inds, idx)
sub = _ind2sub(inds, idx)
for j = 1:N
t[j][i] = sub[j]
end
end
t
end

function ind2sub!(sub::Array{T}, dims::Tuple{Vararg{T}}, ind::T) where T<:Integer
ndims = length(dims)
for i=1:ndims-1
ind2 = div(ind-1,dims[i])+1
sub[i] = ind - dims[i]*(ind2-1)
ind = ind2
end
sub[ndims] = ind
return sub
end

## iteration utilities ##

"""
Expand Down Expand Up @@ -1876,7 +1815,7 @@ function mapslices(f, A::AbstractArray, dims::AbstractVector)
R[ridx...] = r1

nidx = length(otherdims)
indices = Iterators.drop(CartesianRange(itershape), 1)
indices = Iterators.drop(CartesianIndices(itershape), 1)
inner_mapslices!(safe_for_reuse, indices, nidx, idx, otherdims, ridx, Aslice, A, f, R)
end

Expand Down
2 changes: 1 addition & 1 deletion base/abstractarraymath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ cat_fill!(R, X::AbstractArray, inds) = fill!(view(R, inds...), X)
R[axes(A)...] = A
else
inner_indices = [1:n for n in inner]
for c in CartesianRange(axes(A))
for c in CartesianIndices(axes(A))
for i in 1:ndims(A)
n = inner[i]
inner_indices[i] = (1:n) .+ ((c[i] - 1) * n)
Expand Down
4 changes: 2 additions & 2 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ sizeof(a::Array) = Core.sizeof(a)

function isassigned(a::Array, i::Int...)
@_inline_meta
ii = (sub2ind(size(a), i...) % UInt) - 1
ii = (_sub2ind(size(a), i...) % UInt) - 1
@boundscheck ii < length(a) % UInt || return false
ccall(:jl_array_isassigned, Cint, (Any, UInt), a, ii) == 1
end
Expand Down Expand Up @@ -490,7 +490,7 @@ _collect_indices(indsA::Tuple{Vararg{OneTo}}, A) =
copy!(Array{eltype(A)}(uninitialized, length.(indsA)), A)
function _collect_indices(indsA, A)
B = Array{eltype(A)}(uninitialized, length.(indsA))
copy!(B, CartesianRange(axes(B)), A, CartesianRange(indsA))
copy!(B, CartesianIndices(axes(B)), A, CartesianIndices(indsA))
end

# define this as a macro so that the call to Inference
Expand Down
2 changes: 1 addition & 1 deletion base/arrayshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ function show_nd(io::IO, a::AbstractArray, print_matrix::Function, label_slices:
end
tailinds = tail(tail(axes(a)))
nd = ndims(a)-2
for I in CartesianRange(tailinds)
for I in CartesianIndices(tailinds)
idxs = I.I
if limit
for i = 1:nd
Expand Down
2 changes: 1 addition & 1 deletion base/bitarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ gen_bitarray(isz::IteratorSize, itr) = gen_bitarray_from_itr(itr, start(itr))
# generic iterable with known shape
function gen_bitarray(::HasShape, itr)
B = BitArray(uninitialized, size(itr))
for (I,x) in zip(CartesianRange(axes(itr)), itr)
for (I,x) in zip(CartesianIndices(axes(itr)), itr)
B[I] = x
end
return B
Expand Down
4 changes: 2 additions & 2 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ as in `broadcast!(f, A, A, B)` to perform `A[:] = broadcast(f, A, B)`.
shape = broadcast_indices(C)
@boundscheck check_broadcast_indices(shape, A, Bs...)
keeps, Idefaults = map_newindexer(shape, A, Bs)
iter = CartesianRange(shape)
iter = CartesianIndices(shape)
_broadcast!(f, C, keeps, Idefaults, A, Bs, Val(N), iter)
return C
end
Expand Down Expand Up @@ -616,7 +616,7 @@ end
# accommodate later values.
function broadcast_nonleaf(f, s::NonleafHandlingTypes, ::Type{ElType}, shape::Indices, As...) where ElType
nargs = length(As)
iter = CartesianRange(shape)
iter = CartesianIndices(shape)
if isempty(iter)
return Base.similar(Array{ElType}, shape)
end
Expand Down
Loading