嵌套for循环算法 - 动态

时间:2010-08-07 01:58:11

标签: .net algorithm dynamic loops iteration

我有一个类似这样的算法:

for m = 1 to 2
  initialize(work_item(m))
  for l = 1 to 2
    initialize(work_item(l))
    for k = 1 to 2
      initialize(work_item(k))
      for j = 1 to 2
        initialize(work_item(j))
        for i = 1 to 2
          initialize(work_item(i))
          doSomething(work_item(i))
        next
        doSomething(work_item(j))
      next
      doSomething(work_item(k))
    next
    doSomething(work_item(l))
  next
  doSomething(work_item(m))
next

我如何迭代地编写它,使其成为动态的,这样我就不会将自己局限于固定数量的for循环(i,j,k,l,m)(即我可以做(i)或( i,j)或(i,j,k)或(i,j,k,l)等...)?

(我正在用动态的,迭代的解决方案严格寻求答案。如果你不理解这一点,请继续阅读,从上一句开始。)

9 个答案:

答案 0 :(得分:9)

完全按照使用递归的方式编写算法,但使用显式堆栈对象而不是递归。即:

var stack = new Stack();
stack.Push(InitialThingy);
while(stack.Count != 0)
{
    var currentItem = stack.Pop();
    //Do things to current item and add things to stack as we go.
}

答案 1 :(得分:6)

private sub doStuff(depth as integer)
    for i as integer = 1 to 2
        initialize(work_item(i))
        if depth > 1 then doStuff(depth - 1)
        doSomething(work_item(i))
    next
end sub

比你能提出的任何迭代解决方案更优雅,更短,更难处理。向我倾倒所有你想要的东西,并不是那么真实。

答案 2 :(得分:3)

您可能需要recursion

答案 3 :(得分:2)

调用变量i [0] ... i [n]将它们全部初始化为1.要在每一步增加

int j, k;
for(j = n - 1; j >= 0; j--){
  initialize(i[j]);
}

while (true) {
  j = 0;
  while (i[j] == 2) {
    doSomething(i[j]);
    j++;
  }
  if (j < n) {
    doSomething(i[j]);
  } else {
    break;
  }
  for (j = 0; j < n; j++) {
     if (i[j] == 1) {
       i[j] = 2;
       break;
     }
     else {
       i[j] = 1;
     }
  }
  if (j == n) break;
  for (k = j; k >= 0; k--) {
    initialize(i[k]);
  }
}

基本上,您正在实现一个二进制计数器,清除所有小于第一个清除位的设置位,然后设置第一个清除位。这将以与给定循环相同的顺序运行do Things。

你可以用不同的范围做类似的事情,每个i [j]的范围甚至不必相同。

编辑添加初始化。

注意递归在这里可能有些过分,并且比平面实现更不灵活(考虑从内循环中止)。这是在实践中经常出现的问题,并不是那么复杂。我们想要做的是让每个循环看起来像

for i = 1 to 2
   do beginning stuff with i
   do inner loop
   do ending stuff with i
next

如果我们只考虑索引变量,我们看起来像二进制计数器。如果我们在执行最内层循环时查看索引变量的值。

 i j k l
 1 1 1 1
 1 1 1 2
 1 1 2 1

实施计数器很容易。如果我们只是标记我们的变量,那么首先是i -> i[3]j -> i[2]k -> i[1]l -> [0],然后单个递增步骤包含

j = 0
while i[j] == 2
  i[j] = 1
  j++
if j == maxindex
  break out, we're done with all the loops
else
  i[j] = 2

现在,让我们在循环结束时做一些事情。这很简单,循环结束就在我们增加之前发生。所以我们的增量器看起来像

j = 0
while i[j] == 2
  do ending stuff with i[j]
  i[j] = 1
  j++
if j == maxindex
  break out, we're done with all the loops
else
  do ending stuff with i[j]
  i[j] = 2

现在,棘手的部分。它首先表明我们在增量之后就开始做东西,但这有两个问题。

  1. 它错过了初始增量(当我们将初始变量设置为1
  2. 时发生的增量)
  3. 它为最终增量的所有内容调用初始增量例程。
  4. (这些基本上是同一个问题)

    第一个解决方案很容易,我们只是在主循环之外调用开始的东西。

    for j = maxindex - 1 downto 0
      do beginning stuff with i[j]
    while true
      j = 0
      while i[j] == 2
        do ending stuff with i[j]
        i[j] = 1
        j++
      if j == maxindex
        break out, we're done with all the loops
      else
        do ending stuff with i[j]
        i[j] = 2
    

    现在,为了获得其他初始化器,我们在增加计数后执行它们。

    for j = maxindex - 1 downto 0
      do beginning stuff with i[j]
    while true
      j = 0
      while i[j] == 2
        do ending stuff with i[j]
        i[j] = 1
        j++
      if j == maxindex
        break out, we're done with all the loops
      else
        do ending stuff with i[j]
        i[j] = 2
      for k = j downto 0
        do beginning stuff with i[k]
    

    这不会在嵌套循环版本上产生任何开销(调用堆栈等)。它可以很容易地从巢中深入中止。这有点棘手,但如果它比具有显式堆栈的递归版本更复杂,我会感到惊讶。 De gustibus non disputandum est。

答案 4 :(得分:2)

我会upvote deinst的解决方案,但我是新的并且没有15个代表...在跟踪每个算法之后,我确实认为deinst具有正确的动态迭代方法。但是,deinst在开始时一次初始化,而roygbiv在循环嵌套中重复初始化。 (我想我之前已经看过deinst的正确编辑。)

答案 5 :(得分:1)

您需要跟踪depth个变量,其中depth是您的嵌套深度。您可以使用数组或堆栈,或者您可以使用递归的功能并一次分配单个局部变量

function doEverything(depth) {
  if (depth == 0) return;

  var i; // a new local variable on each call
  for i = 1 to 2 {
    initialize(work_item(i));
    doEverything(depth-1);
    doSomething(work_item(i));
  }
}

由于i,j,k,...的范围是1到2,因此也可以将它们解释为单个整数变量的位。

请注意,嵌套循环总共执行2^depth次操作。

答案 6 :(得分:1)

void doStuff(int depth)
{
    int i[depth];
    int sp = depth - 1;
    i[sp] = 0;

    while (1)
    {
        if (++i[sp] <= 2)              // "next".  This is why we init to 0.
        {                              // At this point, i[sp] is 1 or 2.
            // init item i[sp];

            if (sp)                    // If there's space to nest another loop
            {
                i[--sp] = 0;           // Push a 0
                continue;              // Start iterating with the new 0 as TOS
            }
        } 
        else if (++sp >= depth)        // Here, i[sp] is 3.  Leave the inner loop
            break;                     // and exit if stack's now empty

        // use item i[sp];
    }
}

仍然比递归更棘手。除非你绝对必须这样做,否则不要使用它。

答案 7 :(得分:0)

你可以构建一个序列集合,其中每个序列是你的循环变量i,j,k,......的排列。

一旦你有了这个集合,你就可以迭代:

foreach (var seq in myCollection)
{
  initialize(seq[0]);
  initialize(seq[1]);
  initialize(seq[2]);
  ...
  doSomething(...);
}

生成序列集合的一种方法,由Eric Lippert提供,通过LINQ:

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) 
{ 
  IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() }; 
  return sequences.Aggregate( 
    emptyProduct, 
    (accumulator, sequence) =>  
      from accseq in accumulator  
      from item in sequence  
      select accseq.Concat(new[] {item}));                
}

您传递任意长度的序列{{1,2},{1,2},... {1,2}},其中每个内部{1,2}序列替换一个循环变量,并且你会得到一系列值的所有排列。

答案 8 :(得分:0)

您的问题归结为变量n集数的置换问题的解决方案。这可以通过递归(Python)来解决:

a = ('ab', 'AB', '12')

def permutate(a, s='', nLevel=0):
    aResult = []
    if nLevel < len(a):
        for n in range(0, len(a[nLevel])):
            aResult += permutate(a, s + a[nLevel][n], nLevel + 1)
    else:
       aResult = [s]

    return aResult

print(permutate(a))

产生:

['aA1', 'aA2', 'aB1', 'aB2', 'bA1', 'bA2', 'bB1', 'bB2']