【问题标题】:How to convert nested loop in to recursion?如何将嵌套循环转换为递归?
【发布时间】:2018-03-17 21:07:21
【问题描述】:

我想以递归方式转换这个嵌套循环。我如何做到这一点?

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

【问题讨论】:

  • 为什么?你甚至不会保护一行代码
  • 我正在学习递归,所以想更清楚。
  • 这么多答案,但没有被接受或投票......
  • @Endless,拿一个最通用的或者你喜欢的。
  • 不应该来自我,而是来自 Dhruv

标签: javascript loops recursion


【解决方案1】:

这里是递归的另一个例子:

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

【讨论】:

    【解决方案2】:

    通用函数 product 计算其输入的 Cartesian product - 如果您的环境中还没有 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 parameter),它接受 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 ]
    

    使用destructuring assignment,可以有效地创建嵌套循环

    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 也可以写成generators - 下面,我们发现所有完美的Pythagorean triples 都在 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

    【讨论】:

    • 一如既往的精彩:)
    • 我认为使用map(和reduce)虽然它允许更复杂的递归结构,但实际上是一个隐式for循环,它并没有真正回答问题如何将一个转换为重复。但是,如果您还定义了递归mapreduce,那么就可以了:)
    【解决方案3】:

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

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

    【讨论】:

    • 不错的答案 (+1),但您将常量 4 隐藏在代码主体内,这限制了内部循环的长度 j。我把它作为我的参数。我实际上可能更喜欢你的初始逻辑只捕获j == 0
    • @גלעדברקן 感谢您指出,我不会像您已经完成的那样编辑我的代码(如果我编辑我们的答案将是匹配的)。不过谢谢
    【解决方案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)

    【讨论】:

      【解决方案5】:

      这是另一种选择。

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

      另外,一个操作符参数(回调)为每次迭代执行任何逻辑。

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

      【讨论】:

        【解决方案6】:

        您可以将数组用于限制和值。顺序是颠倒的,因为最低索引首先递增。

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

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

        【讨论】:

          【解决方案7】:

          这里是“recurrence relation”的概要,其中“序列的每个后续项......都被定义为前面的项的函数。”

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

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

          我们还可以注意到,外部循环 ijs 的每个循环中保持不变:

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

          【讨论】:

          • 我喜欢这种方法,因为它有数学备份。但是,我不认为单个递归函数等同于两个嵌套循环。
          【解决方案8】:

          您可以通过获取深度和要迭代的值来递归:

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

          唯一真正的用例是更深的循环,例如this one


          如果你完全不想要它:

            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

          【讨论】:

          • 我认为“将 for 循环转换为递归”意味着答案不会包含 for 循环 :)
          • 他在谈论 嵌套的 for 循环 .. 我不认为带有递归的循环很有用
          • 我同意它的用处。我认为 OP 提到希望在他学习时“变得更清楚”。就我自己而言,我有时喜欢将注意力转移到理论上,而远离人们在工作中可能实际使用的东西:)
          • 不错!我喜欢新的范围功能。
          【解决方案9】:

          将嵌套的 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]);

          你必须传递外循环和内循环的范围。

          如您所见,两个递归调用都处于尾部位置。我认为这是我们能得到的最接近的等价物。

          【讨论】:

          • 很酷,但它不是也假设我们希望两个循环中的每一个都具有相同的范围吗?
          • 你能详细说明一下吗?
          • 其实我误解了你loop函数中的每个参数是什么。我认为n 是我们正在转换的两个循环的范围(不包括)范围的开始和结束。通过实验,我发现情况并非如此,但是由于您的loop 函数的参数是答案的重要部分,所以我将留给您解释。他们做出不同的假设,但仍然假设用户有一定的期望。
          • @גלעדברקן p 是一个错误选择的名字。常用于表示谓词,但这里的谓词是===。我编辑了我的答案。
          • 仍不清楚。当你运行loop(3, 5)loop(3,2) 时会发生什么,你会期待什么?我希望您能描述loop 函数中的每个参数代表什么,以便用户提前想象会发生什么。
          【解决方案10】:

          建议的解决方案

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

          【讨论】:

            猜你喜欢
            • 2013-11-29
            • 2019-04-14
            • 1970-01-01
            • 1970-01-01
            • 2018-05-10
            • 2013-01-04
            • 2016-04-24
            • 2021-12-30
            • 1970-01-01
            相关资源
            最近更新 更多