【前端面试小册】JS-第28节:实现 flatten(数组扁平化)

一、核心概念

1.1 什么是数组扁平化

数组扁平化是指将一个多维数组转换为一维数组的过程。

// 示例
[1, [2, 3], [4, [5, 6]]]  // 多维数组
// 扁平化后
[1, 2, 3, 4, 5, 6]        // 一维数组

1.2 应用场景

  • 处理嵌套数据结构
  • API 返回数据的处理
  • 树形结构转一维数组
  • 数据清洗和转换

二、基础实现

2.1 递归实现

const flatten = array => array.reduce((acc, cur) => {
    return Array.isArray(cur) ? [...acc, ...flatten(cur)] : [...acc, cur];
}, []);

// 测试
const demo1 = [2, [3, 4]];
const demo2 = [2, [3, [4, 5]], [6]];

const d1 = flatten(demo1);
const d2 = flatten(demo2);

console.log(d1);  // [ 2, 3, 4 ]
console.log(d2);  // [ 2, 3, 4, 5, 6 ]

2.2 原理解析

执行流程

graph TD
    A[开始 flatten] --> B[遍历数组元素]
    B --> C{是否为数组?}
    C -->|是| D[递归调用 flatten]
    C -->|否| E[添加到结果数组]
    D --> F[展开递归结果]
    F --> E
    E --> G{是否遍历完?}
    G -->|否| B
    G -->|是| H[返回结果]

关键点

  • 使用 reduce 遍历数组
  • 使用 Array.isArray 判断是否为数组
  • 递归处理嵌套数组
  • 使用展开运算符合并结果

三、多种实现方式

3.1 使用 reduce + 递归

function flatten(arr) {
    return arr.reduce((acc, cur) => {
        return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
    }, []);
}

3.2 使用 forEach + 递归

function flatten(arr) {
    const result = [];
    arr.forEach(item => {
        if (Array.isArray(item)) {
            result.push(...flatten(item));
        } else {
            result.push(item);
        }
    });
    return result;
}

3.3 使用 while + 展开运算符

function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

特点

  • 非递归实现
  • 每次展开一层
  • 直到没有嵌套数组

3.4 使用 flat 方法(ES2019)

// 原生方法(推荐)
const flatten = arr => arr.flat(Infinity);

// 测试
const arr = [1, [2, [3, [4]]]];
console.log(flatten(arr));  // [1, 2, 3, 4]

四、支持指定层级

4.1 递归版本

function flatten(arr, depth = Infinity) {
    if (depth === 0) {
        return arr.slice();  // 返回副本
    }
    
    return arr.reduce((acc, cur) => {
        return acc.concat(
            Array.isArray(cur) && depth > 0 
                ? flatten(cur, depth - 1) 
                : cur
        );
    }, []);
}

// 测试
const arr = [1, [2, [3, [4]]]];
console.log(flatten(arr, 1));  // [1, 2, [3, [4]]]
console.log(flatten(arr, 2));  // [1, 2, 3, [4]]
console.log(flatten(arr, Infinity));  // [1, 2, 3, 4]

4.2 优化版本

function flatten(arr, depth = Infinity) {
    return arr.reduce((acc, val) => {
        if (Array.isArray(val) && depth > 0) {
            return acc.concat(flatten(val, depth - 1));
        } else {
            return acc.concat(val);
        }
    }, []);
}

// 测试
const nestedArray = [1, [2, [3, 4], 5], 6];

console.log(flatten(nestedArray));      // 默认全部打平,输出 [1, 2, 3, 4, 5, 6]
console.log(flatten(nestedArray, 1));    // 打平一层,输出 [1, 2, [3, 4], 5, 6]
console.log(flatten(nestedArray, 2));    // 打平两层,输出 [1, 2, 3, 4, 5, 6]

五、性能优化

5.1 迭代版本(避免栈溢出)

function flatten(arr) {
    const stack = [...arr];
    const result = [];
    
    while (stack.length > 0) {
        const next = stack.pop();
        if (Array.isArray(next)) {
            stack.push(...next);
        } else {
            result.push(next);
        }
    }
    
    return result.reverse();
}

优点

  • 避免递归导致的栈溢出
  • 适合处理深度嵌套的数组

5.2 使用生成器(内存友好)

function* flattenGenerator(arr) {
    for (const item of arr) {
        if (Array.isArray(item)) {
            yield* flattenGenerator(item);
        } else {
            yield item;
        }
    }
}

function flatten(arr) {
    return [...flattenGenerator(arr)];
}

优点

  • 内存友好
  • 支持延迟计算
  • 可以处理超大数组

六、实际应用场景

6.1 处理 API 返回数据

// API 返回的嵌套数据
const apiData = [
    { id: 1, children: [
        { id: 2, children: [
            { id: 3 }
        ]}
    ]}
];

// 扁平化处理
function extractIds(data) {
    return flatten(data.map(item => {
        const ids = [item.id];
        if (item.children) {
            ids.push(...extractIds(item.children));
        }
        return ids;
    }));
}

console.log(extractIds(apiData));  // [1, 2, 3]

6.2 树形结构转一维数组

function treeToArray(tree, childrenKey = 'children') {
    return flatten(tree.map(node => {
        const result = [node];
        if (node[childrenKey] && node[childrenKey].length > 0) {
            result.push(...treeToArray(node[childrenKey], childrenKey));
        }
        return result;
    }));
}

// 测试
const tree = [
    { id: 1, name: 'A', children: [
        { id: 2, name: 'B', children: [
            { id: 3, name: 'C' }
        ]}
    ]}
];

console.log(treeToArray(tree));  // 扁平化后的数组

6.3 数据清洗

function cleanData(data) {
    return flatten(data)
        .filter(item => item != null)
        .filter(item => item !== '');
}

// 测试
const dirtyData = [1, [2, null, [3, '', 4]], undefined, 5];
console.log(cleanData(dirtyData));  // [1, 2, 3, 4, 5]

七、边界情况处理

7.1 处理空数组

function flatten(arr) {
    if (!Array.isArray(arr)) {
        throw new TypeError('Argument must be an array');
    }
    
    if (arr.length === 0) {
        return [];
    }
    
    return arr.reduce((acc, cur) => {
        return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
    }, []);
}

7.2 处理稀疏数组

function flatten(arr) {
    return arr.reduce((acc, cur) => {
        // 跳过空位
        if (cur === undefined && !(cur in arr)) {
            return acc;
        }
        return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
    }, []);
}

7.3 处理循环引用

function flatten(arr, visited = new WeakSet()) {
    if (visited.has(arr)) {
        return [];  // 避免循环引用
    }
    visited.add(arr);
    
    return arr.reduce((acc, cur) => {
        return acc.concat(
            Array.isArray(cur) ? flatten(cur, visited) : cur
        );
    }, []);
}

八、面试要点总结

核心知识点

  1. 递归实现:使用 reduce + 递归
  2. 层级控制:支持指定扁平化层级
  3. 性能优化:迭代版本避免栈溢出
  4. 边界处理:空数组、稀疏数组、循环引用

常见面试题

Q1: 如何实现数组扁平化?

答:可以使用递归 + reduce,判断元素是否为数组,如果是数组则递归处理,否则直接添加到结果数组。

Q2: 如何支持指定扁平化层级?

答:添加 depth 参数,每次递归时减 1,当 depth 为 0 时停止递归。

Q3: 如何避免栈溢出?

答:使用迭代版本,使用栈结构代替递归,或者使用生成器函数。

实战建议

  • ✅ 理解递归和迭代的区别
  • ✅ 掌握多种实现方式
  • ✅ 注意性能优化和边界情况
  • ✅ 在实际项目中合理选择实现方式
#前端面试##腾讯##银行##字节##前端#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务