# `view` and `[ ]` with "slice object"

A tuple is often used to encapsulate a set of array sizes: `sz = size(multi_dim_arr)` and use `sz` later. In a similar manner, I sometimes want to encapsulate an array “slice” in an object, but I’m wondering what’s the best way.

``````arr = rand(Float64, 3, 5)
s = size(arr) # -> Tuple (3,5)
b = fill("hello", s) # works
b = fill("hello", 3, 5) # works, too.
slice = ( :, 2)
c = view(arr,  slice) # error
c = view(arr, slice...) # works
d = arr[slice...]  # works, too.
slice2 = CartesianIndices((:, 2)) # error
slice2 = CartesianIndices((1:3, 2)) # fine
e = arr[slice2] # works
``````

As you can see, there isn’t quite a “slice object” that can express a general slice. `CartesianIndices` comes close but it can’t include a `Colon`-only range.

It would be nice if `view` and `[ ]` accepted a slice object that can be used like

``````function func(sl2d)
# build a 4D array
t = view(arr4d, sl2d)
u = arr[sl2d]
func_on_2d_arr(t) #  or u
end
sl = Slice(4,  :,  3,  2:9)
func(sl)
``````

So, my question is whether there is an idiomatic way to encapsulate a slice in a variable (object) and use it later. Is `v[tuple...]` the way to go? Or would it make sense to extend `view` and `[ ]` to take a slice object (as represented by a tuple, for example)?

It seems that your proposed `Slice` object is a reasonable way to do this (although be careful to avoid mixing it up with `Base.Slices`).

An extremely simple implementation might look like this

``````struct ArraySlice{T<:Tuple}
s::T
end

ArraySlice(s...) = ArraySlice(s) # Vararg constructor

Base.getindex(x::AbstractArray, slice::ArraySlice) = Base.getindex(x, slice.s...)
Base.view(x::AbstractArray, slice::ArraySlice) = Base.view(x, slice.s...)
``````

It’s important that a specialized object was used for this (which you appear to suggest already, but I’m emphasizing it here). Do not simply overload `Base.getindex(x::AbstractArray, slice::Tuple)` so that tuples are interpreted like your slice object. That would be type piracy and would have the potential to break things spectacularly if some other code (that you write or load, or that something you load loads) were to try a similar overload.

Another option, if you have the object (or its indices) known to you in advance so that you can resolve `:`, `begin`, and `end` immediately, is to slice the `CartesianIndices` of your target object

``````julia> a = zeros(2,3,4);

julia> sl = CartesianIndices(a)[:,3:-1:1,4]
2×3 Matrix{CartesianIndex{3}}:
CartesianIndex(1, 3, 4)  CartesianIndex(1, 2, 4)  CartesianIndex(1, 1, 4)
CartesianIndex(2, 3, 4)  CartesianIndex(2, 2, 4)  CartesianIndex(2, 1, 4)

julia> sl = @view CartesianIndices(a)[:,3:-1:1,4]
2×3 view(::CartesianIndices{3, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}, Base.OneTo{Int64}}}, :, 3:-1:1, 4) with eltype CartesianIndex{3}:
CartesianIndex(1, 3, 4)  CartesianIndex(1, 2, 4)  CartesianIndex(1, 1, 4)
CartesianIndex(2, 3, 4)  CartesianIndex(2, 2, 4)  CartesianIndex(2, 1, 4)
``````

Note that the `getindex` version of this is producing an `Array` (`Matrix`, in this case), which will allocate. The `view` version should not, so is probably preferable. Note that you don’t need to pass `a`, but can instead use `CartesianIndices(axes(a))` if you only know the `axes` of your target. You can even go so far as to just use the `size`, although that is fragile because it may break for `OffsetArrays` or other non-1-indexed arrays.

1 Like

Colons (and other unusual indexing things) get converted upon indexing/view using `to_indices`. CartesianIndices don’t support `:` because they don’t have the context about the array they’ll eventually be used in. You can give them that context:

``````julia> arr = rand(Float64, 3, 5);

julia> slice = ( :, 2)
(Colon(), 2)

julia> slice2 = CartesianIndices(to_indices(arr, slice))
CartesianIndices((1:3, 2))
``````

Overloading indexing like @mikmoore suggests is going to be a huge headache of ambiguities, I would think. You could overload `to_indices(arr, inds, S::ArraySlice)` if you wanted to index with such an object without splatting. But I’d just splat the tuple.

2 Likes

Thank you folks for your detailed, well-informed discussion! I now know how or in which way I should proceed.

In the meanwhile, as I write small programs, I find myself wanting to write

``````slice = (1:10:end) # strided indexing
. . .
func(v[slice], . . .)
another_func(arr[slice], . . .)
``````

which doesn’t work because `end` isn’t known in that context.

Writing in Fortran, I used to have a lot of little branches. In Julia, most of such branches are gone because you can encapsulate various information in variables (objects). Array slicing is probably the last thing (to me) for which there is no idiomatic or obvious solution.

Perhaps GitHub - JuliaArrays/EndpointRanges.jl: Julia package for doing arithmetic on endpoints in array indexing might help with this?

1 Like

Yes! Thank you for the information!

With that package, I can fulfill all my needs:

``````using EndpointRanges

a1 = 1:11
a3 = reshape(1:(3*2*4), 3, 2, 4)

s1 = (ibegin:3:iend, ) # 1D slice object
s3 = (2, :, ibegin:3:iend) # 3D slice object

b1 = a1[s1...] # get the slice
b3 = a3[s3...] # get the slice

display(collect(b1))
display(collect(b3))
``````

With this, one could modify `Base.getindex` and `Base.view` as suggested by @mikmoore .