# ES6 flat 与数组扁平化

# 前言

  ;flat (opens new window) 用于将多维数组拉平(扁平化),不影响原数组,返回新的数组。

[1, 2, [3, [4]]].flat() // [1, 2, 3, [4]]

  仅有一个参数depth,用于指定拉平的深度,默认值为1。若depth指定为非正数,将返回原数组,指定为Infinity,无论多少层都将扁平化为一维数组。

[1, 2, [3, [4]]].flat(2) // [1, 2, 3, 4]
[1, 2, [3, [4]]].flat(0) // [1, 2, [3, [4]]]
[1, 2, [3, [4]]].flat(Infinity) // [1, 2, 3, 4]

# 二维扁平化

  有很多种替代的实现方案,但是无论是哪一种,核心思路或者原理都是一样的。即不论是多少维的数组,都是一层一层降维下来的,解决了一层的降维问题,多维也就迎刃而解。而至于多维扁平化,无非就是还要配合递归或者循环。

  因此先来实现二维数组的扁平化,为什么不是一维呢,因为一维本身就是扁平的。主要的核心函数是 concat (opens new window),结合相关的操作,将实现降维的功能。另外contat也会返回新的数组,并不会影响原数组,此特性也与flat相契合。

# 扩展运算符

  ;flat与扩展运算符(...)结合,可将数组降维。

var list = [1, 2, [3, 4]]

[].concat(...list) // [1, 2, 3, 4]

# apply

  ;apply可以将数组转换为参数序列,相当于对数组降维。

var list = [1, 2, [3, 4]]

[].concat.apply([], list) // [1, 2, 3, 4]

# 循环遍历

  普通循环也能对数组降维。

var list = [1, 2, [3, 4]]

for (var i = 0, result = []; i < list.length; i++) {
  result = result.concat(list[i])
}
result // [1, 2, 3, 4]

  ;reduce循环。

var list = [1, 2, [3, 4]]

var result = list.reduce((pre, cur) => {
  return pre.concat(cur)
}, [])
result // [1, 2, 3, 4]

# 多维扁平化

  解决了二维扁平化的问题,而对于多维,结合上递归或者循环即可。

# 循环递归

  其中Array.isArray用于判断元素是否为数组类型。

function flatten(arr) {
  for (var i = 0, result = []; i < arr.length; i++) {
    var item = arr[i]

    result = result.concat(Array.isArray(item) ? flatten(item) : item)
  }

  return result
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, 4]

  指定深度的形式。

function flatten(arr, depth = 1) {
  var result = []

  if (depth > 0) {
    for (var i = 0; i < arr.length; i++) {
      var item = arr[i]

      result = result.concat(Array.isArray(item) ? flatten(item, depth - 1) : item)
    }
  } else {
    result = arr.slice()
  }

  return result
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, [4]]

# reduce 递归

  相较于普通循环,reduce会简洁很多。

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

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, 4]

  指定深度的形式。

function flatten(arr, depth = 1) {
  return depth > 0
    ? arr.reduce((pre, cur) => {
      return pre.concat(Array.isArray(cur) ? flatten(cur, depth - 1) : cur)
    }, [])
    : arr.slice()
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, [4]]

# some 循环

function flatten(arr) {
  while (arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr)
    // 或者扩展运算符
    // arr = [].concat.apply([], arr)
  }

  return [...arr]
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, 4]

  指定深度的形式。

function flatten(arr, depth = 1) {
  while (arr.some(item => Array.isArray(item)) && depth > 0) {
    arr = [].concat(...arr)
    depth--
  }

  return [...arr]
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, [4]]

# 堆栈

  注意堆栈的原理是利用unshift与扩展运算符。

function flatten(arr) {
  var result = []
  var stack = [...arr]

  while (stack.length) {
    var item = stack.shift()

    if (Array.isArray(item)) {
      stack.unshift(...item)
    } else {
      result.push(item)
    }
  }

  return result
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, 4]

  指定深度的形式,由于每个成员都将由toDepthArray保存深度,相对来说执行比较低效。

function flatten(arr, depth = 1) {
  var result = []
  var stack = toDepthArray(arr, depth)

  while (stack.length) {
    var { value, depth } = stack.shift()

    if (Array.isArray(value) && depth > 0) {
      stack.unshift(...toDepthArray(value, depth - 1))
    } else {
      result.push(value)
    }
  }

  function toDepthArray(arr, depth) {
    return arr.map(value => ({ value, depth }))
  }

  return result
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, [4]]

# Generator

  注意flat将返回遍历器,还要用扩展运算符来获取最终结果。

function flatten(arr) {
  function* flat(arr) {
    for (var item of arr) {
      if (Array.isArray(item)) {
        yield* flat(item)
      } else {
        yield item
      }
    }
  }

  return [...flat(arr)]
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, 4]

  指定深度的形式。

function flatten(arr, depth = 1) {
  function* flat(arr, depth) {
    for (var item of arr) {
      if (Array.isArray(item) && depth > 0) {
        yield* flat(item, depth - 1)
      } else {
        yield item
      }
    }
  }

  return [...flat(arr, depth)]
}

var list = [1, 2, [3, [4]]]
flatten(list) // [1, 2, 3, [4]]

# 常见问题

# 原型重写

  以循环递归为例。

Array.prototype.flat = function (depth = 1) {
  var result = []

  if (depth > 0) {
    for (var i = 0; i < this.length; i++) {
      var item = this[i]

      result = result.concat(Array.isArray(item) ? item.flat(depth - 1) : item)
    }
  } else {
    result = this.slice()
  }

  return result
}

var list = [1, 2, [3, [4]]]
list.flat() // [1, 2, 3, [4]]

# 数组空位

  以上多维扁平化的替代方案,运行如下示例。

var arr = [, 1, undefined, [2, [3]]] // [empty, 1, undefined, [2, [3]]]
console.log(flatten(arr, 2))

node版本v16.14.2打印结果为[1, undefined, 2, 3]

  很容易发现,除了reduce递归执行正确之外,剩余的都将输出[undefined, 1, undefined, 2, 3]

  那么是为什么呢?

  原因在于,循环递归时,for循环不会忽略空位,并且将其转换为了undefined

var arr = [, 1, undefined, [2, [3]]]

for (var i = 0; i < arr.length; i++) {
  console.log(arr[i])
  // undefined
  // 1
  // undefined
  // [2, [3]]
}

  ;reduce循环时将忽略掉空位。

var arr = [, 1, undefined, [2, [3]]]

arr.reduce((pre, cur) => {
  console.log(cur)
  // 1
  // undefined
  // [2, [3]]
}, [])

  而对于some循环和堆栈的情况,原因则是扩展运算符会将empty转换为undefined

var arr = [, 1, undefined, [2, [3]]]
[...arr] // [undefined, 1, undefined, [2, [3]]]

  ;Generator形式也是for...of循环没有忽略空位,将其转换为undefined造成的。

var arr = [, 1, undefined, [2, [3]]]

for (var item of arr) {
  console.log(item)
  // undefined
  // 1
  // undefined
  // [2, [3]]
}

  有两种解决方式,第一种就是将一般循环修改为忽略空位的循环(forEachreduce或者filter等),例如可以将for循环修改为forEach

  第二种方式,要明确的是,数组是没有空位的索引的。

  因此可以利用in操作符。

var arr = [, 1]
0 in arr // false
1 in arr // true

  还是以循环递归为例。

function flatten(arr, depth = 1) {
  var result = []

  if (depth > 0) {
    for (var i = 0; i < arr.length; i++) {
      var item = arr[i]

      result = result.concat(Array.isArray(item) ? flatten(item, depth - 1) : (i in arr ? item : []))
    }
  } else {
    result = arr.filter(() => true)
  }

  return result
}

var arr = [, 1, undefined, [2, [3]]]
flatten(arr, 2)) // [1, undefined, 2, 3]
console.log(flatten(arr, 0) // [1, undefined, [2, [3]]]

  注意用arr.slice()确实也能复制数组,但是会将空位也复制下来,可以利用filter来跳过空位。

var arr = [, 1]

arr.slice() // [empty, 1]
arr.filter(() => true) // [1]

# 小结

  ;flat的替代方案很多,不论以上中的哪一种,都是将数组一维一维降下来的,由此思路,解决了二维数组的扁平化,然后在其基础上,结合递归或者循环,多维扁平化也就能解决了。

  对于唯一参数depth深度,循环中要把握好判断条件,另外注意堆栈的方式引入depth将会非常低效,原因在于要保存每一个成员的深度。

  针对数组空位的情况,若数据格式非常标准,大可不必考虑。当然也有解决方式,可以将普通循环替换为忽略空位的循环,或者也可以利用in操作符。

# 🎉 写在最后

🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star (opens new window) ✨支持一下哦!

手动码字,如有错误,欢迎在评论区指正💬~

你的支持就是我更新的最大动力💪~

GitHub (opens new window) / Gitee (opens new window)GitHub Pages (opens new window)掘金 (opens new window)CSDN (opens new window) 同步更新,欢迎关注😉~

最后更新时间: 4/10/2022, 2:30:10 PM