简体   繁体   English

如何将嵌套循环转换为递归?

[英]How to convert nested loop in to recursion?

I want to convert this nested loops in recursion. 我想递归地转换这个嵌套循环。 How do I achieve this? 我该如何实现?

for(let i = 0; i < 5; i++) {
  for(let j = 0; j < 5; j++) {
    console.log(i,j);
  }
}

Here another example of this recursion: 这是此递归的另一个示例:

function loop(i,j,limitI,limitJ){
     if(i>=limitI) return;
     if(j>=limitJ) loop(i+1,0,limitI,limitJ);
     else{
       console.log(i,j);
       loop(i,j+1,limitI,limitJ)
     }
 }
loop(0,0,4,4);

Generic function product calculates the Cartesian product of its inputs - You can polyfill Array.prototype.flatMap if it's not already in your environment 通用函数product计算其输入的笛卡尔乘积 -如果环境中尚未存在,则可以polyfill Array.prototype.flatMap

 Array.prototype.flatMap = function (f, context) { return this.reduce ((acc, x) => acc.concat (f (x)), []) } const product = (first = [], ...rest) => { const loop = (comb, first, ...rest) => rest.length === 0 ? first.map (x => [ ...comb, x ]) : first.flatMap (x => loop ([ ...comb, x ], ...rest)) return loop ([], first, ...rest) } const suits = ['♤', '♡', '♧', '♢'] const ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] for (const card of product (ranks, suits)) console.log (card) // [ 'A', '♤' ] // [ 'A', '♡' ] // [ 'A', '♧' ] // [ 'A', '♢' ] // [ '2', '♤' ] // ... // [ 'Q', '♧' ] // [ 'K', '♤' ] // [ 'K', '♡' ] // [ 'K', '♧' ] // [ 'K', '♢' ] 

product is a variadic function (by use of a rest parameter ) which accepts 1 or more inputs product是一个可变参数函数(通过使用rest参数 ),它接受1个或多个输入

const range = (min = 0, max = 0) =>
  max < min
    ? []
    : [ min, ...range (min + 1, max) ]

const r =
  range (0, 2)

for (const comb of product (r, r, r))
  console.log (comb)

// [ 0, 0, 0 ]
// [ 0, 0, 1 ]
// [ 0, 0, 2 ]
// [ 0, 1, 0 ]
// ...
// [ 2, 1, 2 ]
// [ 2, 2, 0 ]
// [ 2, 2, 1 ]
// [ 2, 2, 2 ]

Using destructuring assignment , you can effectively create nested loops 使用解构分配 ,您可以有效地创建嵌套循环

for (const [ i, j ] of product (range (0, 5), range (0, 5)))
  console.log ("i %d, j %d", i, j)

// i 0, j 0
// i 0, j 1
// i 0, j 2
// i 0, j 3
// i 0, j 4
// i 0, j 5
// i 1, j 0
// ...
// i 4, j 5
// i 5, j 0
// i 5, j 1
// i 5, j 2
// i 5, j 3
// i 5, j 4
// i 5, j 5

product can also be written using generators - below, we find all perfect Pythagorean triples under 20 product也可以使用生成器编写-在下面,我们发现20岁以下的所有完美毕达哥拉斯三元组

 const product = function* (first, ...rest) { const loop = function* (comb, first, ...rest) { if (rest.length === 0) for (const x of first) yield [ ...comb, x ] else for (const x of first) yield* loop ([ ...comb, x ], ...rest) } yield* loop ([], first, ...rest) } const range = (min = 0, max = 0) => max < min ? [] : [ min, ...range (min + 1, max) ] const pythagTriple = (x, y, z) => (x * x) + (y * y) === (z * z) const solver = function* (max = 20) { const N = range (1, max) for (const [ x, y, z ] of product (N, N, N)) if (pythagTriple (x, y, z)) yield [ x, y, z ] } console.log ('solutions:', Array.from (solver (20))) // solutions: // [ [ 3, 4, 5 ] // , [ 4, 3, 5 ] // , [ 5, 12, 13 ] // , [ 6, 8, 10 ] // , [ 8, 6, 10 ] // , [ 8, 15, 17 ] // , [ 9, 12, 15 ] // , [ 12, 5, 13 ] // , [ 12, 9, 15 ] // , [ 12, 16, 20 ] // , [ 15, 8, 17 ] // , [ 16, 12, 20 ] // ] 

I think using map (and reduce ), while it allows for more complex recursive structures as you demonstrate, is actually an implicit for loop, which does not really answer the question on how to convert one into a recurrence. 我认为使用map (和reduce )虽然允许您演示更复杂的递归结构,但实际上是隐式的for循环,它并不能真正回答有关如何将其转换为递归的问题。 However, if you also defined a recursive map and reduce , then it would be OK :) - גלעד ברקן 但是,如果您还定义了一个递归mapreduce ,那么就可以了:)-גלעדברקן

Your wish is my command :D 你的愿望是我的命令:D

 const Empty = Symbol () const concat = (xs, ys) => xs.concat (ys) const append = (xs, x) => concat (xs, [ x ]) const reduce = (f, acc = null, [ x = Empty, ...xs ]) => x === Empty ? acc : reduce (f, f (acc, x), xs) const mapReduce = (m, r) => (acc, x) => r (acc, m (x)) const map = (f, xs = []) => reduce (mapReduce (f, append), [], xs) const flatMap = (f, xs = []) => reduce (mapReduce (f, concat), [], xs) const product = (first = [], ...rest) => { const loop = (comb, first, ...rest) => rest.length === 0 ? map (x => append (comb, x), first) : flatMap (x => loop (append (comb, x), ...rest), first) return loop ([], first, ...rest) } const suits = ['♤', '♡', '♧', '♢'] const ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] for (const card of product (ranks, suits)) console.log (card) // same output as above 

This is an alternative. 这是另一种选择。

This approach uses param initialization with comma operator (just to make the code shorter) . 这种方法使用带有逗号运算符的参数初始化(只是为了使代码更短)

Additionally, an operator param (callback) to execute any logic for each iteration. 此外,操作员参数(回调)可为每次迭代执行任何逻辑。

 function loop(n, operator, i = 0, j = 0) { // Param initialization. if (j === n) (j = 0, i++); // Comma operator. if (i === n) return; operator(i, j); loop(n, operator, i, ++j); } loop(5, (i, j) => console.log(i, j)); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

You could recurse by taking a depth and the values to iterate: 您可以通过深度和值进行迭代来递归:

 function loop(start, end, depth, exit, ...args){
   for(let i = start; i < end; i++)
     depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i);
 }

Usable as: 可用作:

 loop(0, 5, 1, (i, j) => console.log(i, j))

The only real usecase would be deeper loops, eg this one 唯一真正的用例是更深的循环,例如这个


If you want it completely without for: 如果您完全不需要以下内容:

  const range = (start, end, cb) =>
     (cb(start), start + 1 >= end || range (start + 1, end, cb));


 function loop(start, end, depth, exit, ...args){
   range(start, end, i => 
     depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i));
 }

Try it 试试吧

I don't recommend this but you can do following(as it is difficult to read, for readability and understandability your code is best). 我不建议这样做,但是您可以执行以下操作(因为难以阅读,出于可读性和可理解性,您的代码是最好的)。

 function forLoop(i,j){ if(j===0){ if(i!==0) forLoop(i-1,4); console.log(i,j); } else{ forLoop(i,j-1); console.log(i,j); } } forLoop(4,4); 

Here's my rendition: 这是我的演译:

  function nested(i, j, maxI, maxJ) { if (i == maxI) return console.log(i, j) if (i < maxI) { ++j < maxJ ? nested(i, j, maxI, maxJ) : nested(++i, 0, maxI, maxJ) } } nested(0, 0, 5, 5) 

You could use an array for limit and values. 您可以将数组用于限制和值。 The order is reversed, because of the incrementing of the lowest index first. 由于首先增加最低索引,所以顺序相反。

This works for an arbitrary count of nested loops and it allowes to use an arbitrary limit of the max values. 这适用于任意数量的嵌套循环,并且允许使用最大值的任意限制。

 function iter(limit, values = limit.map(_ => 0)) { console.log(values.join(' ')); values = values.reduce((r, v, i) => { r[i] = (r[i] || 0) + v; if (r[i] >= limit[i]) { r[i] = 0; r[i + 1] = (r[i + 1] || 0) + 1; } return r; }, [1]); if (values.length > limit.length) { return; } iter(limit, values); } iter([2, 3]); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

Here's an outline of a " recurrence relation ," where "each further term of the sequence ... is defined as a function of the preceding terms." 这是“ 递归关系 ”的概述,其中“序列的每个进一步的术语...被定义为先前术语的函数”。

As you are probably aware, recursive functions usually have at least one base case, terminating the recursion, and at least one recursive call. 您可能已经知道,递归函数通常至少具有一个基本情况(用于终止递归)和至少一个递归调用。 To find a pattern, let's examine the sequence: 为了找到一个模式,让我们检查一下顺序:

0,0
0,1
0,2
0,3
0,4
1,0
1,2
...

Our base case, where the call to a preceding parameter terminates, seems to be 0,0 . 我们对前一个参数的调用终止的基本情况似乎是0,0 But this is also where the console logs begin, which means we first have to call all the way back to the base case. 但这也是控制台日志开始的地方,这意味着我们首先必须一直调用到基本情况。 For convenience, let's assume the function expects positive parameters: 为了方便起见,让我们假设函数需要正参数:

function f(i, j){
  if (i == 0 && j == 0){
    console.log(i,j);
    return;
  }
}

We can also notice that the outer loop, the i , stays constant for each cycle of j s: 我们还可以注意到,外循环i在每个j s周期中保持不变:

function f(i, j){
  if (i == 0 && j == 0){
    console.log(i,j);
    return;
  }

  if (j == 0)
  // ... what happens here?
}

but here we get stuck. 但在这里我们陷入困境。 When j is greater than zero, we can determine that the current term came from f(i, j - 1) , but if j is zero in the current term, we have no way of formulating what it was in the preceding term. j大于零时,我们可以确定当前项来自f(i, j - 1) ,但是如果j在当前项中为零,则无法表示上一项。 We need one more parameter: 我们还需要一个参数:

 function f(i, j, jj){ if (i == 0 && j == 0){ console.log(i,j); return; } if (j == 0) f(i - 1, jj, jj); else f(i, j - 1, jj); console.log(i,j); } f(4,4,4); 

Transforming a nested for loop into its recursive counterpart is surprisingly hard. 将嵌套的for循环转换为其递归对应物非常困难。 Good question! 好问题!

You can transform every loop (without a stack) into a tail recursive algorithm. 您可以将每个循环(无堆栈)转换为尾部递归算法。 So this rule should hold for a nested loop too. 因此,该规则也应适用于嵌套循环。

I think we need two distinct functions to get something equivalent to your two nested loops: 我认为我们需要两个不同的函数来获得与您的两个嵌套循环等效的东西:

 const loop = ([i, j], [k, l]) => { const loop_ = (k_, l_) => { if (k_ >= l_) return; else { console.log(i, k_); loop_(k_ + 1, l_); } }; if (i >= j) return; else { loop_(k, l); loop([i + 1, j], [k, l]); } }; loop([0, 5], [0, 5]); 

You have to pass ranges for both the out and the inner loop. 您必须同时传递out和inner循环的范围。

As you can see both recursive calls are in tail position. 如您所见,两个递归调用都位于尾部位置。 I think this is the closest equivalent we can get. 我认为这是我们可以获得的最接近的等效方法。

suggested solution 建议的解决方案

function recurse(arg1=0, arg2=0, cb) {

    if ( arg2 <= 5 ) {

        let _l = arg2++;

        if ( arg1 === 5 )
            return ;

        if ( ++_l === 6 ) {
            arg2 = 0;
            cb(arg1++, arg2);
            recurse(arg1, arg2, cb);
        } else {
            cb(arg1, arg2 - 1);
            recurse(arg1, arg2, cb);
        }

    }
}

recurse( 0 , 0 , (i,j) => console.log(i,j));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM