Another RayJune

Array(n).fill([]) 的不可用性

“The function Array.fill is using the same object for all the indexes.”

Spiral Matrix

问题来源于 Matrix Spiral 这个经典的 leetcode medium 算法题。题目描述就不在这里粘贴了,可以 check https://leetcode.com/problems/spiral-matrix/

首先来写一个 JS 的解,我用的是这个思路 Create a NxN Matrix Spiral with JavaScript,其中加上了自己的一些个性化处理:

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
40
41
42
43
44
/**
* @param {number} n
* @return {number}
*/
function matrix(n) {
const result = Array(n).fill([]);
let startColumn = 0;
let endColumn = n - 1;
let startRow = 0;
let endRow = n - 1;
let counter = 1;

while (startColumn <= endColumn && startRow <= endRow) {
// top line, row 行不变,column 列变
for (let i = startColumn; i <= endColumn; i++) {
result[startRow][i] = counter;
counter += 1;
}
startRow += 1;

// right line,column 列不变,row 行变
for (let i = startRow; i <= endRow; i++) {
result[i][endColumn] = counter;
counter += 1;
}
endColumn -= 1;

// bottom line,row 行不变,column 列变
for (let i = endColumn; i >= startColumn; i--) {
result[endRow][i] = counter;
counter += 1;
}
endRow -= 1;

// left line,column 列不变,row 行变
for (let i = endRow; i >= startRow; i--) {
result[i][startColumn] = counter;
counter += 1;
}
startColumn += 1;
}

return result;
}

其中和原版最大的不同是,我没有使用常规的 for 循环创建 result 数组的方法,而是使用了看起来更加简洁的 array.fill 方法:

1
2
3
4
5
6
7
8
9
// 常规 for 循环创建
const result = [];

for (let i = 0; i < n; i++) {
result.push([]);
}

// 我的处理,使用 array.fill([])
const result = Array(n).fill([]);

但是运行发现出问题了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
matrix(3);
/*
预期值:
[
[1, 2, 3],
[8, 9, 4],
[7, 6, 5],
]
实际值:
[
[8, 9, 5],
[8, 9, 5],
[8, 9, 5],
]
*/

将创建 result 数组的方式用 for 循环方式替换掉后,运行恢复正常。可以把原因锁定在 Array(n).fill([]) 这个处理上了。

那么是为什么呢?

溯源

发现 MDN 上关于 Array.prototype.fill() 的文档没有这方面特例的说明。

在查遍 stackoverflow 后找到了原因:”The function Array.fill is using the same object for all the indexes.”

带着这个解释我们来还原一下 Array(n).fill([]) 内部处理的过程:

1
2
3
4
5
6
7
8
const result = Array(n).fill([]);
/*
1. 创建一个长度为 n 的数组
2. 将数组中每一项都赋值为 [] 这个对象的引用

所以后续每次对 result 的操作,无论下标怎么变(result[i]),本质
上都在操作原始的 [] 数组,因为 result[i] 引用的地址都是一样的
*/

替代解

既然不能使用 Array(n).fill([]),有没有其他比 for 循环更加简洁的处理呢?

.fill().map()

容易想到可以使用 array.map() 这个方法:

1
const result = Array(n).map(() => []);

但是这样就踏入下一个陷阱了,因为 Array(n) 生成的数组不能直接使用 map 方法:

map only operates on defined integer properties of an array. Array(n) does not set integer properties, while Array(n).fill() does. There’s a difference between a property that doesn’t exist and an extant property whose value is undefined.”

Array(n) sets the length property of the array, but it does not set any properties. The array object does not have any integer properties.

.fill sets all of the integer properties for an array from zero up to one less than length.

1
2
3
// 可以用 Object.keys() 来测试一下二者的不同
console.log(Object.keys(Array(3))); // 0
console.log(Object.keys(Array(3).fill())); // 3

所以应该在 map 前加一个 .fill() 方法,使之拥有 defined integer properties,进而才能使用它:

1
const result = Array(n).fill().map(() => []);

Array.from

Array.from() 同样是个合适的替代方案: The Array.from() method creates a new, shallow-copied Array instance from an array-like or iterable object.

shallow-copied 说明了它在我们这种情况下的可用性:

1
2
3
4
// perfect!
const result = Array.from({
length: n
}, () => []);

延伸 - Pyramid Transition Matrix

话说回来,有没有什么 array.fill 大放异彩的使用场景呢?

是时候搬出另一道 leetcode medium 算法题 Pyramid Transition Matrix 了,题目介绍在 https://leetcode.com/problems/pyramid-transition-matrix/

有了 array.fill,这道题从未如此简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @param {number} n
*/
function pyramid(n) {
const len = 2 * n - 1;

for (let i = 1; i <= len; i += 2) {
const pound = new Array(i).fill('#').join('');
const blank = new Array((len - i) / 2).fill(' ').join('');

console.log(blank + pound + blank);
}
}

// test
pyramid(3);
// ' # '
// ' ### '
// '#####'

Reference & Thanks

stackoverflow: Whats the difference between Array.fill and a for loop to create Array

stackoverflow: differrent-interaction-of-array-constructor-and-using-array-literal

Create a NxN Matrix Spiral with JavaScript

stackoverflow: difference-between-arrayn-and-arrayn-fill

MDN: Array.prototype.fill()

MDN: Array.prototype.map()

文章标题:Array(n).fill([]) 的不可用性

文章作者:RayJune

时间地点:北京百草园

原始链接:https://www.rayjune.me/2020/02/20/trick-in-array.fill/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。