# Why is getindex not implemented for Iterators.ProductIterator?

**URL:** <https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065>\
**Category:** General Usage\
**Tags:** indexing, iterators\
**Created:** [March 13, 2021, 9:29am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065 "2021-03-13T09:29:58Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [March 13, 2021, 9:29am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/1 "2021-03-13T09:29:59Z")

</div>

I find this behavior strange:

```julia
s = Iterators.product(1:9, 10:19)
c = collect(s)
c[5] # works: (5, 10)

s[5] # doesn't work
ERROR: MethodError: no method matching getindex(::Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}, ::Int64)

```

Also, `first(s)` is defined, but `last(s)` is not. Isn’t that unexpected?

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [March 13, 2021, 11:19am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/2 "2021-03-13T11:19:37Z")

</div>

No because iterators are all written in order to work with iterables of unknown length (potentially infinite) where you don’t know what the last element is. They are also meant to iterate, not to index because some iterators or generators can only be accessed one element after another

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [March 13, 2021, 11:28am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/3 "2021-03-13T11:28:43Z")

</div>

So for Cartesian products created with `Iterators.product` I always have to `collect` them before accessing a particular element?

---

<div class="post-metadata">

**Author:** ![pfitzseb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pfitzseb/32/45566_2.png) [@pfitzseb](https://discourse.julialang.org/u/pfitzseb)\
**Post date:** [March 13, 2021, 11:47am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/4 "2021-03-13T11:47:52Z")

</div>

You can also iterate until you get to the element you’re interested in, e.g with

```julia
julia> first(Iterators.drop(s, 4))
(5, 10)

```

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [March 13, 2021, 12:03pm UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/5 "2021-03-13T12:03:44Z")

</div>

And maybe there are other lazy product objects, where you can index as long as the underlying objects are also indexable. That would actually not be hard to implement

---

<div class="post-metadata">

**Author:** ![sostock](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sostock/32/5546_2.png) [@sostock](https://discourse.julialang.org/u/sostock)\
**Post date:** [March 13, 2021, 12:27pm UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/6 "2021-03-13T12:27:00Z")

</div>

Right now, the iterators in the `Iterators` module support only iteration (I think). In this case, indexing could be implemented if all of the arguments have a `length` and are indexable. Similarly, `last` could be implemented if all arguments implement `last`. That seems reasonable to me.

---

<div class="post-metadata">

**Author:** ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)\
**Post date:** [March 13, 2021, 1:17pm UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/7 "2021-03-13T13:17:37Z")

</div>

You can find `n`-th element of the Iterator, by doing `n` iterations. But the problem is that at this point Iterator already lost is first state, so you can’t do it twice (you have to reconstruct it to be able to iterate to the same index again).

Also, you should take into account that iteration usually involve some additional calculations, so if you need to access the same index more than once, or you need random access, it is more efficient to materialize collection. If you need to access elements only once, than iteration over Iterator is enough and `getindex` is not needed.

But it may be of interest not an Iterator, but custom structure, that can store vectors and provide product-like access. Then you would not needed to materialize this big matrix. Probably such a package already exists.

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [March 13, 2021, 11:38pm UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/8 "2021-03-13T23:38:56Z")

</div>

My use case is drawing randomly with replacement from a Cartesian product of 6 dimensions, around 12k elements. I thought I could just pass the Iterator to `rand`. It turns out that `rand` doesn’t have a method for `ProductIterator`, so I started playing around and found that behavior. I guess I can just generate and store the Cartesian product once and then draw from that.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [March 14, 2021, 12:20am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/9 "2021-03-14T00:20:25Z")

</div>

you can make your own pretty easily because in this case you know the “things” that make up the iterator are individually sample-able:

```julia
julia> const BIP = Base.Iterators.ProductIterator
Base.Iterators.ProductIterator

julia> myrand(itr::BIP{NTuple{N, T}}) where {N, T<:AbstractArray} = rand.(itr.iterators)

julia> s = Iterators.product(1:9, 10:19)
Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}((1:9, 10:19))

julia> myrand(s)
2-element Vector{Int64}:
  4
 15

```

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [March 14, 2021, 12:34am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/10 "2021-03-14T00:34:46Z")

</div>

Thank you! I also added a new method for drawing `n` elements:

```julia
function myrand(itr::BIP{NTuple{N, T}}, n) where {N, T<:AbstractArray}
    arr = Array{Any}(undef, n)
    for i in 1:n
        arr[i] = myrand(itr)
    end
    return arr
end

```

I’m worried about the instability of `Any` array. I tried creating the array with elements `NTuple{N, T}`, but I get an error if I go that route `ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type UnitRange{Int64}`.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [March 14, 2021, 12:40am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/11 "2021-03-14T00:40:57Z")

</div>

```julia
julia> function myrand(itr::BIP{NTuple{N, T}}, n::Int) where {N, T<:AbstractArray}
           elts = eltype.(itr.iterators)
           res = Vector{Tuple{elts...}}(undef, n)
           for i in eachindex(res)
               res[i] = Tuple(myrand(itr))
           end
           res
       end

julia> myrand(s,2)
2-element Vector{Tuple{Int64, Int64}}:
 (2, 19)
 (7, 14)

```

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [March 14, 2021, 12:46am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/12 "2021-03-14T00:46:03Z")

</div>

Nice. Is it necessary to wrap `myrand(itr)` in `Tuple` when assigning to `res[i]`?

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [March 14, 2021, 12:47am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/13 "2021-03-14T00:47:43Z")

</div>

it’s because I defined the result to be a `Vector` of `Tuple` (this resembles a vector of coordinates). But our previous `myrand(s)` returns an vector.

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [March 14, 2021, 12:51am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/14 "2021-03-14T00:51:18Z")

</div>

I think you are calling another method, before your edits. The way it’s written now `myrand(s)` returns a `Tuple`.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [March 14, 2021, 12:52am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/15 "2021-03-14T00:52:35Z")

</div>

> [@jling](#):
>
> `julia> myrand(itr::BIP{NTuple{N, T}}) where {N, T<:AbstractArray} = rand.(itr.iterators)`

this returns a vector right? what do you mean before?

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [March 14, 2021, 12:56am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/16 "2021-03-14T00:56:07Z")

</div>

What I meant was that I noticed you made some quick edits, so I thought you had those methods already loaded into Julia and perhaps one of those returns a vector. But now it returns a Tuple:

```julia
julia> myrand(s)
(5, 18)

```

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [March 14, 2021, 12:58am UTC](https://discourse.julialang.org/t/why-is-getindex-not-implemented-for-iterators-productiterator/57065/17 "2021-03-14T00:58:50Z")

</div>

oops, you’re right. So yes, we don’t need `Tuple()` anymore in the n-samples case.

For completeness, let me post everything:

```julia
julia> const BIP = Base.Iterators.ProductIterator
Base.Iterators.ProductIterator

julia> myrand(itr::BIP{NTuple{N, T}}) where {N, T<:AbstractArray} = rand.(itr.iterators)
myrand (generic function with 1 method)

julia> s = Iterators.product(1:9, 10:19)
Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}((1:9, 10:19))

julia> myrand(s)
(7, 14)

julia> function myrand(itr::BIP{NTuple{N, T}}, n::Int) where {N, T<:AbstractArray}
           elts = eltype.(itr.iterators)
           res = Vector{Tuple{elts...}}(undef, n)
           for i in eachindex(res)
               res[i] = myrand(itr)
           end
           res
       end
myrand (generic function with 2 methods)

julia> myrand(s, 2)
2-element Vector{Tuple{Int64, Int64}}:
 (6, 11)
 (6, 16)

```

Note: this has the caveat that the `AbstractArray`s make up the Cartesian product have to have the same type, which is probably fine if they represent different dimensions of something.
