PSA: replacement of ind2sub/sub2ind in Julia 0.7+

Or even simpler (I think; untested):

ind2subv(shape, indices) = Tuple.(CartesianIndices(shape)[indices])
sub2indv(shape, indices) = LinearIndices(shape)[CartesianIndex.(indices)]
4 Likes

Or even easier:

Base._ind2sub
Base._sub2ind
1 Like

But the point of CartesianIndex etc. is that it’s better than the Matlab approach. Reimplementing Matlab functionality to avoid using idiomatic Julia seems counterproductive. What’s the gain?

7 Likes

I agree the Julia approach is better. My functions are not meant as a replacement, but instead as a lightweight wrapper around the Julia methods, to provide the functionality illustrated below:

ind2subv( (3,4,2), [3,5]) = [ (3, 1, 1), (1, 2, 1)]

subv2ind( (3,4,2), [(3,1,1), (1,2,1)]) = [3, 5]

This is useful for applications such as discrete graphical models. As I learn more Julia, maybe I’ll discover a more “Julianic” way to do it without using these wrappers. (BTW, I agree the function names are terrible, and are just meant to help me port my legacy matlab code, but I may change them to lin2cart and cart2lin or something like that.)

Yes, this works great!

I even wrote the “dual” of it in your style.

function cart2lin(shape, cindices)

lndx = LinearIndices(Dims(shape))

return getindex.(Ref(lndx), cindices)

end

However, it’s a pain to have to manually wrap tuples inside the CartesianIndex contructor.

I’d like to pass in a vector of tuples, as in cart2lin([3,4,2],[ (3,1,1), (2,2,1)]),

and have it convert my arguments automatically. I can do this as follows:

function tuple2lin(shape, indices)

lndx = LinearIndices(Dims(shape))

cindices = [CartesianIndex(i) for i in indices]

return getindex.(Ref(lndx), cindices)

end

but I just want to write one function, and depending on the type of the “indices” argument,

convertng from Array{Tuple} to Array{CartesianIndex} if necessary.

I tried writing 2 function signatures but it does not work (always calls the first one even if I pass

in an array of tuples). What am I missing?

function cart2lin(shape::Tuple, cindices::Array{CartesianIndex})…

function cart2lin(shape::Tuple, indices::Array{Tuple}) # == tuple2lin

Sorry, I think my last email was unclear. What I would like to do is to have Julia dispatch to either cart2lin or tuple2lin, depending on the type of the second argument. How do I do this?

function cart2lin(shape, cindices)

lndx = LinearIndices(Dims(shape))

return getindex.(Ref(lndx), cindices)

end

function tuple2lin(shape, indices)

cindices = [CartesianIndex(i) for i in indices]

return cart2lin(cindices)

end

function test()

shape = (3,4,2)

cndx = [CartesianIndex(3,1,1), CartesianIndex(2,2,1)]

lndx = cart2lin(shape, cndx)

@assert lndx == [3,5]

ndx = [(3,1,1), (2,2,1)]

lndx = tuple2lin(shape, ndx)

@assert lndx == [3,5]

shapes = [(3,4,2), (4,1,5,2)]

for shape in shapes

K = prod(shape)

subs = ind2subv(shape, 1:K)

ndx = subv2ind(shape, subs)

@assert ndx == 1:K

end

end

Do you think you can wrap your code in triple backticks and indent properly, like you did in your first post? This is really hard to read.

3 Likes

Because CartesianIndex(cidx::CartesianIndex) = cidx (this is often true for wrapper types), @mbauman’s suggestion should do what you want for both tuples and CartesianIndexes. I just tested this code:

ind2subv(shape, indices) = Tuple.(CartesianIndices(shape)[indices])
sub2indv(shape, indices) = LinearIndices(shape)[CartesianIndex.(indices)]

function test()
  shape = (3, 4, 2)
  cndx = [CartesianIndex(3, 1, 1), CartesianIndex(2, 2, 1)]
  lndx = sub2indv(shape, cndx)
  @assert lndx == [3, 5]
  ndx = [(3, 1, 1), (2, 2, 1)]
  lndx = sub2indv(shape, ndx)
  @assert lndx == [3, 5]
  shapes = [(3,4,2), (4,1,5,2)]
  for shape in shapes
    K = prod(shape)
    subs = ind2subv(shape, 1:K)
    ndx = sub2indv(shape, subs)
    @assert ndx == 1:K
  end
end
1 Like

You guys rock! Thanks a lot. I’m pasting the final solution below for posterity.
(Both versions work.)

    """
    Transform linear indices to cartesian, cf ind2subv.
    Example:
    lin2cart((3,4,2), [3,5]) = [ (3, 1, 1), (2, 2, 1)]
    """
    #https://discourse.julialang.org/t/psa-replacement-of-ind2sub-sub2ind-in-julia-0-7/14666/20
    #return Tuple.(CartesianIndices(shape)[indices])
    return [Base._ind2sub(shape, i) for i in indices]
end

function cart2lin(shape, indices)
    """
    Transform vector of cartesian indices to linear indices, cf subv2ind.
    Example:
    cart2lin([3,4,2], [(3,1,1), (2,2,1)]) =  [3, 5]
    """
    #https://discourse.julialang.org/t/psa-replacement-of-ind2sub-sub2ind-in-julia-0-7/14666/20
    #return  LinearIndices(shape)[CartesianIndex.(indices)]
    return [Base._sub2ind(shape, i...) for i in indices]
end

Whoops, first function got cut off. Sorry, trying one more time.

function lin2cart(shape, indices)
    """
    Transform linear indices to cartesian, cf ind2subv.
    Example:
    lin2cart((3,4,2), [3,5]) = [ (3, 1, 1), (2, 2, 1)]
    """
    #https://discourse.julialang.org/t/psa-replacement-of-ind2sub-sub2ind-in-julia-0-7/14666/20
    #return Tuple.(CartesianIndices(shape)[indices])
    return [Base._ind2sub(shape, i) for i in indices]
end

function cart2lin(shape, indices)
    """
    Transform vector of cartesian indices to linear indices, cf subv2ind.
    Example:
    cart2lin([3,4,2], [(3,1,1), (2,2,1)]) =  [3, 5]
    """
    #https://discourse.julialang.org/t/psa-replacement-of-ind2sub-sub2ind-in-julia-0-7/14666/20
    #return  LinearIndices(shape)[CartesianIndex.(indices)]
    return [Base._sub2ind(shape, i...) for i in indices]
end
1 Like

Glad you found a solution! Two recommendations:

I would not use the underscored functions (_ind2sub and _sub2ind) if I didn’t have to. They are internal functions that could have their functionality changed or be removed in a future version of Julia. They are also undocumented, so people reading your code can’t easily search for these functions to see what they’re doing.

You could also move the documentation to just above each function (not inside the function). That way they will be proper docstrings and available in Julia’s help mode. See details here.

4 Likes

It’s also possible to edit the post with the missing code.

Are you perhaps reading this through an email client? I ask because you seemed previously to reply to a remark that I had deleted, due to a misunderstanding, and referring to a post as an email. As far as I know quite a few posters do that, but it could occasionally lead to some confusion.

If you use the web-interface, you can fix typos, add code formatting (which is missing in some of your posts), and fix other mistakes without having to repost all the contents. You can also delete posts entirely.

2 Likes

I’ve read this very interesting blog post, and came away thoroughly impressed with the ease and power of CartesianIndex/ices. There is just one thing bothering me, and that is the behaviour of max. According to the docs:

help?> max
[...]
Return the maximum of the arguments.

However,

julia> max(CartesianIndex(0, 1), CartesianIndex(1, 0))
CartesianIndex(1, 1)

doesn’t return the max of the two arguments (that is, return either the first or the second argument), but something in between. Compare this to

julia> max((0, 1), (1, 0))
(1, 0)

julia> max.((0, 1), (1, 0))
(1, 1)

Since “a CartesianIndex has to be viewed as a single (scalar) entity, rather than as a container in its own right”, I find this a bit hard to reconcile. max of two index’es behaves more as if it’s been broadcasted over two containers.

Is max the right name for this operation? Or should perhaps the docstring of max be tweaked? Alternatively, how can I get a good intuition about how this works?

7 Likes

That’s worth opening an issue for dedicated discussion.

1 Like

Do you mean a github issue, or a separate thread on discourse?

A GitHub issue.