Recently I needed N (a variable number of) nested for-loops. A vector len contained the max counter of each loop.
The dumb way would have been this:
if n==1
for a=1:len[1]
# Loop body
end
elseif n==2
for a=1:len[1], b=1:len[2]
# Loop body
end
# ... and so on
Instead, here’s what I came up with, which at least is less dumb:
a = ones(Int64,n)
a[n] = 0
breakwhile = false
while true
for k=0:n-1
if a[n-k] < len[n-k]
a[n-k] += 1
break
elseif n-k == 1
breakwhile = true
break
else a[n-k]=1 end
end
breakwhile && break
# Loop body goes here.
end
It was fun coming up with that and it worked, but I feel like this situation arises often enough that, in a perfect world, there would be a macro or command for it.
Maybe something like “@nest for k in v”
where v is a Vector{Int64} of the loop durations, and k is a vector of counters (created locally if not already existing).
Is this just noob babbling, or a good idea? Does it already exist in some form? If not, perhaps I’ll take a stab at making a macro.
It seems like Iterators.product already does what you’re looking for:
julia> for k in Iterators.product(1:2, 1:3, 1:4)
@show k
end
k = (1, 1, 1)
k = (2, 1, 1)
k = (1, 2, 1)
k = (2, 2, 1)
k = (1, 3, 1)
k = (2, 3, 1)
k = (1, 1, 2)
...
For example, you could do something like this:
julia> function loops(v)
for k in Iterators.product(Base.OneTo.(v)...)
@show k
end
end
loops (generic function with 1 method)
julia> loops((2, 3))
k = (1, 1)
k = (2, 1)
k = (1, 2)
k = (2, 2)
k = (1, 3)
k = (2, 3)
While I strongly recommend using a smart multidimensional iterator like Iterators.product or CartesianIndices, we do have an internal macro that generates an arbitrary (albeit constant-at-compile-time) number of nested loops: Base.Cartesian.@nloops.
@nloops doesn’t appear to be a solution. For instance I have a function whose parameters determine the # of nested loops that will be needed, so I don’t know in advance what the # will be.
The docs say:
The first argument must be an integer (not a variable) specifying the number of loops.
Indeed, when I try
n = 3
@nloops n i A begin ...
an error is thrown: no method matching _nloops(::symbol, …
That’s the constant-at-compile-time caveat that I list. The way around it is to use a parametric type and a generated function — the typical answer is to capture the N parameter of an array and splice it into the generated body like this: