数组扁平化(flat)方法实现

920 阅读3分钟

定义

数组的扁平化,就是将一个嵌套多层的数组 array (嵌套可以是任何层数)转换为只有一层的数组。

const arr = [1, 2, [3, 4], [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "你好" }];

实现思路

在数组中找到是数组类型的元素,然后将他们展开。这就是实现数组拍平 flat 方法的关键思路。

  • 第一个要解决的就是遍历数组的每一个元素;
  • 第二个要解决的就是判断元素是否是数组;
  • 第三个要解决的就是将数组的元素展开一层;

遍历数组的方案

const arr = [1, 2, [3, 4], [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "你好" }];
// for 循环
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
// for...of
for (let value of arr) {
  console.log(value);
}
// for...in
for (let i in arr) {
  console.log(arr[i]);
}
// forEach()
arr.forEach(value => {
  console.log(value);
});
// entries()
for (let [index, value] of arr.entries()) {
  console.log(value);
}
// keys()
for (let index of arr.keys()) {
  console.log(arr[index]);
}
// values()
for (let value of arr.values()) {
  console.log(value);
}
// reduce()
arr.reduce((pre, cur) => {
  console.log(cur);
}, []);
// map()
arr.map(value => console.log(value));

判断数组的方案

const arr = [1, 2, [3, 4], [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "你好" }];
arr instanceof Array
// true
arr.constructor === Array
// true
Object.prototype.toString.call(arr) === '[object Array]'
// true
Array.isArray(arr)
// true

数组元素展开一层的方案

const arr = [1, 2, [3, 4], [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "你好" }];
// 扩展运算符 + concat
[].concat(...arr)
// [1, 2, 3, 4, 1, 2, 3, [1,2,3,[1,2,3]], 5, "string", { name: "你好" }]

// concat + apply
[].concat.apply([], arr);
// [1, 2, 3, 4, 1, 2, 3, [1,2,3,[1,2,3]], 5, "string", { name: "你好" }];

// toString  + split
const arr2 =[1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]]]
arr2.toString().split(',').map(v=>parseInt(v))
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3]

具体解决方法

使用栈的思想解决

// 栈思想
function flat(arr) {
  const result = []; 
  const stack = [].concat(arr);  // 将数组元素拷贝至栈,直接赋值会改变原数组
  //如果栈不为空,则循环遍历
  while (stack.length !== 0) {
    const val = stack.pop(); 
    if (Array.isArray(val)) {
      stack.push(...val); //如果是数组再次入栈,并且展开了一层
    } else {
      result.unshift(val); //如果不是数组就将其取出来放入结果数组中
    }
  }
  return result;
}
const arr = [1, 2, 3, 4, [5, 2,  [6, 7, 3, [1, 2, 8]]], 9, "string", { name: "加油" }]
flat(arr)
// [1, 2, 3, 4, 5, 2, 6, 7, 3, 1, 2, 8, 9, "string", { name: "加油" }];

通过传入整数参数控制“拉平”层数

// reduce + 递归
function flat(arr, num = 1) {
  return num > 0
    ? arr.reduce(
        (pre, cur) =>
          pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur),
        []
      )
    : arr.slice();
}
const arr = [1, 2, 3, 4, [5, 2,  [6, 7, 3, [1, 2, 8]]], 9, "string", { name: "加油" }]
flat(arr, Infinity);
// [1, 2, 3, 4, 5, 2, 6, 7, 3, 1, 2, 8, 9, "string", { name: "加油" }];

用 reduce 实现 flat 函数

const arr = [1, 2, 3, 4, [5, 2,  [6, 7, 3, [1, 2, 8]]], 9, "string", { name: "加油" }]

// 首先使用 reduce 展开一层
arr.reduce((pre, cur) => pre.concat(cur), []);
// [1, 2, 3, 4, 5, 2,  [6, 7, 3, [1, 2, 8]], 9, "string", { name: "加油" }];

// 用 reduce 展开一层 + 递归
const flat = arr => {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
  }, []);
};
// [1, 2, 3, 4, 5, 2, 6, 7, 3, 1, 2, 8, 9, "string", { name: "加油" }];

额外注意点

考虑数组空位的情况。

flat 函数执行是会跳过空位的。ES5 大多数数组方法对空位的处理都会选择跳过空位包括:forEach(), filter(), reduce(), every() 和 some() 都会跳过空位。 所以我们可以利用上面几种方法来实现 flat 跳过空位的特性

// reduce + 递归
Array.prototype.fakeFlat = function(num = 1) {
  if (!Number(num) || Number(num) < 0) {
    return this;
  }
  let arr = [].concat(this);
  return num > 0
    ? arr.reduce(
        (pre, cur) =>
          pre.concat(Array.isArray(cur) ? cur.fakeFlat(--num) : cur),
        []
      )
    : arr.slice();
};
const arr = [1, [3, 4], , ,];
arr.fakeFlat()
// [1, 3, 4]

// foEach + 递归
Array.prototype.fakeFlat = function(num = 1) {
  if (!Number(num) || Number(num) < 0) {
    return this;
  }
  let arr = [];
  this.forEach(item => {
    if (Array.isArray(item)) {
      arr = arr.concat(item.fakeFlat(--num));
    } else {
      arr.push(item);
    }
  });
  return arr;
};
const arr = [1, [3, 4], , ,];
arr.fakeFlat()
// [1, 3, 4]