2

What is the most efficient way to create a view on array using, for example, sliding window=2

Let's say we have:

x = collect(1:1:6)
# 1 2 3 4 5 6

And I want to create a view like this:

# 1 2
# 2 3
# 3 4
# 4 5
# 5 6

So far I found only this option, but not sure if it's an optimal one:

y = Array{Float32, 2}(undef, nslides, window)
@inbounds for i in 1:window
    y[:, i] = @view x[i:end-(window-i)]
end
Bogdan Ruzhitskiy
  • 1,167
  • 1
  • 12
  • 21

2 Answers2

3

The one liner is:

view.(Ref(x), (:).(1:length(x)-1,2:length(x)))

Testing:

julia> x=collect(1:6);

julia> view.(Ref(x), (:).(1:length(x)-1,2:length(x)))
5-element Array{SubArray{Int64,1,Array{Int64,1},Tuple{UnitRange{Int64}},true},1}:
 [1, 2]
 [2, 3]
 [3, 4]
 [4, 5]
 [5, 6]

Explanation:

  • creation of views is vectorized by the dot operator .
  • we do not want to vectorize on elements of x so use Ref(x) instead
  • (:) is just a shorter form for UnitRange and again we use the dot operator . to vectorize

I used 2 as the Window size but of course you can write view.(Ref(x), (:).(1:length(x)-(window-1),window:length(x)))

EDIT:

If you want rather a library function this would work for you:

julia> using ImageFiltering

julia> mapwindow(collect, x, 0:1,border=Inner())
5-element OffsetArray(::Array{Array{Int64,1},1}, 1:5) with eltype Array{Int64,1} with indices 1:5:
 [1, 2]
 [2, 3]
 [3, 4]
 [4, 5]
 [5, 6]

Of course you could put them the function that you want to run on the sliding window rather than just collect.

Przemyslaw Szufel
  • 40,002
  • 3
  • 32
  • 62
  • Didn't we just have that :) And I now have found the package I was looking for: [RollingFunctions.jl](https://github.com/JeffreySarnoff/RollingFunctions.jl). – phipsgabler Sep 07 '20 at 06:50
  • Perhaps tidier with a comprehension: `reduce(vcat, @views [x[i-1:i]' for i in 2:length(x)])`. This also concatenates the slices to be the rows of a matrix. – mcabbott Sep 07 '20 at 10:09
  • This however returns a `Matrix` rather than `view` so the original data in `x` will be copied and it benchmarks to be 3.5 times slower. – Przemyslaw Szufel Sep 07 '20 at 10:12
  • @phipsgabler yes! and it is a good one! however this allows you to run function over a rolling window rather than get the window yourself (at least I do not see the code to generate a window without aggregating) – Przemyslaw Szufel Sep 07 '20 at 10:18
  • Sure, `reduce(vcat, ...')` could be applied, or not, to either form. The question both creates a matrix, and says view. Maybe also worth noting that `'` here would be wrong if `x` contained complex numbers, or strings. – mcabbott Sep 07 '20 at 10:25
  • @phipsgabler `ImageFiltering.jl`!!! - see my edit :-) – Przemyslaw Szufel Sep 07 '20 at 10:31
  • Well, I thought it would just be `rolling(identity, xs, window=2)`. But appearently, the output type is [fixed with `eval`](https://github.com/JeffreySarnoff/RollingFunctions.jl/blob/master/src/roll/rolling.jl#L1) for some weird reason... `mapwidnow` is excellent, though. – phipsgabler Sep 07 '20 at 11:08
3

One solution with a package (well, with my package) is this:

julia> using Tullio

julia> x = 1:6; window = 2;

julia> @tullio y[r,c] := x[r+c-1]  (c in 1:window)
5×2 Matrix{Int64}:
 1  2
 2  3
 3  4
 4  5
 5  6
mcabbott
  • 2,329
  • 1
  • 4
  • 8
  • This is nice, however benchmarks 40% slower than my code. – Przemyslaw Szufel Sep 07 '20 at 12:00
  • 3
    Hadn't looked, but for me it's indeed 20ns slower at length 6. Then about equal at length 25, and 3x faster from 100 to 10^6. It is a little faster with `@tullio threads=false ...`, as the macro is not quite smart enough to know ahead of time that multi-threading is never going to pay here. With that, it's just a more readable way to write `@inbounds for r in axes(y,1), c in axes(y,2); y[r, c] = x[(r + c) - 1]; end end` with ranges & allocation taken care of. – mcabbott Sep 07 '20 at 12:32
  • I have just checked it - indeed for large arrays `Tullio` is 3x faster. Looks great! – Przemyslaw Szufel Sep 07 '20 at 13:53