繁体   English   中英

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

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

我想递归地转换这个嵌套循环。 我该如何实现?

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

这是此递归的另一个示例:

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);

通用函数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是一个可变参数函数(通过使用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 ]

使用解构分配 ,您可以有效地创建嵌套循环

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也可以使用生成器编写-在下面,我们发现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 ] // ] 

我认为使用map (和reduce )虽然允许您演示更复杂的递归结构,但实际上是隐式的for循环,它并不能真正回答有关如何将其转换为递归的问题。 但是,如果您还定义了一个递归mapreduce ,那么就可以了:)-גלעדברקן

你的愿望是我的命令: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 

这是另一种选择。

这种方法使用带有逗号运算符的参数初始化(只是为了使代码更短)

此外,操作员参数(回调)可为每次迭代执行任何逻辑。

 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; } 

您可以通过深度和值进行迭代来递归:

 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);
 }

可用作:

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

唯一真正的用例是更深的循环,例如这个


如果您完全不需要以下内容:

  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));
 }

试试吧

我不建议这样做,但是您可以执行以下操作(因为难以阅读,出于可读性和可理解性,您的代码是最好的)。

 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); 

这是我的演译:

  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) 

您可以将数组用于限制和值。 由于首先增加最低索引,所以顺序相反。

这适用于任意数量的嵌套循环,并且允许使用最大值的任意限制。

 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; } 

这是“ 递归关系 ”的概述,其中“序列的每个进一步的术语...被定义为先前术语的函数”。

您可能已经知道,递归函数通常至少具有一个基本情况(用于终止递归)和至少一个递归调用。 为了找到一个模式,让我们检查一下顺序:

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

我们对前一个参数的调用终止的基本情况似乎是0,0 但这也是控制台日志开始的地方,这意味着我们首先必须一直调用到基本情况。 为了方便起见,让我们假设函数需要正参数:

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

我们还可以注意到,外循环i在每个j s周期中保持不变:

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

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

但在这里我们陷入困境。 j大于零时,我们可以确定当前项来自f(i, j - 1) ,但是如果j在当前项中为零,则无法表示上一项。 我们还需要一个参数:

 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); 

将嵌套的for循环转换为其递归对应物非常困难。 好问题!

您可以将每个循环(无堆栈)转换为尾部递归算法。 因此,该规则也应适用于嵌套循环。

我认为我们需要两个不同的函数来获得与您的两个嵌套循环等效的东西:

 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]); 

您必须同时传递out和inner循环的范围。

如您所见,两个递归调用都位于尾部位置。 我认为这是我们可以获得的最接近的等效方法。

建议的解决方案

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