 # Should there be a macro or command for variable-depth nested loops?

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
# Loop body
end
elseif n==2
for a=1:len, b=1:len
# 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.

Have you read https://julialang.org/blog/2016/02/iteration? Seems like a more flexible tool.

3 Likes

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)
``````
6 Likes

Thanks to both! I will play around with those.

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`.

3 Likes

@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, …

Is there a workaround I’m overlooking?

(Sorry if this topic was better suited for Usage)

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:

https://docs.julialang.org/en/v1/devdocs/cartesian/#Supplying-the-number-of-expressions-1

But you can also use a `::Val{N}` argument similarly, passing `Val(3)` to the generated function that creates the loops.

3 Likes

And use a function barrier!

Using recursion is the traditional way to do this though and probably more efficient for many use cases.

2 Likes