# Any way to add outer while loops programmatically?

I have never come across this need before. I think maybe with meta-programming it is possible, but I have never done that before.

I have a function which takes an integer input `nknots`. For every increase in `nknots` by one, I need to add one more outer `while` loop to the code than was there for the original value. (I also need to add one more line of code per `nknot` with the current version, but I think this could be handled by an interior `for` loop.) So far, I have been hard-coding a pseudo-method for every possible value of the integer behind `if` statements. However, I would like to make the code generic so that it can take any integer value larger than 2. The code looks very uniform with all the hard-coded numbers easy to turn into `n`'s, but I don’t know how to add the outer `while` loops in a loop.

I need help replacing the portion of the code below inside the `if` statement with something generic. If `nknots==3`, only the innermost `while` loop should be used. If `nknots==4`, only the inner two `while` loops should be used. If `nknots==6`, an outer `while` loop should be added resembling the others. Ad infinitum.

``````using ElasticArrays
# Computes all possible knot locations on a dataset with `il` number of points, for a minimum segment length of `ilsegmin` and `nknots` number of knots.
function allknots(il::Int,ilsegmin::Int,nknots::Int)

# Initialize first knot combination
knots=ElasticArray{Int}(undef,nknots,1) # Create data storage structure for knots
j=1 # Possible knot location counter
knots=1
for n=2:nknots-1
knots[n]=knots[n-1]+ilsegmin-1
end
knots[nknots]=il

if nknots==5

# Determine all remaining knot combinations
while knots[nknots-3,j] < il-3*ilsegmin+3 # Move thrid from last knot incrementally
while knots[nknots-2,j] < il-2*ilsegmin+2 # Move second to last knot incrementally
while knots[nknots-1,j] < il-ilsegmin+1 # Move last knot incrementally
j+=1
append!(knots,knots[:,j-1])
knots[nknots-1,j]=knots[nknots-1,j]+1
end
j+=1
append!(knots,knots[:,j-1])
knots[nknots-2,j]=knots[nknots-2,j]+1
knots[nknots-1,j]=knots[nknots-2,j]+ilsegmin-1
end
j+=1
append!(knots,knots[:,j-1])
knots[nknots-3,j]=knots[nknots-3,j]+1
knots[nknots-2,j]=knots[nknots-3,j]+ilsegmin-1
knots[nknots-1,j]=knots[nknots-2,j]+ilsegmin-1
end

end

return knots
end
allknots(12,3,5)
``````
``````5×20 ElasticArray{Int64,2,1,Array{Int64,1}}:
1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1
3   3   3   3   3   3   3   3   3   3   4   4   4   4   4   4   5   5   5   6
5   5   5   5   6   6   6   7   7   8   6   6   6   7   7   8   7   7   8   8
7   8   9  10   8   9  10   9  10  10   8   9  10   9  10  10   9  10  10  10
12  12  12  12  12  12  12  12  12  12  12  12  12  12  12  12  12  12  12  12
``````

The example is a bit complicated, so I’m not sure this would work, but have you thought about a function with a single `while` loop that is called recursively? Roughly:

``````function while_knots(nknots, j, k)
if k == 1
# the j+=1 and append code goes here
else
while_knots(nknots, j, k-1)
end
end
``````
4 Likes

@Nathan_Boyer `using Base: @nexprs, @nloops, @nref` may be useful to what you are trying to achieve. Check the documentation of each macro and you may be able to rewrite N-dimensional code more easily.

1 Like

I am not sure I fully understand the example, but if you can map to iterating over a `CartesianIndices`, which you generate with `ntuple`, I would go for that pattern. Otherwise, lispy recursion (see the implementation of CartesianIndices again), or the macros suggested by @juliohm.

1 Like

I updated the `while` loops so that they are all exactly the same now except for a new integer `k`. I also added a comment to each line to explain what is being done. I think @hendri54’s suggestion could work now, but I haven’t had time yet to take it that far. I will look into the other suggestions as well, but that will take longer since they are a bit over my head at the moment.

``````if nknots==5
# Determine all remaining knot combinations
k=1 # Initialize k
while knots[nknots-k,j] < il-k*ilsegmin+k         # Move thrid from last knot incrementally
while knots[nknots-k,j] < il-k*ilsegmin+k     # Move second to last knot incrementally
while knots[nknots-k,j] < il-k*ilsegmin+k # Move last knot incrementally
k=1                                   # Current knot
j+=1                                  # Increment possible knot counter
append!(knots,knots[:,j-1])           # New knot combination same as last knot combination except...
knots[nknots-k,j]+=1                  #  increment current knot and
for kk=k-1:-1:1                       #  reset downstream knots
knots[nknots-kk,j]=knots[nknots-(kk+1),j]+ilsegmin-1
end
end
k=2
j+=1
append!(knots,knots[:,j-1])
knots[nknots-k,j]+=1
for kk=k-1:-1:1
knots[nknots-kk,j]=knots[nknots-(kk+1),j]+ilsegmin-1
end
end
k=3
j+=1
append!(knots,knots[:,j-1])
knots[nknots-k,j]+=1
for kk=k-1:-1:1
knots[nknots-kk,j]=knots[nknots-(kk+1),j]+ilsegmin-1
end
end
end
``````

Full code below working nicely now for any number of knots with @hendri54’s method:

``````using ElasticArrays
# Computes all possible knot locations on a dataset with `il` number of points, for a minimum segment length of `ilsegmin` and `nknots` number of knots.
function allknots(il::Int, ilsegmin::Int, nknots::Int)

# Initialize first knot combination
knots = ElasticArray{Int}(undef, nknots, 1) # Create data storage structure for knots
j = 1 # Possible knot location counter
knots = 1
for n = 2:nknots - 1
knots[n] = knots[n - 1] + ilsegmin - 1
end
knots[nknots] = il

# Ensure first knot combination is possible
if (nknots < 2) || (knots[nknots] - knots[nknots - 1] < ilsegmin)
println("No possible knots with the given parameters.")
return knots = ElasticArray{Int}(undef, nknots, 0)
end

# Determine all remaining knot combinations
function incrementknot(il::Int, ilsegmin::Int, nknots::Int, k::Int=nknots - 2)

while knots[nknots - k, j] < il - k * ilsegmin + k # While current knot not yet at maximum location
if k > 1
incrementknot(il, ilsegmin, nknots, k - 1) # Call function recursively to obtain inner while loops
elseif k < 1
throw(ArgumentError("k must be a counting number"))
end
j += 1                                         # Increment possible knot counter
append!(knots, knots[:, j - 1])                # Create new knot combination same as last knot combination except...
knots[nknots - k, j] += 1                      #  increment current knot and
for kk = k - 1:-1:1                            #  reset all downstream knots
knots[nknots - kk, j] = knots[nknots - (kk + 1), j] + ilsegmin - 1
end
end
return knots
end

incrementknot(il, ilsegmin, nknots)
return knots
end

# Test
for n = 1:7
display(allknots(12, 3, n))
end
``````