实现数组 flatten 方法

基础知识

  1. 什么是数组扁平化? 数组扁平化(Flatten)是指将一个包含嵌套数组的数组,转换为一个不包含嵌套数组的一维新数组。

    // 原始数组
    const nestedArray = [1, [2, 3], [4, [5, 6]]];
    
    // 扁平化后的数组
    const flattenedArray = [1, 2, 3, 4, 5, 6];
    
  2. 扁平化的深度 (Depth) 我们可以选择将数组扁平化到指定的深度。

    • 完全扁平化: 将所有层级的嵌套数组都展开,如上例所示。
    • 扁平化指定深度: 只展开指定层数的嵌套。
    const nestedArray = [1, [2, 3], [4, [5, 6]]];
    // 只扁平化一层
    const oneLevelFlat = [1, 2, 3, 4, [5, 6]];
    
  3. ES2019 内置方法: Array.prototype.flat() 现代 JavaScript 已经内置了 flat() 方法,可以直接使用。它非常方便,也是我们自己实现功能时的对标标准。

    • arr.flat(): 默认扁平化一层。
    • arr.flat(depth): 扁平化指定的 depth 深度。
    • arr.flat(Infinity): 完全扁平化所有层级。

核心思路

手动实现数组扁平化,主要有以下几种核心思路:

  1. 递归 (Recursion): 这是最直观、最容易理解的思路。

    • 遍历数组的每一个元素。
    • 判断当前元素是否还是一个数组。
    • 如果是数组,就对这个子数组递归调用扁平化函数,并将返回的结果合并到最终结果中。
    • 如果不是数组,就直接将其推入最终结果数组中。
    • 这是实现完全扁平化的经典方法。
  2. 迭代与栈 (Iteration with a Stack): 这种方式可以避免递归过深导致的“栈溢出”问题。

    • 创建一个栈(可以用数组模拟),并将原始数组作为初始元素压入。
    • 当栈不为空时,循环执行以下操作:
      • 从栈顶弹出一个元素。
      • 判断该元素是否为数组。
      • 如果是数组,则将其所有元素逆序推入栈中(逆序是为了保证最终结果的顺序正确)。
      • 如果不是数组,则将其收集到结果数组中。
    • 由于结果是反向收集的,最后需要将结果数组反转一次。
  3. 利用 reduce: 这是一种更具函数式编程风格的实现方式。

    • 使用 Array.prototype.reduce 方法遍历数组。
    • reduce 的累加器是一个新数组(我们的最终结果)。
    • 在每次迭代中,检查当前元素是否为数组。
    • 如果是数组,就递归地扁平化它,并将其与累加器数组合并(concat)。
    • 如果不是数组,就直接将其与累加器数组合并。

代码实现

下面我们提供几种不同思路的实现。

版本一:递归实现 (最经典)

/**
 * 递归实现数组完全扁平化
 * @param {Array} arr - 需要扁平化的数组
 * @returns {Array} - 返回一个新的扁平化后的数组
 */
function flattenByRecursion(arr) {
  let result = [];

  arr.forEach((item) => {
    // 1. 判断元素是否为数组
    if (Array.isArray(item)) {
      // 2. 如果是数组,则递归调用,并合并结果
      result = result.concat(flattenByRecursion(item));
    } else {
      // 3. 如果不是数组,直接推入结果数组
      result.push(item);
    }
  });

  return result;
}

版本二:使用 reduce 实现

/**
 * 使用 reduce 实现数组完全扁平化
 * @param {Array} arr - 需要扁平化的数组
 * @returns {Array} - 返回一个新的扁平化后的数组
 */
function flattenByReduce(arr) {
  return arr.reduce((accumulator, current) => {
    return accumulator.concat(
      Array.isArray(current) ? flattenByReduce(current) : current
    );
  }, []); // 初始值是一个空数组
}

版本三:迭代与栈实现 (非递归)

/**
 * 使用迭代和栈实现数组完全扁平化,避免栈溢出
 * @param {Array} arr - 需要扁平化的数组
 * @returns {Array} - 返回一个新的扁平化后的数组
 */
function flattenByStack(arr) {
  const stack = [...arr]; // 将原始数组元素复制到栈中
  const result = [];

  while (stack.length > 0) {
    // 从栈顶取出一个元素
    const item = stack.pop();

    if (Array.isArray(item)) {
      // 如果是数组,将其元素逆序推入栈中
      // 使用 spread operator (...) 可以方便地将元素展开
      stack.push(...item);
    } else {
      // 如果不是数组,则收集到结果中
      result.push(item);
    }
  }

  // 因为是从栈顶弹出,所以结果是反的,需要反转一次
  return result.reverse();
}

代码解析

递归版解析

  1. 数组判断if (Array.isArray(item)) 使用 Array.isArray() 是判断值是否为数组的最可靠方法,避免了 instanceof 在跨窗口/框架环境中的问题。
  2. 递归核心逻辑result.concat(flattenByRecursion(item)) 当遇到子数组时,不是直接添加到结果,而是递归调用自身将子数组拍平,再通过 concat 合并到主结果数组,实现多层嵌套数组的扁平化。
  3. 递归出口result.push(item) 当元素不是数组时,递归结束,直接将元素收集到结果数组,作为递归终止条件。

reduce 版解析

  1. reduce 特性利用arr.reduce((accumulator, current) => { ... }, []) 使用 reduce 方法从左到右遍历数组,累加器 accumulator 初始化为空数组,用于收集扁平化结果。
  2. 累加器与当前值
    • accumulator:最终返回的扁平化数组,每次迭代更新为新的合并数组。
    • current:当前处理的数组元素,可能是基本类型或子数组。
  3. 三元运算符处理
    • current 是数组,递归调用 flattenByReduce(current) 拍平子数组,再与 accumulator 合并。
    • current 不是数组,直接与 accumulator 合并,利用 concat 自动将非数组元素转为单元素数组合并。

迭代与栈版解析

  1. 栈初始化const stack = [...arr] 将原始数组元素"摊平"一层放入栈中,作为处理起点,模拟递归调用栈。
  2. 迭代处理循环while (stack.length > 0) 只要栈中有待处理元素,就从栈顶取出元素处理,避免递归可能导致的栈溢出(如处理深度嵌套数组)。
  3. 栈操作逻辑
    • stack.pop():从栈顶取出元素处理,由于栈的后进先出特性,处理顺序与原始数组顺序相反。
    • stack.push(...item):若取出的是数组,用展开语法 ...item 将其元素逐个入栈,确保子数组元素被正确分解处理。
  4. 结果顺序调整result.reverse() 由于从栈顶(数组末尾)开始处理,收集的结果顺序与原始数组相反,需调用 reverse() 恢复正确顺序。
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""