【问题标题】:Trying to make a recursive function that returns all combinations of an input array. I keep getting a Type Error 'cannot read properties of undefied'尝试创建一个返回输入数组的所有组合的递归函数。我不断收到类型错误“无法读取未定义的属性”
【发布时间】:2021-11-25 16:16:47
【问题描述】:

我很困惑为什么会收到错误,因为我传入了一个已定义且应该有长度的数组。然而,我不断收到“第 XXX 行的类型错误:无法读取未定义的属性(读取'长度')”

这是我正在处理的代码...

function getAllCombos(arr) {
    // declare an array to hold results
  let results = [];
  // declare an innerfunction which takes a prefix and the original array passed in
  function recurse(prefix, arr) { 
    // loop through the original array, our base case will be when the loop ends
    for (let i = 0; i < arr.length; i++) { 
    // push the results of spreading the prefix into an array along with the current element both in an array
    results.push([...prefix, arr[i]]);
    // recursive case: now we build the prefix, we recurse and pass into our prefix parameter an array consisting of the prefix spread in, the current element being iterated on, and the original array sliced after our current element
    recurse([...prefix, arr[i], arr.slice(i+1)])
    }
  }
  // call the inner function with an empry prefix argument and the original array
  recurse([], arr);
  // return the results array
  return results;
}

这里有一些测试用例...

console.log(getAllCombos(['a', 'b'])); // -> [['a','b'], ['a'], ['b'], []]
console.log(getAllCombos(['a', 'b', 'c']));
// -> [
//   ['a', 'b', 'c'],
//   ['a', 'b'],
//   ['a', 'c'],
//   ['a'],
//   ['b', 'c'],
//   ['b'],
//   ['c'],
//   [],
// ]

有人可以向我解释为什么即使我传入一个有长度的数组,我仍然会收到此错误消息吗?

感谢您的指点!

【问题讨论】:

  • function recurse(prefix, arr) 接受两个参数,你传递一个:recurse([...prefix, arr[i], arr.slice(i+1)])
  • 我能看到的是你有一个变量阴影。 arr 在上层作用域和recurse 函数中,在递归调用中,你永远不会通过上层作用域arr,因为你已经定义了内部作用域undefined。不要使用相同的名称。并通过 var 也
  • 谢谢!你们俩!

标签: javascript recursion typeerror combinations


【解决方案1】:

哪里出错了

我认为你打错了recurse -

recurse([...prefix, arr[i], arr.slice(i+1)]) // <- notice `]`

] 移动到arr[i] 之后-

recurse([...prefix, arr[i]], arr.slice(i+1))

新的开始

也就是说,我认为使用更简单的功能可以改善一切 -

function* combinations(t) {
  if (t.length == 0) return yield []
  for (const combo of combinations(t.slice(1))) {
    yield [ t[0], ...combo ]
    yield combo
  }
}

// array
for (const combo of combinations(["a", "b"]))
  console.log(`(${combo.join(",")})`)
  
// or even strings
for (const combo of combinations("abc"))
  console.log(`(${combo.join(",")})`)
(a,b)
(b)
(a)
()
(a,b,c)
(b,c)
(a,c)
(c)
(a,b)
(b)
(a)
()

一些优化

.slice 以上是有效的,但确实会创建一些不必要的中间值。我们可以使用索引i,就像您在原始程序中所做的那样 -

function combinations(t) {
  function* generate(i) {
    if (i >= t.length) return yield []
    for (const combo of generate(i + 1)) {
      yield [ t[i], ...combo ]
      yield combo
    }
  }
  return generate(0)
}

for (const combo of combinations(["?","?","?","?"]))
  console.log(combo.join(""))
  
????
???
???
??
???
??
??
?
???
??
??
?
??
?
?

要将生成器中的所有值收集到一个数组中,请使用Array.from -

const allCombos = Array.from(combinations(...))
  • 要计算固定大小的组合,n 选择 k,请参阅 this Q&A
  • 要使用类似技术计算排列,请参阅this related Q&A

没有生成器

使用生成器解决组合问题的主要优点是每个结果都是延迟提供的,并且可以随时停止/恢复计算。但是,有时您可能想要所有结果。在这种情况下,我们可以跳过使用生成器并急切地计算包含每个可能组合的数组 -

const combinations = t =>
  t.length == 0
    ? [[]]
    : combinations(t.slice(1)).flatMap(c => [[t[0],...c], c])
  
const result =
  combinations(["a", "b", "c"])
  
console.log(JSON.stringify(result))
[["a","b","c"],["b","c"],["a","c"],["c"],["a","b"],["b"],["a"],[]]

【讨论】:

  • 哇,谢谢...我真的需要研究“产量”和“生成”。我什至从未听说过最后一个...谢谢!这教会了我很多!您提供的第一个解决方案让我非常费解……您是如何学会这样思考的?
  • 直到我仔细阅读sicp 之后,我才开始对编程有意义,并且我可以用新的方式思考和“看”事物。我没有这么明确地说,但我们在这篇文章中使用了归纳推理来构建程序。我有很多答案about recursion and inductive thinkingtopic of generators 上的许多其他答案。如果您有任何其他问题,我很乐意为您提供帮助:D
  • 我还应该提到 sicp 包含随附的 video lectures 由 MIT 提供。在任何情况下都不要跳过这些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-23
  • 1970-01-01
  • 2015-10-10
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 2021-02-12
相关资源
最近更新 更多