简体   繁体   English

在javascript中将循环函数转换为递归函数

[英]Converting a loop function into a recursive function in javascript

I am attempting to teach myself how to write recursive functions and someone suggested trying to convert loops into recursions. 我试图教自己如何编写递归函数,有人建议尝试将循环转换为递归。 So I am trying to change the first for loop function into a recursive function. 所以我试图将第一个for循环函数更改为递归函数。 Here is my code: 这是我的代码:

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

The purpose of each function is to return true if there is exactly one argument that is truthy. 如果只有一个参数是真实的,那么每个函数的目的是返回true。 Otherwise the function returns false. 否则该函数返回false。 The for loop function works properly. for循环功能正常工作。 However, when I use the recursive function I get the error: Maximum call stack size exceeded. 但是,当我使用递归函数时,我得到错误:超出了最大调用堆栈大小。 I was wondering what I am doing incorrectly. 我想知道我做错了什么。 Thank you for your help! 谢谢您的帮助!

Your approach has some issues 你的方法有一些问题

  • if(!!array[i] === true) use the boolean value directly: if(array[i]) if(!!array[i] === true)直接使用布尔值: if(array[i])

You're asking for a true value to return true 你要求true价值回归true

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

Return the comparison directly: return count === 1; 直接返回比较: return count === 1;

In your function onlyOne2 your "recursion case" to stop the loop is incorrect. 在你的函数onlyOne2你的“递归情况”来停止循环是不正确的。

 count === array.length-1
 ^         ^ 

You have to use the index i 你必须使用索引i

Look at this code snippet with those fixes 请查看包含这些修补程序的代码段

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

See? 看到? now is working the loop using recursion. 现在正在使用递归来处理循环。

You could take a counter for the wanted truthy values and rest parameters ... for the recursive function. 您可以为需要的truthy值和休息参数...提供一个计数器用于递归函数。

It works with tail recursion and an arbitrary count of arguments. 它适用于尾递归和任意数量的参数。

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

The problem with above code is that every time the function recurses count and numTrue get declared again and hence an infinite loop occurs, Here is the correct code(declaring count and numTrue as global: 上面代码的问题是,每次函数递归count并且numTrue get再次声明并因此发生无限循环时,这是正确的代码(将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)); 

This is interesting. 这是有趣的。 Ok, so we have a function that returns true if exactly one of the items in the array is truthy. 好的,所以我们有一个函数,如果数组中的其中一个项目是真的,则返回true。 Let's think about the basic requirements in a recursion. 让我们考虑一下递归的基本要求。

Base case, array is empty: 基本情况,数组为空:

if (!array.length)
  return false;

Base case, there's only one element in the array: 基本情况下,数组中只有一个元素:

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

Otherwise: 除此以外:

let next = f(rest-of-array)

if (!!array[0] === true)
  return !next
else
  return next

JavaScript code: 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
}

Output: 输出:

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

You can convert any for loop to recursion: 您可以将任何for循环转换for递归:

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

Here a . 这里a b and n changes and the reason is the a value which has the 6th fibonacci number. bn变化和其原因是a具有与第六斐波纳契数值。 So we make them parameters and return: 所以我们将它们作为参数并返回:

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

So for your function that uses for loop: 所以对于使用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;
}

The bindings you are changing are count and i : 你正在改变的绑定是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;
}

Since you only have 3 elements this probably is good enough but imagine you wanted to implement something like this for an array you should plan for returning as fast as you know you are done. 由于你只有3个元素,这可能已经足够好但想象你想要为数组实现这样的东西,你应该计划尽可能快地返回你已经完成的数据。 In a for version it would look like this: for版本中,它看起来像这样:

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

The same with recursion: 递归相同:

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

Notice it's not exactly the same since we are expecting a count from the iteration and return only returns back to the callee. 请注意它并不完全相同,因为我们期望从迭代计数并return仅返回给被调用者。 Thus early return here is just not iterating more values but we still return a count that is the subject of the final result. 因此,这里的早期返回只是不迭代更多的值,但我们仍然返回一个作为最终结果主题的计数。 You could do the same in the for by using break instead of return . 你可以在for使用break而不是return来做同样的事情。

The same goes for any / some which should return at the very first true and all that should return false for the first false value. 这同样适用于any / some应该在第一个真正的并返回all应该针对第一个假值返回false。 No point in iterating through every element when it cannot change the answer. 当它无法改变答案时,迭代每个元素都没有意义。

You might as well try this. 你不妨试试这个。 You dont need to change your function structure or anything and it will work with any number of arguments. 你不需要改变你的函数结构或任何东西,它可以使用任意数量的参数。

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

I would recommend starting with a simpler example: 我建议从一个更简单的例子开始:

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

Then add the return statements: 然后添加return语句:

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

But the real usefulness of recursion is with something like a directory structure. 但递归的真正用处在于目录结构。 Have a go at that next! 下一步吧! You can put arrays into arrays to simulate it: 您可以将数组放入数组中来模拟它:

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

Here's another way to solve it – I recently wrote another answer that aims to help learners achieve high-level thinking using recursion. 这是另一种解决方法 - 我最近写了另一个答案,旨在帮助学习者使用递归实现高级思维。 Techniques used here are written about in detail. 这里使用的技术是详细写的。 If you're interested, you can 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 ) 

Note loop can be made generic so that onlyOne doesn't need to depend on a specialized helper – mind the stack overflow 注意loop可以是通用的,因此onlyOne不需要依赖于专门的帮助程序 - 请注意堆栈溢出

 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's answer is a great example of how function parameters can reduce program complexity – this adaptation shows how the same program can be written in a functional expression 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 ) 

You're going about this the wrong way. 你这是错误的方式。 You're trying to convert the entire function into a recursive function. 您正在尝试将整个函数转换为递归函数。 Instead, as suggested, you only need to convert the loop into a recursive function: 相反,正如所建议的那样,您只需要将循环转换为递归函数:

I am attempting to teach myself how to write recursive functions and someone suggested trying to convert loops into recursions. 我试图教自己如何编写递归函数,有人建议尝试将循环转换为递归。

So, let's start by looking at your original function. 那么,让我们从查看原始功能开始吧。 I cleaned it up for you: 我为你清理了一下:

 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 // | | // vv var count = 0; // +-- loop variant // | +-- initial value of the loop variant // | | // vv for (let i = 0; i < 3; i++) if (array[i]) count++; return count === 1; } function assert(test, condition) { console.log(test, condition ? "passed" : "failed"); } 

Notice that I annotated the state of the loop and the loop variant. 请注意,我注释了循环的状态和循环变量。 These will be the arguments of our recursive function. 这些将是我们的递归函数的参数。 We'll replace the for loop with a call to the recursive function using the initial values of the state and the loop variant as inputs. 我们将使用状态的初始值和循环变量作为输入来调用递归函数来替换for循环。 The result of the recursive function will be the final state of the loop: 递归函数的结果将是循环的最终状态:

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

Hopefully, now you know how to convert any loop into a recursive function. 希望现在您知道如何将任何循环转换为递归函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM