• 面试20个人居然没有一个写出数组扁平化?如何手写flat函数


    前言

    由于上半年参加了校招的技术面试, 前前后后面了20多个人了, 每次面试都会让应聘者手写一下数组扁平化flat(),但是发现居然没有一个能够完成写出来, 所以打算总结一下如果遇到了数组扁平化的题目(也可以叫做手动封装flat()方法),到底应该怎么写,怎么写可以得更高的分;

    话不多说, 接下来我将站在面试官的角度分析所有常见的实现方法, 并附上对应的得分情况: 五星打分制

    满分: ⭐⭐⭐⭐⭐

    题目

    题目描述:

    已有多级嵌套数组 : [1, [2, [3, [4, 5]]], 6] 将其扁平化处理
    输出: [1,2,3,4,5,6]

    什么是扁平化

    定义 : 扁平化就是将多维数组变成一维数组,不存在数组的嵌套

    实现扁平化的方法 封装 flatten

    1. ES6 flat

    flat(depth)
    方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

    参数:

    depth(可选) 指定要提取嵌套数组的结构深度,默认值为 1

    返回值:

    返回一个新数组,包含数组与提取嵌套数组的所有元素的新数组

    使用 Infinity,可展开任意深度的嵌套数组

    封装思路: 使用 es6 自带 API 处理

     const arr = [1, [2, [3, [4, 5]]], 6]
    
     function flatten(params) {
       return params.flat(Infinity)
     }
     console.log(flatten(arr));
    
     // 输出: [1,2,3,4,5,6]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    得分 :

    直接使用自带的方法可以很快的实现, 但是面试官当然不希望就看到这些呀 !

    2. toString

    如果数组的项全为数字,可以使用join(),toString()可以利用数组toString() 转为字符串

    function flatten(arr) {
      return arr.toString().split(',').map(item =>parseFloat(item))
    }
    console.log(flatten(arr));
    // 输出:[ 1, 2, 3, 4, 5, 6 ]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    得分 : ⭐ (并不是要考你的数组的方法调用)

    3. 使用正则替换

    看到嵌套的数组,如果在字符串的角度上看就是多了很多[],如果把它们替换就可以实现简单的扁平化

    function flatten (arr) {
      console.log('JSON.stringify(arr)', typeof JSON.stringify(arr))
      let str= JSON.stringify(arr).replace(/(\[|\])/g, '');
      str = '[' + str + ']';
      arr = JSON.parse(str);
      return arr
    }
    console.log(flatten(arr))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    **得分 : **⭐ (并不是要考你的数组的方法调用)

    4. 循环递归

    4.1 循环 + concat + push

    当只有一层嵌套数组使用push的方式扁平化

    [1, [2, 3,4,5,6]]

    let result = [];
    for (let i = 0; i < arr2.length; i++) {
      
      result = result.concat((arr2[i]));
    
    }
    
    console.log(result);
    [ 1, 2, 3, 4, 5, 6 ]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果有多层嵌套的数组就需要使用 递归的思想 :

    思路

    1. 循环判断数组的每一项是否是数组: Array.isArray(arr[i])
    2. 是数组就递归调用上面的扁平化一层的代码 result = result.concat(flatten(arr[i]));
    3. 不是数组,直接通过push添加到返回值数组
    function flatten(arr) {
      let result = [];
      for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
          result = result.concat(flatten(arr[i]));
        } else {
          result.push(arr[i])
        }
      }
      return result
    }
    
    console.log(flatten(arr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    或者使用forEach 立即执行函数

    // 递归版本的反嵌套
    function flatten(array) {
      var flattend = [];
      (function flat(array) {
        array.forEach(function(el) {
          if (Array.isArray(el)) flat(el);
          else flattend.push(el);
        });
      })(array);
      return flattend;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当然循环可以更改成forEach循环,for of …等其他循环,简单的循环递归就能够一样的解决啦~

    得分: ⭐⭐⭐ (能够使用递归写出数组扁平化,缺少控制层级关系)

    4.2 增加参数控制扁平化深度

    这个可以理解为手写flat()方法啦~

    // forEach 遍历数组会自动跳过空元素
    const eachFlat = (arr = [], depth = 1) => {
      const result = []; // 缓存递归结果
      // 开始递归
      (function flat(arr, depth) {
        // forEach 会自动去除数组空位
        arr.forEach((item) => {
          // 控制递归深度
          if (Array.isArray(item) && depth > 0) {
            // 递归数组
            flat(item, depth - 1)
          } else {
            // 缓存元素
            result.push(item)
          }
        })
      })(arr, depth)
      // 返回递归结果
      return result;
    }
    
    // for of 循环不能去除数组空位,需要手动去除
    const forFlat = (arr = [], depth = 1) => {
      const result = [];
      (function flat(arr, depth) {
        for (let item of arr) {
          if (Array.isArray(item) && depth > 0) {
            flat(item, depth - 1)
          } else {
            // 去除空元素,添加非 undefined 元素
            item !== void 0 && result.push(item);
          }
        }
      })(arr, depth)
      return result;
    }
    
    • 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

    得分: ⭐⭐⭐⭐ (能够使用递归写出数组扁平化,可以通过参数控制层级关系)

    4.3 巧用 reduce

    reduce
    方法为数组中的每个元素按序执行一个reducer函数,每一次运行 reducer 会将先前元素的计算结构作为参数传入,最后将其结果汇总为单个返回值

    参数:

    1. callbackFn
      一个 reducer 函数,包含四个参数:
    • previousVal :上一次调用callbackFn时的返回值,在第一次调用时,若指定了初始值initialValue,previousVal 的值就位 initialValue,否则初始值就是为数组的索引为 0 的元素
    • currentVal:数组中正在处理的元素,在第一次调用时,若指定了初始值,其值则为数组索引为 0 的元素 array[0],否则为 array[1]
    • currentIndex: 数组中正在处理的元素的索引,若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始
    • array : 用于遍历的数组
    1. initialValue(可选) : 作为第一次调用 callback 函数时参数 previousValue 的值

    返回值: 使用 reducer 回调函数遍历整个数组后的结果

    思路:

    当我们使用 reduce 来解析第一层数组,可以得到:

    const arr = [1, [[2, 3], 4],5]
    const result = arr.reduce((acc, val) => acc.concat(val), []);
    console.log(result);
    // 输出: [1,[2,3],4,5]
    
    • 1
    • 2
    • 3
    • 4

    可以看出上面的代码可以扁平化一层数组,对于多层级嵌套的数组, 这个时候就需要使用递归的思想来解决问题了,再次遍历数组,发现数组元素任然是数组的时候,再次执行上面扁平化

    手写方法

    const arr = [1, [[2, 3], 4],5]
    const flatten = (arr, deep = 1) => {
        if (deep <= 0) return arr;
        return arr.reduce((res, curr) => res.concat(Array.isArray(curr) ? flatten(curr, deep - 1) : curr), [])
    }
    // function flatten (arr,deep=1) {
    // return   arr.reduce((acc,val) => acc.concat(Array.isArray(val)? flatten(val,deep-1):val),[])
    // }
    console.log(arr, Infinity);
    // 输出:[ 1, 2, 3, 4, 5, 6 ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    得分: ⭐⭐⭐⭐ (能够使用递归写出数组扁平化,巧用reduce方法可加分)

    4.4 使用 Generator 函数

    GeneratorFunction是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

    它不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别。

    整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。Generator 函数的执行方法如下。

    构造器生成新的生成器函数

    function* flatten(array) {
        for (const item of array) {
            if (Array.isArray(item)) {
                yield* flatten(item);
            } else {
                yield item;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    得分: ⭐⭐⭐ (使用Generator 函数 进行递归)

    5. 使用堆栈 stack 避免递归

    递归循环都可通过维护一个堆结构来解决

    如果不使用递归数组来实现扁平化,可以使用堆栈来解决

    深度的控制比较低效,因为需要检查每一个值的深度

    思路:

    1. 把数组通过一个栈来维护
    2. 当栈不为空的时候循环执行处理
    3. pop()将栈尾出栈
    4. 如果出栈的元素是数组,就将该元素解构后每一元素进行入栈操作
    5. 出栈的元素不是数组就push进返回值res
    6. 反转恢复原数组的顺序
    var arr1 = [1,2,3,[1,2,3,4, [2,3,4]]];
    function flatten(arr) {
      const stack = [...arr];
      const res = [];
      while (stack.length) {
        // 使用 pop 从 stack 中取出并移除值
        const next = stack.pop();
        if (Array.isArray(next)) {
          // 使用 push 送回内层数组中的元素,不会改动原始输入
          stack.push(...next);
        } else {
          res.push(next);
        }
      }
      // 反转恢复原数组的顺序
      return res.reverse();
    }
    flatten(arr1);// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    得分: ⭐⭐⭐⭐ (使用数据结构栈的特性取代递归操作,减少时间复杂度)

    6.while 循环+ some方法

    some :方法测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。

    思路

    通过some来判断数组中是否用数组,通过while不断循环执行判断,
    如果是数组的话可以使用 拓展运算符... … 每次只能展开最外层的数组,加上contact来减少嵌套层数,

    function flatten(arr) {
      while (arr.some(item=> Array.isArray(item))) {
        console.log(...arr)
        arr = [].concat(...arr)
        console.log(arr)
      }
      return arr
    }
    console.log(flatten(arr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    得分: ⭐⭐⭐⭐ (使用while循环取消递归操作, 巧用some操作进行判断)

    总结

    实现数组扁平化的方法有很多, 评分也每个人有各自的看法, 欢迎大家在评论区留下你自己对每个实现方式的评分,一起学习交流哦!

    不会没关系,最重要的是肯学
    大家可以关注我的公众号:程序员Better 让我们一起学习呀~

  • 相关阅读:
    从零到使用vue工程
    Vue3学习记录——(7) 异步加载defineAsyncComponent与Suspense组件
    第07章 文本信息提取
    使用tkinter开发GUI程序6 -- 事件响应
    日志异常检测准确率低?一文掌握日志指标序列分类
    数字称重传感器——电子秤方案传感器应用
    java类加载过程
    2023-09-20 Android CheckBox 让文字显示在选择框的左边
    HarmonyOS小白入门设备开发的“芯”路历程
    【计算机网络实验/wireshark】tcp建立和释放
  • 原文地址:https://blog.csdn.net/pz1021/article/details/127710791