【问题标题】:JavaScript: Writing this solution using higher order functionsJavaScript:使用高阶函数编写此解决方案
【发布时间】:2018-03-22 21:19:25
【问题描述】:

我处理了一个问题,其中给你一个数字数组和一个目标总和,你的工作是找到一对总和等于目标数字的数字。这是我使用简单嵌套 for 循环的解决方案:

function findPairForSum(integers, target) {
  var output = [];

  for (var i = 0; i < integers.length; i++) {

    for (var j = 0; j < integers.length; j++) {

      if (i !== j && integers[i] + integers[j] === target) {
        output.push(integers[i], integers[j]);
        return output;
      }
    }
  }
  return 'not possible';
}

findPairForSum([3, 34, 4, 12, 5, 2], 9);  // --> [4, 5]

我的问题是,有没有一种更简洁的方法来使用高阶函数(也许是 forEach?)来编写这个解决方案

这是我使用 forEach 的尝试:

function findPairForSum(integers, target) {
  var output = [];

  integers.forEach(function(firstNum) {
    integers.forEach(function(secondNum) {

      if (firstNum + secondNum === target) {
        output.push(firstNum, secondNum);
      }
    })
  })

  if (output === []) {
    return 'not possible'; 
  }  
  return output;
}

findPairForSum([3, 34, 4, 12, 5, 2], 9); // --> [ 4, 5, 5, 4 ]

我尝试在两次推送之后返回,但它没有返回任何东西。所以相反,我把回报放在最后。

为什么在最初的两次推送之后它不会返回?我希望它停在那里,只推两个数字。相反,通过将回报放在最后,它推动了 4 个数字。它应该是 [4,5] 但我得到了 [4,5,5,4] 之类的东西。

任何建议和帮助将不胜感激!

【问题讨论】:

  • 你一遍又一遍地做同样的计算。你的内部循环应该是for (var j=i+1
  • 建议:使用 C 风格的 for 循环。出于性能原因,您希望提前终止,因为您不需要所有组合。但是,您不能像使用 C 风格的 for 循环那样提前终止高阶函数

标签: javascript for-loop foreach


【解决方案1】:

假设我们有以下一组数字,我们必须找到两个数字的子集,其和为9

Numbers: 4, 5, 6

您当前的代码使用ij0 迭代到length。这意味着以下迭代符合条件:

Indices: 0, 1, 2
Numbers: 4, 5, 6   //        (i)          (j)
----------------   //         ↓            ↓
         i  j      // Numbers[0] + Numbers[1] === 9
         j  i      // Numbers[1] + Numbers[0] === 9

如您所见,数字 4 和 5 在 2 次迭代中匹配了两次:

  • i === 0 &amp;&amp; j === 1
  • i === 1 &amp;&amp; j === 0

您可以通过确保满足一个简单条件来避免这种情况:

j 必须始终大于 i

可以通过在内部for 循环中使用i + 1 初始化j 来满足此条件:

for (var i = 0; i < integers.length; i++) {
    for (var j = i + 1; j < integers.length; j++) {
        // ...
    }
}

这样,当i1 时,j 永远不会是0,因为内部 for 循环将在 i 再次递增之前运行完成。一旦发生这种情况,就会创建一个全新的内部 for 循环,其中 j 再次设置为 i + 1。结果如下图:

Indices: 0, 1, 2
Numbers: 4, 5, 6
----------------
         i  j
         X  i     // ← j can never be 0 if (i === 1),
                  //   so the same set is never evaluated twice.

也就是说,最多只检查ij的以下组合:

Indices: 0, 1, 2
----------------
         i  j
         i     j
            i  j

有没有更简洁的方法来使用高阶函数(也许是 forEach?)编写这个解决方案

对于您的用例,for 循环实际上是一个很好的解决方案。它们使您能够尽早中断 - 在您第一次找到一对有效数字之后。另一方面,forEach 或其他数组迭代器函数将始终继续,直到访问所有设置的索引。

实际上,您在第一个示例中早早使用了声明 return output;

当您对具有多个有效集合的一组号码使用 forEach 时,您将始终取回所有涉及的号码:

Indices: 0, 1, 2, 3
Numbers: 4, 5, 6, 3    //        (i)          (j)
-------------------    //         ↓            ↓
         i  j          // Numbers[0] + Numbers[1] === 4 + 5 === 9
               i  j    // Numbers[2] + Numbers[3] === 6 + 3 === 9

forEachmapreduce之类的都不允许你提前断。下面的sn-p演示了上图的这个问题:

function findPairForSum(integers, target) {
    var output = [];

    integers.forEach(function(firstNum, i) {
    
        // slice(i + 1) has the same effect as for (var j = i + 1; ...)
        integers.slice(i + 1).forEach(function(secondNum, j) {
        
            if (firstNum + secondNum === target) {
            
                // There is no way here to stop the iteration of either
                // forEach call... T_T
                output.push(firstNum, secondNum);
            }
        });
    })

    if (output.length) {
        return output;
    }

    return 'not possible';
}

console.log(findPairForSum([4, 5, 6, 3], 9)); // --> [4, 5, 6, 3]

这就是为什么我强烈建议在这个特定用例中坚持使用 for 循环。使用 for 循环,只要遇到一组有效数字,您就可以像以前那样简单地 return

function findPairForSum(integers, target) {
    for (var i = 0; i < integers.length; i++) {
        for (var j = i + 1; j < integers.length; j++) {
            if (integers[i] + integers[j] === target) {
                return [integers[i], integers[j]];
            }
        }
    }

    return 'not possible';
}

console.log(findPairForSum([4, 5, 6, 3], 9)); // --> [4, 5]

【讨论】:

  • 哇,非常感谢您!我从阅读中学到了很多。感谢您抽出宝贵时间。一个问题,如果我可以的话:我不太明白你说如果我使用 j > i 可以避免得到两个匹配项,但我看不出你在代码中的什么地方使用了它。这是在第二个循环之后的 if 语句吗?再次感谢您的透彻而深刻的回答。
  • 是的,我同意这有点含糊。我会在一分钟内更新我的答案。很高兴为您提供帮助!
  • @bax 我已经更新了我的答案,并删除了j &gt; i 符号。简而言之,j 必须始终大于 i。希望现在清楚了。
  • 但是如果更多相同的数字,你只返回一对,而不是所有可能的对(至少在最后一个代码中,例如在这个数组[3, 34, 4, 4, 12, 5, 2, 4, 5])。
  • @NinaScholz 是的。此答案假定 - (1) 只需要一对值 - (2) 返回的 2 个数字必须来自 2 个 distinct 数组指数。如果我没记错的话,这些是问题的要求。
【解决方案2】:

这可能是您的解决方案:

function findPairForSum(arr, sum) {
  var pairs = [];
  arr.forEach(n1 => {
    var n2 = arr.find(n2 => n1 + n2 == sum)
    if (n2) pairs.push([n1, n2]);
  });
  return pairs;
}


var sums = findPairForSum([3, 34, 4, 12, 6, 2], 9);
console.log(sums)

【讨论】:

    【解决方案3】:

    问题是,您从数组的开头迭代内部循环。您可以使用从外部循环的索引加一开始的副本,并在找到的值处提前退出。

    但这并不能解决多对的问题。结果完全是错误的。

    function findPairForSum(integers, target) {
        var output = [];
    
        integers.forEach(function(firstNum, i) {
            integers.slice(i + 1).some(function(secondNum) {
                if (firstNum + secondNum === target) {
                    output.push(firstNum, secondNum);
                    return true;
                }
            });
        });
        return output.length && output || 'not possible';
    }
    
    //    console.log(findPairForSum([3, 34, 4, 12, 5, 2], 9));
    console.log(findPairForSum([3, 34, 4, 4, 12, 5, 2, 4, 5], 9));

    对于解决方案,您需要记住使用了哪些对。这种方法仅适用于一个循环和一个用于计算缺失值的哈希表。

    如果找到一对,则计数器递减并将两个值推送到结果集。

    function findPairForSum(integers, target) {
        var hash = Object.create(null),
            output = [];
    
        integers.forEach(function(value) {
            if (hash[value]) {
                output.push(target - value, value);
                hash[value]--;
                return;
            }
            hash[target - value] = (hash[target - value] || 0) + 1;
        });
        return output.length && output || 'not possible';
    }
    
    console.log(findPairForSum([3, 34, 4, 4, 12, 5, 2, 4, 5], 9));

    【讨论】:

    • 谢谢,看到这个我学到了很多。我真的很喜欢一些功能。我假设 if 语句之后的'return true'是所以某些函数停止执行?最后的返回线也很聪明。感谢您回答我的问题,我从您的回答中学到了很多东西。
    • 接受的答案可能并非在所有情况下都返回想要的结果。反正。 forEach 的回调中的 return 使用 exit early 范例 结束函数,这意味着,如果可能,函数应仅使用不带 else 部分的 if 语句并使用 return 代替。 if 后面的任何其他 else 部分都没有使用它,因为 提前返回
    【解决方案4】:

    这是正常现象,因为您没有比较索引
    这个内部数组应该只遍历大于外部索引的索引。
    您可以通过在forEach 的回调函数中使用第二个参数 index 来实现:

    const ints = [3, 34, 4, 12, 5, 6, 2];
    
    function findPairForSum(integers, target) {
      let result;
      integers.forEach((val1, idx1) => {
        integers.forEach((val2, idx2) => {
          if (idx1 < idx2 && val1 + val2 === target) {
            result = [val1, val2];
          }
        })
      })
      return result;
    }
    
    console.log(findPairForSum(ints, 9));

    【讨论】:

    • 如果我们在数组中添加另一个5,这将记录[3, 6, 4, 5]
    • @JeffreyWesterkamp 已更新。因为它确实期望返回不止一对,并且forEach 不支持break。只需返回 1 对就足够了
    【解决方案5】:

    使用 reduce 你的数组到另一个总和等于目标 value:

    const ints = [3, 34, 4, 12, 6, 2];
    const value = 9;
    
    const resp = ints.reduce((acc, ele, idx, self) => {
      let found = self.find(x => x + ele == value)
      return found ? [found, ele] : acc;
    }, []);
    
    console.log(resp); // [3, 6]

    【讨论】:

      【解决方案6】:

      您可以使用 Array.prototype.some,它会在条件成立时立即停止执行。请参阅下面的代码。

      function findPairForSum(arr, sum) {
        var pairs = [];
        arr.some(n1 => {
          var n2 = arr.find(n2 => n1 + n2 == sum)
          if (n2) {
            pairs.push(n1, n2); return true;
          };
          return false;
        });
        return pairs.length > 0 ? pairs : "not possible"; 
      }
      console.log(findPairForSum([3, 34, 4, 12, 7, 2], 9));

      【讨论】:

        猜你喜欢
        • 2019-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多