【问题标题】:JavaScript generator function times out when trying to replicate Python's itertools.combinations尝试复制 Python 的 itertools.combinations 时,JavaScript 生成器函数超时
【发布时间】:2016-11-18 15:15:32
【问题描述】:

在这里的几个答案的帮助下,我已经能够开始学习生成器并开发以下功能:

function* icombinations(arr, k) {

  function* getCombinations(newArr, shift) {
    if (newArr.length === k) {
      yield newArr;
    }

    for (let i = shift; i < arr.length; i++) {
      yield* getCombinations([...newArr, arr[i]], i + 1);
    }
  }

  yield* getCombinations([], 0);

  return [];
}

这里是 repl.it 的链接:https://repl.it/E2QW/1

我可能还没有完全掌握这个概念,因为上面的函数会超时很长的输入,因为我试图首先生成所有可能的组合,然后产生每个组合。你知道我可以如何重构函数,这样我就不会首先生成所有组合吗?

以下是我试图解决的挑战的描述:

编写一个名为 icombinations 的函数,它应该是一个生成器函数,其行为类似于 Python 的 itertools.combinations。您将获得一个数组 arr 的唯一值 项目和一个整数k

您应该在长度为 arr 中生成每个唯一的元素组合 k 没有替换,直到没有可能的唯一 剩下的组合,此时您应该终止生成器 功能。您的生成器将使用next() 调用,在某些情况下 它将被调用直到完成。

此外,重要的是您返回的组合是相同的 order 作为原始数组arr。 (见下面的例子)....

例如:

给定一组唯一元素example_arr 和一个整数 example_k:

example_arr = ['a', 'b', 'c', 'd']example_k = 2;

调用迭代器的next()方法应该返回[ 'a', 'b' ]

如果我们再次调用next(),我们应该得到[ 'a', 'c' ] 等等 上...

所以如果我们得到生成器产生的所有值,我们将拥有 以下:

[ 'a', 'b' ] [ 'a', 'c' ] [ 'a', 'd' ] [ 'b', 'c' ] [ 'b', 'd' ] [ 'c', 'd' ] 再次注意上面的顺序,因为你需要 在您的解决方案中复制它。

还有一些需要考虑的事情:

如果您的解决方案超时,可能是因为您试图 首先生成所有可能的组合,然后产生每个组合。 这违背了发电机的观点。一些输入值将是 大。

arr 中的值始终是唯一的,但它们可能属于不同类型 (即字符串、整数、其他对象)。

您无法生成组合的唯一情况 是其中arr 为空或为空或长度小于k。在 在任何这些情况下,您都应该返回一个空数组。

【问题讨论】:

  • 先不要全部生成;这就是拥有发电机的意义所在。您的描述明确说明了这一点(第一个“要考虑的事情”)。
  • 感谢 Scott,我已经重读了几次,但不知如何修改函数。
  • 我认为唯一的问题是return []。据我所知,那不属于那里。
  • @ScottSauyet 由于挑战底部的要求,我需要添加 return [] 声明。该函数提供了所有需要的结果,唯一的问题是它对长数组超时。
  • @PiotrBerebecki 仅调用icombinations() 或迭代结果时是否超时?另外,这些“长数组”有多长?

标签: javascript python ecmascript-6 combinations permutation


【解决方案1】:

您可能会在 Code Review 方面获得更好的建议,但您可以尝试的一项改进是修剪一些“死胡同”递归路径。由于您知道每个结果的长度必须为 k,因此您应该仅在源数组中剩余足够的元素以实际完成 k 子集时进行递归。

function* icombinations(arr, k) {

    function* getCombinations(newArr, shift) {
        if (newArr.length === k) {
            yield newArr;
        } 
        // if what's available is >= what's needed
        else if (arr.length - shift >= k - newArr.length) {
            for (let i = shift; i < arr.length; i++) {
                yield* getCombinations([...newArr, arr[i]], i + 1);
            }
        }
    }

    yield* getCombinations([], 0);

    return [];
}

但是如果没有您的测试用例或对arr.lengthk 的限制,我们无法知道这是否足够好。您提到arr.length 可能是 50,这意味着当k 为 25 时,最多有 126,410,606,437,752 个子集。无论算法效率如何,都无法在任何合理的时间内完成。即使k 是 5(或等价的 45),您也会看到 2,118,760 种组合。

您可以尝试的另一件事是在内部函数之外预先分配子集数组 (newArr),然后在每次递归调用之前就地更新数组。这避免了每次要向其附加值时都需要复制newArr,但在基本情况下仍需要生成newArr 的副本。然而,与分支修剪相比,这更像是一种微优化。先尝试剪枝,看看每个更改能带来多少改进。

最后,您还可以切换到迭代实现,看看是否可行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-31
    • 2018-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    相关资源
    最近更新 更多