Reverse view of an array

While reviewing a recent pull request in Images.jl, I ran across this line:

    cum_pmf_sq_2 = reverse!(cumsum(reverse!(prob_mass_function_sq)))

Using reverse! is better than reverse here, but writing out the loops manually would certainly be more efficient. I’m wondering if it’s worthwhile to try to add some conveniences for this:

  1. Add a reverse or rev keyword to accumulate (and cumsum, cumprod, etc.)
  2. Add a ReverseView wrapper to a Vector, which reverses the indexes (and therefore iteration), but otherwise leaves the data alone.

@stevengj recently added Iterators.Reverse, but that doesn’t work here, as cumsum (which is based on accumulate) requires an AbstractArray, and Iterators.Reverse isn’t one of those.

I can, of course, make these additions in a package, but they seem general enough that perhaps they could be included in Base. Thoughts?

Cheers,
Kevin

2 Likes

A ReverseView would compose better than extra keywords to functions. If the performance penalty is not too large, it would be my preferred solution, as a middle ground between optimized loops and copying operations.

Is there a fundamental reason why accumulate couldn’t work with any iterator? It looks like we just need to fix that limitation.

3 Likes

SubArray already supports this. Try view(a, lastindex(a):-1:firstindex(a)). (using Compat for firstindex and lastindex on 0.6.)

Having an accumulate function that works on iterators would be great, though it is tricky to make fast and type-stable. Here, however, note that cumsum on floating-point arrays is actually much more accurate than any iterator-based version because it uses pairwise summation. Also, I suspect that the original use-case in this thread might want to use cumsum! so that it could operate in-place, and that wouldn’t be possible for iterators.

8 Likes

Just a side-track: isn’t it possible to use series math to do, for instance, cumsum! on Iterators?

The iterator protocol in Julia provides no way to mutate the iterator contents.

Oh right…

2 posts were split to a new topic: How to create a reversed view that allows for growing arrays?

Could it support multi-dimensional arrays too? Via a dim keyword,

R = ReverseView(A; dims=(2,4))

would reverse indices along dimensions 2 and 4, whereas dimensions 1, 3 (or any other dimension) map to the original array unchanged:

R[i,j,k,l] == A[i, end - j + 1, k, end - l + 1]

To achieve this with view, it seems necessary to specify what happens for each dimension explicitly,

R = @view A[:, lastindex(A,2):-1:firstindex(A,2), :, lastindex(A,4):-1:firstindex(A,4)]

which is considerably more verbose and harder to read. It’s also harder to write in a context where A has an unknown number of dimensions but you know that dimension 2 has to be reversed (which admittedly need not be that common).

This already works:

R = @view A[axes(A,1), reverse(axes(A,2)), axes(A,3), reverse(axes(A,4))]

It would be convenient to just do

I = CartesianIndices(A)
R = @view A[reverse(I, dims=(2,4))]

but I noticed that reversing CartesianIndices is currently highly suboptimal (returning an Array), so that would have to be fixed first: optimized reverse(::CartesianIndices, dims=...) · Issue #49021 · JuliaLang/julia · GitHub

6 Likes