2

Here's a simple for set of nested for loops where the maximum variable values are 1, 2, and 3 respectively:

for ($i = 0; $i -le 1; $i++)
{
    for ($j = 0; $j -le 2; $j++)
    {
        for ($k = 0; $k -le 3; $k++)
        {
            "$i $j $k"
        }
    }
}

I'd like an abstraction for indicating an arbitrary number of nested for loops. So for example, the above would be invoked as something like:

NestedForLoops (1, 2, 3) { Param($i, $j, $k) "$i $j $k" }

Here's one approach based on this answer:

function NestedForLoopsAux([int[]]$max_indices, [int[]]$indices, [int]$index, $func)
{
    if ($max_indices.Count -eq 0) { &($func) $indices }
    else
    {
        $rest = $max_indices | Select-Object -Skip 1

        for ($indices[$index] = 0; $indices[$index] -le $max_indices[0]; $indices[$index]++)
        { NestedForLoopsAux $rest $indices ($index + 1) $func }
    }
}

function NestedForLoops([int[]]$max_indices, $func)
{
    NestedForLoopsAux $max_indices (@(0) * $max_indices.Count) 0 $func
}

Example call:

PS C:\> NestedForLoops (1, 2, 3) { Param([int[]]$indices); $i, $j, $k = $indices; "$i $j $k" }
0 0 0
0 0 1
0 0 2
0 0 3
0 1 0
0 1 1
0 1 2
0 1 3
0 2 0
0 2 1
0 2 2
0 2 3
1 0 0
1 0 1
1 0 2
1 0 3
1 1 0
1 1 1
1 1 2
1 1 3
1 2 0
1 2 1
1 2 2
1 2 3

Is there a better way?

Community
  • 1
  • 1
dharmatech
  • 8,979
  • 8
  • 42
  • 88

1 Answers1

1

I don't know if this is easier but you might see it as a way to open up more options for you. The core of the logic is to build the string of code that will be executed with Invoke-Expression. Mostly just wanted to see if I could do it.

Function New-ForLoopBlock{
    Param(
        [char]$variableLetter,    # {0} Index varialble
        [int]$baseIndex,          # {1} Base Index Value
        [int]$indexMaximum        # {2} Max Index Value
    )
    "for (`${0} = {1}; `${0} -le {2}; `${0}++)" -f $variableLetter,$baseIndex,$indexMaximum
}


Function LoopDeLoop{
    Param(
        [int[]]$maximums,
        [int]$baseIndex = 0 
    )

    # Build a small hashtable with variable and array values.
    $values = @{}
    For($letterIndex = 0; $letterIndex -lt $maximums.Count; $letterIndex++){
        New-Variable -Force -Name [char]($letterIndex + 65)
        $values.([char]($letterIndex + 65)) = $maximums[$letterIndex]
    }

    $nestedLoops = "{}"
    # Build the for loop
    $nestedLoops = $values.GetEnumerator() | Sort-Object Name | ForEach-Object{
        "$(New-ForLoopBlock $_.Name $baseIndex $_.Value){"
    }

    # The output string this exists inside the loop
    $outputString = [string]($values.GetEnumerator() | Sort-Object Name | ForEach-Object{"`$$($_.Name)"})

    # Add the output string and closing braces
    $nestedLoops = "$nestedLoops`r`n`"$outputString`"`r`n$("}" * $maximums.Count)"

    Invoke-Expression $nestedLoops
}

LoopDeLoop takes 2 arguments. Just like yours an array of integers and an optional base value. Create some variables (in the form of $a, $b,....) for each array element from $maximums that will represent the index values for each for loop.

The string $nestedLoops string is then created with the with the output from New-ForLoopBlock. It didn't need to be a function but it just returns a string that has a for loop statement. In earlier iterations more of the logic was there.

Then we need to build the small output string. In your example this is "$i $j $k". Mine is built based on how many variables that was created.

End of the string we close the for loops with the end braces. An example of what $nestedloops looks like:

for ($A = 2; $A -le 3; $A++){ for ($B = 2; $B -le 3; $B++){ for ($C = 2; $C -le 4; $C++){
"$A $B $C"
}}}

Looks bad as far as formatting is concerned but the code is 100% functional. Now lets just look at some sample output from the function:

LoopDeLoop (3,3,4) 2
2 2 2
2 2 3
2 2 4
2 3 2
2 3 3
2 3 4
3 2 2
3 2 3
3 2 4
3 3 2
3 3 3
3 3 4
Matt
  • 45,022
  • 8
  • 78
  • 119