How do you iterate over a vector of ranges of different lengths?

So I have an arbitrary vector of ranges

julia> kthNvals(k) = (1:(2^k))  .- 1;

julia> kthNvals.(1:4)
4-element Vector{UnitRange{Int64}}:
 0:1
 0:3
 0:7
 0:15

I need to generate all the vectors where each index is a value in the relevant range.
The hardcoded version looks like this:

julia> l = [[a,b,c,d] for a in 0:1,b in 0:3,c in 0:7,d in 0:15][:]
1024-element Vector{Vector{Int64}}:
 [0, 0, 0, 0]
 [1, 0, 0, 0]
 [0, 1, 0, 0]
 [1, 1, 0, 0]
 [0, 2, 0, 0]
 [1, 2, 0, 0]
 [0, 3, 0, 0]
 ⋮
 [0, 1, 7, 15]
 [1, 1, 7, 15]
 [0, 2, 7, 15]
 [1, 2, 7, 15]
 [0, 3, 7, 15]
 [1, 3, 7, 15]

How do you make this for any kthNvals.(1:k)?

Would you do it with a recursive macro or some other metaprogramming feature?

If I understand correctly, this should answer the request

collect(Base.product(kthNvals.(1:4)...))[:]
1 Like

The specific ranges given in the question, are easier to iterate over since they are powers of two, and essentially we can see each row as bit fields in a simple Int. This allows to randomly access this list and each entry in the list by direct caclulation. To make this observation concrete:

julia> [(i >> ((k*(k-1))>>1))&((1<<k)-1) for i in 0:20, k in 1:4]
21×4 Matrix{Int64}:
 0  0  0  0
 1  0  0  0
 0  1  0  0
 1  1  0  0
 0  2  0  0
 1  2  0  0
 0  3  0  0

or in Tuple form:

julia> [ntuple(k -> (i >> ((k*(k-1))>>1))&((1<<k)-1), Val(4)) for i in 0:20]
21-element Vector{NTuple{4, Int64}}:
 (0, 0, 0, 0)
 (1, 0, 0, 0)
 (0, 1, 0, 0)
 (1, 1, 0, 0)
 (0, 2, 0, 0)
 (1, 2, 0, 0)
 (0, 3, 0, 0)

or in iterator form:

julia> myiter = Iterators.map(i->ntuple(k -> (i >> ((k*(k-1))>>1))&((1<<k)-1), Val(4)), 0:(1<<(4*3÷2)))
Base.Generator{UnitRange{Int64}, var"#23#25"}(var"#23#25"(), 0:64)

julia> Iterators.take(myiter,7) |> collect
7-element Vector{NTuple{4, Int64}}:
 (0, 0, 0, 0)
 (1, 0, 0, 0)
 (0, 1, 0, 0)
 (1, 1, 0, 0)
 (0, 2, 0, 0)
 (1, 2, 0, 0)
 (0, 3, 0, 0)