目录:
一、介绍
二、个人手写
1、个人未看源码思路
2、个人手写代码
2.1:递归
2.2:reduce
2.3:flat
2.4:堆栈
2.5:队列
三、源码分析
1、源码思路
四、总结
字数:大约900字
一、介绍
名称:arr-flatten
地址:https://github.com/jonschlinkert/arr-flatten.git
这个其实就是数组扁平化啦,面试中也经常遇到。
示例:
var flatten = require('arr-flatten');
flatten(['a', ['b', ['c']], 'd', ['e']]);
//=> ['a', 'b', 'c', 'd', 'e']
二、个人手写
这里按照简单的数组来实现,数组内用基本数据类型数据,所以ts也就不考虑具体类型限制啦。
1、个人未看源码思路
其实实现扁平化有多种方法哈,例如有递归、reduce、堆栈、队列、以及ES10引入的flat函数。我们把几种情况都写一下。
2、个人手写代码
2.1:递归
将一个多维数组分解成多个一维数组,对于每个一维数组都判断是否为数组类型,如果是数组类型,则继续递归直到该数组不再包含数组,然后将元素加入到结果数组中。下面是使用递归的实现方法:
const flatten = (arr: any[]) => {
let result: any[] = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
};
测试
flatten(['a', ['b', ['c']], 'd', ['e']]);
//=> ['a', 'b', 'c', 'd', 'e']
性能:
数组递归扁平化的时间复杂度可以表示为 O(n^m),其中 n 是最后扁平化时的元素数量,m 是数组嵌套的最大深度。
在递归扁平化的过程中,需要遍历每个元素并判断其是否为数组,因此时间复杂度是与数组元素数量成线性关系的 O(n)。但由于存在嵌套的情况,递归的层数最多可以达到数组的最大深度 m,因此总的时间复杂度是 O(n^m)。
需要注意的是,由于递归过程中需要消耗堆栈空间,因此如果数组嵌套深度太大,可能会导致堆栈溢出的问题。因此,在处理大规模、深度较大的数组时,应该考虑非递归的扁平化方式。
2.2:reduce
reduce函数调用一个提供的回调函数并将每个元素依次归约为一个值。在这里,我们使用reduce函数将多维数组转换为一维数组。其实也是一个逻辑
const flatten = (arr: any[]): any[] => {
return arr.reduce(
(accumulator, currentValue) => accumulator.concat(
Array.isArray(currentValue)
? flattenArray(currentValue)
: currentValue
),
[]
)
}
测试
flatten(['a', ['b', ['c']], 'd', ['e']]);
//=> ['a', 'b', 'c', 'd', 'e']
性能:
数组使用 reduce
方法进行扁平化操作,时间复杂度仍然是O(n)。
reduce
方法会对数组中的每一个元素执行一个回调函数,从当前元素开始进行计算,最终返回一个减少的值。在使用 reduce
进行扁平化时,回调函数的操作需要考虑到当前元素是否为数组,如果是数组,则进行递归处理。
由于必须遍历每个元素进行计算,因此时间复杂度是与数组元素数量成线性关系的 O(n)。但是,与递归扁平化不同,reduce
方法没有堆栈限制,因此不会因为嵌套深度太大而导致堆栈溢出的问题。
综上所述,使用 reduce
进行扁平化的性能与使用递归方法类似,但在处理大规模、深度较大的数组时,考虑使用 reduce
可能更可靠一些。
2.3:flat
ES2019推出了一个新函数flat,可以将多维数组变成一维数组。flat函数接收一个参数,用来指定要扁平化的深度,默认为1,如果设置为Infinity,则会将数组扁平化至最深层级。
const flatten = (arr: any[]) => {
return arr.flat(Infinity);
}
测试
flatten(['a', ['b', ['c']], 'd', ['e']]);
//=> ['a', 'b', 'c', 'd', 'e']
性能
flat()
方法的时间复杂度是 O(n),与 reduce()
方法类似。
具体来说,flat()
方法是一种单层级扁平化操作,可以通过参数指定需要扁平化的层数。在一个数组中,每个元素仅被遍历一次,因此它的时间复杂度是线性的 O(n)。
与递归扁平化方法相比,flat()
方法具有更好的性能表现,而且相较于 reduce()
方法,flat()
方法需要更少的代码量和更高的可读性。同时,flat()
方法还可以通过参数级别控制扁平化的深度,更加灵活。
综上所述,flat>reduce>递归。
2.4:堆栈
借助堆栈(Stack)这种数据结构,我们可以将多维数组转化成一维数组。当遇到数组元素时,将其入栈;当遇到不是数组元素时,将其出栈并加入到结果数组中。
const flattenArray = (array: any[]): any[] => {
const stack = [...array];
const result = [];
while (stack.length) {
const nextItem = stack.pop();
if (Array.isArray(nextItem)) {
stack.push(...nextItem);
} else {
result.unshift(nextItem);
}
}
return result;
}
性能:
使用堆栈算法将嵌套数组扁平化的时间复杂度为o(n),其中n是所有嵌套数组元素的数量。在使用堆栈算法时,我们遍历原始数组并将每个元素添加到结果数组中。如果当前元素是一个数组,则我们将其压入堆栈。在下一次迭代中,我们从堆栈中取出元素并将其添加到结果数组中,直到堆栈为空。
2.5:队列
借助队列(Queue)这种数据结构,我们也可以将多维数组转化成一维数组。当遇到数组元素时,将其加入到队列末尾;当遇到不是数组元素时,将其加入到结果数组中。使用队列时,需要注意遍历顺序,因为遍历的顺序会影响最终结果。
const flatten = (arr: any[]): any[] => {
const result = [];
const queue = [...arr];
while (queue.length) {
const next = queue.shift();
if (Array.isArray(next)) {
queue.push(...next);
} else {
result.push(next);
}
}
return result;
}
性能:
因为我们只需遍历每个元素一次,所以时间复杂度为o(n)。然而,将大量元素压入堆栈可能会占用大量内存空间,这也需要考虑到。
使用队列算法对 js 数组进行扁平化的时间复杂度为 o(n),其中 n 是数组中元素的总数量。这是因为在使用队列算法进行扁平化时,我们需要遍历整个数组并将每个元素加入到队列中。然后,我们需要从队列中依次取出每个元素,并将非数组类型的元素添加到结果数组中,将数组类型的元素展开(即将其所有子元素加入到队列尾部)。
由于我们将每个元素都恰好处理一次,并且队列中的元素数量最多为n,因此时间复杂度为o(n)。需要注意的是,某些数组元素可能是嵌套很深的数组。在这种情况下,队列的长度可能会增长得比较快,导致算法的性能变差。
三、源码分析
那我们就要看看人家写的源码。
module.exports = function (arr) {
return flat(arr, []);
};
function flat(arr, res) {
var i = 0, cur;
var len = arr.length;
for (; i < len; i++) {
cur = arr[i];
Array.isArray(cur) ? flat(cur, res) : res.push(cur);
}
return res;
}
1、源码思路
发现它用的方法就是递归啦。学习了上面几种扁平化思路,再来看它这个源码就是很简单的啦。再也不用担心我面试遇到了。
四、总结
1、凡是用到递归实现的,都可以使用队列或者堆栈来替换,可以得到好的效果
2、...扩展符复制
对于数组来说,它使用了可变参数(rest)的语法,可以将一个数组展开成多个独立的值。使用 ... 进行数组的扩展操作时,它会基于源数组创建一个新的数组实例,新数组的元素与源数组相同,但是它们是不同的引用。也就是说,当你改变源数组中的元素时,新数组中的元素不会受到影响,反之亦然。
而对于对象来说,使用 ... 进行对象的扩展操作时,它会将源对象的所有可枚举属性复制到新的对象中。这意味着,新对象中的属性与源对象中的属性具有相同的值和名称,但是它们是不同的引用。因此,在其中一个对象中修改属性时,另一个对象中的属性不会受到影响。