JavaScript实现指定嵌套层级的for循环

JavaScript 实现指定嵌套层级的 for 循环

前言

在 JavaScript 开发中,我们经常遇到需要处理多层嵌套循环的场景,尤其是当嵌套层数不确定时。本文将介绍如何实现指定嵌套层级的 for 循环,并通过一个实际例子:计算 2-8 个 6 位数字组成的数组中,每组抽取一个数字相乘的所有可能乘积,来展示不同的实现方法。

一、问题分析

假设我们有 2-8 个数组,每个数组包含 6 个数字,我们需要从每个数组中抽取一个数字,然后计算这些数字的乘积。我们需要找出所有可能的组合及其对应的乘积。

例如,如果有 3 个数组:

1
2
3
4
5
const arrays = [
[1, 2, 3, 4, 5, 6],
[10, 20, 30, 40, 50, 60],
[100, 200, 300, 400, 500, 600],
];

我们需要计算所有可能的组合,如:

  • 1 × 10 × 100 = 1000
  • 1 × 10 × 200 = 2000
  • 6 × 60 × 600 = 216000

二、传统嵌套循环方法

1. 固定层数的嵌套循环

对于固定数量的数组,我们可以使用传统的嵌套循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function calculateProductsFixed(arrays) {
const results = [];

if (arrays.length === 2) {
for (let i = 0; i < arrays[0].length; i++) {
for (let j = 0; j < arrays[1].length; j++) {
const product = arrays[0][i] * arrays[1][j];
results.push({
combination: [arrays[0][i], arrays[1][j]],
product: product,
});
}
}
} else if (arrays.length === 3) {
for (let i = 0; i < arrays[0].length; i++) {
for (let j = 0; j < arrays[1].length; j++) {
for (let k = 0; k < arrays[2].length; k++) {
const product = arrays[0][i] * arrays[1][j] * arrays[2][k];
results.push({
combination: [arrays[0][i], arrays[1][j], arrays[2][k]],
product: product,
});
}
}
}
}
// 可以继续添加更多层数...

return results;
}

缺点

  • 代码冗长且重复
  • 只能处理固定数量的数组
  • 不够灵活,难以维护

三、递归方法

递归是解决嵌套层级不确定问题的经典方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function calculateProductsRecursive(arrays) {
const results = [];

function backtrack(index, currentCombination, currentProduct) {
// 基本情况:已经处理完所有数组
if (index === arrays.length) {
results.push({
combination: [...currentCombination],
product: currentProduct,
});
return;
}

// 递归情况:处理当前数组的每个元素
for (let i = 0; i < arrays[index].length; i++) {
const value = arrays[index][i];
currentCombination.push(value);

// 如果是第一个元素,直接使用值;否则乘以当前值
const newProduct = index === 0 ? value : currentProduct * value;

backtrack(index + 1, currentCombination, newProduct);

// 回溯:移除当前元素,尝试下一个
currentCombination.pop();
}
}

backtrack(0, [], 1);
return results;
}

优点

  • 代码简洁
  • 可以处理任意数量的数组
  • 逻辑清晰

缺点

  • 对于大量数据可能导致栈溢出
  • 递归深度受 JavaScript 引擎限制

四、迭代方法(使用栈)

为了避免递归可能导致的栈溢出问题,我们可以使用迭代方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function calculateProductsIterative(arrays) {
const results = [];

// 使用栈来模拟递归
const stack = [
{
index: 0,
combination: [],
product: 1,
},
];

while (stack.length > 0) {
const state = stack.pop();

// 如果已经处理完所有数组,保存结果
if (state.index === arrays.length) {
results.push({
combination: [...state.combination],
product: state.product,
});
continue;
}

// 处理当前数组的每个元素
for (let i = arrays[state.index].length - 1; i >= 0; i--) {
const value = arrays[state.index][i];
const newProduct = state.index === 0 ? value : state.product * value;

stack.push({
index: state.index + 1,
combination: [...state.combination, value],
product: newProduct,
});
}
}

return results;
}

优点

  • 避免了递归的栈溢出问题
  • 可以处理更深层级的嵌套

缺点

  • 代码相对复杂
  • 需要额外的内存空间存储栈

五、笛卡尔积方法

这个问题本质上是在计算多个数组的笛卡尔积,然后对每个组合计算乘积:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function cartesianProduct(arrays) {
return arrays.reduce(
(acc, curr) => {
return acc.flatMap((c) => curr.map((n) => [...c, n]));
},
[[]]
);
}

function calculateProductsCartesian(arrays) {
const combinations = cartesianProduct(arrays);

return combinations.map((combination) => ({
combination,
product: combination.reduce((acc, val) => acc * val, 1),
}));
}

优点

  • 代码非常简洁
  • 利用了函数式编程思想
  • 易于理解和维护

缺点

  • 对于大量数据可能消耗较多内存
  • 需要支持 flatMap 的现代浏览器或 polyfill

六、生成器方法

使用 ES6 的生成器函数可以更高效地处理大量数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function* productGenerator(arrays) {
function* backtrack(index, currentCombination, currentProduct) {
if (index === arrays.length) {
yield {
combination: [...currentCombination],
product: currentProduct,
};
return;
}

for (let i = 0; i < arrays[index].length; i++) {
const value = arrays[index][i];
currentCombination.push(value);
const newProduct = index === 0 ? value : currentProduct * value;

yield* backtrack(index + 1, currentCombination, newProduct);

currentCombination.pop();
}
}

yield* backtrack(0, [], 1);
}

// 使用示例
function getProductsUsingGenerator(arrays) {
const results = [];
for (const result of productGenerator(arrays)) {
results.push(result);
}
return results;
}

优点

  • 内存效率高,适合处理大量数据
  • 可以按需生成结果,不需要一次性存储所有结果
  • 代码简洁

七、总结

本文介绍了多种实现指定嵌套层级 for 循环的方法,用于计算多个数组中各取一个元素相乘的所有可能乘积:

  1. 传统嵌套循环:适用于固定层数,但代码冗长且不灵活
  2. 递归方法:代码简洁,可以处理任意层数,但可能导致栈溢出
  3. 迭代方法:避免了递归的栈溢出问题,适合处理更深层级的嵌套
  4. 笛卡尔积方法:利用函数式编程思想,代码简洁但可能消耗较多内存
  5. 生成器方法:内存效率高,适合处理大量数据

在实际应用中,应根据具体需求选择合适的方法:

  • 对于少量数据,递归或笛卡尔积方法简单易用
  • 对于大量数据或深层嵌套,迭代或生成器方法更为合适
  • 如果需要按需处理结果,生成器方法是最佳选择