1. Чим зубчасті масиви відрізняються від двовимірних масивів
Ось ми дійшли до теми, яку часто називають «масивами масивів» або «зубчастими масивами» — англійською jagged arrays. На відміну від двовимірних масивів, зубчасті масиви дають змогу зберігати рядки різної довжини. Це ніби у вас є комплекс будинків, у кожному — своя кількість квартир: в одному будинку — 5 квартир, в іншому — 20, а в третьому — лише одна.
Зубчастий масив — це масив, кожен елемент якого сам по собі є масивом. При цьому вкладені масиви (їх ще називають «підмасивами») можуть мати різну довжину.
Головна відмінність:
- У двовимірному масиві кожен «рядок» (і кожен «стовпець») має однакову кількість елементів. Приклад: int[,] grid = new int[3, 5]; — у нас завжди 3 рядки по 5 елементів.
- У зубчастому масиві кожен рядок може мати різну довжину! Приклад: int[][] jagged = new int[3][]; — і лише потім ми кожен рядок (підмасив) ініціалізуємо по‑своєму.
Ось як це виглядає візуально:
| Двовимірний масив | Зубчастий масив | |
|---|---|---|
| Кількість елементів | Суворо фіксована (наприклад, 3×5) | Може відрізнятися між різними рядками |
| Індексація | |
|
| Гнучкість | Низька | Висока |
| Застосування | Таблиці, математика | Нерівномірні дані: списки студентів із різною кількістю оцінок, трикутники |
Візуалізація: порівняємо двовимірний і зубчастий масив
Двовимірний масив (3x3):
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
├───┼───┼───┤
│ 4 │ 5 │ 6 │
├───┼───┼───┤
│ 7 │ 8 │ 9 │
└───┴───┴───┘
Зубчастий масив (різні довжини):
┌───┬───┐
│ 1 │ 2 │
├───┼───┼───┬───┐
│ 3 │ 4 │ 5 │ 6 │
├───┼───┴───┴───┘
│ 7 │
└───┘
2. Синтаксис оголошення та ініціалізації зубчастого масиву
Оголошення зубчастого масиву не складніше, ніж оголошення інших знайомих вам типів. Не лякайтеся подвійних квадратних дужок:
int[][] jaggedArray = new int[3][];
Це означає, що у нас є масив із 3 елементів, і кожен із них — також масив типу int. Внутрішні масиви ще не створені! Для кращого розуміння давайте розберімо це детальніше.
Покрокова ініціалізація зубчастого масиву
Крок 1 — створення основного (зовнішнього) масиву:
int[][] jaggedArray = new int[3][];
Тепер у нас є 3 «рядки», але наразі всі вони дорівнюють null.
Крок 2 — створення та заповнення внутрішніх масивів (підмасивів):
Наприклад, перший рядок має довжину 2, другий — 4, третій — 3:
jaggedArray[0] = new int[2]; // 2 елементи в першому рядку
jaggedArray[1] = new int[4]; // 4 елементи в другому рядку
jaggedArray[2] = new int[3]; // 3 елементи в третьому рядку
Крок 3 — заповнення значеннями:
Внутрішні масиви — це звичайні масиви! Наприклад:
jaggedArray[0][0] = 1;
jaggedArray[0][1] = 2;
jaggedArray[1][0] = 3;
jaggedArray[1][1] = 4;
jaggedArray[1][2] = 5;
jaggedArray[1][3] = 6;
jaggedArray[2][0] = 7;
jaggedArray[2][1] = 8;
jaggedArray[2][2] = 9;
Коротка ініціалізація зубчастого масиву
Можна створити та заповнити зубчастий масив одразу, якщо ви заздалегідь знаєте значення:
int[][] jaggedArray = new int[][]
{
new int[] { 1, 2 },
new int[] { 3, 4, 5, 6 },
new int[] { 7, 8, 9 }
};
Або ще коротше, опускаючи тип внутрішніх масивів:
int[][] jaggedArray = {
new[] { 1, 2 },
new[] { 3, 4, 5, 6 },
new[] { 7, 8, 9 }
};
3. Перебирання та робота із зубчастими масивами
Перебирати зубчастий масив не складніше, ніж двовимірний, але тепер зовнішній цикл проходить рядками, а внутрішній — елементами кожного рядка (їхня довжина може різнитися):
for (int i = 0; i < jaggedArray.Length; i++)
{
Console.WriteLine($"Рядок {i}:");
for (int j = 0; j < jaggedArray[i].Length; j++)
{
Console.Write($"{jaggedArray[i][j]} ");
}
Console.WriteLine();
}
Результат на екрані:
Рядок 0:
1 2
Рядок 1:
3 4 5 6
Рядок 2:
7 8 9
Можна використовувати foreach, щоб не працювати з індексами вручну:
foreach (int[] row in jaggedArray)
{
foreach (int value in row)
{
Console.Write($"{value} ");
}
Console.WriteLine();
}
4. Влаштування масиву масивів
А зараз ви дізнаєтеся, як насправді влаштовані масиви масивів. Готові?
У випадку зі звичайним масивом змінна‑масив зберігає посилання на контейнер, де містяться елементи масиву. А із зубчастими масивами ситуація інша: змінна‑масив‑масивів зберігає посилання на контейнер, у комірках якого зберігаються посилання на одномірні масиви. Це краще один раз побачити, ніж сто разів пояснювати:
Ліворуч маємо «змінну‑масив‑масивів», яка зберігає посилання на «об’єкт‑контейнер масивів». Посередині — «об’єкт‑контейнер масивів», у комірках якого зберігаються посилання на одномірні масиви — рядки зубчастого масиву. А праворуч ви бачите чотири одномірні масиви — рядки нашого зубчастого масиву.
Ось так насправді влаштовані зубчасті масиви. І такий підхід дає C#‑програмісту кілька переваг:
По‑перше, оскільки «контейнер контейнерів» зберігає посилання на «масиви‑рядки», ми можемо дуже швидко й просто міняти рядки місцями. Щоб отримати доступ до «контейнера контейнерів», достатньо вказати один індекс замість двох. Приклад:int[][] data = new int[2][];
data[0] = new int[5]; // перший рядок — масив з 5 елементів
data[1] = new int[5]; // другий рядок — масив з 5 елементів
int[] row1 = data[0];
int[] row2 = data[1];
Ось так можна поміняти рядки місцями:
// Важлива матриця з даними
int[][] matrix = {
new int[] {1, 2, 3, 4, 5},
new int[] {5, 4, 3, 2, 1}
};
int[] tmp = matrix[0];
matrix[0] = matrix[1];
matrix[1] = tmp;
Якщо ви звертаєтеся до комірки двовимірного масиву, але після імені масиву вказуєте лише один індекс, то звертаєтеся до «контейнера контейнерів», у комірках якого зберігаються посилання на звичайні одномірні масиви.
5. Типові сценарії використання зубчастих масивів
Коли зубчастий масив може бути кориснішим за двовимірний?
- Коли ви зберігаєте для кожного користувача різну кількість певних даних: оцінки з предметів, покупки, коментарі тощо.
- Коли ваші дані мають трикутну або східчасту структуру (наприклад, для виведення «пірамідок», трикутника Паскаля тощо).
- Коли потрібно економити пам’ять: у двовимірному масиві всі рядки фіксовані, а в зубчастому — лише потрібна кількість елементів.
Приклад із життя: менеджер оцінок студентів
Давайте розширимо наш навчальний проєкт! Нехай у кожного студента може бути різна кількість оцінок з кожного предмета. Наприклад, хтось здає більше робіт, хтось — менше. Для цього ідеально підійде зубчастий масив.
Припустимо, у нас є три студенти, і ось їхні оцінки за різні завдання з математики:
| Студент | Оцінки |
|---|---|
| 0 | 5, 4 |
| 1 | 3, 4, 4 |
| 2 | 5 |
Оголосимо такий масив:
int[][] studentMarks = new int[3][];
studentMarks[0] = new int[] { 5, 4 }; // Перший студент - 2 оцінки
studentMarks[1] = new int[] { 3, 4, 4 }; // Другий студент - 3 оцінки
studentMarks[2] = new int[] { 5 }; // Третій студент - 1 оцінка
Виведемо оцінки кожного студента:
for (int i = 0; i < studentMarks.Length; i++)
{
Console.Write($"Студент {i}: ");
for (int j = 0; j < studentMarks[i].Length; j++)
{
Console.Write(studentMarks[i][j] + " ");
}
Console.WriteLine();
}
Використання зубчастих масивів із іншими типами
Зубчастим може бути масив чого завгодно: рядків, масивів інших масивів (глибше!), навіть ваших власних об’єктів.
Приклад: масив рядків
string[][] groups = new string[][]
{
new string[] { "Іван", "Петро" },
new string[] { "Марія", "Олексій", "Сергій" },
new string[] { "Василиса" }
};
6. Особливості та можливі помилки
Зубчасті масиви — річ гнучка, але пастки трапляються на кожному кроці.
- Якщо ви не ініціалізували якийсь із внутрішніх масивів (jaggedArray[1] = ...), спроба звернутися до нього призведе до NullReferenceException. Не забувайте ініціалізувати кожен внутрішній масив!
- Рядки (підмасиви) можуть мати різну довжину. Якщо використовувати фіксований індекс у другому вимірі, легко вийти за межі конкретного підмасиву.
- Не плутайте з двовимірним масивом! Індексація має вигляд: array[i][j], а не array[i, j].
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ