【问题标题】:How to check whether all number elements in an array are even numbers with recursion?如何通过递归检查数组中的所有数字元素是否都是偶数?
【发布时间】:2021-11-08 20:51:19
【问题描述】:

我目前正在尽可能多地学习递归,但我真的不知道如何使用它来检查数组中的所有数字元素是否都是偶数。使用 ES6,我可以使用 "every" 来实现如下期望的结果:

const isEven = (arr) => arr.every((item) => item % 2 === 0);
console.log(isEven([2,4,6,8])); // true
console.log(isEven([2,4,6,9])); // false

以下是我的递归尝试:

const isEven = (arr) => (arr.length === 0) ? true : isEven(arr.slice(1)) % 2 === 0;
console.log(isEven([2,4,6,8])); // true
console.log(isEven([2,4,6,9])); // true

如您所见,第二个示例的结果不正确,我感觉我也没有走在正确的道路上。有人可以告诉我我做错了什么吗?

【问题讨论】:

  • 按照 ES6 的方式来做。这是递归的严重误用。如果您出于教学原因学习递归,那么选择它实际上有用的问题很重要,例如遍历二叉树,这会通过非线性因素分解问题。如果您真的必须这样做,请使用数组索引而不是切片。即使你修复了这个错误,这个算法也不必要地是 O(n*n)。
  • @ggorlen:我非常不同意。头尾列表是一种同样有效的递归结构,对于新手来说比树更简单。虽然 JS 本身不提供列表,但密集数组提供了它们的合理复制,使用 [0].slice(1),或者如果将它们具体化为 headtailfirstrest 更好(请不要一直使用carcdr!)是的,性能会很糟糕,但这不是学习练习的重点。
  • @ScottSauyet 是的,我们过去在这方面存在分歧。问题是新手并没有真正意识到您在生产 JS 中永远会做这种事情。他们被教导递归,就好像人们在 JS 中编写商业应用程序的方式一样。它不是。 arr.every 更加地道。这比性能问题更糟糕:如果列表中包含的元素超过几千个微不足道的元素,那么它就会破坏堆栈。为什么像 Haskell 一样教 JS?用 Haskell 教 Haskell 范式,用 JS 教 JS 范式。否则人们只会感到非常困惑,正如我每天在recursion 标签中看到的那样。
  • 留下这样的帖子没有评论递归打开的意外二次复杂性和安全漏洞对未来的访问者和可能试图除了未能引导学生批判性地思考他们被赋予的任务之外,在生产中使用这种根本上被破坏的代码。请提前原谅我,因为我每次看到都会留下这样的评论!
  • 安全漏洞是导致应用程序崩溃的未捕获崩溃。如果学生们在这些帖子的开头写着“我意识到这是一种糟糕的做法,但我的教授让我这样做”,我会很好。但几乎从来没有这样。我对 CS 教育系统强迫学生左右用螺丝刀敲钉子而没有解释警告感到沮丧。递归实际上非常有用,所以如果他们可以简单地选择比任何替代方法都简单的问题,学生们会喜欢这个工具的。我不相信人为的例子。

标签: javascript recursion ecmascript-6


【解决方案1】:

您的实现的问题是isEven 返回一个boolean,但您尝试将结果当作数字来使用。因此,事实证明,返回结果仅基于元素的数量,因为每次翻转:

console.log("true % 2 === 0 :", true % 2 === 0);
console.log("false % 2 === 0:", false % 2 === 0);

const isEven = (arr) => (arr.length === 0) ? true : isEven(arr.slice(1)) % 2 === 0;
console.log(isEven([]));     // true
console.log(isEven([1]));    // false
console.log(isEven([1, 1])); // true

如果我们将您的函数完全写成单个条件运算符,我们可以通过添加一些日志来更好地了解发生了什么:

const isEven = (arr) => {
  if (arr.length === 0) {
    console.log("base case - finish recursion. result: true");
    return true;
  }
  const recursionValue = isEven(arr.slice(1));
  const result = recursionValue % 2 === 0;
  console.log(`After recursion: 
  recursionValue: ${recursionValue} 
  result        : ${result}`);
  return result;
}

console.log(isEven([2,4,6,9])); // true
.as-console-wrapper { max-height: 100% !important; }

或者以不同的方式说明这一点,这就是递归的解决方式:

isEven([2,4,6,9])
| --> | isEven([4,6,9]) % 2 === 0
|     | --> | isEven([6,9]) % 2 === 0
|     |     | --> | isEven([9]) % 2 === 0
|     |     |     | --> isEven([]) % 2 === 0
|     |     |     |   | --> arr.length === 0 --> true
|     |     |     |   | true % 2 === 0 --> false
|     |     |     | false % 2 === 0  --> true
|     |     | true % 2 === 0 --> false
|     | false % 2 === 0 --> true
| true

要实现您想要的,您需要每次都检查第一个元素,而不是 isEven 的结果。

  • 如果数组为空,那么整个数组一定是偶数。这是基本情况。递归结束,结果为true
  • 如果第一个元素是奇数,则并非所有数组都是偶数。这是一个终止条件。结束递归并返回false - 这意味着不会达到基本情况。
  • 如果数组中有项并且第一项是偶数,则只需使用数组的其余部分进行递归,每次检查上述两个条件。

这可确保您在第二个条件从未达到时获得true,或者在曾经达到时获得false

const isEven = (arr) => {
  if (arr.length === 0) // empty - base case
    return true;
  if (arr[0] % 2 === 1) // odd - stop and return false
    return false;
    
  return isEven(arr.slice(1)); // recursion
}

console.log(isEven([2,4,6,8])); // true
console.log(isEven([2,4,6,9])); // false

【讨论】:

  • VLAZ--> 我要感谢您对事情如何运作的彻底解释。这真的帮助我学习和提高。您在每个递归调用中检查奇数的第一个元素的用例非常聪明。最初,我想编写一个函数来检查数组中的所有元素是否为 [ 偶数 |奇怪的 ]。通过您与我分享您的知识,我能够用您的逻辑实现这一点:) --> const check = (arr) => (!arr.length) ? '偶数' : (arr[0] % 2 === 1) ? '奇数' : 检查(arr.slice(1));
  • @noirsociety 我不会返回字符串文字 'even''odd'。返回逻辑布尔真/假类型。然后,调用者可以习惯性地和动态地使用此类型来格式化字符串以获得漂亮的输出,if 需要,例如:console.log(isEven(whatever) ? "even" : "odd")。使用字符串方法,调用者不能依赖普通的布尔代码if (isEven(whatever)) {},因为所有字符串都是真实的,即使它们的内容在语言上表示错误。
  • @VLAZ 这是一个很好的观点。谢谢你教我这个例子的所有最佳实践??
【解决方案2】:

问题出在isEven(arr.slice(1)) % 2 === 0 部分。 isEven 函数应该返回一个布尔值,对其取模没有意义。

相反,您可以这样做:

isEven = arr => arr.length === 0 ? true : arr[0] % 2 === 0 && isEven(arr.slice(1));

【讨论】:

    【解决方案3】:

    前提应该是:

    数组是偶数,如果第一个元素是偶数,数组的其余部分(也是一个数组)也是偶数

    所以基本上:

    const isEven = (arr) => (arr.length === 0) ? true : arr[0] % 2 === 0 && isEven(arr.slice(1));
    

    这可以通过解构稍微重写以更好地举例说明。

    const isEven = ([first, ...rest]) =>
        (first % 2 === 0) &&
        (rest.length ? isEven(rest) : true);
    

    【讨论】:

    • "即使第一个元素是偶数,数组的其余部分(也是数组)也是偶数"这意味着一个空数组甚至不。这与[].every(x => x % 2 === 0) 不同 - 该代码返回true。 OP 使用.every() 作为比较,一个空数组满足了条件。
    • 可以添加条件,例如“数组即使是空的或......”完成。
    • 解构示例与该描述不匹配..
    • 解构示例可以使用const isEven = ([first, ...rest]) => first == undefined || (first % 2 == 0 && isEven (rest)) 处理,只要undefined 不是允许的输入,并且如果需要可以使用本地符号处理:const None = Symbol(); const isEven = ([first = None, ...rest]) => first == None || (first % 2 == 0 && isEven (rest))。 (是的,我知道括号不是必需的,但我认为它们更干净。)
    • 如果您以前从未见过Symbol,您可以在这里阅读他们的相关信息levelup.gitconnected.com/…
    猜你喜欢
    • 2022-11-14
    • 2015-01-08
    • 2018-06-08
    • 2021-12-27
    • 2016-05-07
    • 1970-01-01
    • 2021-11-19
    • 2019-03-15
    • 2023-02-05
    相关资源
    最近更新 更多