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

时间:2018-03-17 21:07:22

标签: javascript loops recursion

我想在递归中转换这个嵌套循环。我如何实现这一目标?

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

10 个答案:

答案 0 :(得分:3)

这是递归的另一个例子:

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

答案 1 :(得分:3)

通用函数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),它接受一个或多个输入

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

答案 2 :(得分:1)

这是另一种选择。

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

此外,运算符param(回调)为每次迭代执行任何逻辑。

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

答案 3 :(得分:1)

您可以通过获取深度和值来迭代:

 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

答案 4 :(得分:1)

我不建议这样做,但你可以这样做(因为它很难阅读,为了便于阅读和理解你的代码是最好的。)

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

答案 5 :(得分:1)

这是我的演绎:

&#13;
&#13;
  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)
&#13;
&#13;
&#13;

答案 6 :(得分:1)

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

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

&#13;
&#13;
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]);
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
&#13;
&#13;

答案 7 :(得分:1)

这是&#34; recurrence relation,&#34;的概述。其中&#34;序列的每个进一步的术语......被定义为前面术语的函数。&#34;

您可能已经知道,递归函数通常至少有一个基本情况,终止递归,以及至少一个递归调用。要查找模式,请检查序列:

0,0

我们的基本情况,即对前一个参数的调用终止,似乎是function f(i, j){ if (i == 0 && j == 0){ console.log(i,j); return; } } 。但这也是控制台日志开始的地方,这意味着我们首先必须一直调用回基本情况。为方便起见,我们假设函数需要正参数:

i

我们还可以注意到外部循环jfunction f(i, j){ if (i == 0 && j == 0){ console.log(i,j); return; } if (j == 0) // ... what happens here? } s的每个循环中保持不变:

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);在当前字词中为零,则我们无法确定其中的内容前一任期。我们还需要一个参数:

&#13;
&#13;
Set.containsAll(Collection<?> c);
&#13;
&#13;
&#13;

答案 8 :(得分:1)

将嵌套的for循环转换为它的递归副本是非常困难的。好问题!

您可以将每个循环(没有堆栈)转换为尾递归算法。所以这个规则也应该适用于嵌套循环。

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

&#13;
&#13;
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]);
&#13;
&#13;
&#13;

你必须传递out和inner循环的范围。

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

答案 9 :(得分:0)

建议的解决方案

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