Multiple loops

Hello,

I tried to write some code on multiple but unknown number of loops. But didn’t find ways to do it. Can someone give me a hint? Thanks in advanced.
Suppose I have an array

a = [2,3,4,5]

I want to write a loop that is equivalent to the following

A = Array{Array{Int64}}(undef,0)
for i_1 in 1:a[1]
       for i_2 in 1:a[2]
             for i_3 in 1:a[3]
                 for i_4 in 1:a[4]
                   append!(A,[[i_1,i_2,i_3,i_4]])
                 end
             end
       end
end

output will be

A 
144-element Array{Array{Int64,N} where N,1}:
 [1, 1, 1, 1]
 [1, 1, 1, 2]
 [1, 1, 1, 3]
 [1, 1, 1, 4]
 [1, 1, 1, 5]
 [1, 1, 2, 1]
 [1, 1, 2, 2]
 [1, 1, 2, 3]
 [1, 1, 2, 4]

.
.
.

But in a more general way, so that it will also can deal with for other 1D arrays like

a = [2,3,4,5,6]
a = [2,3,4,5,6,7]

Baicai

I think this may be useful:
https://julialang.org/blog/2016/02/iteration

2 Likes

You might want to use macros to generate multiple loops if the size of a is known at compile time.

with some tricks and the comments above, i did an awfully looking function that does what you requested, maybe it can be of some help

function testfunc(x::Array{II,1}) where II <: Integer
    A = Array{Array{Int64,1},1}(undef,reduce(*,x)) #prealocate array
    A0 = Array{Int8,length(x)}(undef,tuple(x...)...) 
#this matrix have in his indices the values that we want

    k = 1 
    for idx in CartesianIndices(A0)
    A[k] = vcat(Tuple(idx)...) 
    #tried to use collect, the error message recommended this
    k +=1
end
return A
end`

Use recursion. Metaprogramming and Cartesians can work, but it’s just overkill and much more complex than a simple recursive implementation.

5 Likes

I believe this solution iterates in the wrong order, it should be the last index incrementing first:

julia> testfunc([2,3,4])
24-element Array{Array{Int64,1},1}:
 [1, 1, 1]
 [2, 1, 1]
 [1, 2, 1]
 [2, 2, 1]
 [1, 3, 1]
 [2, 3, 1]
 [1, 1, 2]
...

Here’s one attempt that uses Iterators.product:

function myprod(v)
    x = [1:n for n in reverse(v)]
    return vec([[reverse(p)...] for p in Iterators.product(x...)])
end

It gets complicated because you want to increment the last elements first. Otherwise you could drop the reverse calls.

You could optimize it in various ways: using Iterators.reverse(v) instead of reverse(v), if you could use tuples instead of vectors you wouldn’t need to splat [reverse(p)...], and so on.

EDIT: oups, @DNF proposed a similar solution based on Iterators.product 2min earlier already!

Here is a simple solution for the specific case of your question. It doesn’t output exactly what you asked for, but can easily be adjusted:

julia> foreach(Iterators.product((1:x for x in a)...)) do i
           push!(A, [i...])
       end

julia> A
120-element Array{Array{Int64,N} where N,1}:
 [1, 1, 1, 1]
 [2, 1, 1, 1]
 [1, 2, 1, 1]
 [2, 2, 1, 1]
 [1, 3, 1, 1]
 [2, 3, 1, 1]
 [1, 1, 2, 1]
 [2, 1, 2, 1]
 [1, 2, 2, 1]
 [2, 2, 2, 1]
 [1, 3, 2, 1]
 [2, 3, 2, 1]
 [1, 1, 3, 1]
 [2, 1, 3, 1]
 [1, 2, 3, 1]
 [2, 2, 3, 1]
 [1, 3, 3, 1]
 [2, 3, 3, 1]
 [1, 1, 4, 1]
 [2, 1, 4, 1]
 [1, 2, 4, 1]
 ⋮

BTW, it is a bit important to realize that Array{Int} is an abstract type, and using this can make a negative impact on performance, and even introduce bugs.

If you want a one-dimensional array, you have to write Array{Int, 1}, not Array{Int}. This is also why I recommend using Vector{Int} and Matrix{Int}, instead of Array. It is too easy to forget the dimension parameter, and it’s also harder to read.

1 Like

another implementation, using a looped_counter function that gives the next element, using recursion, you can add the type annotations and size checks to improve performance later, but this proof of concept works and gives you the order that you want

function looped_counter(x0,xmin, xmax,k=length(x0))
    x = copy(x0)
    k == 0 && return copy(xmin)
    if x[k]==xmax[k]
        x[k] = xmin[k]
        looped_counter(x,xmin,xmax,k-1)
    elseif x[k]<xmax[k]
        x[k]+=1
        return x
    else
        throw("wtf")
    end
end

function myfunc(xmin,xmax)
    A = Array{Array{Int64,1},1}(undef,0)
    a = copy(xmin)
    a = looped_counter(a,xmin,xmax)
    while a!= xmin
        push!(A,a)
        a = looped_counter(a,xmin,xmax)
    end
    return A
end

a simple example:

xmin = [1,1,1]
xmax = [3,4,3]
myfunc[xmin,xmax]

35-element Array{Array{Int64,1},1}:
 [1, 1, 2]
 [1, 1, 3]
 [1, 2, 1]
 [1, 2, 2]
 [1, 2, 3]
 [1, 3, 1]
 [1, 3, 2]
 [1, 3, 3]
 [1, 4, 1]
 [1, 4, 2]
 [1, 4, 3]
 [2, 1, 1]
 ⋮
 [3, 1, 2]
 [3, 1, 3]
 [3, 2, 1]
 [3, 2, 2]
 [3, 2, 3]
 [3, 3, 1]
 [3, 3, 2]
 [3, 3, 3]
 [3, 4, 1]
 [3, 4, 2]
 [3, 4, 3]

an advantage of the implementation is that you can use a different xmin to give different results (and it can be used as a clock, IDK)