Two-dimensional arrays and matrices in TypeScript 5 (Node.js)
Введение: зачем нужны двумерные массивы?
Представьте себе шахматную доску. У неё есть строки (горизонтали) и столбцы (вертикали). Чтобы описать такую структуру в коде, обычного одномерного массива недостаточно. Здесь на помощь приходят двумерные массивы — массивы, элементами которых являются другие массивы. В программировании их часто называют матрицами.
В этой статье мы разберёмся, как создавать, заполнять и обрабатывать двумерные массивы в TypeScript 5 (среда Node.js). Вы узнаете, как работать с таблицами данных, изображениями (пиксели — это тоже матрица), игровыми полями и многим другим.
Основные концепции
Что такое двумерный массив?
Формально, двумерный массив — это массив, каждый элемент которого сам является массивом. Если мы говорим о матрице размером M x N, то у нас есть M строк и N столбцов. Доступ к элементу осуществляется по двум индексам: arr[строка][столбец].
В TypeScript (как и в JavaScript) двумерные массивы не являются встроенным типом данных — это просто массив массивов. Но благодаря строгой типизации мы можем описать их форму.
Типизация в TypeScript 5
Для объявления двумерного массива чисел используется синтаксис:
let matrix: number[][] = [];
Или более строгий вариант с указанием размера:
let grid: Array<Array<number>> = [];
Если вы хотите создать матрицу с заранее известными размерами, но пустую, можно сделать так:
const rows = 3;
const cols = 4;
// Создаём массив из rows элементов, каждый из которых — массив из cols нулей
const matrix: number[][] = Array.from({ length: rows }, () => new Array(cols).fill(0));
console.log(matrix);
// [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
fill() заполняет массив ссылками на один и тот же объект, если вы используете его для массивов или объектов. Для примитивов (числа, строки) это безопасно. Для матриц всегда создавайте новый подмассив через Array.from или цикл.
Создание и инициализация матриц
1. Статическая инициализация (литералы)
Самый простой способ — перечислить все элементы:
const identityMatrix: number[][] = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
console.log(identityMatrix[1][1]); // 1
2. Динамическое создание (циклы)
Часто размер матрицы становится известен только во время выполнения программы. Используем вложенные циклы:
function createMatrix(rows: number, cols: number, initialValue: number = 0): number[][] {
const matrix: number[][] = [];
for (let i = 0; i < rows; i++) {
const row: number[] = [];
for (let j = 0; j < cols; j++) {
row.push(initialValue);
}
matrix.push(row);
}
return matrix;
}
const myMatrix = createMatrix(2, 3, 7);
console.log(myMatrix); // [[7, 7, 7], [7, 7, 7]]
3. Использование Array.from (современный способ)
const rows = 4;
const cols = 5;
const matrix: number[][] = Array.from({ length: rows }, () => Array(cols).fill(0));
// Более короткая запись, но помните про fill() для объектов!
Обход и работа с элементами
Обход по строкам и столбцам
Классический способ — два вложенных цикла for:
const matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
console.log(`Элемент [${i}][${j}] = ${matrix[i][j]}`);
}
}
Обход с помощью forEach
Более функциональный стиль:
matrix.forEach((row, rowIndex) => {
row.forEach((value, colIndex) => {
console.log(`[${rowIndex}][${colIndex}] = ${value}`);
});
});
Изменение элемента
matrix[0][1] = 100; // Теперь первая строка: [1, 100, 3]
Примеры кода: полезные алгоритмы
1. Транспонирование матрицы
Транспонирование — это превращение строк в столбцы. Если матрица была размером M x N, после транспонирования она станет N x M.
function transpose(matrix: number[][]): number[][] {
const rows = matrix.length;
const cols = matrix[0].length;
const result: number[][] = Array.from({ length: cols }, () => new Array(rows).fill(0));
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
result[j][i] = matrix[i][j];
}
}
return result;
}
const original = [
[1, 2, 3],
[4, 5, 6]
];
console.log(transpose(original));
// [[1, 4], [2, 5], [3, 6]]
2. Сумма элементов матрицы
function sumMatrix(matrix: number[][]): number {
let sum = 0;
for (const row of matrix) {
for (const value of row) {
sum += value;
}
}
return sum;
}
const data = [[1, 2], [3, 4], [5, 6]];
console.log(sumMatrix(data)); // 21
3. Поиск максимального элемента
function findMax(matrix: number[][]): number {
let max = -Infinity;
for (const row of matrix) {
for (const value of row) {
if (value > max) max = value;
}
}
return max;
}
const matrix = [[-5, 12], [0, 8], [3, -1]];
console.log(findMax(matrix)); // 12
Практика: задача "Морской бой" (упрощённая)
Давайте напишем небольшую программу, которая создаёт игровое поле 5x5 и случайным образом расставляет 3 однопалубных корабля (1 клетка). Затем программа проверяет, есть ли корабль по заданным координатам.
// Тип для клетки: 0 - пусто, 1 - корабль
type Cell = 0 | 1;
type BattleField = Cell[][];
function createField(size: number): BattleField {
// Заполняем нулями
const field: BattleField = Array.from({ length: size }, () => new Array(size).fill(0) as Cell[]);
// Ставим 3 корабля случайным образом (без проверки на пересечение для простоты)
let placed = 0;
while (placed < 3) {
const row = Math.floor(Math.random() * size);
const col = Math.floor(Math.random() * size);
if (field[row][col] === 0) {
field[row][col] = 1;
placed++;
}
}
return field;
}
function checkShot(field: BattleField, row: number, col: number): string {
if (row < 0 || row >= field.length || col < 0 || col >= field[0].length) {
return "Мимо! (за пределами поля)";
}
return field[row][col] === 1 ? "Попадание!" : "Мимо!";
}
// Демонстрация
const field = createField(5);
console.log("Поле (0 - пусто, 1 - корабль):");
field.forEach(row => console.log(row.join(' ')));
const testRow = 2, testCol = 3;
console.log(`\nВыстрел по координатам [${testRow}][${testCol}]: ${checkShot(field, testRow, testCol)}`);
checkShot проверяет, не вышли ли мы за границы, и возвращает результат.
Работа с нерегулярными (зубчатыми) массивами
В TypeScript строки матрицы могут быть разной длины. Это называется зубчатый массив (jagged array). Например:
const jagged: number[][] = [
[1, 2],
[3, 4, 5, 6],
[7]
];
console.log(jagged[1].length); // 4
При обходе таких массивов всегда используйте matrix[i].length во внутреннем цикле, а не фиксированное значение.
Производительность и best practices
- Избегайте частого изменения размера — операции
pushв цикле могут быть медленными для больших матриц. Лучше заранее выделить память черезnew Array(size). - Типизируйте вложенные массивы — используйте
number[][]илиArray<Array<number>>. Для сложных структур создавайте отдельный тип. - Проверяйте границы — обращение по несуществующему индексу вернёт
undefined, что может привести к ошибкам. - Для математических операций (умножение матриц, детерминант) используйте готовые библиотеки, например
mathjs, но для обучения полезно писать свои реализации.
Итог
Мы разобрали, что такое двумерные массивы и матрицы в TypeScript 5, научились их создавать, заполнять и обрабатывать. Вы увидели, как работают вложенные циклы, как транспонировать матрицу и даже написали мини-игру "Морской бой".
Двумерные массивы — фундаментальная структура данных. Они лежат в основе работы с таблицами, изображениями, графами (матрицы смежности) и многими другими областями. Освоив их, вы сделаете большой шаг вперёд в изучении программирования.
Попробуйте самостоятельно модифицировать примеры: добавьте проверку на пересечение кораблей, реализуйте умножение матриц или напишите функцию, которая поворачивает матрицу на 90 градусов. Удачи!
```