Enumeratezip

I often would like to use something like enumerate(a,b,c) and get the functionality:

a,b,c = rand(10),rand(10),rand(10)
for (i,ai,bi,ci) in enumerate(a,b,c)
   @show i ai bi ci
end

something that could emulate this is enumeratezip(a...) = zip(eachindex(first(a)), a...) so that

a,b,c = rand(10),rand(10),rand(10)
for (i,ai,bi,ci) in enumeratezip(a,b,c)
   @show i ai bi ci
end

I don’t know if this can be problematic in some cases and maybe there’s a more elegant version to do the same thing without defining enumeratezip or writing the zip by hand. I would be interested to know! Maybe this feature is something that someone else came across wanting to have?

What about

julia> for (i,(ai,bi,ci)) in enumerate(zip(a,b,c))
          @show i ai bi ci
          end
i = 1
ai = 0.9570998904436379
bi = 0.5775517184568557
ci = 0.3987638983159012
i = 2
ai = 0.9761040253755489
bi = 0.7214272853124479
ci = 0.7986223352162409
i = 3
ai = 0.9435897685169268
bi = 0.10027768357097111
ci = 0.41432976381125064
i = 4
...

?

7 Likes

Looks like a decent idea to me, but you could run into a couple issues with just using eachindex(a) if a, b, and c aren’t the same…:

  1. …shape: you could want to index into b and c with i, but eachindex(a) could return indices that only work for a. Simple to fix, just compute indices from eachindex(b) and eachindex(c) too.
  2. …size: if b or c is smaller than a, the loop will terminate before you exhaust eachindex(a). This isn’t necessarily a problem, but someone not looking too closely could think a was exhausted at the last iteration because that’s what you chose to eachindex. A comment or adding those indices from b and c would help make it clearer, I think.

It’s not bad! But still I find it a little unnecessarily clumsy.

Really? I really like the elegance of composition, instead of having to make a custom function.

2 Likes

oh no of course that’s much better! I guess I just would like to have enumerate to support splatting :slight_smile:

You can already use zip(rand(10),rand(11)), so something similar must be possible enumerating the whole thing?

Note that that enumerate does always start at 1, while eachindex might not. For example:

julia> using OffsetArrays: OffsetVector

julia> v = OffsetVector(['a', 'b', 'c'], 5)
3-element OffsetArray(::Vector{Char}, 6:8) with eltype Char with indices 6:8:
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)

julia> enumerate(v) |> collect
3-element Vector{Tuple{Int64, Char}}:
 (1, 'a')
 (2, 'b')
 (3, 'c')

julia> zip(eachindex(v), v) |> collect
3-element OffsetArray(::Vector{Tuple{Int64, Char}}, 6:8) with eltype Tuple{Int64, Char} with indices 6:8:
 (6, 'a')
 (7, 'b')
 (8, 'c')

Not really sure if there is a proper enumerate equivalent for multiple iterables. enumerate( zip (a, b, c) ) wouldn’t work because whatever index you get is intended for the zip object, not a,b,c.
zip(eachindex(a), eachindex(b), a, b) would be closer, but there must be a more elegant way to do that given d = (a, b, c), similar to your enumeratezip(a...) call.

My intention was actually not meant for the case of an OffsetArray. I guess the solution by @DNF is what I will go with. And after looking at it a bit longer I might get used to the double brackets, separating index and elements!