You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript惰性扁平化生成器:优化多倍数求和代码

惰性求值优化:无需转数组扁平化Range生成器

我已经完成了Exercism上的sum-of-multiples习题,现有两个实现版本,但现在想让代码具备更强的惰性求值特性——能不能扁平化range生成器,不用每次都把它转成数组[...range(n, limit_in, n)]?

现有版本代码

版本一

function sum(multiples, limit_in) {
  return [...multiples.map(n => [...range(n, limit_in, n)])
    .reduce((numbers, array) => {
      array.forEach(value => numbers.add(value));
      return numbers;
    }, new Set())
    .values()]
    .reduce((sum, n) => sum + n, 0);
}

function* range(start, stop, step = 1) {
  if (stop == null) {
    stop = start;
    start = 0;
  }

  for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
    yield i;
  }
}

const result = sum([5, 6, 8], 150);
console.log(result); // The result is 4419

版本二

function sum(multiples, limit_in) {
  return [...multiples.flatMap(n => [...range(n, limit_in, n)])
    .reduce((numbers, value) => {
      numbers.add(value);
      return numbers;
    }, new Set())
    .values()]
    .reduce((sum, n) => sum + n, 0);
}

function* range(start, stop, step = 1) {
  if (stop == null) {
    stop = start;
    start = 0;
  }

  for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
    yield i;
  }
}

const result = sum([5, 6, 8], 150);
console.log(result); // Result 4419

优化方案:全程惰性迭代,避免中间数组

当然可以!核心思路是直接遍历每个range生成器的迭代器,不用先转成数组。我们可以改写sum函数,直接迭代每个multiples对应的range生成器,把值加入Set,最后再迭代Set求和——全程没有中间数组,完全保持惰性。

优化后的代码:

function sum(multiples, limit_in) {
  const uniqueMultiples = new Set();
  // 直接遍历每个range生成器,惰性取值
  for (const n of multiples) {
    for (const value of range(n, limit_in, n)) {
      uniqueMultiples.add(value);
    }
  }
  // 迭代Set求和,同样惰性
  let total = 0;
  for (const num of uniqueMultiples) {
    total += num;
  }
  return total;
}

function* range(start, stop, step = 1) {
  if (stop == null) {
    stop = start;
    start = 0;
  }

  for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
    yield i;
  }
}

const result = sum([5, 6, 8], 150);
console.log(result); // 输出 4419

为什么这更惰性?

  • 原版本里的[...range(...)]会一次性把生成器的所有值都提取出来存入数组,提前占用内存;优化后只有在遍历到对应值时才会让生成器产生下一个值。
  • 整个流程没有创建任何中间数组(比如map/flatMap返回的数组),内存效率更高,尤其是当limit_in很大的时候优势明显。

如果想保持函数式风格,也可以写一个扁平化生成器的生成器函数:

function* flatGenerate(generators) {
  for (const gen of generators) {
    yield* gen;
  }
}

function sum(multiples, limit_in) {
  const uniqueMultiples = new Set();
  for (const val of flatGenerate(multiples.map(n => range(n, limit_in, n)))) {
    uniqueMultiples.add(val);
  }
  return [...uniqueMultiples].reduce((a, b) => a + b, 0);
}

这个版本用yield*来扁平化多个生成器,同样避免了中间数组,保持惰性——唯一的数组是最后转Set求和时的[...uniqueMultiples],如果想完全避免,还是用前面的for循环求和更彻底。

内容的提问来源于stack exchange,提问作者Laszlo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:13:36