Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / algorithm

Iterating Over Dynamic Number of Nested Loops

4.71/5 (10 votes)
26 Jun 2015CPOL1 min read 19.3K  
This example shows how to iterate over an unknown number of nested loops.

Introduction

Sometimes, you need to iterate over an unknown number of for or foreach loops. For example, let's say we have the following collection of collections of strings:

C#
var data = new List<List<string>>{
    new List<string>{"red", "green", "blue"},
    new List<string>{"apple", "banana", "peach", "mellon"},
    new List<string>{"one", "two"}
};

And we need to generate the following sequence (all possible ordered combinations):

  • red-apple-one
  • red-apple-two
  • red-banana-one
  • red-banana-two
  • red-peach-one
  • red-peach-two
  • red-mellon-one
  • red-mellon-two
  • green-apple-one
  • green-apple-two
  • green-banana-one
  • green-banana-two
  • green-peach-one
  • green-peach-two
  • green-mellon-one
  • green-mellon-two
  • blue-apple-one
  • blue-apple-two
  • blue-banana-one
  • blue-banana-two
  • blue-peach-one
  • blue-peach-two
  • blue-mellon-one
  • blue-mellon-two

This can be achieved by the following C# code:

C#
for(var x = 0; x < data[0].Count; x++)
    for(var y = 0; y < data[1].Count; y++)
        for(var z = 0; z < data[2].Count; z++)
            Console.WriteLine("{0}-{1}-{2}", data[0][x], data[1][y], data[2][z]);

As you can see, we had to execute 3 nested loops in order to access information we need. What if the data collection had more sub-collections? We would have to code as many nested loops as many sub-collections contained in the parent data collection. Below is an example that addresses this issue and allows iterating over any number of nested sub-collections producing the same result as shown above.

Solution

The idea is to maintain 2 arrays of integers (bounds and counters) of size N, where N is the number of nested collections. The bounds array will be populated with sizes of all nested collections while the counters array will be initialized with zeros and will represent current state (iteration indexes) of all nested loops. In the while loop, we will keep incrementing the counter at loopIndex position till it hits the bound. When the bound is hit, we will increment adjacent counter (loopIndex - 1) while setting current one to zero to start it over, and so on.

C# Implementation

C#
private static void Main()
{
    var data = new List<List<string>>
    {
        new List<string> {"red", "green", "blue"},
        new List<string> {"apple", "banana", "peach", "mellon"},
        new List<string> {"one", "two"}
    };

    foreach (var item in IterateDynamicLoop(data).Select(x => string.Join("-", x)))
        Console.WriteLine(item);

    Console.ReadLine();
}

public static IEnumerable<IEnumerable<T>> IterateDynamicLoop<T>(IList<List<T>> data)
{
    var count = data.Count;

    var loopIndex = count - 1;
    var counters = new int[count];
    var bounds = data.Select(x => x.Count).ToArray();

    do
    {
        yield return Enumerable.Range(0, count).Select(x => data[x][counters[x]]);
    } while (IncrementLoopState(counters, bounds, ref loopIndex));
}

private static bool IncrementLoopState(IList<int> counters, IList<int> bounds, ref int loopIndex)
{
    if (loopIndex < 0)
        return false;

    counters[loopIndex] = counters[loopIndex] + 1;

    var result = true;

    if (counters[loopIndex] >= bounds[loopIndex])
    {
        counters[loopIndex] = 0;
        loopIndex--;
        result = IncrementLoopState(counters, bounds, ref loopIndex);
        loopIndex++;
    }

    return result;
}

Delphi Implementation

program DynamicLoopDemo;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, Variants;

type
  TDynStringArray = array of string;

function IncrementLoopState(var ACounters, ABounds: array of Integer; var ALoopIndex: Integer): Boolean;
begin
  Result := False;
  if ALoopIndex < 0 then
    Exit;

  ACounters[ALoopIndex] := ACounters[ALoopIndex] + 1;

  if ACounters[ALoopIndex] >= ABounds[ALoopIndex] then
  begin
    ACounters[ALoopIndex] := 0;
    Dec(ALoopIndex);
    Result := IncrementLoopState(ACounters, ABounds, ALoopIndex);
    Inc(ALoopIndex);
  end
  else
    Result := True;
end;

procedure IterateDynamicLoop(AList: TStrings; AData: Variant);
var
  I, lMaxCount, lLoopIndex: Integer;
  lCounters, lBounds: array of Integer;
  lItem: string;
begin
  lMaxCount := VarArrayHighBound(AData, 1) + 1;
  lLoopIndex := lMaxCount - 1;
  SetLength(lCounters, lMaxCount);
  SetLength(lBounds, lMaxCount);

  for I := 0 to lMaxCount - 1 do
  begin
    lCounters[I] := 0;
    lBounds[I] := VarArrayHighBound(AData[I], 1) + 1;
  end;

  repeat
    lItem := '';
    for I := VarArrayLowBound(AData, 1) to VarArrayHighBound(AData, 1) do
    begin
      if lItem <> '' then
        lItem := lItem + '-';
      lItem := lItem + AData[I][lCounters[I]];
    end;
    AList.Add(lItem);
  until not IncrementLoopState(lCounters, lBounds, lLoopIndex);
end;

procedure RunDemo;
var
  lData: Variant;
  lList: TStrings;
  I: Integer;
begin
  lData := VarArrayOf([
    VarArrayOf(['red', 'green', 'blue']),
    VarArrayOf(['apple', 'banana', 'peach', 'mellon']),
    VarArrayOf(['one', 'two'])]);

  lList := TStringList.Create;
  try
    IterateDynamicLoop(lList, lData);
    for I := 0 to lList.Count - 1 do
      WriteLn(lList[I]);
  finally
    lList.Free;
  end;
end;

begin
  RunDemo;
  ReadLn;
end.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)