【问题标题】:Converting a loop function into a recursive function in javascript在javascript中将循环函数转换为递归函数
【发布时间】:2018-07-13 07:50:11
【问题描述】:

我正在尝试自学如何编写递归函数,有人建议尝试将循环转换为递归。所以我试图将第一个 for 循环函数更改为递归函数。这是我的代码:

// Function that uses for loop.
function onlyOne(value1, value2, value3) {
    var array = [value1, value2, value3];
    var count = 0;
    for(var i = 0; i < array.length; i++) {
        if(!!array[i] === true) {
            count ++;
        }
    } if(count === 1) {
        return true;
    } else {
        return false;
    }
}

// Function that uses recursion.
function onlyOne2(a, b, c) {
    var array = [a, b, c];
    var count = 0;
    var numTrue = 0;
    if(!!array[count] === true) {
        numTrue++;
    }
    if(count === array.length-1) {
        if(numTrue === 1) {
            return true;
        } else {
            return false;
        }
    }else {
        count++;
        return onlyOne2(a, b, c);
    }
}

console.log(onlyOne2(true, false, false));

每个函数的目的是在只有一个参数为真时返回真。否则函数返回假。 for 循环功能正常工作。但是,当我使用递归函数时,出现错误:超出最大调用堆栈大小。我想知道我做错了什么。感谢您的帮助!

【问题讨论】:

  • 您必须每次都将数组的尾部作为参数传递,而不是重新创建它
  • 你在递归函数中失去了count的踪迹,每次执行它都会设置回0
  • 你最好学习如何从递归中循环。

标签: javascript function loops recursion tail-recursion


【解决方案1】:

你做错了。您正在尝试将整个函数转换为递归函数。相反,正如建议的那样,您只需要将循环转换为递归函数:

我正在尝试自学如何编写递归函数,有人建议尝试将循环转换为递归。

所以,让我们从查看您的原始函数开始。我帮你清理了:

assert("Test 1", onlyOne(false, 0, "")  === false);
assert("Test 2", onlyOne(false, 0, "x") === true);
assert("Test 3", onlyOne(false, 1, "")  === true);
assert("Test 4", onlyOne(false, 1, "x") === false);
assert("Test 5", onlyOne(true,  0, "")  === true);
assert("Test 6", onlyOne(true,  0, "x") === false);
assert("Test 7", onlyOne(true,  1, "")  === false);
assert("Test 8", onlyOne(true,  1, "x") === false);

function onlyOne(a, b, c) {
    const array = [a, b, c];

    //    +-- state of the loop
    //    |     +-- initial value of the state
    //    |     |
    //    v     v
    var count = 0;

    //       +-- loop variant
    //       |   +-- initial value of the loop variant
    //       |   |
    //       v   v
    for (let i = 0; i < 3; i++)
        if (array[i]) count++;

    return count === 1;
}

function assert(test, condition) {
    console.log(test, condition ? "passed" : "failed");
}

请注意,我注释了循环的状态和循环变体。这些将是我们递归函数的参数。我们将替换for 循环,调用递归函数,使用状态的初始值和循环变量作为输入。递归函数的结果将是循环的最终状态:

assert("Test 1", onlyOne(false, 0, "")  === false);
assert("Test 2", onlyOne(false, 0, "x") === true);
assert("Test 3", onlyOne(false, 1, "")  === true);
assert("Test 4", onlyOne(false, 1, "x") === false);
assert("Test 5", onlyOne(true,  0, "")  === true);
assert("Test 6", onlyOne(true,  0, "x") === false);
assert("Test 7", onlyOne(true,  1, "")  === false);
assert("Test 8", onlyOne(true,  1, "x") === false);

function onlyOne(a, b, c) {
    const array = [a, b, c];

    const count = loop(0, 0);

    return count === 1;

    function loop(count, i) {
        return i < 3 ?
            loop(array[i] ? count + 1 : count, i + 1) :
            count; // return final state of the loop
    }
}

function assert(test, condition) {
    console.log(test, condition ? "passed" : "failed");
}

希望现在您知道如何将任何循环转换为递归函数。

【讨论】:

    【解决方案2】:

    您可以为所需的truthy 值和rest parameters ... 为递归函数设置一个计数器。

    它适用于tail recursion 和任意数量的参数。

    function only(count, v, ...rest) {
        if (v) {                       // check value
            if (!count) {              // check count is zero
                return false;          // because found more truthy than needed
            }
            count--;                   // decrement count
        }
        if (!rest.length) {            // check length is zero
            return !count;             // return not count
        }
        return only(count, ...rest);   // tail call with count and rest
    }
    
    console.log(only(1, false, false, false)); // false
    console.log(only(1, false, false, true));  //  true
    console.log(only(1, false, true, false));  //  true
    console.log(only(1, false, true, true));   // false
    console.log(only(1, true, false, false));  //  true
    console.log(only(1, true, false, true));   // false
    console.log(only(1, true, true, false));   // false
    console.log(only(1, true, true, true));    // false
    console.log('-----');
    console.log(only(2, false, false, false)); // false
    console.log(only(2, false, false, true));  // false
    console.log(only(2, false, true, false));  // false
    console.log(only(2, false, true, true));   //  true
    console.log(only(2, true, false, false));  // false
    console.log(only(2, true, false, true));   //  true
    console.log(only(2, true, true, false));   //  true
    console.log(only(2, true, true, true));    // false
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

      【解决方案3】:

      这是解决它的另一种方法 - 我最近写了另一个答案,旨在帮助学习者使用递归实现高级思维。此处使用的技术已详细介绍。有兴趣的可以read it here

      const None =
        Symbol ()
      
      const loop = ([ x = None, ...xs ], foundTruthy = false) =>
        x === None
          ? foundTruthy
          : Boolean (x)
            ? foundTruthy
              ? false
              : loop (xs, true)
            : loop (xs, foundTruthy)
      
      const onlyOne = (...xs) =>
        loop (xs, false)
      
      console.log
        ( onlyOne ()                 // false
        , onlyOne (0)                // false
        , onlyOne (1)                // true
        , onlyOne (0, '', false, 1)  // true
        , onlyOne (0, 0, 0, true, 1) // false
        )

      注意loop 可以设为通用,这样onlyOne 就不需要依赖专门的帮助器——mind the stack overflow

      const None =
        Symbol ()
      
      const always = x => _ =>
        x
      
      const recur = (...values) =>
        ({ recur, values })
      
      const loop = (f = always (null) , acc = f ()) =>
        acc && acc.recur === recur
          ? loop (f, f (...acc.values))
          : acc
      
      const onlyOne = (...list) =>
        loop (([ x = None, ...xs ] = list, foundTruthy = false) =>
          x === None
            ? foundTruthy
            : Boolean (x)
              ? foundTruthy
                ? false
                : recur (xs, true)
              : recur (xs, foundTruthy))
      
      console.log
        ( onlyOne ()                 // false
        , onlyOne (0)                // false
        , onlyOne (1)                // true
        , onlyOne (0, '', false, 1)  // true
        , onlyOne (0, 0, 0, true, 1) // false
        )

      Nina 的回答是函数参数如何降低程序复杂性的一个很好的例子——这种改编展示了如何在函数表达式中编写相同的程序

      const None =
        Symbol ()
      
      const only = (count = 1, x = None, ...xs) =>
        x === None
          ? count === 0
          : Boolean (x)
            ? only (count - 1, ...xs)
            : only (count, ...xs)
            
      console.log
        ( only ()                           // false
        , '---'
        , only (0)                          // true
        , only (0, false)                   // true
        , only (0, true)                    // false
        , '---'
        , only (1)                          // false
        , only (1, false)                   // false
        , only (1, false, true)             // true
        , only (1, false, true, true)       // false
        , '---'
        , only (2, false, true)             // false
        , only (2, false, true, true)       // true
        , only (2, false, true, true, true) // false
        )

      【讨论】:

        【解决方案4】:

        我建议从一个更简单的例子开始:

        function logRecurse(i, arr) {
            if (i < arr.length) {
                console.log(arr[i]);
                logRecurse(i+1, arr);
            }
        }
        
        var array = [false, false, true, false];
        logRecurse(0, array);

        然后添加返回语句:

        function truthyRecurse(i, arr) {
            if (i < arr.length) {
                 if (arr[i]) { // could be just: return arr[i] || truthyRecurse(...)
                    return true;
                 } else {
                    return truthyRecurse(i+1, arr);
                 }
            }
            return false;
        }
        
        var array = [false, false, true, false];
        console.log(truthyRecurse(0, array));

        但递归的真正用处在于目录结构之类的东西。接下来就试试吧!可以将数组放入数组中来模拟:

        function logFiles(dir) {
            // log filename (typeof string) or recurse into dir
        }
        
        var dir = [
            "file1",
            "file2",
            [ // sub dir
                "subfile1",
                [ // sub-sub dir
                    "sub-subfile1",
                    "sub-subfile2",
                    "sub-subfile3",
                ],
                "subfile2",
            ]
        ];
        
        logFiles(dir);
        

        【讨论】:

          【解决方案5】:

          这很有趣。好的,所以我们有一个函数,如果数组中的某个项目是真的,则返回 true。让我们考虑一下递归中的基本要求。

          基本情况,数组为空:

          if (!array.length)
            return false;
          

          基本情况,数组中只有一个元素:

          if (array.length == 1)
            return !!array[0] === true
          

          否则:

          let next = f(rest-of-array)
          
          if (!!array[0] === true)
            return !next
          else
            return next
          

          JavaScript 代码:

          function f(arr, i){
            // Same as !array.length
            if (i == arr.length)
              return false;
          
            // Same as array.length == 1
            if (i == arr.length - 1)
              return !!arr[i]
          
            // Same as f(rest-of-array)
            let next = f(arr, i + 1)
          
            if (!!arr[i])
              return !next
          
            return next
          }
          

          输出:

             f([undefined,2,0], 0)
          => true
             f([1,2,undefined], 0)
          => false
             f([undefined,false,1], 0)
          => true
             f([5,false,false], 0)
          => true
          

          【讨论】:

            【解决方案6】:

            你不妨试试这个。你不需要改变你的函数结构或任何东西,它可以使用任意数量的参数。

            function onlyOne(a,b,c){
             let arr = Array.from(arguments);
             let count = 0;
             let item = arr.shift();
             if(true === item){
                ++count;
             }
             if(arr.length > 0) {
                count += onlyOne.apply(this,arr);
                return count == 1;
             }
             return count;
            }
            

            【讨论】:

              【解决方案7】:

              您可以将任何for 循环转换为递归:

              let a=0, b=1,
              for (let n=5; n > 0; n--) {
                const tmpa = a;
                a = b; 
                b += tmpa;
              }
              

              这里abn 发生变化,原因是 a 值具有第 6 个斐波那契数。所以我们将它们设为参数并返回:

              function forLoop(n, a, b) {
                if(n > 0) {
                  return forLoop(n - 1, b, a + b);
                }
                return a;
              }
              const a = forLoop(5, 0, 1);
              

              所以对于使用for 循环的函数:

              function onlyOne(value1, value2, value3) {
                const array = [value1, value2, value3];
                let count = 0;
                for(var i = 0; i < array.length; i++) {
                  if(!!array[i] === true) {
                    count ++;
                  }
                } 
                if(count === 1) {
                  return true;
                }
                return false;
              }
              

              您要更改的绑定是counti

              function onlyOne(value1, value2, value3) {
                function iterate(count, i) {
                  if (i < array.length) {
                    if (!!array[i] === true) {
                      return iterate(count + 1, i + 1);
                    }
                    return iterate(count, i + 1);
                  }
                  return count;
                }
                const array = [value1, value2, value3];
                const count = iterate(0, 0);
                if(count === 1) {
                  return true;
                }
                return false;
              }
              

              由于您只有 3 个元素,这可能已经足够了,但假设您想为一个数组实现类似的东西,您应该计划在完成后尽快返回。在forversion 中,它看起来像这样:

              function onlyOne(arr) {
                let count = 0;
                const len = array.length; // cache
                for (index = 0; index < len; index++) {
                  if (arr[index]) {
                    count++; 
                    if(count > 1) return false; // early return
                  }
                }
                return count === 1;
              }
              

              递归也一样:

              function onlyOne(arr) {
                function iterate (index, count) {
                  if (index < len && count <= 1) {
                    return iterate(
                      index + 1, 
                      count + (array[index] ? 1 : 0)
                    );
                  }
                  return count;
                }
                const len = array.length; // cache
                const count = iterate(0, 0);
                return count === 1;
              }
              

              请注意,它并不完全相同,因为我们期望从迭代中获得计数,而 return 只返回给被调用者。因此这里的提前返回只是没有迭代更多的值,但我们仍然返回一个作为最终结果主题的计数。您可以在for 中使用break 而不是return 来执行相同的操作。

              any/some 也是如此,它应该在第一个 true 时返回,all 对于第一个 false 值应该返回 false。当它不能改变答案时,迭代每个元素是没有意义的。

              【讨论】:

                【解决方案8】:

                您的方法存在一些问题

                • if(!!array[i] === true) 直接使用布尔值:if(array[i])

                您要求true 值返回true

                if (count === 1) { // <- Use this comparison to return the specific value.
                    return true;
                    ^
                } else {
                    return false;
                    ^
                }
                

                直接返回比较:return count === 1;

                在您的函数onlyOne2 中,您停止循环的“递归案例”不正确。

                 count === array.length-1
                 ^         ^ 
                   
                

                你必须使用索引i

                看看这段代码 sn-p 与那些修复

                // Function that uses for loop.
                function onlyOne(value1, value2, value3) {
                  var array = [value1, value2, value3];
                  var count = 0;
                  for (var i = 0; i < array.length; i++) {
                    if (array[i]) {
                      count++;
                    }
                  }
                
                  return count === 1;
                }
                
                console.log(onlyOne(true, false, false));
                
                function onlyOne2(value1, value2, value3, count, i) {
                  var array = [value1, value2, value3];
                
                  if (i === array.length) return count === 1;
                  
                  if (array[i]) count++;
                
                  return onlyOne2(value1, value2, value3, count, ++i);
                }
                
                console.log(onlyOne2(true, false, false, 0, 0));

                看到了吗?现在正在使用递归进行循环。

                【讨论】:

                  【解决方案9】:

                  上面代码的问题是每次函数递归countnumTrue都会再次声明,因此会发生无限循环,这是正确的代码(将countnumTrue声明为全局:

                  var count = 0;
                  var numTrue = 0;
                  
                  function onlyOne2(a, b, c) {
                      var array = [a, b, c];
                      if(!!array[count] === true) {
                          numTrue++;
                      }
                      if(count === array.length-1) {
                          if(numTrue === 1) {
                              return true;
                          } else {
                              return false;
                          }
                      }else {
                          count++;
                          return onlyOne2(a, b, c);
                      }
                  }
                  
                  console.log(onlyOne2(true, false, false));

                  【讨论】:

                    猜你喜欢
                    • 2013-05-31
                    • 2012-02-03
                    • 2019-05-30
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-01-06
                    • 1970-01-01
                    • 2018-03-15
                    相关资源
                    最近更新 更多