簡體   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