Click here to Skip to main content
15,885,546 members
Articles / General Programming / Algorithms
Tip/Trick

Iterating Over Dynamic Number of Nested Loops

Rate me:
Please Sign up or sign in to vote.
4.71/5 (10 votes)
26 Jun 2015CPOL1 min read 18.6K   2   4
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)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 4 Pin
ramakrishnankt21-Aug-18 9:22
ramakrishnankt21-Aug-18 9:22 
QuestionA more classical solution Pin
Member 1169993130-Jun-15 5:57
Member 1169993130-Jun-15 5:57 
AnswerRe: A more classical solution Pin
Yuriy Magurdumov30-Jun-15 7:35
Yuriy Magurdumov30-Jun-15 7:35 
GeneralMy vote of 5 Pin
Mostafa A. Ali26-Jun-15 7:59
professionalMostafa A. Ali26-Jun-15 7:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.