Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow indexing into Tuple by another Tuple #33306

Closed
wants to merge 1 commit into from

Conversation

milesfrain
Copy link

Seems like this behavior would be an added convenience.
Could also allow indexing into AbstractArray by Tuple. Indexing into Tuple by AbstractArray is already supported.
Wanted to get some feedback on whether there are any downsides to this feature before working on whatever other changes need to be made for this to be accepted.

julia> t = tuple((10 .* (1:5))...)
(10, 20, 30, 40, 50)

julia> r = (2,4)
(2, 4)

julia> t[r]
(20, 40)

To try locally without incorporating this change:

Base.getindex(t::Tuple, r::Tuple) = ([t[ri] for ri in r]...,)

@timholy
Copy link
Sponsor Member

timholy commented Sep 18, 2019

I think the problem is that many people would expect this to be extremely fast, and it's not.

julia> t = tuple((10 .* (1:5))...)
(10, 20, 30, 40, 50)

julia> r = (2,4)
(2, 4)

julia> using BenchmarkTools

julia> slowgetindex(t::Tuple, r::Tuple) = ([t[ri] for ri in r]...,)
slowgetindex (generic function with 1 method)

julia> fastgetindex(t::Tuple, r::Tuple) = map(i->t[i], r)
fastgetindex (generic function with 1 method)

julia> @btime slowgetindex($t, $r)
  206.451 ns (2 allocations: 128 bytes)
(20, 40)

julia> @btime fastgetindex($t, $r)
  0.016 ns (0 allocations: 0 bytes)
(20, 40)

The latter is so short you can't take it seriously, it's just a sign that the compiler elided the entire computation.

A concern is that we sometimes interpret a tuple as a multidimensional shape, and that's close to interpreting them as a multidimensional index. We have CartesianIndex to disambiguate that meaning, but various people have floated the idea of removing the need for CartesianIndex. At that point now have a problem, because indexing with a tuple-of-ints would have two different (and incompatible) meanings.

@milesfrain
Copy link
Author

Base.getindex(t::Tuple, r::Tuple) = map(i->t[i], r)

Any downsides to this faster version?

Is the multidimensional indexing with tuples proposal planning to enable the following:

julia> t = tuple((tuple(3n-2:3n...) for n in 1:3)...)
((1, 2, 3), (4, 5, 6), (7, 8, 9))

julia> r = (2,1);

julia> t[r]
4

If tuples were to replace CartesianIndex, could indexing with a tuple-of-ints be disambiguated with the following rules? :

  • Allow multidimensional indexing only if tuple dimension matches struct dimension. For example NTuple with N=2 could index into a 2D array, but not a 3D array.
  • Allow any tuple to index into one-dimensional types (1D array, other tuples, etc).

To clarify, is foreseen issue with multidimensional indexing the following?

  • When using tuple(2,3) to index into 2D array, whether to return:
    • 2nd and 3rd columns: [:, 2:3]
    • Element at 2nd row, 3rd column: [2,3]
  • When using tuple(2,3) to index into 3D array, whether to return:
    • Two 2D slices: [:, :, 2:3]
    • Single 1D slice: `[:, 2, 3]

@dkarrasch
Copy link
Member

dkarrasch commented Sep 19, 2019

The benchmarking can be made more useful by adding a little setup.

julia> @btime fastgetindex($t, s) setup=(s = tuple(rand(1:5, 2)...))
  20.056 ns (1 allocation: 32 bytes)

So it does seem to be consistently faster, and as fast as TupleTools.jl's recursive getindices function.

julia> @btime TupleTools.getindices($t, s) setup=(s = tuple(rand(1:5, 2)...))
  19.470 ns (1 allocation: 32 bytes)

I don't quite understand how the concern is relevant here. This PR allows indexing tuples via tuples. So where do we interpret the "value tuple" as an array, in which case it would be indeed ambiguous whether (2, 1) means the element of a matrix with index (2, 1) or the second and first element as obtained from linear indexing?

EDIT: If you make t have length 50 and r have length 20, then the recursive version from TupleTools is much faster! This is what it does.

https://github.com/Jutho/TupleTools.jl/blob/f6d7d4a9febb0c6cc64060e4622d18e286bb919c/src/TupleTools.jl#L265-L267

@timholy
Copy link
Sponsor Member

timholy commented Sep 19, 2019

To clarify, is foreseen issue with multidimensional indexing the following?

Yep, you got it. There's also a second issue, that "vector indexing" should perhaps be replaced by broadcasting (#30845) and so in a sense this is taking us in a direction that we probably don't want to be moving (in the long run). While I hate to hold up a good PR for hypotheticals, the flip side is that the implementation of this functionality is so simple that it would be a shame to have it be the source of trouble for more fundamental changes.

To clarify, I'm not vehemently opposed to this---it does have a certain consistency. But I am not enthusiastic either.

@milesfrain
Copy link
Author

Good points. I'm happy to use TupleTools.getindices instead.

@milesfrain milesfrain closed this Sep 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants