Unpacking CartesianIndex

for (i, j) in CartesianIndices(a)
    println(i, j)
end

produces an error:

ERROR: iteration is deliberately unsupported for CartesianIndex. Use `I` rather than `I...`, or use `Tuple(I)...`

This is an unreasonable design of the Julia interface in my opinion, which implements unpacking using iterator. I suggest Julia have an additional function unpack which is called when unpacking syntax is used and only falls back to iterate by default.

Take this:

for (i, j) in Tuple.(CartesianIndices(a))
  println(i, j)
end
2 Likes

While the wording of this suggestion is rather forceful, there is merit to it, I think.

1 Like

It was using map to get around this. Thanks

Note that this (and equivalent map version) are allocating because they materialize a vector of tuples.

Compare with a non-allocating alternative (unpack2 below) which runs about 7x faster on my machine.

function unpack1(dims)
    s = 0
    for (i, j) in Tuple.(CartesianIndices(dims))
        #println(i, j)
        s += i + j
    end
    return s
end

function unpack2(dims)
    s = 0
    for ci in CartesianIndices(dims)
        i, j = Tuple(ci)
        #println(i, j)
        s += i + j
    end
    return s
end
using BenchmarkTools
const dims = (1000, 1000)
@btime unpack1($dims)
@btime unpack2($dims)

Apparently, iteration of a CartesianIndex is disabled to avoid a possible performance trap when splatting a CartesianIndex. See #23719

If you want to live dangerously, you could define iteration on CartesianIndex yourself.
This version (unpack3 below) is also non-allocating, and is also ~7x faster than the “materializing” version.

Base.iterate(ci::CartesianIndex) = iterate(Tuple(ci))
Base.iterate(ci::CartesianIndex, state) = iterate(Tuple(ci), state)

function unpack3(dims)
    s = 0
    for (i, j) in CartesianIndices(dims)
        #println(i, j)
        s += i + j
    end
    return s
end

In this case, a generator expression also works well (although in general generators sometimes are way slower due to type-problems), see:

julia> function unpack4(dims)
           s = 0
           for (i,j) in (Tuple(c) for c in CartesianIndices(dims))
               s += i + j
           end
           return s
       end

which on my system is as fast as unpack2.

I thought map returns a generator, which is how python works. It turns out to return a vector.

Depending on the situation, eager operations is usually more efficient that lazy ones if everything fits into the memory. I’m not sure why in this case it is slower.