From 9edd085513162c36f5287bcb33ec33346add33c9 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 15 Feb 2021 12:39:49 -0800 Subject: [PATCH 01/21] Add SliceArray type for eachslice/eachcol/eachrow --- base/Base.jl | 1 + base/abstractarraymath.jl | 112 +------------------ base/exports.jl | 3 + base/slicearray.jl | 224 ++++++++++++++++++++++++++++++++++++++ doc/src/base/arrays.md | 3 + test/arrayops.jl | 51 ++++++++- 6 files changed, 278 insertions(+), 116 deletions(-) create mode 100644 base/slicearray.jl diff --git a/base/Base.jl b/base/Base.jl index e3fec462215ef..d50228de198a1 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -181,6 +181,7 @@ include("multinverses.jl") using .MultiplicativeInverses include("abstractarraymath.jl") include("arraymath.jl") +include("slicearray.jl") # SIMD loops sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 9690fc0f2e4c4..0c41181dbe5af 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -515,114 +515,4 @@ function repeat_inner(arr, inner) return out end -end#module - -""" - eachrow(A::AbstractVecOrMat) - -Create a generator that iterates over the first dimension of vector or matrix `A`, -returning the rows as `AbstractVector` views. - -See also [`eachcol`](@ref), [`eachslice`](@ref), [`mapslices`](@ref). - -!!! compat "Julia 1.1" - This function requires at least Julia 1.1. - -# Example - -```jldoctest -julia> a = [1 2; 3 4] -2×2 Matrix{Int64}: - 1 2 - 3 4 - -julia> first(eachrow(a)) -2-element view(::Matrix{Int64}, 1, :) with eltype Int64: - 1 - 2 - -julia> collect(eachrow(a)) -2-element Vector{SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: - [1, 2] - [3, 4] -``` -""" -eachrow(A::AbstractVecOrMat) = (view(A, i, :) for i in axes(A, 1)) - - -""" - eachcol(A::AbstractVecOrMat) - -Create a generator that iterates over the second dimension of matrix `A`, returning the -columns as `AbstractVector` views. - -See also [`eachrow`](@ref) and [`eachslice`](@ref). - -!!! compat "Julia 1.1" - This function requires at least Julia 1.1. - -# Example - -```jldoctest -julia> a = [1 2; 3 4] -2×2 Matrix{Int64}: - 1 2 - 3 4 - -julia> first(eachcol(a)) -2-element view(::Matrix{Int64}, :, 1) with eltype Int64: - 1 - 3 - -julia> collect(eachcol(a)) -2-element Vector{SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: - [1, 3] - [2, 4] -``` -""" -eachcol(A::AbstractVecOrMat) = (view(A, :, i) for i in axes(A, 2)) - -""" - eachslice(A::AbstractArray; dims) - -Create a generator that iterates over dimensions `dims` of `A`, returning views that select all -the data from the other dimensions in `A`. - -Only a single dimension in `dims` is currently supported. Equivalent to `(view(A,:,:,...,i,:,: -...)) for i in axes(A, dims))`, where `i` is in position `dims`. - -See also [`eachrow`](@ref), [`eachcol`](@ref), [`mapslices`](@ref), and [`selectdim`](@ref). - -!!! compat "Julia 1.1" - This function requires at least Julia 1.1. - -# Example - -```jldoctest -julia> M = [1 2 3; 4 5 6; 7 8 9] -3×3 Matrix{Int64}: - 1 2 3 - 4 5 6 - 7 8 9 - -julia> first(eachslice(M, dims=1)) -3-element view(::Matrix{Int64}, 1, :) with eltype Int64: - 1 - 2 - 3 - -julia> collect(eachslice(M, dims=2)) -3-element Vector{SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: - [1, 4, 7] - [2, 5, 8] - [3, 6, 9] -``` -""" -@inline function eachslice(A::AbstractArray; dims) - length(dims) == 1 || throw(ArgumentError("only single dimensions are supported")) - dim = first(dims) - dim <= ndims(A) || throw(DimensionMismatch("A doesn't have $dim dimensions")) - inds_before = ntuple(Returns(:), dim-1) - inds_after = ntuple(Returns(:), ndims(A)-dim) - return (view(A, inds_before..., i, inds_after...) for i in axes(A, dim)) -end +end#module \ No newline at end of file diff --git a/base/exports.jl b/base/exports.jl index a8c8ff8dcda33..98d2786837fb1 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -41,6 +41,7 @@ export ComplexF32, ComplexF16, ComposedFunction, + ColSliceVector, DenseMatrix, DenseVecOrMat, DenseVector, @@ -80,8 +81,10 @@ export RoundNearestTiesUp, RoundToZero, RoundUp, + RowSliceVector, Set, Some, + SliceArray, StepRange, StepRangeLen, StridedArray, diff --git a/base/slicearray.jl b/base/slicearray.jl new file mode 100644 index 0000000000000..175f2806614fb --- /dev/null +++ b/base/slicearray.jl @@ -0,0 +1,224 @@ +""" + SliceArray{N,L,P,CI,S} <: AbstractArray{S,N} + +An `AbstractArray` of slices into a parent array. + +- `N` is the dimension of the array of slices. +- `L` is a tuple of length `ndims(parent)`, denoting how each dimension should be handled: + - an integer `i`: this is the `i`th dimension of the outer `SliceArray`. + - `nothing`: an "inner" dimension +- `P` is the type of the parent array +- `CI` is the type of the Cartesian iterator +- `S` is the element type of the `SliceArray` (the return type of `view`). + +These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) or +[`eachrow`](@ref). + +[`parent(S::SliceArray)`](@ref) will return the parent array. +""" +struct SliceArray{N,L,P,CI,S} <: AbstractArray{S,N} + """ + Parent array + """ + parent::P + """ + `CartesianIndices` iterator used to index each slice + """ + cartiter::CI +end + +unitaxis(::AbstractArray) = Base.OneTo(1) + +function SliceArray{N,L}(A::P, iter::CI) where {N,L,P,CI} + S = Base._return_type(view, Tuple{P, map((a,l) -> l === nothing ? Colon : eltype(a), axes(A), L)...}) + SliceArray{N,L,P,CI,S}(A, iter) +end + + +_slice_check_dims(N) = nothing +function _slice_check_dims(N, dim, dims...) + 1 <= dim <= N || throw(DimensionMismatch("Invalid dimension $dim")) + dim in dims && throw(DimensionMismatch("Dimensions $dims are not unique")) + _slice_check_dims(N,dims...) +end + +@inline function _eachslice(A::AbstractArray{T,N}, dims::NTuple{M,Integer}, drop::Bool) where {T,N,M} + _slice_check_dims(N,dims...) + if drop + # if N = 4, dims = (3,1) then + # iter = CartesianIndices(axes(A,3), axes(A,1)) + # L = (2, nothing, 1, nothing) + iter = CartesianIndices(map(dim -> axes(A,dim), dims)) + L = ntuple(dim -> findfirst(isequal(dim), dims), N) + return SliceArray{M,L}(A, iter) + else + # if N = 4, dims = (3,1) then + # iter = CartesianIndices(axes(A,1), OneTo(1), axes(A,3), OneTo(1)) + # L = (1, nothing, 3, nothing) + iter = CartesianIndices(ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N)) + L = ntuple(dim -> dim in dims ? dim : nothing, N) + return SliceArray{N,L}(A, iter) + end +end +@inline function _eachslice(A::AbstractArray{T,N}, dim::Integer, drop::Bool) where {T,N} + _eachslice(A, (dim,), drop) +end + +""" + eachslice(A::AbstractArray; dims, drop=true) + +Create a [`SliceArray`](@ref) that is indexed over dimensions `dims` of `A`, returning +views that select all the data from the other dimensions in `A`. `dims` can either by an +integer or a tuple of integers. + +If `drop = true` (the default), the outer `SliceArray` will drop the inner dimensions, and +the ordering of the dimensions will match those in `dims`. If `drop = false`, then the +`SliceArray` will have the same dimensionality as the underlying array, with inner +dimensions having size 1. + +See also [`eachrow`](@ref), [`eachcol`](@ref), and [`selectdim`](@ref). + +!!! compat "Julia 1.1" + This function requires at least Julia 1.1. + +!!! compat "Julia 1.7" + Prior to Julia 1.7, this returned an iterator, and only a single dimension `dims` was supported. + +# Example + +```jldoctest +julia> M = [1 2 3; 4 5 6; 7 8 9] +3×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 + +julia> S = eachslice(M,dims=1) +3-element SliceArray{1, (1, nothing), Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: + [1, 2, 3] + [4, 5, 6] + [7, 8, 9] + +julia> S[1] +3-element view(::Matrix{Int64}, 1, :) with eltype Int64: + 1 + 2 + 3 + +julia> T = eachslice(M,dims=1,drop=false) +3×1 SliceArray{2, (1, nothing), Matrix{Int64}, CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: + [1, 2, 3] + [4, 5, 6] + [7, 8, 9] +``` +""" +@inline function eachslice(A; dims, drop=true) + _eachslice(A, dims, drop) +end + +""" + eachrow(A::AbstractVecOrMat) + +Create a [`RowSliceVector`](@ref) that indexes over the rows of a vector or matrix `A`. + +See also [`eachcol`](@ref) and [`eachslice`](@ref). + +!!! compat "Julia 1.1" + This function requires at least Julia 1.1. + +!!! compat "Julia 1.7" + Prior to Julia 1.7, this returned an iterator. + +# Example + +```jldoctest +julia> a = [1 2; 3 4] +2×2 Matrix{Int64}: + 1 2 + 3 4 + +julia> S = eachrow(a) +2-element SliceArray{1, (1, nothing), Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: + [1, 2] + [3, 4] + +julia> S[1] +2-element view(::Matrix{Int64}, 1, :) with eltype Int64: + 1 + 2 +``` +""" +eachrow(A::AbstractMatrix) = _eachslice(A, (1,), true) +eachrow(A::AbstractVector) = eachrow(reshape(A, size(A,1), 1)) + +""" + eachcol(A::AbstractVecOrMat) + +Create a [`ColSliceVector`](@ref) that iterates over the second dimension of matrix `A`, returning the +columns as `AbstractVector` views. + +See also [`eachrow`](@ref) and [`eachslice`](@ref). + +!!! compat "Julia 1.1" + This function requires at least Julia 1.1. + +!!! compat "Julia 1.7" + Prior to Julia 1.7, this returned an iterator. + +# Example + +```jldoctest +julia> a = [1 2; 3 4] +2×2 Matrix{Int64}: + 1 2 + 3 4 + +julia> S = eachcol(a) +2-element SliceArray{1, (nothing, 1), Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: + [1, 3] + [2, 4] + +julia> S[1] +2-element view(::Matrix{Int64}, :, 1) with eltype Int64: + 1 + 3 +``` +""" +eachcol(A::AbstractMatrix) = _eachslice(A, (2,), true) +eachcol(A::AbstractVector) = eachcol(reshape(A, size(A,1), 1)) + +""" + RowSliceVector{M,CI,S} + +A special case of [`SliceArray`](@ref) that is a vector of row slices of a matrix, as +constructed by [`eachrow`](@ref). + +[`parent(S)`](@ref) can be used to get the underlying matrix. +""" +const RowSliceVector{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(1,nothing),P,CI,S} + +""" + ColSliceVector{M,CI,S} + +A special case of [`SliceArray`](@ref) that is a vector of column slices of a matrix, as +constructed by [`eachcol`](@ref). + +[`parent(S)`](@ref) can be used to get the underlying matrix. +""" +const ColSliceVector{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(nothing,1),P,CI,S} + + +IteratorSize(::Type{SliceArray{N,L,P,CI,S}}) where {N,L,P,CI,S} = IteratorSize(CI) +size(s::SliceArray) = size(s.cartiter) +size(s::SliceArray, dim) = size(s.cartiter, dim) +length(s::SliceArray) = length(s.cartiter) + +@inline function _slice_index(s::SliceArray{N,L,P,CI,S}, I...) where {N,L,P,CI,S} + c = s.cartiter[I...] + return map(l -> l === nothing ? (:) : c[l], L) +end + +getindex(s::SliceArray, I...) = view(s.parent, _slice_index(s, I...)...) +setindex!(s::SliceArray, val, I...) = s.parent[_slice_index(s, I...)...] = val + +parent(s::SliceArray) = s.arr diff --git a/doc/src/base/arrays.md b/doc/src/base/arrays.md index 1dc2d8ed926af..24a1329a11803 100644 --- a/doc/src/base/arrays.md +++ b/doc/src/base/arrays.md @@ -30,6 +30,9 @@ Base.StridedArray Base.StridedVector Base.StridedMatrix Base.StridedVecOrMat +Base.SliceArray +Base.RowSliceVector +Base.ColSliceVector Base.getindex(::Type, ::Any...) Base.zeros Base.ones diff --git a/test/arrayops.jl b/test/arrayops.jl index e289f6d87d889..82ad226a621f2 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2166,17 +2166,58 @@ end # row/column/slice iterator tests using Base: eachrow, eachcol @testset "row/column/slice iterators" begin + # check type aliases + @test RowSliceVector <: AbstractVector{<:AbstractVector} + @test eachrow(ones(3)) isa RowSliceVector + @test eachrow(ones(3,3)) isa RowSliceVector + @test ColSliceVector <: AbstractVector{<:AbstractVector} + @test eachcol(ones(3)) isa ColSliceVector + @test eachcol(ones(3,3)) isa ColSliceVector + # Simple ones M = [1 2 3; 4 5 6; 7 8 9] - @test collect(eachrow(M)) == collect(eachslice(M, dims = 1)) == [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - @test collect(eachcol(M)) == collect(eachslice(M, dims = 2)) == [[1, 4, 7], [2, 5, 8], [3, 6, 9]] + @test eachrow(M) == eachslice(M, dims = 1) == [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + @test eachcol(M) == eachslice(M, dims = 2) == [[1, 4, 7], [2, 5, 8], [3, 6, 9]] @test_throws DimensionMismatch eachslice(M, dims = 4) - # Higher-dimensional case - M = reshape([(1:16)...], 2, 2, 2, 2) + SR = @inferred eachrow(M) + @test SR[2] isa eltype(SR) + SR[2] = [14,15,16] + @test SR[2] == M[2,:] == [14,15,16] + + SC = @inferred eachcol(M) + @test SC[3] isa eltype(SR) + SC[3] = [23,26,29] + @test SC[3] == M[:,3] == [23,26,29] + + # Higher-dimensional cases + M = reshape(collect(1:16), (2,2,2,2)) @test_throws MethodError collect(eachrow(M)) @test_throws MethodError collect(eachcol(M)) - @test collect(eachslice(M, dims = 1))[1][:, :, 1] == [1 5; 3 7] + + S1 = eachslice(M, dims = 1) + @test size(S1) = (2,) + @test S1[1] == reshape(collect(1:2:16), (2,2,2)) + + S1K = eachslice(M, dims = 1, drop=false) + @test size(S1K) = (2,1,1,1) + @test S1K[1,1,1,1] == reshape(collect(1:2:16), (2,2,2)) + + S23 = eachslice(M, dims = (2,3)) + @test size(S23) = (2,2) + @test S23[2,1] == [3 11; 4 12] + + S23K = eachslice(M, dims = (2,3), drop=false) + @test size(S23K) = (1,2,2,1) + @test S23K[1,2,1,1] == [3 11; 4 12] + + S32 = eachslice(M, dims = (3,2)) + @test size(S32) = (2,2) + @test S23[1,2] == [3 11; 4 12] + + S32K = eachslice(M, dims = (3,2), drop=false) + @test size(S32K) = (1,2,2,1) + @test S32K[1,2,1,1] == [3 11; 4 12] end ### From 9671e8f42a3efebbf84b4094d8bf941b79e55220 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 15 Feb 2021 14:52:42 -0800 Subject: [PATCH 02/21] rename aliases to Rows/Columns --- base/exports.jl | 4 ++-- base/slicearray.jl | 18 +++++++++--------- doc/src/base/arrays.md | 4 ++-- test/arrayops.jl | 16 +++++++++------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index 98d2786837fb1..fd65263b31367 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -41,7 +41,7 @@ export ComplexF32, ComplexF16, ComposedFunction, - ColSliceVector, + Columns, DenseMatrix, DenseVecOrMat, DenseVector, @@ -81,7 +81,7 @@ export RoundNearestTiesUp, RoundToZero, RoundUp, - RowSliceVector, + Rows, Set, Some, SliceArray, diff --git a/base/slicearray.jl b/base/slicearray.jl index 175f2806614fb..3de6078a8b70e 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -94,7 +94,7 @@ julia> M = [1 2 3; 4 5 6; 7 8 9] 7 8 9 julia> S = eachslice(M,dims=1) -3-element SliceArray{1, (1, nothing), Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +3-element Rows{Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2, 3] [4, 5, 6] [7, 8, 9] @@ -119,7 +119,7 @@ end """ eachrow(A::AbstractVecOrMat) -Create a [`RowSliceVector`](@ref) that indexes over the rows of a vector or matrix `A`. +Create a [`Rows`](@ref) that indexes over the rows of a vector or matrix `A`. See also [`eachcol`](@ref) and [`eachslice`](@ref). @@ -138,7 +138,7 @@ julia> a = [1 2; 3 4] 3 4 julia> S = eachrow(a) -2-element SliceArray{1, (1, nothing), Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +2-element Rows{Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2] [3, 4] @@ -154,7 +154,7 @@ eachrow(A::AbstractVector) = eachrow(reshape(A, size(A,1), 1)) """ eachcol(A::AbstractVecOrMat) -Create a [`ColSliceVector`](@ref) that iterates over the second dimension of matrix `A`, returning the +Create a [`Columns`](@ref) that iterates over the second dimension of matrix `A`, returning the columns as `AbstractVector` views. See also [`eachrow`](@ref) and [`eachslice`](@ref). @@ -174,7 +174,7 @@ julia> a = [1 2; 3 4] 3 4 julia> S = eachcol(a) -2-element SliceArray{1, (nothing, 1), Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: +2-element Columns{Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: [1, 3] [2, 4] @@ -188,24 +188,24 @@ eachcol(A::AbstractMatrix) = _eachslice(A, (2,), true) eachcol(A::AbstractVector) = eachcol(reshape(A, size(A,1), 1)) """ - RowSliceVector{M,CI,S} + Rows{M,CI,S} A special case of [`SliceArray`](@ref) that is a vector of row slices of a matrix, as constructed by [`eachrow`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const RowSliceVector{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(1,nothing),P,CI,S} +const Rows{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(1,nothing),P,CI,S} """ - ColSliceVector{M,CI,S} + Columns{M,CI,S} A special case of [`SliceArray`](@ref) that is a vector of column slices of a matrix, as constructed by [`eachcol`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const ColSliceVector{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(nothing,1),P,CI,S} +const Columns{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(nothing,1),P,CI,S} IteratorSize(::Type{SliceArray{N,L,P,CI,S}}) where {N,L,P,CI,S} = IteratorSize(CI) diff --git a/doc/src/base/arrays.md b/doc/src/base/arrays.md index 24a1329a11803..5aa8367cd649d 100644 --- a/doc/src/base/arrays.md +++ b/doc/src/base/arrays.md @@ -31,8 +31,8 @@ Base.StridedVector Base.StridedMatrix Base.StridedVecOrMat Base.SliceArray -Base.RowSliceVector -Base.ColSliceVector +Base.Rows +Base.Columns Base.getindex(::Type, ::Any...) Base.zeros Base.ones diff --git a/test/arrayops.jl b/test/arrayops.jl index 82ad226a621f2..be77a79a5254a 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2167,12 +2167,12 @@ end using Base: eachrow, eachcol @testset "row/column/slice iterators" begin # check type aliases - @test RowSliceVector <: AbstractVector{<:AbstractVector} - @test eachrow(ones(3)) isa RowSliceVector - @test eachrow(ones(3,3)) isa RowSliceVector - @test ColSliceVector <: AbstractVector{<:AbstractVector} - @test eachcol(ones(3)) isa ColSliceVector - @test eachcol(ones(3,3)) isa ColSliceVector + @test Rows <: AbstractVector{<:AbstractVector} + @test eachrow(ones(3)) isa Rows + @test eachrow(ones(3,3)) isa Rows + @test Columns <: AbstractVector{<:AbstractVector} + @test eachcol(ones(3)) isa Columns + @test eachcol(ones(3,3)) isa Columns # Simple ones M = [1 2 3; 4 5 6; 7 8 9] @@ -2184,11 +2184,13 @@ using Base: eachrow, eachcol @test SR[2] isa eltype(SR) SR[2] = [14,15,16] @test SR[2] == M[2,:] == [14,15,16] + @test parent(SR) === M SC = @inferred eachcol(M) - @test SC[3] isa eltype(SR) + @test SC[3] isa eltype(SC) SC[3] = [23,26,29] @test SC[3] == M[:,3] == [23,26,29] + @test parent(SC) === M # Higher-dimensional cases M = reshape(collect(1:16), (2,2,2,2)) From 4259630040f88aff564d5262ffddef6152921d37 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 15 Feb 2021 17:16:12 -0800 Subject: [PATCH 03/21] fix field name --- base/slicearray.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 3de6078a8b70e..7f8729a1cf683 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -221,4 +221,4 @@ end getindex(s::SliceArray, I...) = view(s.parent, _slice_index(s, I...)...) setindex!(s::SliceArray, val, I...) = s.parent[_slice_index(s, I...)...] = val -parent(s::SliceArray) = s.arr +parent(s::SliceArray) = s.parent From 0794f870f27fd23a9ed1565aa03f64030cf0e71e Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 15 Feb 2021 18:44:42 -0800 Subject: [PATCH 04/21] fix test --- test/arrayops.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/arrayops.jl b/test/arrayops.jl index be77a79a5254a..6200f27313c53 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2198,27 +2198,27 @@ using Base: eachrow, eachcol @test_throws MethodError collect(eachcol(M)) S1 = eachslice(M, dims = 1) - @test size(S1) = (2,) + @test size(S1) == (2,) @test S1[1] == reshape(collect(1:2:16), (2,2,2)) S1K = eachslice(M, dims = 1, drop=false) - @test size(S1K) = (2,1,1,1) + @test size(S1K) == (2,1,1,1) @test S1K[1,1,1,1] == reshape(collect(1:2:16), (2,2,2)) S23 = eachslice(M, dims = (2,3)) - @test size(S23) = (2,2) + @test size(S23) == (2,2) @test S23[2,1] == [3 11; 4 12] S23K = eachslice(M, dims = (2,3), drop=false) - @test size(S23K) = (1,2,2,1) + @test size(S23K) == (1,2,2,1) @test S23K[1,2,1,1] == [3 11; 4 12] S32 = eachslice(M, dims = (3,2)) - @test size(S32) = (2,2) + @test size(S32) == (2,2) @test S23[1,2] == [3 11; 4 12] S32K = eachslice(M, dims = (3,2), drop=false) - @test size(S32K) = (1,2,2,1) + @test size(S32K) == (1,2,2,1) @test S32K[1,2,1,1] == [3 11; 4 12] end From 1997799222ffafc3cdae04d1f182a858a3f29821 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 15 Feb 2021 20:34:54 -0800 Subject: [PATCH 05/21] more test fixes --- test/arrayops.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/arrayops.jl b/test/arrayops.jl index 6200f27313c53..e8e4a04521221 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2199,27 +2199,27 @@ using Base: eachrow, eachcol S1 = eachslice(M, dims = 1) @test size(S1) == (2,) - @test S1[1] == reshape(collect(1:2:16), (2,2,2)) + @test S1[1] == M[1,:,:,:] S1K = eachslice(M, dims = 1, drop=false) @test size(S1K) == (2,1,1,1) - @test S1K[1,1,1,1] == reshape(collect(1:2:16), (2,2,2)) + @test S1K[1,1,1,1] == M[1,:,:,:] S23 = eachslice(M, dims = (2,3)) @test size(S23) == (2,2) - @test S23[2,1] == [3 11; 4 12] + @test S23[2,1] == M[:,2,1,:] S23K = eachslice(M, dims = (2,3), drop=false) @test size(S23K) == (1,2,2,1) - @test S23K[1,2,1,1] == [3 11; 4 12] + @test S23K[1,2,1,1] == M[:,2,1,:] S32 = eachslice(M, dims = (3,2)) @test size(S32) == (2,2) - @test S23[1,2] == [3 11; 4 12] + @test S32[2,1] == M[:,1,2,:] S32K = eachslice(M, dims = (3,2), drop=false) @test size(S32K) == (1,2,2,1) - @test S32K[1,2,1,1] == [3 11; 4 12] + @test S32K[1,2,1,1] == M[:,2,1,:] end ### From ddd0a4f3e12446f63f5d674ee7cda2ee485bf5bb Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 13:39:52 -0700 Subject: [PATCH 06/21] use axes instead of cartiter --- base/slicearray.jl | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 7f8729a1cf683..8ba4af62b34dd 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -16,7 +16,7 @@ These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) [`parent(S::SliceArray)`](@ref) will return the parent array. """ -struct SliceArray{N,L,P,CI,S} <: AbstractArray{S,N} +struct SliceArray{N,L,P,AX,S} <: AbstractArray{S,N} """ Parent array """ @@ -24,14 +24,14 @@ struct SliceArray{N,L,P,CI,S} <: AbstractArray{S,N} """ `CartesianIndices` iterator used to index each slice """ - cartiter::CI + axes::AX end unitaxis(::AbstractArray) = Base.OneTo(1) -function SliceArray{N,L}(A::P, iter::CI) where {N,L,P,CI} +function SliceArray{N,L}(A::P, axes::AX) where {N,L,P,AX} S = Base._return_type(view, Tuple{P, map((a,l) -> l === nothing ? Colon : eltype(a), axes(A), L)...}) - SliceArray{N,L,P,CI,S}(A, iter) + SliceArray{N,L,P,AX,S}(A, axes) end @@ -48,14 +48,14 @@ end # if N = 4, dims = (3,1) then # iter = CartesianIndices(axes(A,3), axes(A,1)) # L = (2, nothing, 1, nothing) - iter = CartesianIndices(map(dim -> axes(A,dim), dims)) + axes = map(dim -> axes(A,dim), dims) L = ntuple(dim -> findfirst(isequal(dim), dims), N) return SliceArray{M,L}(A, iter) else # if N = 4, dims = (3,1) then # iter = CartesianIndices(axes(A,1), OneTo(1), axes(A,3), OneTo(1)) # L = (1, nothing, 3, nothing) - iter = CartesianIndices(ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N)) + axes = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) L = ntuple(dim -> dim in dims ? dim : nothing, N) return SliceArray{N,L}(A, iter) end @@ -188,33 +188,31 @@ eachcol(A::AbstractMatrix) = _eachslice(A, (2,), true) eachcol(A::AbstractVector) = eachcol(reshape(A, size(A,1), 1)) """ - Rows{M,CI,S} + Rows{M,AX,S} A special case of [`SliceArray`](@ref) that is a vector of row slices of a matrix, as constructed by [`eachrow`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Rows{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(1,nothing),P,CI,S} +const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = SliceArray{1,(1,nothing),P,AX,S} """ - Columns{M,CI,S} + Columns{M,AX,S} A special case of [`SliceArray`](@ref) that is a vector of column slices of a matrix, as constructed by [`eachcol`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Columns{P<:AbstractMatrix,CI,S<:AbstractVector} = SliceArray{1,(nothing,1),P,CI,S} +const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = SliceArray{1,(nothing,1),P,AX,S} -IteratorSize(::Type{SliceArray{N,L,P,CI,S}}) where {N,L,P,CI,S} = IteratorSize(CI) -size(s::SliceArray) = size(s.cartiter) -size(s::SliceArray, dim) = size(s.cartiter, dim) -length(s::SliceArray) = length(s.cartiter) +IteratorSize(::Type{SliceArray{N,L,P,AX,S}}) where {N,L,P,AX,S} = HasShape{N}() +axes(s::SliceArray) = s.axes +size(s::SliceArray) = map(length, s.axes) -@inline function _slice_index(s::SliceArray{N,L,P,CI,S}, I...) where {N,L,P,CI,S} - c = s.cartiter[I...] +@inline function _slice_index(s::SliceArray{N,L}, c::Vararg{Int,N}) where {N,L} return map(l -> l === nothing ? (:) : c[l], L) end From 16c8b6645a8c94d111dea21a59d9cee39b6d78d7 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 13:41:00 -0700 Subject: [PATCH 07/21] rename SliceArray => Slices --- base/exports.jl | 2 +- base/slicearray.jl | 48 +++++++++++++++++++++--------------------- doc/src/base/arrays.md | 2 +- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index fd65263b31367..af239091a2d08 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -84,7 +84,7 @@ export Rows, Set, Some, - SliceArray, + Slices, StepRange, StepRangeLen, StridedArray, diff --git a/base/slicearray.jl b/base/slicearray.jl index 8ba4af62b34dd..ae2ea307049a8 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -1,22 +1,22 @@ """ - SliceArray{N,L,P,CI,S} <: AbstractArray{S,N} + Slices{N,L,P,CI,S} <: AbstractArray{S,N} An `AbstractArray` of slices into a parent array. - `N` is the dimension of the array of slices. - `L` is a tuple of length `ndims(parent)`, denoting how each dimension should be handled: - - an integer `i`: this is the `i`th dimension of the outer `SliceArray`. + - an integer `i`: this is the `i`th dimension of the outer `Slices`. - `nothing`: an "inner" dimension - `P` is the type of the parent array - `CI` is the type of the Cartesian iterator -- `S` is the element type of the `SliceArray` (the return type of `view`). +- `S` is the element type of the `Slices` (the return type of `view`). These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) or [`eachrow`](@ref). -[`parent(S::SliceArray)`](@ref) will return the parent array. +[`parent(S::Slices)`](@ref) will return the parent array. """ -struct SliceArray{N,L,P,AX,S} <: AbstractArray{S,N} +struct Slices{N,L,P,AX,S} <: AbstractArray{S,N} """ Parent array """ @@ -29,9 +29,9 @@ end unitaxis(::AbstractArray) = Base.OneTo(1) -function SliceArray{N,L}(A::P, axes::AX) where {N,L,P,AX} +function Slices{N,L}(A::P, axes::AX) where {N,L,P,AX} S = Base._return_type(view, Tuple{P, map((a,l) -> l === nothing ? Colon : eltype(a), axes(A), L)...}) - SliceArray{N,L,P,AX,S}(A, axes) + Slices{N,L,P,AX,S}(A, axes) end @@ -50,14 +50,14 @@ end # L = (2, nothing, 1, nothing) axes = map(dim -> axes(A,dim), dims) L = ntuple(dim -> findfirst(isequal(dim), dims), N) - return SliceArray{M,L}(A, iter) + return Slices{M,L}(A, iter) else # if N = 4, dims = (3,1) then # iter = CartesianIndices(axes(A,1), OneTo(1), axes(A,3), OneTo(1)) # L = (1, nothing, 3, nothing) axes = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) L = ntuple(dim -> dim in dims ? dim : nothing, N) - return SliceArray{N,L}(A, iter) + return Slices{N,L}(A, iter) end end @inline function _eachslice(A::AbstractArray{T,N}, dim::Integer, drop::Bool) where {T,N} @@ -67,13 +67,13 @@ end """ eachslice(A::AbstractArray; dims, drop=true) -Create a [`SliceArray`](@ref) that is indexed over dimensions `dims` of `A`, returning +Create a [`Slices`](@ref) that is indexed over dimensions `dims` of `A`, returning views that select all the data from the other dimensions in `A`. `dims` can either by an integer or a tuple of integers. -If `drop = true` (the default), the outer `SliceArray` will drop the inner dimensions, and +If `drop = true` (the default), the outer `Slices` will drop the inner dimensions, and the ordering of the dimensions will match those in `dims`. If `drop = false`, then the -`SliceArray` will have the same dimensionality as the underlying array, with inner +`Slices` will have the same dimensionality as the underlying array, with inner dimensions having size 1. See also [`eachrow`](@ref), [`eachcol`](@ref), and [`selectdim`](@ref). @@ -106,7 +106,7 @@ julia> S[1] 3 julia> T = eachslice(M,dims=1,drop=false) -3×1 SliceArray{2, (1, nothing), Matrix{Int64}, CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +3×1 Slices{2, (1, nothing), Matrix{Int64}, CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2, 3] [4, 5, 6] [7, 8, 9] @@ -190,33 +190,33 @@ eachcol(A::AbstractVector) = eachcol(reshape(A, size(A,1), 1)) """ Rows{M,AX,S} -A special case of [`SliceArray`](@ref) that is a vector of row slices of a matrix, as +A special case of [`Slices`](@ref) that is a vector of row slices of a matrix, as constructed by [`eachrow`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = SliceArray{1,(1,nothing),P,AX,S} +const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(1,nothing),P,AX,S} """ Columns{M,AX,S} -A special case of [`SliceArray`](@ref) that is a vector of column slices of a matrix, as +A special case of [`Slices`](@ref) that is a vector of column slices of a matrix, as constructed by [`eachcol`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = SliceArray{1,(nothing,1),P,AX,S} +const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(nothing,1),P,AX,S} -IteratorSize(::Type{SliceArray{N,L,P,AX,S}}) where {N,L,P,AX,S} = HasShape{N}() -axes(s::SliceArray) = s.axes -size(s::SliceArray) = map(length, s.axes) +IteratorSize(::Type{Slices{N,L,P,AX,S}}) where {N,L,P,AX,S} = HasShape{N}() +axes(s::Slices) = s.axes +size(s::Slices) = map(length, s.axes) -@inline function _slice_index(s::SliceArray{N,L}, c::Vararg{Int,N}) where {N,L} +@inline function _slice_index(s::Slices{N,L}, c::Vararg{Int,N}) where {N,L} return map(l -> l === nothing ? (:) : c[l], L) end -getindex(s::SliceArray, I...) = view(s.parent, _slice_index(s, I...)...) -setindex!(s::SliceArray, val, I...) = s.parent[_slice_index(s, I...)...] = val +getindex(s::Slices, I...) = view(s.parent, _slice_index(s, I...)...) +setindex!(s::Slices, val, I...) = s.parent[_slice_index(s, I...)...] = val -parent(s::SliceArray) = s.parent +parent(s::Slices) = s.parent diff --git a/doc/src/base/arrays.md b/doc/src/base/arrays.md index 5aa8367cd649d..56730c3d7ea2c 100644 --- a/doc/src/base/arrays.md +++ b/doc/src/base/arrays.md @@ -30,7 +30,7 @@ Base.StridedArray Base.StridedVector Base.StridedMatrix Base.StridedVecOrMat -Base.SliceArray +Base.Slices Base.Rows Base.Columns Base.getindex(::Type, ::Any...) From 1c932bc5264b74b39bc516fdca0fd857f4d94621 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 13:45:14 -0700 Subject: [PATCH 08/21] use : as sentinel instead of nothing --- base/slicearray.jl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index ae2ea307049a8..385168a254a4c 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -6,7 +6,7 @@ An `AbstractArray` of slices into a parent array. - `N` is the dimension of the array of slices. - `L` is a tuple of length `ndims(parent)`, denoting how each dimension should be handled: - an integer `i`: this is the `i`th dimension of the outer `Slices`. - - `nothing`: an "inner" dimension + - `:`: an "inner" dimension - `P` is the type of the parent array - `CI` is the type of the Cartesian iterator - `S` is the element type of the `Slices` (the return type of `view`). @@ -30,7 +30,7 @@ end unitaxis(::AbstractArray) = Base.OneTo(1) function Slices{N,L}(A::P, axes::AX) where {N,L,P,AX} - S = Base._return_type(view, Tuple{P, map((a,l) -> l === nothing ? Colon : eltype(a), axes(A), L)...}) + S = Base._return_type(view, Tuple{P, map((a,l) -> l === (:) ? Colon : eltype(a), axes(A), L)...}) Slices{N,L,P,AX,S}(A, axes) end @@ -46,17 +46,17 @@ end _slice_check_dims(N,dims...) if drop # if N = 4, dims = (3,1) then - # iter = CartesianIndices(axes(A,3), axes(A,1)) - # L = (2, nothing, 1, nothing) + # axes = (axes(A,3), axes(A,1)) + # L = (2, :, 1, :) axes = map(dim -> axes(A,dim), dims) - L = ntuple(dim -> findfirst(isequal(dim), dims), N) + L = ntuple(dim -> coalesce(findfirst(isequal(dim), dims), (:)), N) return Slices{M,L}(A, iter) else # if N = 4, dims = (3,1) then - # iter = CartesianIndices(axes(A,1), OneTo(1), axes(A,3), OneTo(1)) - # L = (1, nothing, 3, nothing) + # axes = (axes(A,1), OneTo(1), axes(A,3), OneTo(1)) + # L = (1, :, 3, :) axes = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) - L = ntuple(dim -> dim in dims ? dim : nothing, N) + L = ntuple(dim -> dim in dims ? dim : (:), N) return Slices{N,L}(A, iter) end end @@ -106,7 +106,7 @@ julia> S[1] 3 julia> T = eachslice(M,dims=1,drop=false) -3×1 Slices{2, (1, nothing), Matrix{Int64}, CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +3×1 Slices{2, (1, Colon()), Matrix{Int64}, CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2, 3] [4, 5, 6] [7, 8, 9] @@ -195,7 +195,7 @@ constructed by [`eachrow`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(1,nothing),P,AX,S} +const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(1,:),P,AX,S} """ Columns{M,AX,S} @@ -205,7 +205,7 @@ constructed by [`eachcol`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(nothing,1),P,AX,S} +const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(:,1),P,AX,S} IteratorSize(::Type{Slices{N,L,P,AX,S}}) where {N,L,P,AX,S} = HasShape{N}() @@ -213,7 +213,7 @@ axes(s::Slices) = s.axes size(s::Slices) = map(length, s.axes) @inline function _slice_index(s::Slices{N,L}, c::Vararg{Int,N}) where {N,L} - return map(l -> l === nothing ? (:) : c[l], L) + return map(l -> l === (:) ? (:) : c[l], L) end getindex(s::Slices, I...) = view(s.parent, _slice_index(s, I...)...) From 1f710f7b0b316792ca0feb869941d8245f9c19d1 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 14:37:26 -0700 Subject: [PATCH 09/21] typos --- base/slicearray.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 385168a254a4c..6fb49217190be 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -1,5 +1,5 @@ """ - Slices{N,L,P,CI,S} <: AbstractArray{S,N} + Slices{N,L,P,AX,S} <: AbstractArray{S,N} An `AbstractArray` of slices into a parent array. @@ -8,7 +8,7 @@ An `AbstractArray` of slices into a parent array. - an integer `i`: this is the `i`th dimension of the outer `Slices`. - `:`: an "inner" dimension - `P` is the type of the parent array -- `CI` is the type of the Cartesian iterator +- `AX` is the type of the `axes` field - `S` is the element type of the `Slices` (the return type of `view`). These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) or @@ -48,16 +48,16 @@ end # if N = 4, dims = (3,1) then # axes = (axes(A,3), axes(A,1)) # L = (2, :, 1, :) - axes = map(dim -> axes(A,dim), dims) + ax = map(dim -> axes(A,dim), dims) L = ntuple(dim -> coalesce(findfirst(isequal(dim), dims), (:)), N) return Slices{M,L}(A, iter) else # if N = 4, dims = (3,1) then # axes = (axes(A,1), OneTo(1), axes(A,3), OneTo(1)) # L = (1, :, 3, :) - axes = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) + ax = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) L = ntuple(dim -> dim in dims ? dim : (:), N) - return Slices{N,L}(A, iter) + return Slices{N,L}(A, ax) end end @inline function _eachslice(A::AbstractArray{T,N}, dim::Integer, drop::Bool) where {T,N} From a5ee96ef8bda85ddb6d0e16e69c67d38b57a24ae Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 15:14:58 -0700 Subject: [PATCH 10/21] more fixes --- base/slicearray.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 6fb49217190be..01a8ca867fb45 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -29,9 +29,9 @@ end unitaxis(::AbstractArray) = Base.OneTo(1) -function Slices{N,L}(A::P, axes::AX) where {N,L,P,AX} +function Slices{N,L}(A::P, ax::AX) where {N,L,P,AX} S = Base._return_type(view, Tuple{P, map((a,l) -> l === (:) ? Colon : eltype(a), axes(A), L)...}) - Slices{N,L,P,AX,S}(A, axes) + Slices{N,L,P,AX,S}(A, ax) end @@ -49,8 +49,8 @@ end # axes = (axes(A,3), axes(A,1)) # L = (2, :, 1, :) ax = map(dim -> axes(A,dim), dims) - L = ntuple(dim -> coalesce(findfirst(isequal(dim), dims), (:)), N) - return Slices{M,L}(A, iter) + L = ntuple(dim -> something(findfirst(isequal(dim), dims), (:)), N) + return Slices{M,L}(A, ax) else # if N = 4, dims = (3,1) then # axes = (axes(A,1), OneTo(1), axes(A,3), OneTo(1)) @@ -212,7 +212,7 @@ IteratorSize(::Type{Slices{N,L,P,AX,S}}) where {N,L,P,AX,S} = HasShape{N}() axes(s::Slices) = s.axes size(s::Slices) = map(length, s.axes) -@inline function _slice_index(s::Slices{N,L}, c::Vararg{Int,N}) where {N,L} +@inline function _slice_index(s::Slices{N,L}, c...) where {N,L} return map(l -> l === (:) ? (:) : c[l], L) end From 28ce9c812486657d13110632b15e522936d1da57 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 15:25:24 -0700 Subject: [PATCH 11/21] fix indexing dispatch --- base/slicearray.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 01a8ca867fb45..0488c8aa561c7 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -216,7 +216,7 @@ size(s::Slices) = map(length, s.axes) return map(l -> l === (:) ? (:) : c[l], L) end -getindex(s::Slices, I...) = view(s.parent, _slice_index(s, I...)...) -setindex!(s::Slices, val, I...) = s.parent[_slice_index(s, I...)...] = val +getindex(s::Slices{N}, I::Vararg{Int,N}) where {N} = view(s.parent, _slice_index(s, I...)...) +setindex!(s::Slices{N}, val, I::Vararg{Int,N}) where {N} = s.parent[_slice_index(s, I...)...] = val parent(s::Slices) = s.parent From c104154d7fcd1eb7cd416a41ad304bb157586e1a Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 15:54:00 -0700 Subject: [PATCH 12/21] move to slicemap to a field --- base/slicearray.jl | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 0488c8aa561c7..dc2ae463ee506 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -1,40 +1,37 @@ """ - Slices{N,L,P,AX,S} <: AbstractArray{S,N} + Slices{N,P,SM,AX,S} <: AbstractArray{S,N} An `AbstractArray` of slices into a parent array. -- `N` is the dimension of the array of slices. -- `L` is a tuple of length `ndims(parent)`, denoting how each dimension should be handled: - - an integer `i`: this is the `i`th dimension of the outer `Slices`. - - `:`: an "inner" dimension -- `P` is the type of the parent array -- `AX` is the type of the `axes` field -- `S` is the element type of the `Slices` (the return type of `view`). - These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) or [`eachrow`](@ref). [`parent(S::Slices)`](@ref) will return the parent array. """ -struct Slices{N,L,P,AX,S} <: AbstractArray{S,N} +struct Slices{N,P,SM,AX,S} <: AbstractArray{S,N} """ Parent array """ parent::P """ - `CartesianIndices` iterator used to index each slice + A tuple of length `ndims(parent)`, denoting how each dimension should be handled: + - an integer `i`: this is the `i`th dimension of the outer `Slices`. + - `:`: an "inner" dimension + """ + slicemap::SM + """ + A tuple of length `N` containing the axes. """ axes::AX end unitaxis(::AbstractArray) = Base.OneTo(1) -function Slices{N,L}(A::P, ax::AX) where {N,L,P,AX} - S = Base._return_type(view, Tuple{P, map((a,l) -> l === (:) ? Colon : eltype(a), axes(A), L)...}) - Slices{N,L,P,AX,S}(A, ax) +function Slices{N}(A::P, slicemap::SM, ax::AX) where {N,P,SM,AX} + S = Base._return_type(view, Tuple{P, map((a,l) -> l === (:) ? Colon : eltype(a), axes(A), slicemap)...}) + Slices{N,P,SM,AX,S}(A, slicemap, ax) end - _slice_check_dims(N) = nothing function _slice_check_dims(N, dim, dims...) 1 <= dim <= N || throw(DimensionMismatch("Invalid dimension $dim")) @@ -47,17 +44,17 @@ end if drop # if N = 4, dims = (3,1) then # axes = (axes(A,3), axes(A,1)) - # L = (2, :, 1, :) + # slicemap = (2, :, 1, :) ax = map(dim -> axes(A,dim), dims) - L = ntuple(dim -> something(findfirst(isequal(dim), dims), (:)), N) - return Slices{M,L}(A, ax) + slicemap = ntuple(dim -> something(findfirst(isequal(dim), dims), (:)), N) + return Slices{M}(A, slicemap, ax) else # if N = 4, dims = (3,1) then # axes = (axes(A,1), OneTo(1), axes(A,3), OneTo(1)) - # L = (1, :, 3, :) + # slicemap = (1, :, 3, :) ax = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) - L = ntuple(dim -> dim in dims ? dim : (:), N) - return Slices{N,L}(A, ax) + slicemap = ntuple(dim -> dim in dims ? dim : (:), N) + return Slices{N}(A, slicemap, ax) end end @inline function _eachslice(A::AbstractArray{T,N}, dim::Integer, drop::Bool) where {T,N} @@ -208,12 +205,12 @@ constructed by [`eachcol`](@ref). const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(:,1),P,AX,S} -IteratorSize(::Type{Slices{N,L,P,AX,S}}) where {N,L,P,AX,S} = HasShape{N}() +IteratorSize(::Type{S}) where {S<:Slices{N}} where {N} = HasShape{N}() axes(s::Slices) = s.axes size(s::Slices) = map(length, s.axes) -@inline function _slice_index(s::Slices{N,L}, c...) where {N,L} - return map(l -> l === (:) ? (:) : c[l], L) +@inline function _slice_index(s::Slices, c...) + return map(l -> l === (:) ? (:) : c[l], s.slicemap) end getindex(s::Slices{N}, I::Vararg{Int,N}) where {N} = view(s.parent, _slice_index(s, I...)...) From 51e091d51bdfd76867be36c3e621acb2b35f58b9 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Thu, 25 Mar 2021 21:10:29 -0700 Subject: [PATCH 13/21] doc fixes --- base/slicearray.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index dc2ae463ee506..909acf2f68f66 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -15,12 +15,12 @@ struct Slices{N,P,SM,AX,S} <: AbstractArray{S,N} parent::P """ A tuple of length `ndims(parent)`, denoting how each dimension should be handled: - - an integer `i`: this is the `i`th dimension of the outer `Slices`. + - an integer `i`: this is the `i`th dimension of the outer `Slices` object. - `:`: an "inner" dimension """ slicemap::SM """ - A tuple of length `N` containing the axes. + A tuple of length `N` containing the [`axes`](@ref) of the `Slices` object. """ axes::AX end @@ -91,7 +91,7 @@ julia> M = [1 2 3; 4 5 6; 7 8 9] 7 8 9 julia> S = eachslice(M,dims=1) -3-element Rows{Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +3-element Rows{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2, 3] [4, 5, 6] [7, 8, 9] @@ -103,7 +103,7 @@ julia> S[1] 3 julia> T = eachslice(M,dims=1,drop=false) -3×1 Slices{2, (1, Colon()), Matrix{Int64}, CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +3×1 Slices{2, Matrix{Int64}, Tuple{Int64, Colon}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2, 3] [4, 5, 6] [7, 8, 9] @@ -135,7 +135,7 @@ julia> a = [1 2; 3 4] 3 4 julia> S = eachrow(a) -2-element Rows{Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +2-element Rows{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2] [3, 4] @@ -171,7 +171,7 @@ julia> a = [1 2; 3 4] 3 4 julia> S = eachcol(a) -2-element Columns{Matrix{Int64}, CartesianIndices{1, Tuple{Base.OneTo{Int64}}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: +2-element Columns{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: [1, 3] [2, 4] @@ -192,7 +192,7 @@ constructed by [`eachrow`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(1,:),P,AX,S} +const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,P,Tuple{Int,Colon},AX,S} """ Columns{M,AX,S} @@ -202,7 +202,7 @@ constructed by [`eachcol`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,(:,1),P,AX,S} +const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,P,Tuple{Colon,Int},AX,S} IteratorSize(::Type{S}) where {S<:Slices{N}} where {N} = HasShape{N}() From 2b44296040c81607d2edba50b3972ab5a8a75961 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Tue, 30 Mar 2021 08:59:34 -0700 Subject: [PATCH 14/21] rearrange parameters --- base/slicearray.jl | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 909acf2f68f66..c0d69f7b8f220 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -1,5 +1,5 @@ """ - Slices{N,P,SM,AX,S} <: AbstractArray{S,N} + Slices{P,SM,AX,S,N} <: AbstractArray{S,N} An `AbstractArray` of slices into a parent array. @@ -8,7 +8,7 @@ These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) [`parent(S::Slices)`](@ref) will return the parent array. """ -struct Slices{N,P,SM,AX,S} <: AbstractArray{S,N} +struct Slices{P,SM,AX,S,N} <: AbstractArray{S,N} """ Parent array """ @@ -27,9 +27,10 @@ end unitaxis(::AbstractArray) = Base.OneTo(1) -function Slices{N}(A::P, slicemap::SM, ax::AX) where {N,P,SM,AX} +function Slices(A::P, slicemap::SM, ax::AX) where {P,SM,AX} + N = length(ax) S = Base._return_type(view, Tuple{P, map((a,l) -> l === (:) ? Colon : eltype(a), axes(A), slicemap)...}) - Slices{N,P,SM,AX,S}(A, slicemap, ax) + Slices{P,SM,AX,S,N}(A, slicemap, ax) end _slice_check_dims(N) = nothing @@ -47,14 +48,14 @@ end # slicemap = (2, :, 1, :) ax = map(dim -> axes(A,dim), dims) slicemap = ntuple(dim -> something(findfirst(isequal(dim), dims), (:)), N) - return Slices{M}(A, slicemap, ax) + return Slices(A, slicemap, ax) else # if N = 4, dims = (3,1) then # axes = (axes(A,1), OneTo(1), axes(A,3), OneTo(1)) # slicemap = (1, :, 3, :) ax = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) slicemap = ntuple(dim -> dim in dims ? dim : (:), N) - return Slices{N}(A, slicemap, ax) + return Slices(A, slicemap, ax) end end @inline function _eachslice(A::AbstractArray{T,N}, dim::Integer, drop::Bool) where {T,N} @@ -103,7 +104,7 @@ julia> S[1] 3 julia> T = eachslice(M,dims=1,drop=false) -3×1 Slices{2, Matrix{Int64}, Tuple{Int64, Colon}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +3×1 Slices{Matrix{Int64}, Tuple{Int64, Colon}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}, 2}: [1, 2, 3] [4, 5, 6] [7, 8, 9] @@ -192,7 +193,7 @@ constructed by [`eachrow`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,P,Tuple{Int,Colon},AX,S} +const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Int,Colon},AX,S,1} """ Columns{M,AX,S} @@ -202,10 +203,10 @@ constructed by [`eachcol`](@ref). [`parent(S)`](@ref) can be used to get the underlying matrix. """ -const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{1,P,Tuple{Colon,Int},AX,S} +const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Colon,Int},AX,S,1} -IteratorSize(::Type{S}) where {S<:Slices{N}} where {N} = HasShape{N}() +IteratorSize(::Type{Slices{P,SM,AX,S,N}}) where {P,SM,AX,S,N} = HasShape{N}() axes(s::Slices) = s.axes size(s::Slices) = map(length, s.axes) @@ -213,7 +214,9 @@ size(s::Slices) = map(length, s.axes) return map(l -> l === (:) ? (:) : c[l], s.slicemap) end -getindex(s::Slices{N}, I::Vararg{Int,N}) where {N} = view(s.parent, _slice_index(s, I...)...) -setindex!(s::Slices{N}, val, I::Vararg{Int,N}) where {N} = s.parent[_slice_index(s, I...)...] = val +getindex(s::Slices{P,SM,AX,S,N}, I::Vararg{Int,N}) where {P,SM,AX,S,N} = + view(s.parent, _slice_index(s, I...)...) +setindex!(s::Slices{P,SM,AX,S,N}, val, I::Vararg{Int,N}) where {P,SM,AX,S,N} = + s.parent[_slice_index(s, I...)...] = val parent(s::Slices) = s.parent From 98d64e66b6f9730e6f715a02551917aedb376ec3 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Wed, 6 Oct 2021 10:24:27 -0700 Subject: [PATCH 15/21] Apply suggestions from code review Co-authored-by: Milan Bouchet-Valat --- base/slicearray.jl | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index c0d69f7b8f220..b1eaa26132e0c 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -58,14 +58,14 @@ end return Slices(A, slicemap, ax) end end -@inline function _eachslice(A::AbstractArray{T,N}, dim::Integer, drop::Bool) where {T,N} +@inline function _eachslice(A::AbstractArray, dim::Integer, drop::Bool) _eachslice(A, (dim,), drop) end """ eachslice(A::AbstractArray; dims, drop=true) -Create a [`Slices`](@ref) that is indexed over dimensions `dims` of `A`, returning +Create a [`Slices`](@ref) object that is an array of slices over dimensions `dims` of `A`, returning views that select all the data from the other dimensions in `A`. `dims` can either by an integer or a tuple of integers. @@ -91,7 +91,7 @@ julia> M = [1 2 3; 4 5 6; 7 8 9] 4 5 6 7 8 9 -julia> S = eachslice(M,dims=1) +julia> S = eachslice(M, dims=1) 3-element Rows{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2, 3] [4, 5, 6] @@ -103,7 +103,7 @@ julia> S[1] 2 3 -julia> T = eachslice(M,dims=1,drop=false) +julia> T = eachslice(M, dims=1, drop=false) 3×1 Slices{Matrix{Int64}, Tuple{Int64, Colon}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}, 2}: [1, 2, 3] [4, 5, 6] @@ -115,9 +115,10 @@ julia> T = eachslice(M,dims=1,drop=false) end """ - eachrow(A::AbstractVecOrMat) + eachrow(A::AbstractVecOrMat) <: AbstractVector -Create a [`Rows`](@ref) that indexes over the rows of a vector or matrix `A`. +Create a [`Rows`](@ref) object that is a vector of rows of matrix or vector `A`. +Row slices are returned as `AbstractVector` views of `A`. See also [`eachcol`](@ref) and [`eachslice`](@ref). @@ -150,10 +151,10 @@ eachrow(A::AbstractMatrix) = _eachslice(A, (1,), true) eachrow(A::AbstractVector) = eachrow(reshape(A, size(A,1), 1)) """ - eachcol(A::AbstractVecOrMat) + eachcol(A::AbstractVecOrMat) <: AbstractVector -Create a [`Columns`](@ref) that iterates over the second dimension of matrix `A`, returning the -columns as `AbstractVector` views. +Create a [`Columns`](@ref) object that is a vector of columns of matrix or vector `A`. +Column slices are returned as `AbstractVector` views of `A`. See also [`eachrow`](@ref) and [`eachslice`](@ref). @@ -183,7 +184,7 @@ julia> S[1] ``` """ eachcol(A::AbstractMatrix) = _eachslice(A, (2,), true) -eachcol(A::AbstractVector) = eachcol(reshape(A, size(A,1), 1)) +eachcol(A::AbstractVector) = eachcol(reshape(A, size(A, 1), 1)) """ Rows{M,AX,S} From 45271e767e23ab95b584fcc74dc5011a0bb7e35e Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 15 Nov 2021 14:22:25 -0800 Subject: [PATCH 16/21] Update base/slicearray.jl Co-authored-by: David Widmann --- base/slicearray.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index b1eaa26132e0c..967c4b18498f8 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -215,9 +215,9 @@ size(s::Slices) = map(length, s.axes) return map(l -> l === (:) ? (:) : c[l], s.slicemap) end -getindex(s::Slices{P,SM,AX,S,N}, I::Vararg{Int,N}) where {P,SM,AX,S,N} = +Base.@propagate_inbounds getindex(s::Slices{P,SM,AX,S,N}, I::Vararg{Int,N}) where {P,SM,AX,S,N} = view(s.parent, _slice_index(s, I...)...) -setindex!(s::Slices{P,SM,AX,S,N}, val, I::Vararg{Int,N}) where {P,SM,AX,S,N} = +Base.@propagate_inbounds setindex!(s::Slices{P,SM,AX,S,N}, val, I::Vararg{Int,N}) where {P,SM,AX,S,N} = s.parent[_slice_index(s, I...)...] = val parent(s::Slices) = s.parent From 5e69131f205fbc744289595ffc124fdb470fb3de Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Wed, 11 May 2022 09:50:17 +0200 Subject: [PATCH 17/21] Rename types, add supertype, some minor fixes --- base/exports.jl | 5 +-- base/slicearray.jl | 71 ++++++++++++++++++++++++------------------ doc/src/base/arrays.md | 4 +-- test/arrayops.jl | 19 ++++++----- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index af239091a2d08..9843aa060d3b5 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -18,6 +18,7 @@ export AbstractMatrix, AbstractRange, AbstractSet, + AbstractSlices, AbstractUnitRange, AbstractVector, AbstractVecOrMat, @@ -41,7 +42,7 @@ export ComplexF32, ComplexF16, ComposedFunction, - Columns, + ColumnSlices, DenseMatrix, DenseVecOrMat, DenseVector, @@ -81,7 +82,7 @@ export RoundNearestTiesUp, RoundToZero, RoundUp, - Rows, + RowSlices, Set, Some, Slices, diff --git a/base/slicearray.jl b/base/slicearray.jl index 967c4b18498f8..d5b4e7edea094 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -1,14 +1,25 @@ """ - Slices{P,SM,AX,S,N} <: AbstractArray{S,N} + AbstractSlices{S,N} <: AbstractArray{S,N} -An `AbstractArray` of slices into a parent array. +Supertype for arrays of slices into a parent array over some dimension(s), +returning views that select all the data from the other dimensions. + +`parent` will return the parent array. +""" +abstract type AbstractSlices{T,N} <: AbstractArray{T,N} end + +""" + Slices{P,SM,AX,S,N} <: AbstractSlices{S,N} + +An `AbstractArray` of slices into a parent array over specified dimension(s), +returning views that select all the data from the other dimension(s). These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) or [`eachrow`](@ref). -[`parent(S::Slices)`](@ref) will return the parent array. +[`parent(s::Slices)`](@ref) will return the parent array. """ -struct Slices{P,SM,AX,S,N} <: AbstractArray{S,N} +struct Slices{P,SM,AX,S,N} <: AbstractSlices{S,N} """ Parent array """ @@ -74,36 +85,36 @@ the ordering of the dimensions will match those in `dims`. If `drop = false`, th `Slices` will have the same dimensionality as the underlying array, with inner dimensions having size 1. -See also [`eachrow`](@ref), [`eachcol`](@ref), and [`selectdim`](@ref). +See also [`eachrow`](@ref), [`eachcol`](@ref), [`mapslices`](@ref) and [`selectdim`](@ref). !!! compat "Julia 1.1" This function requires at least Julia 1.1. -!!! compat "Julia 1.7" - Prior to Julia 1.7, this returned an iterator, and only a single dimension `dims` was supported. +!!! compat "Julia 1.9" + Prior to Julia 1.9, this returned an iterator, and only a single dimension `dims` was supported. # Example ```jldoctest -julia> M = [1 2 3; 4 5 6; 7 8 9] +julia> m = [1 2 3; 4 5 6; 7 8 9] 3×3 Matrix{Int64}: 1 2 3 4 5 6 7 8 9 -julia> S = eachslice(M, dims=1) -3-element Rows{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +julia> s = eachslice(m, dims=1) +3-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2, 3] [4, 5, 6] [7, 8, 9] -julia> S[1] +julia> s[1] 3-element view(::Matrix{Int64}, 1, :) with eltype Int64: 1 2 3 -julia> T = eachslice(M, dims=1, drop=false) +julia> eachslice(M, dims=1, drop=false) 3×1 Slices{Matrix{Int64}, Tuple{Int64, Colon}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}, 2}: [1, 2, 3] [4, 5, 6] @@ -117,16 +128,16 @@ end """ eachrow(A::AbstractVecOrMat) <: AbstractVector -Create a [`Rows`](@ref) object that is a vector of rows of matrix or vector `A`. +Create a [`RowSlices`](@ref) object that is a vector of rows of matrix or vector `A`. Row slices are returned as `AbstractVector` views of `A`. -See also [`eachcol`](@ref) and [`eachslice`](@ref). +See also [`eachcol`](@ref), [`eachslice`](@ref) and [`mapslices`](@ref). !!! compat "Julia 1.1" This function requires at least Julia 1.1. -!!! compat "Julia 1.7" - Prior to Julia 1.7, this returned an iterator. +!!! compat "Julia 1.9" + Prior to Julia 1.9, this returned an iterator. # Example @@ -136,12 +147,12 @@ julia> a = [1 2; 3 4] 1 2 3 4 -julia> S = eachrow(a) -2-element Rows{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: +julia> s = eachrow(a) +2-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: [1, 2] [3, 4] -julia> S[1] +julia> s[1] 2-element view(::Matrix{Int64}, 1, :) with eltype Int64: 1 2 @@ -153,10 +164,10 @@ eachrow(A::AbstractVector) = eachrow(reshape(A, size(A,1), 1)) """ eachcol(A::AbstractVecOrMat) <: AbstractVector -Create a [`Columns`](@ref) object that is a vector of columns of matrix or vector `A`. +Create a [`ColumnSlices`](@ref) object that is a vector of columns of matrix or vector `A`. Column slices are returned as `AbstractVector` views of `A`. -See also [`eachrow`](@ref) and [`eachslice`](@ref). +See also [`eachrow`](@ref), [`eachslice`](@ref) and [`mapslices`](@ref). !!! compat "Julia 1.1" This function requires at least Julia 1.1. @@ -172,12 +183,12 @@ julia> a = [1 2; 3 4] 1 2 3 4 -julia> S = eachcol(a) -2-element Columns{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: +julia> s = eachcol(a) +2-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: [1, 3] [2, 4] -julia> S[1] +julia> s[1] 2-element view(::Matrix{Int64}, :, 1) with eltype Int64: 1 3 @@ -187,24 +198,24 @@ eachcol(A::AbstractMatrix) = _eachslice(A, (2,), true) eachcol(A::AbstractVector) = eachcol(reshape(A, size(A, 1), 1)) """ - Rows{M,AX,S} + RowSlices{M,AX,S} A special case of [`Slices`](@ref) that is a vector of row slices of a matrix, as constructed by [`eachrow`](@ref). -[`parent(S)`](@ref) can be used to get the underlying matrix. +[`parent`](@ref) can be used to get the underlying matrix. """ -const Rows{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Int,Colon},AX,S,1} +const RowSlices{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Int,Colon},AX,S,1} """ - Columns{M,AX,S} + ColumnSlices{M,AX,S} A special case of [`Slices`](@ref) that is a vector of column slices of a matrix, as constructed by [`eachcol`](@ref). -[`parent(S)`](@ref) can be used to get the underlying matrix. +[`parent`](@ref) can be used to get the underlying matrix. """ -const Columns{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Colon,Int},AX,S,1} +const ColumnSlices{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Colon,Int},AX,S,1} IteratorSize(::Type{Slices{P,SM,AX,S,N}}) where {P,SM,AX,S,N} = HasShape{N}() diff --git a/doc/src/base/arrays.md b/doc/src/base/arrays.md index 56730c3d7ea2c..853e4c7a4ec1b 100644 --- a/doc/src/base/arrays.md +++ b/doc/src/base/arrays.md @@ -31,8 +31,8 @@ Base.StridedVector Base.StridedMatrix Base.StridedVecOrMat Base.Slices -Base.Rows -Base.Columns +Base.RowSlices +Base.ColumnSlices Base.getindex(::Type, ::Any...) Base.zeros Base.ones diff --git a/test/arrayops.jl b/test/arrayops.jl index e8e4a04521221..9639281f60b04 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2164,15 +2164,14 @@ end end # row/column/slice iterator tests -using Base: eachrow, eachcol @testset "row/column/slice iterators" begin # check type aliases - @test Rows <: AbstractVector{<:AbstractVector} - @test eachrow(ones(3)) isa Rows - @test eachrow(ones(3,3)) isa Rows - @test Columns <: AbstractVector{<:AbstractVector} - @test eachcol(ones(3)) isa Columns - @test eachcol(ones(3,3)) isa Columns + @test RowSlices <: AbstractSlices{<:AbstractVector, 1} <: AbstractVector{<:AbstractVector} + @test eachrow(ones(3)) isa RowSlices + @test eachrow(ones(3,3)) isa RowSlices + @test ColumnSlices <: AbstractSlices{<:AbstractVector, 1} <: AbstractVector{<:AbstractVector} + @test eachcol(ones(3)) isa ColumnSlices + @test eachcol(ones(3,3)) isa ColumnSlices # Simple ones M = [1 2 3; 4 5 6; 7 8 9] @@ -2198,26 +2197,32 @@ using Base: eachrow, eachcol @test_throws MethodError collect(eachcol(M)) S1 = eachslice(M, dims = 1) + @test S1 isa AbstractSlices{<:AbstractArray{Int, 3}, 1} @test size(S1) == (2,) @test S1[1] == M[1,:,:,:] S1K = eachslice(M, dims = 1, drop=false) + @test S1K isa AbstractSlices{<:AbstractArray{Int, 3}, 4} @test size(S1K) == (2,1,1,1) @test S1K[1,1,1,1] == M[1,:,:,:] S23 = eachslice(M, dims = (2,3)) + @test S23 isa AbstractSlices{<:AbstractArray{Int, 2}, 2} @test size(S23) == (2,2) @test S23[2,1] == M[:,2,1,:] S23K = eachslice(M, dims = (2,3), drop=false) + @test S23K isa AbstractSlices{<:AbstractArray{Int, 2}, 4} @test size(S23K) == (1,2,2,1) @test S23K[1,2,1,1] == M[:,2,1,:] S32 = eachslice(M, dims = (3,2)) + @test S32 isa AbstractSlices{<:AbstractArray{Int, 2}, 2} @test size(S32) == (2,2) @test S32[2,1] == M[:,1,2,:] S32K = eachslice(M, dims = (3,2), drop=false) + @test S32K isa AbstractSlices{<:AbstractArray{Int, 2}, 4} @test size(S32K) == (1,2,2,1) @test S32K[1,2,1,1] == M[:,2,1,:] end From d62adb6b5120d6d4d27c0f76fe2d29ff0d1369c1 Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Wed, 11 May 2022 11:26:04 +0200 Subject: [PATCH 18/21] Fix doctest --- base/slicearray.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index d5b4e7edea094..48063cb17b52a 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -114,7 +114,7 @@ julia> s[1] 2 3 -julia> eachslice(M, dims=1, drop=false) +julia> eachslice(m, dims=1, drop=false) 3×1 Slices{Matrix{Int64}, Tuple{Int64, Colon}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}, 2}: [1, 2, 3] [4, 5, 6] From 48d9c83707241c501d23a97eb6ba1f09ead4a2d6 Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Wed, 11 May 2022 12:25:35 +0200 Subject: [PATCH 19/21] Fix version in docstring Co-authored-by: David Widmann --- base/slicearray.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 48063cb17b52a..c9371622f6aff 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -172,8 +172,8 @@ See also [`eachrow`](@ref), [`eachslice`](@ref) and [`mapslices`](@ref). !!! compat "Julia 1.1" This function requires at least Julia 1.1. -!!! compat "Julia 1.7" - Prior to Julia 1.7, this returned an iterator. +!!! compat "Julia 1.9" + Prior to Julia 1.9, this returned an iterator. # Example From ba675a6f08762705fca4ea5e976c4c5479f77cb8 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Wed, 11 May 2022 19:30:20 -0700 Subject: [PATCH 20/21] Update base/abstractarraymath.jl Co-authored-by: Milan Bouchet-Valat --- base/abstractarraymath.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 0c41181dbe5af..70c304d9060c1 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -515,4 +515,4 @@ function repeat_inner(arr, inner) return out end -end#module \ No newline at end of file +end#module From fa7fdcc169e8b23e760e958c14c21afbff48e4c8 Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Mon, 16 May 2022 10:12:17 +0200 Subject: [PATCH 21/21] Update NEWS.md --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 36660f0078c76..89d61c58081ca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -59,6 +59,8 @@ Library changes * `Dict` can be now shrunk manually by `sizehint!` ([#45004]). * `@time` now separates out % time spent recompiling invalidated methods ([#45015]). * `@time_imports` now shows any compilation and recompilation time percentages per import ([#45064]). +* `eachslice` now works over multiple dimensions; `eachslice`, `eachrow` and `eachcol` return + a `Slices` object, which allows dispatching to provide more efficient methods ([#32310]). Standard library changes ------------------------