
本文详细介绍了如何根据一个预定义的分组大小数组来批量分割另一个元素数组。核心方法是维护一个当前处理的偏移量和已知的最大分组长度。首先按指定大小进行分组,当预设分组用尽而元素数组仍有剩余时,则按照之前遇到的最大分组长度继续切分,直至所有元素被分组。
在javaScript开发中,我们经常会遇到需要将一个大型数组按照特定规则分割成若干个子数组(或批次)的需求。其中一种复杂的场景是,分组的大小并非固定,而是由另一个数组动态指定,并且当所有指定分组用尽后,剩余的元素需要按照之前出现过的最大分组长度继续分组。本文将提供一个健壮的解决方案来处理这种动态数组分组问题。
问题描述
假设我们有一个包含多个元素的数组 elements,以及一个定义了分组大小的数组 groupSizes。我们的目标是根据 groupSizes 依次从 elements 中取出相应数量的元素形成批次。当 groupSizes 中的所有大小都被使用完毕,但 elements 数组中仍有剩余元素时,这些剩余元素应按照 groupSizes 中出现过的最大值进行分组,直到 elements 被完全处理。
示例需求:
- elements = [‘a’,’b’,’c’,’d’,’e’,’f’,’g’,’h’,’i’,’j’,’k’,’l’]
- groupSizes = [1, 3, 5]
预期输出:[[‘a’], [‘b’,’c’,’d’], [‘e’,’f’,’g’,’h’,’i’], [‘j’,’k’,’l’]]
注意,groupSizes 用尽后,剩余的 [‘j’,’k’,’l’] 按照 groupSizes 中最大值 5 进行分组,但由于只有3个元素,所以只取3个。
解决方案核心思路
为了实现上述逻辑,我们需要跟踪几个关键状态:
- 当前处理偏移量 (offset): 记录 elements 数组中下一个分组应该从哪个索引开始。
- 最大分组长度 (maxLength): 记录 groupSizes 数组中出现过的最大分组长度。这用于处理 groupSizes 用尽后的剩余元素。
- 循环迭代:
- 首先,遍历 groupSizes 数组,根据每个指定大小从 elements 中切片。
- 同时,在每次切片后更新 offset 和 maxLength。
- 当 groupSizes 遍历完毕,如果 elements 数组中仍有未处理的元素(即 offset 小于 elements.length),则继续循环,每次都按照 maxLength 进行切片,直到所有元素都被处理。
代码实现 (ecmascript 5 兼容)
/** * 根据动态分组大小数组将元素数组进行分组。 * * @param {Array} array 待分组的元素数组。 * @param {Array} groupSizes 定义分组大小的数组。 * @returns {Array<Array>} 包含所有分组的二维数组。 */ function splitIntoGroups(array, groupSizes) { var output = []; // 存储最终分组结果的数组 var maxLength = 1; // 记录 groupSizes 中出现过的最大分组长度,初始值设为1以处理最小分组 var offset = 0; // 记录当前处理到 array 的哪个位置 // 第一阶段:根据 groupSizes 数组进行分组 // 循环条件:i < groupSizes.length 确保不超出 groupSizes 边界 // offset < array.length 确保 array 还有元素可供分组 for (var i = 0; i < groupSizes.length && offset < array.length; i++) { var currentGroupSize = groupSizes[i]; // 从当前 offset 位置开始,切片 currentGroupSize 长度的元素 output.push(array.slice(offset, offset + currentGroupSize)); // 更新 offset,为下一个分组做准备 offset += currentGroupSize; // 更新 maxLength,记录出现过的最大分组长度 maxLength = math.max(maxLength, currentGroupSize); } // 第二阶段:如果 array 仍有剩余元素,则按照 maxLength 进行分组 // 循环条件:offset < array.length 确保 array 还有元素可供分组 while (offset < array.length) { // 从当前 offset 位置开始,切片 maxLength 长度的元素 output.push(array.slice(offset, offset + maxLength)); // 更新 offset offset += maxLength; } return output; }
详细解释
-
初始化:
- output = []: 用于收集所有生成的子数组。
- maxLength = 1: 初始化为1。这是因为即使 groupSizes 中最小的元素也是1,所以任何时候至少可以按1个元素分组。
- offset = 0: 表示我们从 array 的起始位置开始处理。
-
第一阶段循环 (for 循环):
- for (var i = 0; i < groupSizes.length && offset < array.length; i++): 这个循环会遍历 groupSizes 数组。offset < array.length 是一个重要的条件,它确保我们不会尝试从一个已经处理完的 array 中切片。
- output.push(array.slice(offset, offset + currentGroupSize)): 使用 slice() 方法从 array 中提取一个子数组。slice() 方法的第二个参数是不包含的结束索引。
- offset += currentGroupSize: 更新 offset 到当前分组的末尾,为下一个分组的开始做准备。
- maxLength = Math.max(maxLength, currentGroupSize): 每次迭代都比较当前 currentGroupSize 和已知的 maxLength,并更新 maxLength 为两者中的较大值。
-
第二阶段循环 (while 循环):
- while (offset < array.length): 这个循环只在 groupSizes 数组已经遍历完毕,但 array 中仍有未处理元素时执行。
- output.push(array.slice(offset, offset + maxLength)): 此时,我们使用在第一阶段确定好的 maxLength 来切片剩余的元素。
- offset += maxLength: 再次更新 offset。
使用示例
让我们通过几个示例来演示 splitIntoGroups 函数的用法和行为。
// 示例数据 var elements = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p']; var groupsShort = [1, 3, 5]; var groupsLong = [1, 3, 5, 5, 5, 1000]; // 包含超大分组,但实际只取 array.length console.log("--- 示例 1: 基础分组 (groupSizes 用尽,有剩余) ---"); // Input = ['a','b','c','d','e','f','g','h','i','j','k','l'] // Output = [['a'], ['b','c','d'],['e','f','g','h','i'],['j','k','l']] console.log(splitIntoGroups(elements.slice(0, 12), groupsShort)); // 预期输出: [ [ 'a' ], [ 'b', 'c', 'd' ], [ 'e', 'f', 'g', 'h', 'i' ], [ 'j', 'k', 'l' ] ] console.log("n--- 示例 2: 元素数组小于 groupSizes 总和 ---"); // Input = ['a','b','c'] // Output = [['a'], ['b','c']] console.log(splitIntoGroups(elements.slice(0, 3), groupsShort)); // 预期输出: [ [ 'a' ], [ 'b', 'c' ] ] console.log("n--- 示例 3: 元素数组刚好覆盖 groupSizes 部分 ---"); // Input = ['a','b','c','d','e'] // Output = [['a'], ['b','c','d'], ['e']] console.log(splitIntoGroups(elements.slice(0, 5), groupsShort)); // 预期输出: [ [ 'a' ], [ 'b', 'c', 'd' ], [ 'e' ] ] console.log("n--- 示例 4: 元素数组远大于 groupSizes 总和 ---"); // Input = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'] // Output = [['a'], ['b','c','d'],['e','f','g','h','i'],['j','k','l','m','n'], ['o','p']] console.log(splitIntoGroups(elements, groupsShort)); // 预期输出: [ [ 'a' ], [ 'b', 'c', 'd' ], [ 'e', 'f', 'g', 'h', 'i' ], [ 'j', 'k', 'l', 'm', 'n' ], [ 'o', 'p' ] ] console.log("n--- 示例 5: groupSizes 中包含超大值,但不会超出 array 长度 ---"); // 此时 groupSizes 中的 1000 实际上会被 array 的剩余长度截断 console.log(splitIntoGroups(elements, groupsLong)); // 预期输出: [ [ 'a' ], [ 'b', 'c', 'd' ], [ 'e', 'f', 'g', 'h', 'i' ], [ 'j', 'k', 'l', 'm', 'n', 'o', 'p' ] ]
注意事项与总结
- ECMAScript 5 兼容性: 此解决方案完全使用ES5语法编写,可以在较旧的javascript环境中运行。
- slice() 方法: slice() 方法在JavaScript中创建数组的浅拷贝,这意味着它不会修改原始 array。
- maxLength 的重要性: maxLength 的跟踪是处理 groupSizes 用尽后剩余元素的关键。它确保了后续分组的逻辑一致性。
- 数组长度与分组大小: 即使 groupSizes 中某个值非常大(例如1000),slice() 方法也会自动截断到 array 的实际剩余长度,所以不会出现越界错误或空元素。
- 灵活性: 这种方法提供了一个高度灵活的机制,可以根据业务需求动态地调整分组策略。
通过以上步骤和代码示例,您可以有效地将数组元素按照动态指定的分组大小进行批量处理,并优雅地处理所有剩余元素,确保数据的完整性和逻辑的连贯性。