Delete the nth frontal of a 3th-order tensor(array)

Consider the following tensor

A = reshape(1:24,4,2,3)

I want to split it into two elements: B, which is the second frontal of A; and C, which is the remaining elements. Getting B is pretty obvious,

B = A[:,:,2]

But I did not find a clever way to get C since functions such as popat!() and deleteat!() do not work for high-order arrays.

popat!(A, :, :, 2)

ERROR: MethodError: no method matching popat!(::Array{Int64, 3}, ::Colon, ::Colon, ::Int64)
Closest candidates are:
  popat!(::Vector, ::Integer, ::Any) at /usr/share/julia/base/array.jl:1296
Stacktrace:
 [1] top-level scope
   @ REPL[58]:1

PS: For those who don’t know what a frontal is, here it is:

I just found the answer, I didn’t know that it is possible to select an element like that is julia

B = A[:,:,2]
C = A[:,:,1:end.!=2]

you can also do:

using InvertedIndices
A[:, :, Not(2)]
3 Likes

That is nice but is not built-in :frowning:

Nothing wrong with a small dependency. That’s why packages exist.

FWIW InvertedIndices.jl has no dependencies so it really is super light weight.

That is not the point. IMHO, the built-in solution solve it in a idiomatic way, making InvertedIndices unnecessary for this purpose. That is why I put my comment as solution.

InvertedIndices is also much slower than the builtin solution:

julia> A = rand(10, 10, 10);

julia> @btime $A[:, :, (1:end) .!= 2];
  1.457 μs (3 allocations: 7.28 KiB)
julia> @btime $A[:, :, Not(2)];
  4.558 μs (48 allocations: 8.62 KiB)


julia> A = rand(100, 100, 1000);

julia> @btime @view($A[:, :, (1:end) .!= 2]);
  2.210 μs (4 allocations: 12.34 KiB)
julia> @btime @view($A[:, :, Not(2)]);
  272.543 μs (5978 allocations: 179.41 KiB)

Not sure if these performance issues are fundamental or can be improved.

1 Like

On my PC the following is even better:

@btime @view($A[:, :, [1;3:end]]);

The drawback is that you cannot make it generic, while you can change 2 in A[:, :, (1:end) .!= 2]) by a variable.

I think you can and it’s still faster:

n = 4
@btime @view($A[:, :, [1:($n-1);($n+1):end]]);
2 Likes

Nice/Boa!

Below is a generalization that does not penalize the performance of the solution found by @rafael.guerra

n=[3,5,9]
@btime @view($A[:, :, deleteat!(collect(1:size($A,ndims($A))),$n)]);

here a function that takes advantage of the fact that the set of indices to be excluded is ordered.

@btime @view($A[:, :, notin($A,$n)]);

function notin(A,n)
    j=1
    k=1
    s=size(A,ndims(A))
    sn=length(n)
    nin=Vector{Int64}(undef, s-sn)
    for i in 1:s
        if i!=n[k]
            nin[j]=i
            j+=1
        else
            if k==sn
                copyto!(nin,j,1:s,i+1,s-i)
                break
            else
                k+=1
            end
        end
    end
    nin
end

You should file an issue. I would expect Not to be faster.

There are unanswered performance issues already, likely related: Significant performance cost over naively filtering indices · Issue #14 · JuliaData/InvertedIndices.jl · GitHub and Bad performance caused by type unstable `iterate` · Issue #27 · JuliaData/InvertedIndices.jl · GitHub. I just assumed that performance isn’t a priority for InvertedIndices and so don’t use them myself (but wanted to).