From 04dd4e79917a92bf12197b29d56c17ba9b18a176 Mon Sep 17 00:00:00 2001 From: Bart Janssens Date: Tue, 28 Nov 2017 17:33:31 +0100 Subject: [PATCH] CartesianRange constructor and eachindex --- NEWS.md | 5 ++++ base/abstractarray.jl | 11 +++++++++ base/multidimensional.jl | 48 ++++++++++++++++++++++++++++++++++++ doc/src/devdocs/subarrays.md | 5 ++-- test/abstractarray.jl | 3 +++ 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index d3a5986905af1..f05535e65c760 100644 --- a/NEWS.md +++ b/NEWS.md @@ -388,6 +388,11 @@ Library improvements This supersedes the old behavior of reinterpret on Arrays. As a result, reinterpreting arrays with different alignment requirements (removed in 0.6) is once again allowed ([#23750]). + * `CartesianRange` changes ([#24715]): + - Inherits from `AbstractArray` + - Constructor taking an array + - `eachindex` returns the linear indices into a reshaped array, as `sub2ind` alternative + Compiler/Runtime improvements ----------------------------- diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 70d1fd4828d4e..a030cf2dcf84b 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -811,6 +811,17 @@ if all inputs have fast linear indexing, a [`CartesianRange`](@ref) otherwise). If the arrays have different sizes and/or dimensionalities, `eachindex` returns an iterable that spans the largest range along each dimension. + +For a CartesianRange, this returns a reshaped range of the linear indices into +the range, e.g.: + +```jldoctest +julia> eachindex(CartesianRange((1:2,1:3))) +2×3 reshape(::Base.OneTo{Int64}, 2, 3) with eltype Int64: + 1 3 5 + 2 4 6 +``` + """ eachindex(A::AbstractArray) = (@_inline_meta(); eachindex(IndexStyle(A), A)) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 536e23d928caf..8c7c167d02ee8 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -172,6 +172,11 @@ module IteratorsMD Consequently these can be useful for writing algorithms that work in arbitrary dimensions. + CartesianRange(A::AbstractArray) -> R + + As a convenience, constructing a CartesianRange from an array makes a + range of its indices. + # Examples ```jldoctest julia> foreach(println, CartesianRange((2, 2, 2))) @@ -183,7 +188,47 @@ module IteratorsMD CartesianIndex(2, 1, 2) CartesianIndex(1, 2, 2) CartesianIndex(2, 2, 2) + + julia> CartesianRange(ones(2,3)) + 2×3 CartesianRange{2,Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}}: + CartesianIndex(1, 1) CartesianIndex(1, 2) CartesianIndex(1, 3) + CartesianIndex(2, 1) CartesianIndex(2, 2) CartesianIndex(2, 3) ``` + + ## Conversion between linear and cartesian indices + + Linear index to cartesian index conversion exploits the fact that a + `CartesianRange` is an `AbstractArray` and can be indexed linearly: + + ```jldoctest subarray + julia> cartesian = CartesianRange(1:3,1:2) + 3×2 CartesianRange{2,Tuple{UnitRange{Int64},UnitRange{Int64}}}: + CartesianIndex(1, 1) CartesianIndex(1, 2) + CartesianIndex(2, 1) CartesianIndex(2, 2) + CartesianIndex(3, 1) CartesianIndex(3, 2) + + julia> cartesian[4] + CartesianIndex(1, 2) + ``` + + For cartesian to linear index conversion, [`eachindex`](@ref) returns a + reshaped version of the linear indices when called on a `CartesianRange`: + + ```jldoctest subarray + julia> linear = eachindex(cartesian) + 3×2 reshape(::Base.OneTo{Int64}, 3, 2) with eltype Int64: + 1 4 + 2 5 + 3 6 + + julia> linear[1,2] + 4 + + julia> linear[cartesian[4]] + 4 + ``` + + """ struct CartesianRange{N,R<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractArray{CartesianIndex{N},N} indices::R @@ -204,6 +249,8 @@ module IteratorsMD CartesianRange(inds::NTuple{N,Union{<:Integer,AbstractUnitRange{<:Integer}}}) where {N} = CartesianRange(map(i->first(i):last(i), inds)) + CartesianRange(A::AbstractArray) = CartesianRange(indices(A)) + convert(::Type{Tuple{}}, R::CartesianRange{0}) = () convert(::Type{NTuple{N,AbstractUnitRange{Int}}}, R::CartesianRange{N}) where {N} = R.indices @@ -225,6 +272,7 @@ module IteratorsMD # AbstractArray implementation Base.IndexStyle(::Type{CartesianRange{N,R}}) where {N,R} = IndexCartesian() @inline Base.getindex(iter::CartesianRange{N,R}, I::Vararg{Int, N}) where {N,R} = CartesianIndex(first.(iter.indices) .- 1 .+ I) + Base.eachindex(iter::CartesianRange) = reshape(linearindices(iter), size(iter)) ndims(R::CartesianRange) = ndims(typeof(R)) ndims(::Type{CartesianRange{N}}) where {N} = N diff --git a/doc/src/devdocs/subarrays.md b/doc/src/devdocs/subarrays.md index 5c87b62034ee3..686bd897fa2ea 100644 --- a/doc/src/devdocs/subarrays.md +++ b/doc/src/devdocs/subarrays.md @@ -22,9 +22,8 @@ computation (such as interpolation), and the type under discussion here, `SubArr For these types, the underlying information is more naturally described in terms of cartesian indexes. -You can manually convert from a cartesian index to a linear index with `sub2ind`, and vice versa -using `ind2sub`. `getindex` and `setindex!` functions for `AbstractArray` types may include similar -operations. +The `getindex` and `setindex!` functions for `AbstractArray` types may include automatic conversion +between indexing types. For explicit conversion, [`CartesianRange`](@ref) can be used. While converting from a cartesian index to a linear index is fast (it's just multiplication and addition), converting from a linear index to a cartesian index is very slow: it relies on the diff --git a/test/abstractarray.jl b/test/abstractarray.jl index cf834b5ff9e03..db36f74bf7ad9 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -894,4 +894,7 @@ end j = (i_lin-i) ÷ length(xrng) + 1 @test CR[i_lin] == CartesianIndex(xrng[i],yrng[j]) end + + @test CartesianRange(ones(2,3)) == CartesianRange((2,3)) + @test eachindex(CartesianRange((2,3))) == [1 3 5; 2 4 6] end