Tablice postrzępione [][]
Tablica dwuwymiarowa jest prostokątem — każdy wiersz ma tyle samo kolumn. Ale w prawdziwych danych rzadko tak bywa: jeden uczeń ma cztery oceny, inny dwanaście. Tablica postrzępiona to tablica tablic, w której każdy wiersz może mieć własną długość.
Czego się dziś nauczysz
- Utworzysz tablicę, której wiersze mają różne długości
- Odróżnisz zapis
[w][k]od[w, k]i będziesz wiedział, kiedy który - Przejdziesz taką tablicę pętlą, w której warunek zależy od bieżącego wiersza
- Użyjesz metod klasy
Arrayna pojedynczym wierszu - Wybierzesz właściwy rodzaj tablicy do konkretnego zadania
Czym jest tablica postrzępiona
Nazwa („postrzępiona”, ang. jagged) bierze się z wyglądu: prawa krawędź jest nierówna, poszarpana.
tablica dwuwymiarowa int[3,4] tablica postrzepiona int[3][]
[ 1 ][ 2 ][ 3 ][ 4 ] [ 1 ][ 2 ][ 3 ][ 4 ]
[ 5 ][ 6 ][ 7 ][ 8 ] [ 5 ][ 6 ]
[ 9 ][10 ][11 ][12 ] [ 7 ][ 8 ][ 9 ]
prostokat - kazdy wiersz kazdy wiersz ma wlasna
ma tyle samo kolumn dlugosc
Kluczowa różnica jest jednak głębsza niż wygląd. Tablica dwuwymiarowa to jeden obiekt w pamięci. Tablica postrzępiona to tablica, której elementami są inne tablice — każdy wiersz jest osobnym, niezależnym obiektem.
Stąd bierze się cała reszta
Skoro każdy wiersz jest zwykłą tablicą jednowymiarową, to ma własne Length, można go posortować przez Array.Sort, wypisać przez string.Join, a nawet podmienić na inny. Nic z tego nie działa na tablicy dwuwymiarowej. To największa praktyczna zaleta tablic postrzępionych.
Dlaczego to ważne na egzaminie
Tablice postrzępione pojawiają się w wymaganiach kwalifikacji INF.03. Zadania egzaminacyjne lubią dane w rodzaju „każdy uczeń ma inną liczbę ocen” albo „każdy dzień ma inną liczbę pomiarów” — a to jest dokładnie ten przypadek.
Tworzenie
Tworzenie odbywa się w dwóch krokach: najpierw powstaje tablica na wiersze, potem każdy wiersz z osobna.
// KROK 1: tablica na 3 wiersze - same wiersze jeszcze nie istnieja
int[][] dane = new int[3][];
// KROK 2: kazdy wiersz osobno, kazdy moze miec inna dlugosc
dane[0] = new int[4];
dane[1] = new int[2];
dane[2] = new int[3];
dane[0][0] = 1;
dane[1][0] = 5;
dane[2][2] = 9;
Console.WriteLine(dane[0][0]); // 1
Console.WriteLine(dane[2][2]); // 9
Bez kroku drugiego program przerwie działanie
Po new int[3][] masz trzy „puste miejsca na wiersze”, ale samych wierszy jeszcze nie ma. Odwołanie dane[0][0] przed przypisaniem wiersza kończy się błędem NullReferenceException. To najczęstsza pomyłka przy pierwszym użyciu tablic postrzępionych.
Zapis skrócony
Gdy znasz wartości od razu, wszystko mieści się w jednym wyrażeniu:
int[][] oceny =
{
new int[] { 4, 5, 3, 4 }, // wiersz 0 - cztery elementy
new int[] { 3, 3 }, // wiersz 1 - dwa
new int[] { 5, 5, 5, 4, 6, 5 } // wiersz 2 - szesc
};
// od C# 12 mozna jeszcze krocej:
int[][] krocej =
{
[4, 5, 3, 4],
[3, 3],
[5, 5, 5, 4, 6, 5]
};
W przeciwieństwie do tablicy dwuwymiarowej każdy wiersz wymaga własnego new int[] (albo nawiasów kwadratowych w nowszym zapisie) — bo każdy jest osobną tablicą.
Rozmiary wierszy
Nie ma tu GetLength — jest po prostu Length, tylko trzeba wiedzieć, czyje:
int[][] oceny =
{
new int[] { 4, 5, 3, 4 },
new int[] { 3, 3 },
new int[] { 5, 5, 5, 4, 6, 5 }
};
Console.WriteLine(oceny.Length); // 3 - liczba WIERSZY
Console.WriteLine(oceny[0].Length); // 4 - dlugosc wiersza 0
Console.WriteLine(oceny[1].Length); // 2 - dlugosc wiersza 1
Console.WriteLine(oceny[2].Length); // 6 - dlugosc wiersza 2
// lacznej liczby elementow trzeba policzyc samemu
int razem = 0;
foreach (int[] wiersz in oceny)
{
razem += wiersz.Length;
}
Console.WriteLine($"Wszystkich elementow: {razem}"); // 12
3
4
2
6
Wszystkich elementow: 12
| Zapis | Znaczenie | Odpowiednik przy int[,] |
|---|---|---|
t.Length | liczba wierszy | t.GetLength(0) |
t[w].Length | długość wiersza w | t.GetLength(1) — ale ta sama dla wszystkich |
| trzeba policzyć pętlą | liczba wszystkich elementów | t.Length |
Uwaga na tę różnicę
Dla int[,] właściwość Length daje liczbę wszystkich komórek. Dla int[][] daje liczbę wierszy. Ta sama nazwa, dwa różne znaczenia — bo w drugim przypadku elementami naprawdę są wiersze.
Przechodzenie tablicy
Schemat jest ten sam co przy dwóch wymiarach, z jedną istotną zmianą: warunek wewnętrznej pętli zależy od bieżącego wiersza.
for (int w = 0; w < oceny.Length; w++)
{
Console.Write($"Wiersz {w}: ");
for (int k = 0; k < oceny[w].Length; k++) // uwaga: oceny[w].Length
{
Console.Write($"{oceny[w][k]} ");
}
Console.WriteLine();
}
Wiersz 0: 4 5 3 4
Wiersz 1: 3 3
Wiersz 2: 5 5 5 4 6 5
Warunek k < oceny[0].Length to błąd
Użycie długości pierwszego wiersza dla wszystkich to najczęstsza pomyłka w tej lekcji. Przy wierszu krótszym program wyjdzie poza zakres, przy dłuższym po cichu pominie końcówkę danych. Zawsze oceny[w].Length — z tym samym w, po którym chodzi pętla zewnętrzna.
Wersja z foreach
Tu foreach jest wyraźnie ładniejszy niż przy tablicach dwuwymiarowych, bo zmienna pętli zewnętrznej to cała tablica-wiersz:
foreach (int[] wiersz in oceny) // typ zmiennej to int[], nie int!
{
int suma = 0;
foreach (int x in wiersz)
{
suma += x;
}
Console.WriteLine($"{wiersz.Length} elementow, suma {suma}, " +
$"srednia {(double)suma / wiersz.Length:F2}");
}
4 elementow, suma 16, srednia 4,00
2 elementow, suma 6, srednia 3,00
6 elementow, suma 30, srednia 5,00
Typ zmiennej pętli
W foreach (int[] wiersz in oceny) zmienna ma typ int[] — bo elementami tablicy postrzępionej są tablice. To najczęstsze miejsce, w którym warto użyć słowa var: foreach (var wiersz in oceny), i kompilator sam ustali typ.
Wiersz to zwykła tablica
To zdanie jest sednem całej lekcji. Skoro oceny[1] jest zwykłą tablicą int[], to wszystko, czego nauczyłeś się w lekcjach 17 i 18, działa na niej bez zmian:
int[][] dane =
{
new int[] { 42, 17, 93, 8 },
new int[] { 5, 3 },
new int[] { 20, 10, 30 }
};
// sortowanie pojedynczego wiersza
Array.Sort(dane[0]);
Console.WriteLine(string.Join(", ", dane[0]));
// wypisanie wiersza jedna instrukcja
Console.WriteLine(string.Join(" | ", dane[2]));
// szukanie w wierszu
Console.WriteLine(Array.IndexOf(dane[2], 30));
// posortowanie WSZYSTKICH wierszy
foreach (int[] wiersz in dane)
{
Array.Sort(wiersz);
}
// podmiana calego wiersza
dane[1] = new int[] { 100, 200, 300, 400, 500 };
Console.WriteLine(string.Join(", ", dane[1]));
8, 17, 42, 93
20 | 10 | 30
2
100, 200, 300, 400, 500
Dlaczego Array.Sort w foreach działa
Pamiętasz z lekcji 17, że w foreach nie wolno przypisywać do zmiennej pętli? To wciąż prawda — ale my niczego jej nie przypisujemy. Zmienna wiersz zawiera adres tablicy, a Array.Sort zmienia zawartość spod tego adresu. Zakaz dotyczy wiersz = coś, nie Array.Sort(wiersz). To ta sama zasada, którą poznałeś przy referencjach.
Podmiana wiersza zmienia długość
Instrukcja dane[1] = new int[5]; jest całkowicie legalna — po prostu w to miejsce trafia nowa, inna tablica. Tego przy int[,] zrobić się nie da, bo tam nie ma osobnych wierszy do podmiany.
Którą tablicę wybrać
| Cecha | int[,] — dwuwymiarowa | int[][] — postrzępiona |
|---|---|---|
| Kształt | prostokąt | dowolny, „schodkowy” |
| Zapis elementu | t[w, k] | t[w][k] |
| Liczba wierszy | t.GetLength(0) | t.Length |
| Długość wiersza | t.GetLength(1) | t[w].Length |
| Tworzenie | jedna instrukcja | tablica wierszy, potem każdy wiersz |
Metody klasy Array | nie działają | działają na wierszu |
| Podmiana wiersza | niemożliwa | możliwa |
| Pamięć | jeden zwarty blok | osobne obiekty, nieco więcej narzutu |
| Szybkość odczytu | trochę wyższa | trochę niższa (dwa skoki po adresie) |
Prosta reguła wyboru
Dane naprawdę prostokątne — plansza do gry, macierz, obraz — to int[,]. Dane o wierszach różnej długości albo takie, na których chcesz używać Array.Sort i string.Join — to int[][]. W wątpliwych przypadkach wybierz postrzępioną: jest wygodniejsza w obsłudze, a różnica w szybkości przy szkolnych rozmiarach danych jest niemierzalna.
Użyte w tej lekcji elementy
| Nazwa | Zwraca | Działanie |
|---|---|---|
t.Length | int |
Liczba wierszy tablicy postrzępionej (a nie wszystkich elementów, jak przy int[,]). |
t[w].Length | int |
Długość konkretnego wiersza. To zwykłe Length zwykłej tablicy. |
t[w][k] | element | Element: najpierw wybieramy wiersz, potem pozycję w nim. Dwie pary nawiasów. |
Array.Sort(t[w]) | nic | Sortuje jeden wiersz. Na tablicy dwuwymiarowej niemożliwe. |
string.Join(sep, t[w]) | string |
Wypisuje cały wiersz jedną instrukcją. |
Array.IndexOf(t[w], x) | int |
Szuka wartości w jednym wierszu; -1 oznacza brak. |
t[w] = new int[n] | — | Podmiana całego wiersza na nowy, o innej długości. |
var | — | Słowo pozwalające pominąć nazwę typu tam, gdzie kompilator sam ją ustali — wygodne przy foreach (var wiersz in dane). |
Oceny o różnej liczbie
Cztery osoby, każda z inną liczbą ocen — dokładnie ta sytuacja, w której tablica dwuwymiarowa się nie sprawdza.
string[] uczniowie = { "Kowalski", "Nowak", "Wisniewska", "Zielinski" };
int[][] oceny =
{
new int[] { 4, 5, 3, 4 },
new int[] { 3, 3 },
new int[] { 5, 5, 5, 4, 6, 5 },
new int[] { 2, 4, 3 }
};
// --- naglowek ---
Console.WriteLine($"{"Uczen",-12}{"Ocen",6}{"Srednia",10} Oceny");
Console.WriteLine(new string('-', 40));
// --- wiersze ---
int wszystkichOcen = 0;
int sumaWszystkich = 0;
int najlepszy = 0;
double najlepszaSrednia = 0;
for (int w = 0; w < oceny.Length; w++)
{
int suma = 0;
foreach (int o in oceny[w]) // wiersz to zwykla tablica
{
suma += o;
}
int ile = oceny[w].Length;
double srednia = (double)suma / ile;
Console.WriteLine($"{uczniowie[w],-12}{ile,6}{srednia,10:F2} " +
$"{string.Join(", ", oceny[w])}");
wszystkichOcen += ile;
sumaWszystkich += suma;
if (srednia > najlepszaSrednia)
{
najlepszaSrednia = srednia;
najlepszy = w;
}
}
Console.WriteLine(new string('-', 40));
Console.WriteLine();
Console.WriteLine($"Wszystkich ocen: {wszystkichOcen}");
Console.WriteLine($"Srednia klasy: {(double)sumaWszystkich / wszystkichOcen:F2}");
Console.WriteLine($"Najlepszy: {uczniowie[najlepszy]} ({najlepszaSrednia:F2})");
Console.WriteLine();
// --- oceny kazdego posortowane rosnaco ---
Console.WriteLine("Oceny posortowane:");
for (int w = 0; w < oceny.Length; w++)
{
int[] kopia = oceny[w][..]; // kopia, zeby nie zepsuc kolejnosci
Array.Sort(kopia);
Console.WriteLine($" {uczniowie[w],-12}{string.Join(", ", kopia)}");
}
Uczen Ocen Srednia Oceny
----------------------------------------
Kowalski 4 4,00 4, 5, 3, 4
Nowak 2 3,00 3, 3
Wisniewska 6 5,00 5, 5, 5, 4, 6, 5
Zielinski 3 3,00 2, 4, 3
----------------------------------------
Wszystkich ocen: 15
Srednia klasy: 4,07
Najlepszy: Wisniewska (5,00)
Oceny posortowane:
Kowalski 3, 4, 4, 5
Nowak 3, 3
Wisniewska 4, 5, 5, 5, 5, 6
Zielinski 2, 3, 4
Omówienie
| Fragment | Co robi i dlaczego tak |
|---|---|
foreach (int o in oceny[w]) |
oceny[w] to zwykła tablica int[], więc foreach po niej wygląda tak samo jak w lekcji 17. Nie trzeba znać jej długości — pętla sama wie, kiedy skończyć. |
int ile = oceny[w].Length; |
Długość tego wiersza. Przy tablicy dwuwymiarowej byłaby jedna liczba dla wszystkich; tutaj każdy uczeń ma swoją. |
string.Join(", ", oceny[w]) |
Cały wiersz jedną instrukcją. Na int[,] to nie zadziała — trzeba by pisać pętlę. |
wszystkichOcen += ile; |
Liczby wszystkich ocen nie da się odczytać z żadnej właściwości — sumujemy ją po drodze. To jedyna rzecz, którą tablica dwuwymiarowa dałaby za darmo (przez Length). |
sumaWszystkich / wszystkichOcen |
Uwaga: to nie to samo, co średnia ze średnich uczniów. Wisniewska ma sześć ocen, Nowak dwie, więc jej wyniki ważą więcej. Średnia ze średnich dałaby (4,00 + 3,00 + 5,00 + 3,00) / 4 = 3,75, a średnia ze wszystkich ocen to 4,07. Obie liczby są poprawne — odpowiadają na różne pytania. |
int[] kopia = oceny[w][..]; |
Zakres z lekcji 17 zastosowany do wiersza. Sortujemy kopię, bo pierwotna kolejność ocen (chronologiczna) ma swoją wartość. |
Array.Sort(kopia) |
Metoda klasy Array na jednym wierszu — to właśnie ta możliwość, której nie ma przy int[,]. |
Sprawdź rachunek
Kowalski: 4 + 5 + 3 + 4 = 16, średnia 16 / 4 = 4,00. Wisniewska: 5+5+5+4+6+5 = 30, średnia 30 / 6 = 5,00. Wszystkich ocen: 4 + 2 + 6 + 3 = 15, ich suma 16 + 6 + 30 + 9 = 61, więc średnia klasy 61 / 15 = 4,0666… → 4,07.
Na czym się najczęściej potykamy
| Błąd | Co się dzieje i jak poprawić |
|---|---|
new int[3][4] |
Nie skompiluje się. Tablicę postrzępioną tworzy się w dwóch krokach: new int[3][], a potem każdy wiersz osobno. |
| brak tworzenia wierszy | NullReferenceException — po new int[3][] wierszy jeszcze nie ma. Każdy trzeba utworzyć: dane[0] = new int[4];. |
t[w, k] na tablicy postrzępionej |
CS0022 — zła liczba indeksów. Poprawnie: t[w][k], dwie pary nawiasów. |
k < t[0].Length w pętli wewnętrznej |
Długość pierwszego wiersza użyta dla wszystkich. Krótszy wiersz → wyjście poza zakres, dłuższy → ciche pominięcie danych. Zawsze t[w].Length. |
t.Length jako liczba wszystkich elementów |
To liczba wierszy. Wszystkie elementy trzeba policzyć pętlą. |
t.GetLength(1) |
Nie ma sensu — tablica postrzępiona ma jeden wymiar tablic. GetLength(0) zadziała (to liczba wierszy), ale przyjęło się pisać Length. |
foreach (int x in dane) |
CS0030 — elementami są tablice, nie liczby. Poprawnie: foreach (int[] wiersz in dane) albo foreach (var wiersz in dane). |
brak new int[] przy każdym wierszu |
W zapisie skróconym każdy wiersz potrzebuje własnego new int[] { ... } — albo, od C# 12, samych nawiasów kwadratowych. |
| sortowanie oryginału zamiast kopii | Array.Sort(dane[w]) nieodwracalnie zmienia kolejność w tym wierszu. Gdy jest jeszcze potrzebna, sortuj dane[w][..]. |
Zadania
Utwórz tablicę postrzępioną o pięciu wierszach, w której wiersz numer w ma w + 1 elementów, wypełnij ją kolejnymi liczbami naturalnymi i wypisz.
pokaż rozwiązanie
int[][] trojkat = new int[5][];
int licznik = 1;
for (int w = 0; w < trojkat.Length; w++)
{
trojkat[w] = new int[w + 1]; // wiersz 0 ma 1 element, wiersz 4 ma 5
for (int k = 0; k < trojkat[w].Length; k++)
{
trojkat[w][k] = licznik;
licznik++;
}
}
foreach (int[] wiersz in trojkat)
{
Console.WriteLine(string.Join(" ", wiersz));
}
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15
Tworzenie wiersza (new int[w + 1]) odbywa się wewnątrz pętli zewnętrznej, tuż przed jego wypełnieniem. Tego kroku nie ma przy tablicy dwuwymiarowej i właśnie o nim najłatwiej zapomnieć.
licznik jest zadeklarowany przed obiema pętlami, bo ma rosnąć nieprzerwanie przez cały trójkąt. Zerowany w pętli zewnętrznej dałby w każdym wierszu 1, 2, 3…
Dla tablicy postrzępionej wypisz każdy wiersz wraz z jego długością, sumą i średnią. Na końcu podaj wiersz o największej sumie.
pokaż rozwiązanie
int[][] dane =
{
new int[] { 5, 8, 2 },
new int[] { 10, 10 },
new int[] { 1, 2, 3, 4, 5, 6 },
new int[] { 40 }
};
int najlepszyWiersz = 0;
int najwiekszaSuma = 0;
for (int w = 0; w < dane.Length; w++)
{
int suma = 0;
foreach (int x in dane[w])
{
suma += x;
}
double srednia = (double)suma / dane[w].Length;
Console.WriteLine($"Wiersz {w}: {dane[w].Length} el., suma {suma,3}, " +
$"srednia {srednia,6:F2}");
if (suma > najwiekszaSuma)
{
najwiekszaSuma = suma;
najlepszyWiersz = w;
}
}
Console.WriteLine();
Console.WriteLine($"Najwieksza suma: wiersz {najlepszyWiersz} ({najwiekszaSuma})");
Wiersz 0: 3 el., suma 15, srednia 5,00
Wiersz 1: 2 el., suma 20, srednia 10,00
Wiersz 2: 6 el., suma 21, srednia 3,50
Wiersz 3: 1 el., suma 40, srednia 40,00
Najwieksza suma: wiersz 3 (40)
Zwróć uwagę, że wiersz o największej sumie (wiersz 3, jeden element o wartości 40) to co innego niż wiersz o największej średniej — akurat tutaj to ten sam wiersz, ale przy innych danych już nie musi tak być. Zawsze dopytaj, o którą wielkość chodzi.
Suma zeruje się na początku każdego obiegu pętli zewnętrznej — dokładnie ta sama zasada co przy sumach wierszy w lekcji 19.
Zapytaj o liczbę uczniów, a potem dla każdego — o liczbę jego ocen i o same oceny. Na koniec wypisz zestawienie.
pokaż rozwiązanie
int ilu;
bool ok;
do
{
Console.Write("Ilu uczniow (1-10)? ");
ok = int.TryParse(Console.ReadLine(), out ilu) && ilu >= 1 && ilu <= 10;
if (!ok)
{
Console.WriteLine(" Podaj liczbe od 1 do 10.");
}
}
while (!ok);
string[] imiona = new string[ilu];
int[][] oceny = new int[ilu][];
for (int w = 0; w < ilu; w++)
{
Console.WriteLine();
Console.Write($"Imie ucznia {w + 1}: ");
imiona[w] = Console.ReadLine() ?? $"Uczen {w + 1}";
int ile;
do
{
Console.Write(" Ile ocen (1-20)? ");
ok = int.TryParse(Console.ReadLine(), out ile) && ile >= 1 && ile <= 20;
if (!ok)
{
Console.WriteLine(" Podaj liczbe od 1 do 20.");
}
}
while (!ok);
oceny[w] = new int[ile]; // TERAZ tworzymy wiersz
for (int k = 0; k < ile; k++)
{
int ocena;
do
{
Console.Write($" Ocena {k + 1}: ");
ok = int.TryParse(Console.ReadLine(), out ocena) && ocena >= 1 && ocena <= 6;
if (!ok)
{
Console.WriteLine(" Ocena musi byc z zakresu 1-6.");
}
}
while (!ok);
oceny[w][k] = ocena;
}
}
Console.WriteLine();
Console.WriteLine("=== ZESTAWIENIE ===");
for (int w = 0; w < ilu; w++)
{
int suma = 0;
foreach (int o in oceny[w])
{
suma += o;
}
Console.WriteLine($"{imiona[w],-14}{string.Join(", ", oceny[w]),-24}" +
$"srednia {(double)suma / oceny[w].Length:F2}");
}
Ilu uczniow (1-10)? 2
Imie ucznia 1: Ala
Ile ocen (1-20)? 3
Ocena 1: 4
Ocena 2: 5
Ocena 3: 3
Imie ucznia 2: Bartek
Ile ocen (1-20)? 2
Ocena 1: 2
Ocena 2: 4
=== ZESTAWIENIE ===
Ala 4, 5, 3 srednia 4,00
Bartek 2, 4 srednia 3,00
Kolejność jest tu wszystkim. Wiersz można utworzyć dopiero wtedy, gdy znamy jego długość — czyli po pytaniu „ile ocen”. To właśnie ta elastyczność, której tablica dwuwymiarowa nie daje: tam rozmiar wszystkich wierszy trzeba znać z góry.
Trzy zagnieżdżone pętle do-while to trzy niezależne walidacje. Każda pilnuje swojego pytania i każda używa tego samego wzorca z lekcji 14.
Zmienna ok jest zadeklarowana raz, na samej górze, i używana ponownie w każdej pętli. To dopuszczalne, bo w każdej chwili interesuje nas tylko ostatnie sprawdzenie.
Zapis {string.Join(", ", oceny[w]),-24} pokazuje ciekawą rzecz: wyrównanie stosuje się do wyniku wyrażenia, więc można wyrównać cały sklejony napis.
Zbuduj trójkąt Pascala o zadanej liczbie wierszy: pierwszy i ostatni element każdego wiersza to 1, a każdy element w środku jest sumą dwóch elementów stojących nad nim.
pokaż rozwiązanie
const int Wierszy = 6;
int[][] pascal = new int[Wierszy][];
for (int w = 0; w < Wierszy; w++)
{
pascal[w] = new int[w + 1];
pascal[w][0] = 1; // pierwszy zawsze 1
pascal[w][w] = 1; // ostatni zawsze 1
for (int k = 1; k < w; k++) // tylko srodek - stad start od 1
{
pascal[w][k] = pascal[w - 1][k - 1] + pascal[w - 1][k];
}
}
foreach (int[] wiersz in pascal)
{
foreach (int x in wiersz)
{
Console.Write($"{x,4}");
}
Console.WriteLine();
}
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
Wewnętrzna pętla idzie od 1 do w (bez w). Skrajne elementy są już ustawione, więc liczymy tylko środek. Dla wierszy 0 i 1 pętla nie wykona się ani razu — to poprawne, bo tam nie ma żadnego środka.
pascal[w][0] = 1; pascal[w][w] = 1; — dla wiersza 0 obie linie zapisują ten sam element. Nie szkodzi, wartość jest ta sama.
Wzór pascal[w-1][k-1] + pascal[w-1][k] sięga do poprzedniego wiersza, który jest już policzony. To dlatego wiersze muszą powstawać po kolei, od góry.
Sprawdź na wierszu 4: 4 = 1 + 3 (z wiersza 3), 6 = 3 + 3, 4 = 3 + 1.
To zadanie idealnie pokazuje sens tablicy postrzępionej — wiersz w ma dokładnie w + 1 elementów, więc prostokąt marnowałby połowę miejsca i wymagał pilnowania zer.
Dla siedmiu dni tygodnia zapisano różną liczbę pomiarów temperatury. Dla każdego dnia podaj liczbę pomiarów, średnią oraz rozstęp (różnicę między największym a najmniejszym). Wskaż dzień o największym rozstępie.
pokaż rozwiązanie
string[] dni = { "Pn", "Wt", "Sr", "Cz", "Pt", "So", "Nd" };
double[][] pomiary =
{
new double[] { 12.5, 15.0, 18.5, 14.0 },
new double[] { 10.0, 11.5 },
new double[] { 16.0, 19.5, 22.0, 20.5, 17.0 },
new double[] { 21.0 },
new double[] { 8.5, 12.0, 15.5 },
new double[] { 14.0, 16.5, 15.0, 13.5, 12.0, 11.0 },
new double[] { 19.0, 20.0 }
};
Console.WriteLine($"{"Dzien",-7}{"Pomiarow",10}{"Srednia",10}{"Min",8}{"Max",8}{"Rozstep",10}");
Console.WriteLine(new string('-', 53));
int dzienMaxRozstepu = 0;
double maxRozstep = -1;
for (int d = 0; d < pomiary.Length; d++)
{
double[] dzien = pomiary[d]; // krotsza nazwa dla wiersza
double suma = 0;
double min = dzien[0];
double max = dzien[0];
foreach (double t in dzien)
{
suma += t;
min = Math.Min(min, t);
max = Math.Max(max, t);
}
double srednia = suma / dzien.Length;
double rozstep = max - min;
Console.WriteLine($"{dni[d],-7}{dzien.Length,10}{srednia,10:F2}" +
$"{min,8:F1}{max,8:F1}{rozstep,10:F1}");
if (rozstep > maxRozstep)
{
maxRozstep = rozstep;
dzienMaxRozstepu = d;
}
}
Console.WriteLine(new string('-', 53));
Console.WriteLine();
Console.WriteLine($"Najwiekszy rozstep: {dni[dzienMaxRozstepu]} ({maxRozstep:F1} stopnia)");
Dzien Pomiarow Srednia Min Max Rozstep
-----------------------------------------------------
Pn 4 15,00 12,5 18,5 6,0
Wt 2 10,75 10,0 11,5 1,5
Sr 5 19,00 16,0 22,0 6,0
Cz 1 21,00 21,0 21,0 0,0
Pt 3 12,00 8,5 15,5 7,0
So 6 13,67 11,0 16,5 5,5
Nd 2 19,50 19,0 20,0 1,0
-----------------------------------------------------
Najwiekszy rozstep: Pt (7,0 stopnia)
double[] dzien = pomiary[d]; to nie kopia, tylko drugi klucz do tej samej tablicy — zmienna referencyjna z lekcji 17. Robimy to wyłącznie dla skrócenia zapisu: dzien[0] czyta się lepiej niż pomiary[d][0]. Gdybyśmy chcieli tę tablicę zmieniać bez wpływu na oryginał, potrzebna byłaby kopia pomiary[d][..].
min i max startują od dzien[0] — i tu jest to szczególnie ważne, bo temperatury mogą być ujemne. Start od zera dałby dla mroźnego dnia maksimum równe 0.
Czwartek ma tylko jeden pomiar, więc rozstęp wynosi 0, a średnia równa się temu jedynemu pomiarowi. Program radzi sobie z tym bez żadnego dodatkowego warunku — to zasługa tego, że min i max zaczynają od pierwszego elementu.
maxRozstep = -1 na starcie, bo rozstęp nie może być ujemny — dowolny prawdziwy wynik go przebije. Start od zera byłby tu ryzykowny: gdyby wszystkie dni miały po jednym pomiarze, żaden nie przebiłby zera i program wskazałby dzień 0 bez uzasadnienia.
Sprawdź środę: 16,0 + 19,5 + 22,0 + 20,5 + 17,0 = 95,0, średnia 95 / 5 = 19,00, rozstęp 22,0 − 16,0 = 6,0.
Posortuj wiersze tablicy postrzępionej malejąco według ich sumy — tak, żeby wiersz o największej sumie znalazł się na górze. Wykorzystaj Array.Sort z dwiema tablicami.
pokaż rozwiązanie
int[][] dane =
{
new int[] { 5, 8, 2 }, // suma 15
new int[] { 10, 10 }, // suma 20
new int[] { 1, 2, 3, 4, 5, 6 }, // suma 21
new int[] { 40 }, // suma 40
new int[] { 7, 7, 7 } // suma 21
};
// --- klucz sortowania: suma kazdego wiersza ---
int[] sumy = new int[dane.Length];
for (int w = 0; w < dane.Length; w++)
{
foreach (int x in dane[w])
{
sumy[w] += x;
}
}
Console.WriteLine("Przed sortowaniem:");
Wypisz(dane, sumy);
// --- sortujemy: sumy jako klucz, wiersze jada za nimi ---
Array.Sort(sumy, dane);
Array.Reverse(sumy);
Array.Reverse(dane); // OBIE tablice!
Console.WriteLine();
Console.WriteLine("Po sortowaniu malejaco:");
Wypisz(dane, sumy);
static void Wypisz(int[][] t, int[] s)
{
for (int w = 0; w < t.Length; w++)
{
Console.WriteLine($" suma {s[w],3}: {string.Join(", ", t[w])}");
}
}
Przed sortowaniem:
suma 15: 5, 8, 2
suma 20: 10, 10
suma 21: 1, 2, 3, 4, 5, 6
suma 40: 40
suma 21: 7, 7, 7
Po sortowaniu malejaco:
suma 40: 40
suma 21: 7, 7, 7
suma 21: 1, 2, 3, 4, 5, 6
suma 20: 10, 10
suma 15: 5, 8, 2
To jest coś, czego nie da się zrobić na tablicy dwuwymiarowej. Array.Sort(sumy, dane) traktuje dane jak zwykłą tablicę pięciu elementów — a że jej elementami są tablice, przestawia całe wiersze naraz. Przy int[,] wierszy nie ma czego przestawiać, bo to jeden zwarty obiekt.
sumy[w] += x; działa bez wcześniejszego zerowania, bo świeżo utworzona tablica int[] jest wypełniona zerami. To ta wartość domyślna z lekcji 17.
Pamiętaj o odwróceniu obu tablic. Odwrócenie samych sum rozjechałoby je z wierszami — i program pokazałby złe liczby, nie zgłaszając żadnego błędu. Ten sam problem był w lekcji 18 przy rankingu uczniów.
Zwróć uwagę na dwa wiersze o sumie 21: po sortowaniu stanęły obok siebie, ale ich wzajemna kolejność mogła się zmienić. Array.Sort nie gwarantuje stabilności — przy równych kluczach nie wiadomo, który element trafi wyżej.
Co trzeba zapamiętać
- Tablica postrzępiona to tablica tablic:
int[][], dwie pary nawiasów. - Tworzenie odbywa się w dwóch krokach: najpierw
new int[n][], potem każdy wiersz osobno. - Bez utworzenia wiersza odwołanie do niego kończy się
NullReferenceException. - Element to
t[w][k], niet[w, k]. t.Lengthto liczba wierszy,t[w].Length— długość konkretnego wiersza.- W pętli wewnętrznej warunek to
k < t[w].Length— z bieżącymw. - Każdy wiersz jest zwykłą tablicą, więc działają na nim
Array.Sort,Array.IndexOfistring.Join. - Cały wiersz można podmienić na inny, także innej długości.
- Liczby wszystkich elementów nie da się odczytać — trzeba ją policzyć pętlą.
- Dane prostokątne →
int[,]. Wiersze różnej długości albo potrzeba metodArray→int[][].