JS 编写一个程序将数组(多维数组)扁平化后进行数组去重,最终得到一个升序且不重复的一维数组。
var arr = [[12,2],[3,4,5,5],[6,7,8,9,[11,12,[12,13,[14]]]]]
这个题有一种非常简洁的方法,一行搞定。
这里直接用 .from() , .flat(),.sort()三个API加上Set,这样非常的简明。这里
var arr = [[12, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]]];
console.log(Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>a-b));
在这里我们主要讨论的是:是否可以不用flat这个API而对数组进行扁平化呢?
答:当然能了!
方法一:使用 concat()
concat() 方法用于合并多个数组,是一个纯函数,因为此方法不会改变原数组,而是返回一个新数组。
先来看一个简单的例子吧:
var arr1 = [1,2,3];
var arr2 = [4,5];
console.log(arr1.concat(arr2)); // [1, 2, 3, 4, 5]
由上面例子可以看出,concat()是可以合并多个数组的。难么,如果是一个多维数组呢?还可以合并吗?当然能了呀,但是右下图可见此数组最后一个元素是一个数组,那么今天的重点来了!
知道了concat是干什么的之后,就要考虑用concat 怎样才能实现扁平化呢?
主要思路如下:
- 首先封装一个扁平化的函数 function flatten(){ };
- 在函数里遍历整个数组
- 在flatten()函数里定义一个新数组用于接受扁平化的新数组
- 在flatten()函数里还需要定义一个函数去判断遍历元素是否是一个数组,若是遍历元素1一个数组,则需要在此调用flatten(),若判断遍历元素1里面还有数组,那么继续调用flatten()…知道没有数组位置,这个过程就是所谓的递归;若遍历元素1是一个正常的字符串则将次元素存到nArr这个新数组里面。
var arr = [[12, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]]];
// 封装flatten()函数
function flatten(array) {
// 创建一个新数组
let nArr = [];
// 定义函数判断是否是数组
function isArr(isarr) {
// {}.toString.call(isarr)调用了根元素的
return {}.toString.call(isarr) == '[object Array]';
}
for (var i = 0; i < array.length; i++) {
if (isArr(array[i])) {
nArr = nArr.concat(flatten(array[i]))
} else {
nArr.push(array[i])
}
}
}
console.log(flatten(arr)); // [12, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14]
console.log(Array.from(new Set(flatten(arr).sort((a, b) => { return a - b }))));
// [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14]
注: {}.toString.call( ) 检测数据的原型
“根”原型(Object.prototype)下,有个toString的方法,记录着所有 数据类型(构造函数)
.call作用是改this指向。让传入的对象,执行 “根”原型的toString方法
也可以在Array.prototype上添加一个myflatten属性
<script>
var arr = [[12, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]]];
Array.prototype.myflatten = function(){
let _arr = this;
let nArr = [];
let toStr = {}.toString;
_arr.forEach(function(item){
toStr.call(item) === '[object Array]'
? nArr = nArr.concat(item.myflatten())
: nArr.push(item)
})
return nArr;
}
console.log(arr.myflatten()); // [12, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14]
console.log(Array.from(new Set(arr.myflatten().sort((a, b) => a - b ))));
// [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14]
</script>
方法二:reduce()
array.reduce(function(total, currentValue, currentIndex, arr), initialValue) 第一个参数是一个函数,每一次运行返回的结果会将作为参数传入;第二个参数是传递给函数的初始值,会赋给total。
举个例子具体感受以下reduce()的作用吧!
let arr=[1,2,5,9,12,13,15];
arr.reduce(function(prev,next){
console.log("prev:",prev,"next:",next);
return prev+next;
},100)
结果如下:
prev: 100 next: 1
prev: 101 next: 2
prev: 103 next: 5
prev: 108 next: 9
prev: 117 next: 12
prev: 129 next: 13
prev: 142 next: 15
首先prev的初值就是100,next的初值为1。运行一次,所返回的值就是下一次prev的值,而next的值则是依次遍历数组的值。所以第一次运行时 prev:100;next:1;第二次运行时 prev:100+1=101;next:2; 第三次运行时 prev:101+2=103;next:5;以此类推…最终得出结果。
我们可以利用reduce这个特点进行对数组的扁平化,在Array.prototype上添加一个myflatten属性,,myflatten属性返回一个由于调用reduce这个API而得到的一个扁平化数组,给reduce的第二个参数初始值赋一个空数组,reduce第一个参数的形参prev与next进行拼接,拼接前需要判断next是否是一个数组,如果next是一个数组则需要调用此数组的myflatten属性进行扁平化,继续执行判断(也就是递归),如果next不是数组则直接与prev拼接,最后返回prev与next 的拼接数组
代码如下:
<script>
var arr = [[12, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]]];
Array.prototype.myflatten = function () {
let _arr = this;
let toStr = {}.toString;
return _arr.reduce(function (prev, next) {
return prev.concat(toStr.call(next) === "[object Array]" ? next.myflatten() : next)
}, [])
}
console.log(arr.myflatten()); // [12, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14]
console.log(Array.from(new Set(arr.myflatten())).sort((a,b)=>a-b));
// [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14]
</script>
也可以封装成一个函数进行扁平化:
var arr = [[12, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]]];
let myflatten=(arr)=> {
return arr.reduce((prev, next)=>{
return prev.concat({}.toString.call(next) === "[object Array]" ? myflatten(next) : next)
}, [])
}
console.log(myflatten(arr)); // [12, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14]
console.log(Array.from(new Set(myflatten(arr))).sort((a,b)=>a-b));
// [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14]