数组扁平化(一层、全部展开、指定深度)

Posted by CodingWithAlice on July 7, 2021

数组扁平化(一层、全部展开、指定深度)

参考文章:数组拍平连环问 - 只研究到第二问 reduce,没有再深入,一共七问

题目:

如何实现 flat 方法?多维数组=>一维数组

方法总结:

  • 展开一层:

    1、concat直接传一组参数:『[].concat(...arr)

    2、concat通过apply/call传参数:[].concat.apply([], arr) / [].concat.call([], ...arr)

    3、flat:arr.flat()

  • 全部展开:

    1、flat:arr.flat(Infinity)

    2、toString 法:arr.toString().split(','),toString 转成字符串会将数组全部打平

    3、reduce法递归: concat的参数为数组/值:

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

    常见 :遍历forEach组装res - 本质就是 reduce

  • 根据指定深度扁平数组 『deep-1』『deep>1』

      function flattenByDeep(arr, deep = 1) {
          return arr.reduce((pre, cur) =>
                            pre.concat(Array.isArray(cur) && deep > 1 ? flattenByDeep(cur, deep - 1) : cur)
                            , [])
      }
    

详细解答:

  • 方法一: 直接调用 Arrayflat 方法 - arr = arrays.flat(Infinity);

    作用是将嵌套的数组“拉平”,变成一维的数组。返回一个新数组,对原数据没有影响。

    • 不传参数时,默认“拉平”一层
    • 传入一个整数参数,整数即“拉平”的层数
    • Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
    • 传入 <=0 的整数将返回原数组,不“拉平”
    • 如果原数组有空位,flat() 方法会跳过空位
      const animals = ["🐷", ["🐶", "🐶"], ["🐶", ["🐑", ["🐲"]], "🐷"]];
        
      // 不传参数时,默认“拉平”一层
      animals.flat();
      // ["🐷", "🐶", "🐶", "🐶", ["🐑", ["🐲"]], "🐷"]
        
      // 传入一个整数参数,整数即“拉平”的层数
      animals.flat(2);
      // ["🐷", "🐶", "🐶", "🐶", "🐑", ["🐲"], "🐷"]
        
      // Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
      animals.flat(Infinity);
      // ["🐷", "🐶", "🐶", "🐶", "🐑", "🐲", "🐷"]
        
      // 传入 <=0 的整数将返回原数组,不“拉平”
      animals.flat(0);
      animals.flat(-10);
      // ["🐷", ["🐶", "🐶"], ["🐶", ["🐑", ["🐲"]], "🐷"]]
        
      // 如果原数组有空位,flat()方法会跳过空位
      ["🐷", "🐶", "🐂", "🐎",,].flat();
      // ["🐷", "🐶", "🐂", "🐎"]
    
  • 方法二:

    自己实现的核心思路:在数组中找到是数组类型的元素,然后将他们展开

    • 第一个要解决的就是遍历数组的每一个元素;

      for 循环for...offor...inforEach()entries()keys()values()reduce()map()

        // entries()- 返回可枚举【属性的键值对】数组
        for (let [index, value] of arr.entries()) {console.log(index, value)}
        // keys() - 返回可枚举【属性】组成的数组
        for (let index of arr.keys()) {console.log(index)}
        // values() - 返回可枚举【属性值】的对象
        for (let value of arr.values()) {console.log(value)}
        // reduce() - 详细见同目录下 reduce 博文
        arr.reduce((pre, cur) => {console.log(pre, cur); return pre;}, []);
        // map() - 一个由原数组每个元素执行回调函数的结果组成的新数组
        arr.map(value => console.log(value));
      
    • 第二个要解决的就是判断元素是否是数组; - 详细查看单独博客

      instanceofconstructor(可被改写,不准确)、Object.prototype.toString(可被改写,不准确)、isArray

        let arr = [1, 2, 3];
        arr instanceof Array; // true
        arr.constructor === Array; // true
        Object.prototype.toString.call(arr) === '[object Array]'; // true
        Array.isArray(arr);// true
      
    • 第三个要解决的就是将数组的元素 展开

      • 展开一层

      第1⃣️种:... 扩展运算符 + concat

      【注意】:扩展运算符(…)内部使用 for…of 循环

        const animals = ["🐷", ["🐶", "🐶"], ["🐶", ["🐑", ["🐲"]], "🐷"]];
        arrays = [].concat(...animals);// ["🐷", "🐶", "🐶", "🐶", Array(2), "🐷"]
      

      第2⃣️种:apply + concat

      apply 在绑定作用域时,会将传入的 数组元素作为单独的参数 的传入到要执行的函数中

        arrays = [].concat.apply([], animals);
        // 实际逐一执行类似于
        [].concat("🐷").concat(["🐶", "🐶"]).concat(["🐶", ["🐑", ["🐲"]]]).concat("🐷");
      
      • 全部展开

      第3⃣️种:toString + split

        arr2.toString().split(',');
        // toString() 方法会把数组打平 - "🐷,🐶,🐶,🐶,🐑,🐲,🐷"
      

      扩展:通过 正则 + split

        let str = JSON.stringify(arrays); // 处理成字符串
        // 将全局所有 []"" 替换成空
        arr1 = str.replace(/(\]|\[|\"|\")/g, '').split(',');
      
        let str = JSON.stringify(arrays); // 处理成字符串
        str = str.replace(/(\]|\[|\"|\")/g, '');
        str = '[' + str + ']';
        // 通过 JSON.parse 将字符串转换成数组
        ary = JSON.parse(str);
      

      第4⃣️种:递归

      找到所有数组元素,递归,直到找到底层非数组元素,返回抛出

        function flatFun(arr) {
            let res = [];
            arr.forEach(item => {
                if (Array.isArray(item)) {
                    res = res.concat(flatFun(item));
                } else {
                    res.push(item);
                }
                // 上面的 if 逻辑可以利用concat特性,简写成
                // res = res.concat(Array.isArray(item) ? flatFun(item) : item)
            });
            return res;
        }
        // 另一种写法 - 每次非数组都要执行一次递归,消耗大,不采用
        function flatFun2(target) {
            if (!Array.isArray(target)) {
                return [target]; // 同时在这边返回长度为1的数组类型
            }
            let res = [];
            target.forEach(it => res.push(...flatFun2(it))); // 这边注意一定要加 ... 因为返回的res都是数组
            return res;
        }
      

      第5⃣️种:reduce

        var flatFun = (arrays) => {
            return arrays.reduce((pre, cur) => {
        		// 写法一:        
                return pre.concat(Array.isArray(cur) ? flatFun(cur) : cur);
                // 写法二:
                // return (Array.isArray(cur) ? pre.concat(flatFun(cur)) : pre.concat(cur));
            },[])
        }
      
  • 补充情况:根据指定深度扁平数组

    • 第⑥种

        function flattenByDeep(arr, deep = 1) {
            return arr.reduce((pre, cur) =>
                              Array.isArray(cur) && deep > 1 ?
                              pre.concat(flattenByDeep(cur, deep - 1)) :
                              pre.concat(cur)
                              // 可以简写成 
                              // pre.concat(Array.isArray(cur) && deep > 1 ? flattenByDeep(cur, deep - 1) : cur)
                              , []
                             )
        }