JavaRush /Курси /C# SELF /Зубчасті масиви (Jagged Arrays) у C#

Зубчасті масиви (Jagged Arrays) у C#

C# SELF
Рівень 7 , Лекція 5
Відкрита

1. Чим зубчасті масиви відрізняються від двовимірних масивів

Ось ми дійшли до теми, яку часто називають «масивами масивів» або «зубчастими масивами» — англійською jagged arrays. На відміну від двовимірних масивів, зубчасті масиви дають змогу зберігати рядки різної довжини. Це ніби у вас є комплекс будинків, у кожному — своя кількість квартир: в одному будинку — 5 квартир, в іншому — 20, а в третьому — лише одна.

Зубчастий масив — це масив, кожен елемент якого сам по собі є масивом. При цьому вкладені масиви (їх ще називають «підмасивами») можуть мати різну довжину.

Головна відмінність:

  • У двовимірному масиві кожен «рядок» (і кожен «стовпець») має однакову кількість елементів. Приклад: int[,] grid = new int[3, 5]; — у нас завжди 3 рядки по 5 елементів.
  • У зубчастому масиві кожен рядок може мати різну довжину! Приклад: int[][] jagged = new int[3][]; — і лише потім ми кожен рядок (підмасив) ініціалізуємо по‑своєму.

Ось як це виглядає візуально:

Двовимірний масив Зубчастий масив
Кількість елементів Суворо фіксована (наприклад, 3×5) Може відрізнятися між різними рядками
Індексація
[i, j]
[i][j]
Гнучкість Низька Висока
Застосування Таблиці, математика Нерівномірні дані:
списки студентів із різною кількістю оцінок, трикутники

Візуалізація: порівняємо двовимірний і зубчастий масив

Двовимірний масив (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].
1
Опитування
Масиви, рівень 7, лекція 5
Недоступний
Масиви
Знайомство з масивами
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ