I can enumerate a multi-dimensional array with
for (index, value) in pairs([i+j for i in 1:10, j in 1:10])
...
end
Can I do similar things for generators? Say g = (i+j for i in 1:10, j in 1:10)
, then the type information (too lengthy to paste) knows that g
is a product iterator. Consequently, collect(g)
yields a multi-dimensional array. But I cant use pairs
or eachindex
on g
:
for (index, value) in pairs(i+j for i in 1:10, j in 1:10)
...
end
fails.
Is this possible to achieve?
You cannot use pairs
to create a collection of key => value
pairs from a generator in Julia because generators do not define a keys
method:
pairs((i+j for i in 1:10, j in 1:10))
# ERROR: MethodError: no method matching keys(::Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}})
This makes some amount of sense: a generator’s purpose is to generate values on demand when iterated, not to allow random access to individual elements of a collection. If you want random access to elements generated by a generator expression, collect
them into a data type that allows such access:
g = (i+j for i in 1:10, j in 1:10);
A = collect(g);
@assert A[2,4] == 2+4
This, of course, allocates memory to create A
, so you may not be able to do this if your generator produces a lot of (or infinite, which is allowed) values. If you want to simply iterate through a generator expression with an index, like your example does with pairs
acting on an Array
, you can use enumerate
to produce a running count of the number of values generated by the generator:
g = (i+j for i in 1:3, j in 1:3);
for (index, value) in enumerate(g)
println("$index: $value")
end
# 1: 2
# 2: 3
# 3: 4
# 4: 3
# ...
This is just a running count, not a true index into a collection like what pairs
returns because, again, a generator does not by itself have a way to index into individual elements–it just produces values on demand when you iterate over it.
But if you truly want to produce something like a CartesianIndex
when iterating over a generator expression without allocating a new Array
object, then you have to supply the dimension information yourself, using something like CartesianIndices
to do the conversion between the linear index and a CartesianIndex
:
g = (i+j for i in 1:3, j in 1:3);
const CI = CartesianIndices((3, 3))
for (index, value) in enumerate(g)
println("$(CI[index]): $value")
end
# CartesianIndex(1, 1): 2
# CartesianIndex(2, 1): 3
# CartesianIndex(3, 1): 4
# CartesianIndex(1, 2): 3
# ...
Or, to make it more look and function like pairs
:
gen_pairs(x, ci) = ((ci[i], v) for (i, v) in enumerate(x));
g = (i+j for i in 1:3, j in 1:3);
for (index, value) in gen_pairs(g, CartesianIndices((3, 3)))
println("$index: $value")
end
Note that CartesianIndices
is a very lightweight struct, so it does not cost much to use one like this.