javaScript之数组扁平化

扁平化

数组扁平化,就是将一个嵌套多层的数组array(嵌套可以是任何层数)转化为只有一层的数组。

1
2
3
var arr = [1,[2,3,4,[5,6,7],8],9];
转化后
arr = [1,2,3,4,5,6,7,8,9];

递归

循环数组元素,如果还是一个数组,就递归调用该方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arr = [1,[2,[3,4]]];

function flatten(arr) {
var result = [];
for(var i = 0, len = arr.length; i < len; i ++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
}else {
result.push(arr[i]);
}
}
return result;
}
console.log(flatten(arr));

toString

如果数组的元素都是数字,那么我们可以考虑使用toString方法,因为:

1
[1, [2, [3, 4]]].toString() // "1,2,3,4"

使用了同String方法会返回个元素的字符串形式,默认是一逗号分隔

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

function flatten(arr) {
return arr.toString().split(',').map(function(item){
return +item
})
}

console.log(flatten(arr))

使用场景只限于纯数字的情况下。

redcue

既然是对数组进行处理,且最终返回一个值,可以考虑使用reduce来简化代码:

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

function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, ['a'])
}
console.log(flatten(arr))
// ["a", 1, "a", 2, "a", 3, 4]

ES6增加了扩展运算符,用于取出参数对象的所有可遍历属性,拷贝到当前对象之中:

1
2
var arr = [1, [2, [3, 4]]];
console.log([].concat(...arr)); [1,2,[3,4]]

似乎不是我们要的效果,但是思路还是一样的

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

function flatten(arr) {
// Array.some() -- 为每个数组元素执行一次回调函数,直到回调函数return一个true
while(arr.some(item => Array.isArray(item))){
arr = [].concat(...arr)
}
return arr;
}

console.log(flatten(arr))

underscore

这里是underscore的_.flatten的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 数组扁平化
* @param {Array} input 要处理的数组
* @param {boolean} shallow 是否只扁平一层
* @param {boolean} strict 是否严格处理元素,下面有解释
* @param {Array} output 这是为了方便递归而传递的参数
* 源码地址:https://github.com/jashkenas/underscore/blob/master/underscore.js#L528
*/
function flatten(input, shallow, strict, output) {

// 递归使用的时候会用到output
output = output || [];
var idx = output.length;

for (var i = 0, len = input.length; i < len; i++) {

var value = input[i];
// 如果是数组,就进行处理
if (Array.isArray(value)) {
// 如果是只扁平一层,遍历该数组,依此填入 output
if (shallow) {
var j = 0, length = value.length;
while (j < length) output[idx++] = value[j++];
}
// 如果是全部扁平就递归,传入已经处理的 output,递归中接着处理 output
else {
flatten(value, shallow, strict, output);
idx = output.length;
}
}
// 不是数组,根据 strict 的值判断是跳过不处理还是放入 output
else if (!strict){
output[idx++] = value;
}
}

return output;

}

strict和shallow各种值对应的结果:

  • shallow: true + stric: faslse :正常扁平一层
  • shallow: false + stric: false : 正常扁平所有层
  • shallow: true + stric: true : 去掉非数组元素
  • shallow: false + stric: true : 返回一个[]
    让我们看看在underscore中那些方法调用了flatten这个函数:
    _.flatten
    1
    2
    3
    4
    5

    _.flatten = function(array, shallow) {
    // 正常的扁平中,并不需要去掉非数组元素
    return flatten(array, shallow, false);
    }
_.union

接下来是_.union
这个函数传入多个数组,然后返回传入的数组的并集

1
2
3
_.union([1, 2, 3], [1 ,2 ,3 ,4 , 5, 6], [4 ,5 ,6 ,7, 8, 9]);

输出:[1,2,3,4,5,6,7,8,9]

如果传入的参数不是数组,就会跳过该参数:

1
2
3
_.union([1, 2, 3], {value: 4}, [3, 4, 5], ,6 7);

输出:[1,2,3,4,5]

为了实现这个效果,我们可以将传入的所有数组扁平化,然后去重,因为只能传入数组,这个时候我们可以让strict 为 true,就可以跳过传入的非数组的元素。

1
2
3
4
5
6
7
8
9
function unique(array) {
// set 是 ES6的新方法,可以用来进行数组去重。
return Array.from(new Set(array));
}

_.union = function() {
// inpu shallow strict output
return unique(flatten(arguments, true, true ));
}
_.difference

通过strict的灵活运用,我们了解了_.flatten 和 _.union,接下来在看一个 _.difference
语法:

.difference(array, *others)
效果是取出来自array数组,并且不存在于多个other数组的元素。和
.union一样,都会排除掉不是数组的元素。

1
2
_.difference([1, 2, 3, 4, 5], [5, 2, 10], [4], 1);
=> [1, 3]

实现方法

1
2
3
4
5
6
7
function difference(array, ...rest) {
rest = flatten(rest, true, true);
// 过滤掉rest中有的元素
return array.filter(function(item) {
return rest.indexOf(item) === -1;
})
}

大神的写法

1
flatten = Function.apply.bind([].concat, [])

这里解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.apply.bind([].concat, [])

// 相当于

function(arg) {
return Function.apply.call([].concat, [], arg)
}

// 相当于

function(arg) {
return [].concat.apply([], arg)
}

// 应该是

function(arg) {
return [].concat(...arg)
}