В рамках функций ES6 метод Array.prototype.fill () позволяет нам добавлять, заменять или генерировать новые элементы в массиве.
Для меня это умная альтернатива старым циклам for, когда дело доходит до заполнения одних и тех же элементов внутри массива.
Например, если вы хотите создать массив из трех одинаковых элементов, вместо того, чтобы создавать пустой массив и вставлять каждый новый элемент в массив с помощью циклов for, этот однострочный элемент сделает свое дело:
const threeFives = Array(3).fill(5)
threeFives
// [5, 5, 5]
Теперь давайте попробуем реализовать метод fill()
в этой задаче классического алгоритма спиральной матрицы.
/* Direction: Write a function that accepts an integer N, and returns a NxN spiral matrix. */
// Examples:
matrix(2) // [[1, 2] // [4, 3]]
matrix(3) // [[1, 2, 3], // [8, 9, 4], // [7, 6, 5]]
matrix(4) // [[1, 2, 3, 4], // [12, 13, 14, 5], // [11, 16, 15, 6], // [10, 9, 8, 7]]
Если вы не работали над этой задачей, я рекомендую вам сначала попробовать, прежде чем читать дальше.
Большинство найденных мной решений начинаются с заполнения пустого массива пустых подмассивов, например этого:
function matrix(n) { const results = [] for (let i = 0; i < n; i++) { results.push([]) }
let counter = 1 let startColumn = 0 let endColumn = n - 1 let startRow = 0 let endRow = n - 1 while (startColumn <= endColumn && startRow <= endRow){ // top row for (let i = startColumn; i <= endColumn; i++) { results[startRow][i] = counter counter++ } startRow++ // right column for (let i = startRow; i <= endRow; i++) { results[i][endColumn] = counter counter++ } endColumn-- // bottom row for (let i = endColumn; i >= startColumn; i--) { results[endRow][i] = counter counter++ } endRow-- // start column for (let i = endRow; i >= startRow; i--) { results[i][startColumn] = counter counter++ } startColumn++ } return results }
Как упоминалось ранее, мы можем использовать fill()
вместо цикла for в первых трех строках. Итак, вместо:
const results = []
for (let i = 0; i < n; i++) {
results.push([])
}
Мы могли бы использовать fill()
так:
const results = Array(n).fill([])
На этом этапе, когда вы регистрируете results
в своей консоли, вы можете видеть, что они производят одинаковый вывод.
Круто, так что если мы сохраним ту же логику в остальной части решения, мы увидим тот же результат, не так ли?
Неправильный.
Согласно документации MDN, если первым параметром метода fill()
является объект, каждый слот в массиве будет ссылаться на этот объект.
И именно здесь использование fill()
может начать испортить вашу красиво созданную матричную функцию.
Как показано в той же документации MDN, если вы передаете объект и изменяете элементы внутри одного объекта, все последующие объекты в массиве будут заполнены одними и теми же элементами.
let arr = Array(3).fill({}) // [{}, {}, {}]
arr[0].hi = "hi" // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]
Точно так же в матричной функции все подмассивы, которые мы создали с помощью fill()
, изменятся, даже если мы хотим изменить только один подмассив.
Здесь я заносил в консоль результаты каждой итерации при производстве основной матрицы. Как вы можете видеть ниже, все подмассивы, созданные с помощью fill([])
, продолжают зеркалировать первый подмассив:
using for loop: [ [ 1 ], [] ]
using fill([]): [ [ 1 ], [ 1 ] ]
=====
using for loop: [ [ 1, 2 ], [] ]
using fill([]): [ [ 1, 2 ], [ 1, 2 ] ]
=====
using for loop: [ [ 1, 2 ], [ <1 empty item>, 3 ] ]
using fill([]): [ [ 1, 3 ], [ 1, 3 ] ]
=====
using for loop: [ [ 1, 2 ], [ 4, 3 ] ]
using fill([]): [ [ 4, 3 ], [ 4, 3 ] ]
=====
Означает ли это, что мы не должны использовать fill()
для создания массива пустых подмассивов?
Вот решение, которое я нашел, которое, похоже, успешно создает заполнители массива без побочных эффектов:
const results = Array(n).fill().map(()=>Array(n).fill());
Вместо того, чтобы заполнять массив массивами, которые являются типами объектов, мы просто заполняем его пустотой. В JavaScript «ничто» - это undefined
, который НЕ является объектом, что позволяет нам переназначать новые элементы на каждой итерации, не затрагивая другие подмассивы.
Вы можете увидеть разницу, сравнив все три способа создания массивов и подмассивов:
using for loop: [ [ 1 ], [] ]
using fill([]): [ [ 1 ], [ 1 ] ]
using fill().map(): [ [ 1, undefined ], [ undefined, undefined ] ]
=====
using for loop: [ [ 1, 2 ], [] ]
using fill([]): [ [ 1, 2 ], [ 1, 2 ] ]
using fill().map(): [ [ 1, 2 ], [ undefined, undefined ] ]
=====
using for loop: [ [ 1, 2 ], [ <1 empty item>, 3 ] ]
using fill([]): [ [ 1, 3 ], [ 1, 3 ] ]
using fill().map(): [ [ 1, 2 ], [ undefined, 3 ] ]
=====
using for loop: [ [ 1, 2 ], [ 4, 3 ] ]
using fill([]): [ [ 4, 3 ], [ 4, 3 ] ]
using fill().map(): [ [ 1, 2 ], [ 4, 3 ] ]
=====
В следующий раз, когда у вас возникнет искушение использовать метод fill()
, обязательно проверьте тип значения, которое вы передаете. Это был тяжелый урок, который я усвоил, почти взломав задачу кода спиральной матрицы со второй попытки .