面试官:JavaScript如何实现数组拍平(扁平化)方法?
面试官:JavaScript如何实现数组拍平(扁平化)方法?
1 什么叫数组拍平?
概念很简单,意思是将一个“多维”数组降维,比如:
// 原数组是一个“三维”数组
const array = [1, 2, [3, 4, [5, 6], 7], 8, 9]
// 可以降成二维
newArray1 = [1, 2, 3, 4, [5, 6], 7, 8, 9]
// 也可以降成一维
newArray2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
数组拍平也称数组扁平化、数组降维。
2 JS标准库中的数组拍平方法
JavaScript标准库中已经实现了数组拍平方法Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
语法:var newArray = arr.flat([depth])
参数:depth为可选值,表示要遍历多维数组的深度,默认值为1。可以理解为想要展开(或者说降维)的层数。
返回值:遍历到的元素和子数组的元素组合成的新数组
举例:
const array = [1, 2, [3, 4, [5, 6], 7], 8, 9]
const newArray1 = array.flat() // 等价于array.flat(1);降1维
// newArray1: [1, 2, 3, 4, [ 5, 6 ], 7, 8, 9]
const newArray2 = array.flat(2) // 降2维
// newArray2:[1, 2, 3, 4, 5, 6, 7, 8, 9]
特殊:
depth<=0时,返回的数组和原数组维数一样(注意只是维数一样,空位情况见第3点)
const array = [1, 2, [3, 4, [5, 6], 7], 8, 9]
array.flat(-1)
// [1, 2, [3, 4, [5, 6], 7], 8, 9]
depth=Infinity,返回的数组变成一维
array.flat(Infinity)
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
- 原数组有空位,flat方法会消除空位,即使是flat(0)也会消除空位,所以第1点说的是“只是维数一样”。并且flat方法展开到哪一层,空位就会消除到哪一层,再深层的空位不会消除
const array1 = [1, , 2, [3, ,4, [5, 6], 7], 8, 9]
// 注意这个数组有两个空位
array.flat(0)
// [ 1, 2, [ 3, ,4, [ 5, 6 ], 7 ], 8, 9 ]
// 第一个空位没了,第二个空位还在
3 实现一个flat方法
flat方法展开一层(降1维)的步骤:遍历数组,判断当前元素是否为数组,如果不是数组,直接保存;如果是数组,将其展开后保存
flat方法展开多层(降多维)无非就是在展开一层的基础上,使用递归将数组子元素进行同样的操作。
可以将这个方法拆分成三步:
1、如何遍历数组
2、如何判断元素是否为数组
3、递归
实现上述三步,将他们组合起来就可以得到不同的flat实现
3.1 如何遍历一个数组
方法特别多,这里介绍3类:
1、for相关
for 循环for...of
for...in是为遍历对象属性而构建的,不建议与数组一起使用
const array = [1, 2, [3, 4, [5, 6], 7], 8, 9]
// for循环
for (let i = 0; i < array.length; i++) {
const element = array[i];
}
// for...of
for (const element of array) {
}
2、数组方法:能直接取到数组元素的方法
forEach()reduce()map()
// forEach()
array.forEach(element => {
});
// reduce()
array.reduce((pre, cur) => {
const element = cur
}, [])
// map()
array.map(element => {
})
3、数组方法:返回遍历器(Iterator)对象的方法
keys()values()entries()
// 这三种方式仅仅是获得遍历器对象,还需搭配for...of来进行遍历
// keys()
for (let i of array.keys()) {
const element = array[i]
}
// values()
for (let element of array.values() ) {
}
// entries()
for (let [i, element] of array.entries()) {
console.log(array[i])
console.log(element)
}
3.2 如何判断元素是否为数组
设有一变量a,判断其是否为数组。这里提供4种方法:
Array有一个静态方法Array.isArray()用于判断某个变量是否是一个数组instanceof运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。若
a是数组,则其原型链上会出现Array.prototype通过对象的
constructor判断(此方法可能失效,因为constructor可以手动更改)通过
Object.prototype.toString()来判断,该方法可以返回一个表示该对象的字符串
// 方法1
Array.isArray(a)
// 方法2
a instanceof Array
// 方法3
a.constructor === Array
// 方法4
// 使用call来调用Object.prototype上的toString方法
Object.prototype.toString.call(a) === '[object Array]'
// 不能这么判断,因为这个toString已经覆盖了Object.prototype.toString
// 只有Object.prototype.toString能正确判断类型
a.toString()
3.3 递归
递归:对子元素进行同样的操作
function flat() {
let res = []
遍历数组 {
if (当前元素是数组) {
flat(当前元素)得到一维数组
将一维数组拼接到res中
} else {
res.push(当前元素)
}
}
return res
}
3.4 初步实现flat方法
挑选遍历方式和判断数组的方式,搭配递归就可以初步实现flat方法,如:
function myFlat(arr) {
let res = [];
for (const item of arr) {
if (Array.isArray(item)) {
res = res.concat(myFlat(item));
// 注意concat方法返回一个新数组,不会改变原数组
} else {
res.push(item);
}
}
return res;
}
myFlat方法可以实现将"多维"数组拉平成一维数组,但是不能指定展开深度depth,并且也无法处理数组空位
4 优化
4.1 指定展开深度
处理展开深度其实很简单,我们可以增设一个递归终止条件,即depth<=0,代码如下:
function myFlat(arr, depth = 1) {
// 若depth<=0,则直接返回
if (depth <= 0) {
return arr
}
let res = [];
for (const item of arr) {
if (Array.isArray(item)) {
// 每次递归调用,将depth-1
res = res.concat(myFlat(item, depth - 1));
} else {
res.push(item);
}
}
return res;
}
4.2 数组空位处理
事实上我们应该尽量避免出现数组空位的情况
前面我们提到了遍历数组的不同方法,它们对于数组空位的处理不尽相同
其中forEach、reduce、map遍历时遇到空位会直接忽略;而for...of则不会忽略,它遇到空位会将其当作undefined处理
4.2.1 for...of增加空位判断
因此我们需要改进for...of遍历数组的myFlat方法:
function myFlat(arr, depth = 1) {
if (depth <= 0) {
return arr;
}
let res = [];
for (const item of arr) {
if (Array.isArray(item)) {
res = res.concat(myFlat(item, depth - 1));
} else {
// 判断数组空位
item !== undefined && res.push(item);
}
}
return res;
}
4.2.2 forEach、map方法遍历
当然也可以使用forEach、map方法来遍历数组,这样就不用手动判断了
但是这里有一个特殊情况需要考虑,就是当depth <= 0时,我们用filter方法来消除数组空位
// forEach
function myFlat(arr, depth = 1) {
if (depth <= 0) {
return arr.filter(item => item !== undefined);
}
let res = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
res = res.concat(myFlat(item, depth - 1));
} else {
res.push(item);
}
});
return res;
}
// map
function myFlat(arr, depth = 1) {
if (depth <= 0) {
return arr.filter(item => item !== undefined);
}
let res = [];
arr.map((item) => {
if (Array.isArray(item)) {
res = res.concat(myFlat(item, depth - 1));
} else {
res.push(item);
}
});
return res;
}
4.2.3 reduce方法
其中,使用reduce方法实现的最为简洁,也是面试中常考的方法之一
function myFlat(arr, depth = 1) {
return depth > 0
? arr.reduce(
(pre, cur) =>
pre.concat(Array.isArray(cur) ? myFlat(cur, depth - 1) : cur),
[]
)
: arr.filter((item) => item !== undefined);
}
5 其他
5.1 栈
理论上,递归方法通常可以转换成非递归方法,即使用栈
function myFlat(arr) {
let res = [];
const stack = [].concat(arr);
while (stack.length > 0) {
const item = stack.pop();
if (Array.isArray(item)) {
// 用扩展运算符展开一层
stack.push(...item);
} else {
item !== undefined && res.unshift(item);
}
}
return res;
}
但是此方法不能指定展开深度,只能彻底展开成一维数组
5.2 改进
针对栈不能指定展开深度的缺点进行改进,代码如下:
function myFlat(arr, depth = 1) {
if (depth <= 0) {
return arr.filter((item) => item !== undefined);
}
let res;
let queue = [].concat(arr);
while (depth > 0) {
res = [];
queue.forEach((item) => {
if (Array.isArray(item)) {
// 注意用扩展运算符将数组展开前先用filter方法去掉空位
res.push(...item.filter((e) => e !== undefined));
} else {
res.push(item);
}
});
depth--;
queue = res;
}
return res;
}
面试官:JavaScript如何实现数组拍平(扁平化)方法?的更多相关文章
- js数组拍平
js数组拍平 var arr=[1,[[[7,2],8,9],3]]; function f(arr) { if (Object.prototype.toString.call(arr) != &qu ...
- JavaScript移除数组元素减少长度的方法
JavaScript移除数组元素减少长度的方法,代码如下: //数组移除长度方法 var array=[]; array[0]="张三"; array[1]="李四& ...
- 前端面试之ES6新增了数组中的的哪些方法?!
前端面试之ES6新增了数组中的的哪些方法?! 我们先来看看数组中以前有哪些常用的方法吧! 1 新增的方法! 1 forEach() 迭代遍历数组 回调函数中的三个参数 value: 数组中的每一个元素 ...
- JavaScript中,数组和对象的遍历方法总结
循环遍历是写程序很频繁的操作,JavaScript 提供了很多方法来实现. 这篇文章将分别总结数组和对象的遍历方法,新手可以通过本文串联起学过的知识. 数组遍历 方法一:for 循环 for 循环是使 ...
- JavaScript中的数组遍历forEach()与map()方法以及兼容写法
原理: 高级浏览器支持forEach方法 语法:forEach和map都支持2个参数:一个是回调函数(item,index,list)和上下文: forEach:用来遍历数组中的每一项:这个方法执行是 ...
- 阿里P7岗位面试,面试官问我:为什么HashMap底层树化标准的元素个数是8
前言 先声明一下,本文有点标题党了,像我这样的菜鸡何德何能去面试阿里的P7岗啊,不过,这确实是阿里p7级岗位的面试题,当然,参加面试的人不是我,而是我部门的一个大佬.他把自己的面试经验分享给了我,也让 ...
- JavaScript几种数组去掉重复值的方法
数组去重复是一个常见的需求,我们暂时考虑同类型的数组去重复.主要是理清思路和考虑下性能.以下方法,网上基本都有,这里只是简单地总结一下. 思路: 遍历数组,一一比较,比较到相同的就删除后面的 遍历数组 ...
- javascript语言精粹数组篇之Array的方法注意事项
本文并没有详细列出Array方法详解,本文侧重点在于使用Array编程时候要注意的问题.1.Array.concat var o = {name:"Gavin"}; var a1 ...
- 面试官问:JS的this指向
前言 面试官出很多考题,基本都会变着方式来考察this指向,看候选人对JS基础知识是否扎实.读者可以先拉到底部看总结,再谷歌(或各技术平台)搜索几篇类似文章,看笔者写的文章和别人有什么不同(欢迎在评论 ...
随机推荐
- PHP的Mcrypt加密扩展知识了解
今天我们来学习的是 PHP 中的一个过时的扩展 Mcrypt .在 PHP7 之前,这个扩展是随 PHP 安装包一起内置发布的,但是现在新版本的 PHP 中已经没有了,需要使用这个扩展的话我们需要单独 ...
- 这个 MySQL bug 让我大开眼界
这周收到一个 sentry 报警,如下 SQL 查询超时了. select * from order_info where uid = 5837661 order by id asc limit 1 ...
- 取得get参数 从url
1. getUrlParam.js define(function() { // url参数 var data, index; (function init() { data = []; index ...
- php 设计模式 --适配器
1,目标:实现一个不同的类不同方法,符合一定的规范: 规范类 <?php interface Iplay{ function Attack(); function Defence(); } cl ...
- 给你一个app,怎么测试
安装卸载 安装卸载路径是否能自己选择,在不同操作系统下(Android.ios)安装是否正常,能正常运行,安装的文件及文件夹是否写入了指定的目录里,安装来自不同来源的(应用宝.360助手)下是否正常. ...
- YbtOJ#912-神秘语言【结论,欧拉定理】
正题 题目链接:http://www.ybtoj.com.cn/problem/912 题目大意 给出\(L,R\),求有多少长度在\([L,R]\)之间的字符串满足依次取出所有偶数位置的放在最前面后 ...
- ansible远程运维操作
1.command 用于查看文件内容,查看磁盘,内存,启动命令等纯命令信息 ansible portal -m command -a "cat /test1/test"2.ping ...
- 人力节省 50%,研发效能提升 40%,阿里 Serverless 架构落地实践
作者 | 万佳 嘉宾 | 杨皓然(不瞋) 导读:云的下一波浪潮是什么?杨皓然称"是 Serverless".作为一名阿里老兵,他早在 2010 年即加入阿里云,曾深度参与阿里云飞天 ...
- JavaScript有同步任务和异步任务,浏览器是怎么处理的?
1.在讨论浏览器与JavaScript之前,我们先来简单了解一下进程与线程 进程(process):资源分配的最小单位 进程是应用程序的执行实例,是操作系统进行资源分配和调度的一个独立单位. 线程(t ...
- Task 异步小技巧
原文地址:Task 异步小技巧 - 一事冇诚 - 博客园 (cnblogs.com) async Task 语法糖出来后,异步编程变得非常简单,适合需要耗费较长时间的任务. 有些小伙伴使用后可能会非常 ...