前言

面试手写代码在大厂面试中非常常见,秋招中面试小米就手写了一道flat实现的代码题,当时通过递归方式实现了数组扁平化逻辑,但没有考虑多种实现方案及其边界条件(主要是对所涉及到高阶函数的知识点不够熟练,也没有考虑空位处理),现在从头梳理一下,并尽可能全面地总结数组扁平化的实现方案。

数组扁平化

数组扁平化即将一个嵌套多层的数组array(嵌套可以是任意层数)转换为只有一层的数组,如将数组[1,[2,[3,[4,5]]]]转换为[1,2,3,4,5]。

最直接的数组扁平化方案是使用Array.prototype.flat()方法(兼容性差),其次是通过遍历数组元素递归实现每一层的数组拉平。

00x1 Array.prototype.flat()

按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回,对原数据没有影响。

语法:var newArray = arr.flat([depth])

说明:

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

参数depth值 <=0 时返回原数组;

参数depth为Infinity 关键字时,无论多少层嵌套,都会转为一维数组,

flat()方法会移除数组中的空项,即原数组有空位,会跳过这个空位。

代码示例:

var arr1 = [1, 2, [3, 4]];

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

var arr2 = [1, 2, [3, 4, [5, 6]]];

arr2.flat();//[1, 2, 3, 4, [5, 6]]

var arr3 = [1, 2, [3, 4, [5, 6]]];

arr3.flat(2);//[1, 2, 3, 4, 5, 6]

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

var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];

arr4.flat(Infinity);//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

var arr5 = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];

arr5.flat(Infinity);//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }];

//移除数组中的空项

var arr6 = [1, 2, , 4, 5];

arr6.flat();//[1, 2, 4, 5]

数组扁平化flat函数封装实现方案

实现思路

首先遍历获取数组的每一个元素,其次判断该元素类型是否为数组,最后将数组类型的元素展开一层。同时递归遍历获取该数组的每个元素进行拉平处理。

遍历数组方案

for循环

for...of

for...in

entries()

keys()

values()

forEach()

map()

reduce()

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];//本文只枚举常用的几种数组遍历方法//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 inarr) {

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));

判断数组元素是否为数组

instanceof

constructor

Object.prototype.toString

isArray

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];

arrinstanceofArray//true

arr.constructor ===Array//true

Object.prototype.toString.call(arr) === '[object Array]'

//true

Array.isArray(arr)//true

注:

instanceof 操作符是假定只有一种全局环境,如果网页中包含多个框架,多个全局环境,如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。(所以在这种情况下会不准确)

typeof 操作符对数组取类型将返回 object

constructor可以被重写,不能确保一定是数组

const str = 'abc';

str.constructor=Array;

str.constructor===Array//true

数组元素展开一层方案

扩展运算符 + concat

concat +appl

toString + split

不推荐使用toString+split方法,操作字符串是很危险的,数组中元素都是数字时可行。

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];//扩展运算符 + concat

[].concat(...arr)//[1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { type: "对象" }];

//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]

00x1 手写一个最简单的flat函数实现

这里使用ES6语法中的箭头函数定义函数,注意箭头函数没有arguments,caller,callee,同时要区分于ES5使用function的两种函数声明定义方式。

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];

const flat= (arr) =>{

let arrResult=[]for(let i=0, len=arr.length; i

arrResult.push(...flat(arr[i]))//arrResult = arrResult.concat(flat(arr[i]))

}else{

arrResult.push(arr[i])

}

}returnarrResult;

}

flat(arr)//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

循环部分同理可用for...of / for...in 来实现。

OK,现在你已经具备了基本的手撕代码能力,但面试官常常希望你能掌握各种高阶函数方法的应用。接下来继续列举实现flat的几种方案。

00x2 用map/forEach实现flat函数

仍然是遍历+循环的原理,这里循环用map/forEach实现。

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];

const flat= (arr) =>{

let arrResult=[]

arr.map(item=>{if(Array.isArray(item)){

arrResult.push(...flat(item))//arrResult = arrResult.concat(flat(item))

}else{

arrResult.push(item)

}

})returnarrResult;

}

flat(arr)//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

00x3 归并方法:用reduce实现flat函数

我们用reduce函数进行遍历,把prev的初值赋值为[],如果当前的值是数组的话,那么我们就递归遍历它的孩子,如果当前的值不是数组,那么我们就把它拼接进数组里。

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];functionflat(arr) {return arr.reduce((prev, cur)=>{return prev.concat(Array.isArray(cur)?flat(cur):cur);

}, [])

}

flat(arr)//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

00x4 用Generator实现flat函数

function*flat(arr, num) {if (num === undefined) num = 1;for(const item of arr) {if (Array.isArray(item) && num > 0) { //num > 0

yield* flat(item, num - 1);

}else{

yield item;

}

}

}

const arr= [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]]//调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。//也就是遍历器对象(Iterator Object)。所以我们要用一次扩展运算符得到结果

[...flat(arr, Infinity)]//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

00x5 在原型链上重写 flat 函数

Array.prototype.fakeFlat = function(num = 1) {if (!Number(num) || Number(num) < 0) {return this;

}

let arr= this.concat(); //获得调用 fakeFlat 函数的数组

while (num > 0) {if (arr.some(x =>Array.isArray(x))) {

arr= [].concat.apply([], arr); //数组中还有数组元素的话并且 num > 0,继续展开一层数组

} else{break; //数组中没有数组元素并且不管 num 是否依旧大于 0,停止循环。

}

num--;

}returnarr;

};

const arr= [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["type", { name: "对象"}]]

arr.fakeFlat(Infinity)//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

00x6 使用栈的思想实现 flat 函数

//栈思想

functionflat(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);//如果不是数组就将其取出来放入结果数组中

}

}returnresult;

}

const arr= [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]]

flat(arr)//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

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

//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, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]]

flat(arr, Infinity);//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

00x8 数组空位的处理

flat 函数执行是会跳过空位的。

ES5 对空位的处理,大多数情况下会忽略空位。

forEach(), filter(), reduce(), every() 和 some() 都会跳过空位。

map() 会跳过空位,但会保留这个值。

join() 和 toString() 会将空位视为 undefined,而 undefined 和 null 会被处理成空字符串。

ES6 明确将空位转为 undefined。

entries()、keys()、values()、find()和 findIndex() 会将空位处理成 undefined。

for...of 循环会遍历空位。

fill() 会将空位视为正常的数组位置。

copyWithin() 会连空位一起拷贝。

扩展运算符(...)也会将空位转为 undefined。、

Array.from 方法会将数组的空位,转为 undefined。

00x1 for...of 循环遍历实现flat函数

const arr1 = [1, 2, 3, , , 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, ["string", { type: "对象"}]];

const flat= (arr) =>{

let arrResult=[]for(let item of arr){if(Array.isArray(item)){

arrResult.push(...flat(item))//arrResult = arrResult.concat(flat(arr[i]))

}else{

arrResult.push(item)

}

}returnarrResult;

}

flat(arr1)//[1, 2, 3, undefined, undefined, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { type: "对象" }]

## 总结

现在的前端面试中,大厂面试官基本都会考察手撕代码的能力,不仅要能答得上来实现数组扁平化的几种方案,也不仅是要能手写实现,还要能理解,能讲清楚其中包涵的详细知识点及代码的边界情况,能在基础版本上再写出一个更完美的版本。

而我们在写代码的过程中,也要养成这样的习惯,多问问自己还有没有别的替代实现方案,还能不能进一步优化,才能写出优美漂亮的代码,编程能力自然而然也就提高啦!

JAVA数组扁平化整合_一文搞定数组扁平化(超全面的数组拉平方案及实现)相关推荐

  1. php带参数单元测试_一文搞定单元测试核心概念

    基础概念 单元测试(unittesting),是指对软件中的最小可测试单元进行检查和验证,这里的最小可测试单元通常是指函数或者类.单元测试是即所谓的白盒测试,一般由开发人员负责测试,因为开发人员知道被 ...

  2. 旋转数组的最小数 php 牛客网_一文搞定—移掉K个数字amp;amp;旋转数组amp;amp;全排列...

    给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小.注意:num 的长度小于 10002 且 ≥ k. num 不会包含任何前导零. (1)本不该,单独开一章节来讲 ...

  3. java+输出流++空值_一文搞定Java的输入输出流等常见流

    点赞再看,养成习惯,常用流,多看多练准没错!文章较长,建议收藏再看! 1.IO流分析 什么是IO? I:Input O:Output 通过IO可以完成对硬盘的读和写. IO流的分类. 有多种分类方式: ...

  4. JAVA秒杀mysql层实现_一文搞懂MySQL的Join,聊一聊秒杀架构设计

    正文 MySQL的Join到底能不能用 经常听到2种观点: join性能低,尽量少用 多表join时,变为多个SQL进行多次查询 其实对于上面的观点一定程度上是正确的,但不是完全正确.但之所以流传这么 ...

  5. java 偶校验_一文搞定校验码(奇偶校验,海明,CRC 码)

    效验码 校验码:指能够发现或能够自动纠正错误的数据编码,也称检错纠错编码. 实现原理:通过加一冗余码,来检验或纠错编码 码字 : 由若干位代码组成的一个字 码距:将两个码字逐位进行对比,具有不同的位的 ...

  6. 谷粒商城高级篇资料_一文搞定剑指offer面试题【分文别类篇】

    点击上方"蓝字",关注了解更多 数组: 面试题3:数组中重复的数字 面试题4:二维数组中的查找 面试题21:调整数组顺序使奇数位于偶数前面 面试题39:数组中出现次数超过一半的数字 ...

  7. c++求矩阵的秩_一文搞定矩阵相关概念 使用Excel矩阵计算不用愁

    知识要点: 矩阵的本质 矩阵的计算公式 实例演示 背景 矩阵计算是我们经常碰到的一个问题.在几乎所有跟"学问"二字稍微沾点边的东西里,矩阵这个家伙从不缺席.那Excel在其中扮演什 ...

  8. hfss matlab联合仿真_一文搞定matlab 与 STK 联合调试仿真环境配置(文末附软件下载链接)...

    最近在做导师给的课题:卫星星座的快速优化设计. 需要用到matlab 和 STK 来进行联合调试仿真,但是这第一步的环境配置就让我头疼了几天.在好几次重装,失败和查找资料之后,我终于成功实现了matl ...

  9. koa 接口返回数据_一文搞定 Koa 中间件实现原理

    Koa是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小.更富有表现力.更健壮的基石. 通过利用 async 函数, Koa ...

最新文章

  1. ztree实现左边动态生成树,右边为具体信息功能
  2. 如何把握创业时机:当前的痛点也许是巨大的风险
  3. 简朴的生活、高贵的灵魂是人生的至高境界。——杨绛
  4. Linux centos 主机名颜色设置 和 别名设置
  5. CF1598E-Staircases【计数】
  6. 鸿蒙os更新要求,华为鸿蒙OS即将迎来升级 手机版本或仍需时间
  7. PyCharm光标变粗的解决办法
  8. 二、PHP基础——连接msql数据库进行增删改查操作 实战:新闻管理项目
  9. 10.数据库中的内置函数
  10. System.Threading.Thread类方法
  11. 《Python程序设计实例教程》课后习题参考答案
  12. 2021-2027全球与中国大数据工程服务市场现状及未来发展趋势
  13. note4-WEB源码拓展
  14. IEMS_11_课程信息相关的初始数据导入
  15. 【RDMA】intel 因特尔RDMA 驱动和ibverslib 库安装笔记
  16. 基于docer 构建微服务基础设施
  17. PHPAdmin删除外键约束
  18. 这个微信小游戏我就坚持了五秒 好玩的休闲游戏推荐
  19. oracle查询挂起,表挂起更新查询Oracle 11g(Table hangs on Update query Oracle 11g)
  20. python拍照搜题_Python数据分析实践,中国大学MOOC(慕课)答案公众号搜题

热门文章

  1. php织梦网站如何设置头,织梦dedecms网站的安全设置教程
  2. php留言本在线制作,Flash+php+mysql简单留言本制作
  3. 从零开始学习D2-Admin,只要学习,任何时候都不晚!
  4. N皇问题(判断优化)
  5. 壳聚苯乙烯/单分散核-壳结构/中空/介孔二氧化硅微球的研究
  6. 谁爱Internet标签?谁不需要?
  7. IT人士获得老板青睐器重的五大秘决
  8. 微信公众平台-测试号网页授权-获取openid方法
  9. windows RT开发笔记:WinRT DLL及其调用研究
  10. 【Ubuntu】Ubuntu16.04的主题和终端美化