Tablice dwuwymiarowe
Tablica jednowymiarowa to lista. Dwuwymiarowa to tabela: wiersze i kolumny, jak w arkuszu kalkulacyjnym albo w dzienniku ocen. Element wskazujemy dwoma numerami — wierszem i kolumną — a do przejścia całości potrzebne są dwie pętle, po jednej na każdy wymiar.
Czego się dziś nauczysz
- Utworzysz tablicę dwuwymiarową i wypełnisz ją danymi
- Odczytasz element po numerze wiersza i kolumny
- Odróżnisz
GetLength(0)odGetLength(1)i odLength - Przejdziesz całą tabelę dwiema pętlami i wypiszesz ją w równych kolumnach
- Policzysz sumy i średnie osobno dla wierszy i dla kolumn
Czym jest tablica dwuwymiarowa
Wróćmy do obrazka z szafką. Tablica jednowymiarowa to jeden rząd ponumerowanych szuflad. Dwuwymiarowa to regał: żeby wskazać szufladę, trzeba podać półkę i miejsce na niej.
| kol. 0 | kol. 1 | kol. 2 | kol. 3 | |
|---|---|---|---|---|
| wiersz 0 | [0,0] | [0,1] | [0,2] | [0,3] |
| wiersz 1 | [1,0] | [1,1] | [1,2] | [1,3] |
| wiersz 2 | [2,0] | [2,1] | [2,2] | [2,3] |
Kolejność jest zawsze taka sama: najpierw wiersz
oceny[2, 1] to trzeci wiersz, druga kolumna. Ta kolejność obowiązuje w całym C# — i w matematyce przy macierzach, i w większości języków. Zapamiętaj ją raz, bo zamiana indeksów miejscami to najczęstszy błąd w tej lekcji.
Kiedy warto sięgnąć po dwa wymiary:
| Zastosowanie | Co jest wierszem, co kolumną |
|---|---|
| dziennik ocen | wiersz — uczeń, kolumna — przedmiot |
| plansza do gry | wiersz i kolumna to współrzędne pola (szachy, saper, kółko i krzyżyk) |
| tabela sprzedaży | wiersz — produkt, kolumna — miesiąc |
| obraz | wiersz i kolumna to współrzędne piksela |
| macierz | to samo, co w matematyce |
Tworzenie i dostęp
// 1. pusta tablica: 3 wiersze na 4 kolumny
int[,] plansza = new int[3, 4];
// 2. z podanymi wartosciami - kazdy wiersz w osobnych klamrach
int[,] oceny =
{
{ 4, 5, 3 }, // wiersz 0
{ 3, 3, 4 }, // wiersz 1
{ 5, 5, 5 } // wiersz 2
};
// 3. zapis pelny
int[,] male = new int[,] { { 1, 2 }, { 3, 4 } };
Przecinek w nawiasach kwadratowych — int[,] — mówi „dwa wymiary”. Trzy wymiary to int[,,], i tak dalej.
int[,] oceny =
{
{ 4, 5, 3 },
{ 3, 3, 4 },
{ 5, 5, 5 }
};
Console.WriteLine(oceny[0, 0]); // 4 - pierwszy wiersz, pierwsza kolumna
Console.WriteLine(oceny[1, 2]); // 4 - drugi wiersz, trzecia kolumna
Console.WriteLine(oceny[2, 1]); // 5 - trzeci wiersz, druga kolumna
oceny[0, 2] = 6; // zmiana pojedynczego elementu
Console.WriteLine(oceny[0, 2]); // 6
4
4
5
6
Wszystkie wiersze muszą mieć tyle samo elementów
Tablica dwuwymiarowa to prostokąt. Zapis z wierszami różnej długości nie skompiluje się. Jeśli potrzebujesz „schodków” — na przykład różnej liczby ocen dla różnych uczniów — służą do tego tablice postrzępione z następnej lekcji.
Jak przy tablicach jednowymiarowych, świeżo utworzona tablica jest wypełniona wartościami domyślnymi — dla int samymi zerami.
GetLength kontra Length
Przy dwóch wymiarach jedna liczba nie wystarczy. Właściwość Length nadal istnieje, ale znaczy co innego, niż się wydaje:
int[,] oceny = new int[3, 4]; // 3 wiersze, 4 kolumny
Console.WriteLine(oceny.GetLength(0)); // 3 - liczba WIERSZY
Console.WriteLine(oceny.GetLength(1)); // 4 - liczba KOLUMN
Console.WriteLine(oceny.Length); // 12 - WSZYSTKIE elementy (3 * 4)
Console.WriteLine(oceny.Rank); // 2 - liczba wymiarow
3
4
12
2
| Zapis | Wynik | Znaczenie |
|---|---|---|
GetLength(0) | 3 | rozmiar pierwszego wymiaru — liczba wierszy |
GetLength(1) | 4 | rozmiar drugiego wymiaru — liczba kolumn |
Length | 12 | iloczyn obu — liczba wszystkich komórek |
Rank | 2 | ile wymiarów ma tablica |
Użycie Length jako liczby wierszy to gotowy błąd
Pętla for (int i = 0; i < oceny.Length; i++) na tablicy 3×4 wykona się dwanaście razy, choć wierszy jest trzy — i przerwie program przy czwartym obiegu. Do wymiarów służy wyłącznie GetLength.
Jak zapamiętać, który numer to co
Numer wymiaru odpowiada pozycji indeksu w nawiasach. W zapisie oceny[wiersz, kolumna] wiersz jest pierwszy, więc jego rozmiar to GetLength(0). Kolumna jest druga, więc GetLength(1). Ta sama kolejność, zawsze.
Przechodzenie tablicy
Dwa wymiary to dwie pętle zagnieżdżone — dokładnie ten mechanizm z lekcji 16, tylko teraz zamiast rysować tabelę, chodzimy po prawdziwych danych.
int[,] oceny =
{
{ 4, 5, 3 },
{ 3, 3, 4 },
{ 5, 5, 5 }
};
for (int w = 0; w < oceny.GetLength(0); w++) // wiersze
{
for (int k = 0; k < oceny.GetLength(1); k++) // kolumny
{
Console.Write($"{oceny[w, k],4}"); // Write - bez nowej linii
}
Console.WriteLine(); // koniec wiersza
}
4 5 3
3 3 4
5 5 5
Nazywaj liczniki po ludzku
w i k (wiersz, kolumna) albo wiersz i kolumna czyta się nieporównanie lepiej niż i i j. Przy dwóch wymiarach zamiana i z j to błąd, którego nie widać — a oceny[kolumna, wiersz] aż kłuje w oczy.
Pętla foreach
foreach też działa i przechodzi wszystkie elementy, wiersz po wierszu. Nie daje jednak dostępu do numerów, więc nadaje się tylko wtedy, gdy pozycja nie jest potrzebna:
int suma = 0;
int najwyzsza = oceny[0, 0];
foreach (int ocena in oceny) // idzie po wszystkich 9 elementach
{
suma += ocena;
if (ocena > najwyzsza)
{
najwyzsza = ocena;
}
}
Console.WriteLine($"Suma wszystkich: {suma}");
Console.WriteLine($"Najwyzsza: {najwyzsza}");
Console.WriteLine($"Srednia: {(double)suma / oceny.Length:F2}");
Suma wszystkich: 37
Najwyzsza: 5
Srednia: 4,11
Tu oceny.Length jest akurat tym, czego potrzeba — chodzi o liczbę wszystkich ocen, czyli 9.
Sumy wierszy i kolumn
To najważniejsza umiejętność w tej lekcji. Różnica między nimi sprowadza się do jednego: która pętla jest na zewnątrz.
for (int w = 0; w < oceny.GetLength(0); w++) // wiersz na zewnatrz
{
int suma = 0; // zerujemy dla KAZDEGO wiersza
for (int k = 0; k < oceny.GetLength(1); k++)
{
suma += oceny[w, k]; // wiersz staly, kolumna sie zmienia
}
Console.WriteLine($"Wiersz {w}: suma {suma}");
}
for (int k = 0; k < oceny.GetLength(1); k++) // KOLUMNA na zewnatrz
{
int suma = 0;
for (int w = 0; w < oceny.GetLength(0); w++)
{
suma += oceny[w, k]; // kolumna stala, wiersz sie zmienia
}
Console.WriteLine($"Kolumna {k}: suma {suma}");
}
Wiersz 0: suma 12
Wiersz 1: suma 10
Wiersz 2: suma 15
Kolumna 0: suma 12
Kolumna 1: suma 13
Kolumna 2: suma 12
Zerowanie sumy musi być wewnątrz pętli zewnętrznej
Deklaracja int suma = 0; przed obiema pętlami dałaby jedną narastającą sumę wszystkiego. Suma dotyczy jednego wiersza, więc zeruje się przy każdym nowym wierszu — dokładnie jak flaga pierwsza w teście pierwszości z lekcji 16.
Wzrokowa kontrola
W zapisie oceny[w, k] ta zmienna, która stoi w wewnętrznej pętli, „przesuwa się” po tabeli. Przy sumie wiersza zmienia się k, czyli idziemy w poziomie. Przy sumie kolumny zmienia się w, czyli idziemy w pionie. Jeśli nie masz pewności, którą pętlę dać na zewnątrz — narysuj strzałkę.
Więcej wymiarów
Wymiarów może być więcej niż dwa. Zasada jest ta sama: każdy dodatkowy przecinek to jeden dodatkowy wymiar i jedna dodatkowa pętla.
// oceny[klasa, uczen, przedmiot]
int[,,] szkola = new int[4, 30, 12];
Console.WriteLine(szkola.GetLength(0)); // 4 - klasy
Console.WriteLine(szkola.GetLength(1)); // 30 - uczniowie
Console.WriteLine(szkola.GetLength(2)); // 12 - przedmioty
Console.WriteLine(szkola.Length); // 1440 - wszystkie komorki
Console.WriteLine(szkola.Rank); // 3
W praktyce rzadko przekracza się dwa wymiary
Trzy pętle zagnieżdżone są już trudne do ogarnięcia, a cztery — praktycznie nieczytelne. Zwykle lepszym rozwiązaniem jest tablica obiektów, o których dowiesz się w bloku o klasach. Trzeci wymiar bywa naturalny w grafice (szerokość, wysokość, kanał koloru) i w symulacjach przestrzennych.
Pamięć rośnie szybko
Tablica int[1000, 1000] to milion liczb, czyli około 4 MB. int[1000, 1000, 1000] to już miliard liczb, czyli około 4 GB — program po prostu się nie uruchomi. Przy dużych rozmiarach zawsze pomnóż wymiary, zanim naciśniesz F5.
Użyte w tej lekcji elementy
| Nazwa | Zwraca | Działanie |
|---|---|---|
t.GetLength(n) | int |
Metoda (z nawiasami) — rozmiar wymiaru o numerze n. GetLength(0) to wiersze, GetLength(1) kolumny. |
t.Length | int |
Właściwość — liczba wszystkich komórek, czyli iloczyn wymiarów. Nie liczba wierszy! |
t.Rank | int |
Właściwość — liczba wymiarów tablicy (2 dla int[,]). |
t[w, k] | element | Odczyt lub zapis komórki. Najpierw wiersz, potem kolumna. |
Array.Clear(t) | nic | Przywraca wartości domyślne wszystkim komórkom — działa też na tablicach wielowymiarowych. |
Math.Max(a, b) | liczba | Większa z dwóch wartości — wygodne przy szukaniu maksimum bez pisania if. |
new string(z, n) | string |
Linie oddzielające w tabelach. |
Czego nie da się zrobić na tablicy dwuwymiarowej
Array.Sort, Array.Reverse, Array.IndexOf i string.Join działają tylko na tablicach jednowymiarowych. Na int[,] dostaniesz błąd przy kompilacji albo RankException przy uruchomieniu. Sortowanie wierszy tabeli trzeba napisać samemu albo przenieść dane do tablicy postrzępionej z następnej lekcji.
Dziennik klasy
Cztery osoby, pięć przedmiotów. Program wypisuje tabelę, średnią każdego ucznia (wiersz), średnią każdego przedmiotu (kolumna) i średnią całej klasy.
string[] uczniowie = { "Kowalski", "Nowak", "Wisniewska", "Zielinski" };
string[] przedmioty = { "Mat", "Pol", "Ang", "Inf", "WF" };
int[,] oceny =
{
{ 4, 5, 3, 4, 5 }, // Kowalski
{ 3, 3, 4, 2, 3 }, // Nowak
{ 5, 5, 5, 4, 6 }, // Wisniewska
{ 2, 4, 3, 3, 4 } // Zielinski
};
int wierszy = oceny.GetLength(0);
int kolumn = oceny.GetLength(1);
// --- naglowek ---
Console.Write($"{"Uczen",-12}");
foreach (string p in przedmioty)
{
Console.Write($"{p,5}");
}
Console.WriteLine($"{"Srednia",10}");
Console.WriteLine(new string('-', 47));
// --- wiersze: uczen po uczniu ---
int najlepszyUczen = 0;
double najlepszaSrednia = 0;
for (int w = 0; w < wierszy; w++)
{
Console.Write($"{uczniowie[w],-12}");
int sumaWiersza = 0;
for (int k = 0; k < kolumn; k++)
{
Console.Write($"{oceny[w, k],5}");
sumaWiersza += oceny[w, k];
}
double sredniaUcznia = (double)sumaWiersza / kolumn;
Console.WriteLine($"{sredniaUcznia,10:F2}");
if (sredniaUcznia > najlepszaSrednia)
{
najlepszaSrednia = sredniaUcznia;
najlepszyUczen = w;
}
}
// --- stopka: przedmiot po przedmiocie ---
Console.WriteLine(new string('-', 47));
Console.Write($"{"Srednia",-12}");
int najlepszyPrzedmiot = 0;
double najwyzszaSredniaPrzedmiotu = 0;
for (int k = 0; k < kolumn; k++)
{
int sumaKolumny = 0;
for (int w = 0; w < wierszy; w++)
{
sumaKolumny += oceny[w, k];
}
double sredniaPrzedmiotu = (double)sumaKolumny / wierszy;
Console.Write($"{sredniaPrzedmiotu,5:F2}");
if (sredniaPrzedmiotu > najwyzszaSredniaPrzedmiotu)
{
najwyzszaSredniaPrzedmiotu = sredniaPrzedmiotu;
najlepszyPrzedmiot = k;
}
}
Console.WriteLine();
Console.WriteLine();
// --- podsumowanie: jedno przejscie po wszystkim ---
int sumaWszystkich = 0;
foreach (int o in oceny)
{
sumaWszystkich += o;
}
Console.WriteLine($"Srednia klasy: {(double)sumaWszystkich / oceny.Length:F2}");
Console.WriteLine($"Najlepszy uczen: {uczniowie[najlepszyUczen]} ({najlepszaSrednia:F2})");
Console.WriteLine($"Najlepszy przedmiot: {przedmioty[najlepszyPrzedmiot]} ({najwyzszaSredniaPrzedmiotu:F2})");
Uczen Mat Pol Ang Inf WF Srednia
-----------------------------------------------
Kowalski 4 5 3 4 5 4,20
Nowak 3 3 4 2 3 3,00
Wisniewska 5 5 5 4 6 5,00
Zielinski 2 4 3 3 4 3,20
-----------------------------------------------
Srednia 3,50 4,25 3,75 3,25 4,50
Srednia klasy: 3,85
Najlepszy uczen: Wisniewska (5,00)
Najlepszy przedmiot: WF (4,50)
Omówienie
| Fragment | Co robi i dlaczego tak |
|---|---|
int wierszy = oceny.GetLength(0); |
Rozmiary zapisujemy raz, do zmiennych o mówiących nazwach. Dalej piszemy k < kolumn zamiast k < oceny.GetLength(1) — krócej i trudniej się pomylić o wymiar. |
| trzy tablice opisujące te same dane | uczniowie[w] to nazwisko z wiersza w, przedmioty[k] to nazwa kolumny k. Ten sam układ „równoległych tablic” co w lekcji 17, tylko rozciągnięty na dwa wymiary. |
| suma wiersza w trakcie wypisywania | Jedna pętla robi dwie rzeczy: wypisuje komórkę i dolicza ją do sumy. Drugie przejście byłoby zbędne — wszystko, czego potrzeba, jest już pod ręką. |
najlepszaSrednia = 0; |
Tutaj start od zera jest bezpieczny, bo oceny są dodatnie, więc pierwsza średnia na pewno go przebije. Przy danych mogących być ujemnymi (temperatury, salda) trzeba zacząć od pierwszego elementu — jak w lekcji 17. |
| pętla stopki ma kolumnę na zewnątrz | To jedyna różnica między sumą wiersza a sumą kolumny. Wewnątrz zmienia się w, więc schodzimy w dół po jednej kolumnie. |
foreach (int o in oceny) |
Do sumy wszystkiego numery są niepotrzebne, więc foreach jest krótszy i bezpieczniejszy. Przechodzi wszystkie 20 komórek. |
oceny.Length jako 20 |
Tu Length jest właściwym wyborem — chodzi o liczbę wszystkich ocen (4 × 5), a nie o wymiar. |
Sprawdź rachunek
Kowalski: 4 + 5 + 3 + 4 + 5 = 21, średnia 21 / 5 = 4,20. Kolumna „Mat”: 4 + 3 + 5 + 2 = 14, średnia 14 / 4 = 3,50. Wszystkich ocen jest 20, ich suma to 77, więc średnia klasy 77 / 20 = 3,85. Zwróć uwagę, że średnia klasy jest jednocześnie średnią ze średnich uczniów: (4,20 + 3,00 + 5,00 + 3,20) / 4 = 3,85 — bo każdy uczeń ma tyle samo ocen. Przy różnej liczbie ocen ta równość by nie zachodziła.
Szerokość linii oddzielającej
47 to suma kolumn: 12 (nazwisko) + 5 × 5 (przedmioty) + 10 (średnia). Gdy zmienisz którąkolwiek szerokość albo dodasz przedmiot, przelicz to na nowo — albo, elegancko, policz w kodzie: new string('-', 12 + 5 * kolumn + 10).
Na czym się najczęściej potykamy
| Błąd | Co się dzieje i jak poprawić |
|---|---|
int[][] t = new int[3][4]; |
To zapis tablicy postrzępionej, a nie dwuwymiarowej, i w tej postaci nie skompiluje się. Tablica prostokątna to int[,] t = new int[3, 4]; — jedne nawiasy, przecinek w środku. |
t[w][k] |
Ten zapis też należy do tablic postrzępionych. Dla int[,] jest to CS0021. Poprawnie: t[w, k]. |
Length zamiast GetLength(0) |
Pętla wykona zbyt wiele obiegów i przerwie program. Length to liczba wszystkich komórek. |
GetLength bez nawiasów |
GetLength to metoda, więc wymaga argumentu: GetLength(0). To odwrotnie niż Length, które jest właściwością. |
zamiana indeksów: t[k, w] |
Przy tablicy kwadratowej program zadziała i po cichu poda złe wyniki — najgorszy rodzaj błędu. Przy prostokątnej najczęściej przerwie działanie. Trzymaj się kolejności [wiersz, kolumna]. |
| zerowanie sumy przed obiema pętlami | Zamiast sum poszczególnych wierszy dostajesz jedną narastającą sumę. Zerowanie należy do pętli zewnętrznej. |
| wiersze różnej długości | Tablica dwuwymiarowa musi być prostokątna. Do „schodków” służą tablice postrzępione z lekcji 20. |
Array.Sort(t) na int[,] |
RankException — metody klasy Array działają na jednym wymiarze. |
WriteLine w pętli wewnętrznej |
Każda komórka w osobnej linii zamiast tabeli. Wewnątrz Write, WriteLine po pętli kolumn. |
Zadania
Utwórz tablicę 10×10, wypełnij ją iloczynami odpowiednich numerów wiersza i kolumny, a potem wypisz jej zawartość.
pokaż rozwiązanie
int[,] tabliczka = new int[10, 10];
// wypelnienie
for (int w = 0; w < tabliczka.GetLength(0); w++)
{
for (int k = 0; k < tabliczka.GetLength(1); k++)
{
tabliczka[w, k] = (w + 1) * (k + 1); // indeks 0 to liczba 1
}
}
// wypisanie
for (int w = 0; w < tabliczka.GetLength(0); w++)
{
for (int k = 0; k < tabliczka.GetLength(1); k++)
{
Console.Write($"{tabliczka[w, k],5}");
}
Console.WriteLine();
}
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
W lekcji 16 ta sama tabela powstawała „w locie”, bez zapamiętywania. Teraz najpierw ją wypełniamy, potem wypisujemy — i dzięki temu dane zostają w pamięci, więc można je jeszcze policzyć, posortować albo zapisać do pliku. To typowy podział pracy w programach na tablicach.
(w + 1) * (k + 1) — bo wiersz 0 ma zawierać wielokrotności jedynki.
Dla tablicy 3×4 wypełnionej wybranymi liczbami policz sumę wszystkich elementów, ich średnią oraz wartość najmniejszą i największą.
pokaż rozwiązanie
int[,] dane =
{
{ 12, 7, 20, 3 },
{ 8, 15, 6, 11 },
{ 19, 2, 14, 9 }
};
int suma = 0;
int najmniejsza = dane[0, 0];
int najwieksza = dane[0, 0];
foreach (int x in dane)
{
suma += x;
najmniejsza = Math.Min(najmniejsza, x);
najwieksza = Math.Max(najwieksza, x);
}
Console.WriteLine($"Elementow: {dane.Length}");
Console.WriteLine($"Suma: {suma}");
Console.WriteLine($"Srednia: {(double)suma / dane.Length:F2}");
Console.WriteLine($"Najmniejsza: {najmniejsza}");
Console.WriteLine($"Najwieksza: {najwieksza}");
Elementow: 12
Suma: 126
Srednia: 10,50
Najmniejsza: 2
Najwieksza: 20
Skoro numery nie są potrzebne, foreach wystarcza — i przy okazji nie da się w nim pomylić wymiarów.
Math.Min i Math.Max zastępują dwie instrukcje if. Zapis najwieksza = Math.Max(najwieksza, x) czyta się jako „zostaw większą z dwóch”.
Rachunek: 12+7+20+3 = 42, 8+15+6+11 = 40, 19+2+14+9 = 44, razem 126. Średnia 126 / 12 = 10,50.
Znajdź w tablicy dwuwymiarowej podaną wartość i podaj jej współrzędne (wiersz i kolumnę). Jeśli występuje wielokrotnie, wypisz wszystkie pozycje.
pokaż rozwiązanie
int[,] dane =
{
{ 12, 7, 20, 7 },
{ 8, 15, 6, 11 },
{ 7, 2, 14, 9 }
};
Console.Write("Czego szukamy? ");
bool ok = int.TryParse(Console.ReadLine(), out int szukana);
if (!ok)
{
Console.WriteLine("Podaj liczbe calkowita.");
}
else
{
int znalezione = 0;
for (int w = 0; w < dane.GetLength(0); w++)
{
for (int k = 0; k < dane.GetLength(1); k++)
{
if (dane[w, k] == szukana)
{
Console.WriteLine($" znaleziono w wierszu {w}, kolumnie {k}");
znalezione++;
}
}
}
if (znalezione == 0)
{
Console.WriteLine($"Wartosci {szukana} nie ma w tabeli.");
}
else
{
Console.WriteLine($"Lacznie wystapien: {znalezione}");
}
}
Czego szukamy? 7
znaleziono w wierszu 0, kolumnie 1
znaleziono w wierszu 0, kolumnie 3
znaleziono w wierszu 2, kolumnie 0
Lacznie wystapien: 3
Szukamy wszystkich wystąpień, więc nie ma tu break. Gdyby wystarczyło pierwsze, trzeba by wyjść z dwóch pętli naraz — czyli sięgnąć po flagę z lekcji 16.
Zwróć uwagę na kolejność wypisywania: pętla po wierszach jest zewnętrzna, więc trafienia pojawiają się wierszami, od lewej do prawej. To najbardziej naturalny porządek czytania tabeli.
Tablica 4×3 zawiera sprzedaż czterech produktów w trzech miesiącach kwartału. Wypisz tabelę z sumami wierszy (produkt) i kolumn (miesiąc) oraz sumą całkowitą w prawym dolnym rogu.
pokaż rozwiązanie
string[] produkty = { "Zeszyty", "Dlugopisy", "Bloki", "Linijki" };
string[] miesiace = { "Sty", "Lut", "Mar" };
int[,] sprzedaz =
{
{ 120, 140, 110 },
{ 340, 300, 380 },
{ 85, 90, 75 },
{ 45, 50, 40 }
};
int wierszy = sprzedaz.GetLength(0);
int kolumn = sprzedaz.GetLength(1);
// naglowek
Console.Write($"{"Produkt",-12}");
foreach (string m in miesiace)
{
Console.Write($"{m,7}");
}
Console.WriteLine($"{"RAZEM",9}");
Console.WriteLine(new string('-', 42));
// wiersze
for (int w = 0; w < wierszy; w++)
{
Console.Write($"{produkty[w],-12}");
int sumaWiersza = 0;
for (int k = 0; k < kolumn; k++)
{
Console.Write($"{sprzedaz[w, k],7}");
sumaWiersza += sprzedaz[w, k];
}
Console.WriteLine($"{sumaWiersza,9}");
}
// stopka
Console.WriteLine(new string('-', 42));
Console.Write($"{"RAZEM",-12}");
int sumaCalkowita = 0;
for (int k = 0; k < kolumn; k++)
{
int sumaKolumny = 0;
for (int w = 0; w < wierszy; w++)
{
sumaKolumny += sprzedaz[w, k];
}
Console.Write($"{sumaKolumny,7}");
sumaCalkowita += sumaKolumny;
}
Console.WriteLine($"{sumaCalkowita,9}");
Produkt Sty Lut Mar RAZEM
------------------------------------------
Zeszyty 120 140 110 370
Dlugopisy 340 300 380 1020
Bloki 85 90 75 250
Linijki 45 50 40 135
------------------------------------------
RAZEM 590 580 605 1775
Suma całkowita powstaje z doliczania sum kolumn — nie trzeba trzeciego przejścia. Można ją też policzyć z sum wierszy: 370 + 1020 + 250 + 135 = 1775. Obie drogi muszą dać ten sam wynik i to jest doskonały sposób na sprawdzenie, czy w programie nie pomyliłeś wymiarów.
Szerokość linii: 12 + 3 × 7 + 9 = 42.
Kolumny sum: 590 = 120 + 340 + 85 + 45, 580 = 140 + 300 + 90 + 50, 605 = 110 + 380 + 75 + 40.
Utwórz nową tablicę będącą transpozycją danej — zamień wiersze z kolumnami, tak że element z pozycji [w, k] trafia na [k, w].
pokaż rozwiązanie
int[,] a =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 }
};
int wierszy = a.GetLength(0); // 2
int kolumn = a.GetLength(1); // 4
// UWAGA: wymiary sie zamieniaja!
int[,] t = new int[kolumn, wierszy];
for (int w = 0; w < wierszy; w++)
{
for (int k = 0; k < kolumn; k++)
{
t[k, w] = a[w, k]; // to jest cala transpozycja
}
}
Console.WriteLine($"Oryginal ({wierszy}x{kolumn}):");
Wypisz(a);
Console.WriteLine();
Console.WriteLine($"Transpozycja ({kolumn}x{wierszy}):");
Wypisz(t);
static void Wypisz(int[,] m)
{
for (int w = 0; w < m.GetLength(0); w++)
{
for (int k = 0; k < m.GetLength(1); k++)
{
Console.Write($"{m[w, k],4}");
}
Console.WriteLine();
}
}
Oryginal (2x4):
1 2 3 4
5 6 7 8
Transpozycja (4x2):
1 5
2 6
3 7
4 8
Najważniejsza linia to new int[kolumn, wierszy]. Tablica 2×4 po transpozycji ma wymiary 4×2. Utworzenie jej o tych samych wymiarach co oryginał skończyłoby się wyjściem poza zakres — chyba że macierz jest kwadratowa, i wtedy błąd zostałby niezauważony aż do pierwszej macierzy prostokątnej.
Sama zamiana to jedna linia: t[k, w] = a[w, k];. Warto ją prześledzić na kartce dla elementu 7: stoi na a[1, 2], trafia na t[2, 1].
Metoda Wypisz jest tu tylko po to, żeby nie powtarzać tych samych ośmiu linii dwa razy. To zapowiedź bloku o funkcjach.
Plansza 3×3 zawiera znaki 'X', 'O' i spacje. Sprawdź, czy któryś z graczy wygrał — trzy takie same znaki w wierszu, kolumnie lub na przekątnej.
pokaż rozwiązanie
char[,] plansza =
{
{ 'X', 'O', 'O' },
{ 'O', 'X', 'O' },
{ 'X', 'O', 'X' }
};
// --- wypisanie planszy ---
for (int w = 0; w < 3; w++)
{
Console.WriteLine($" {plansza[w, 0]} | {plansza[w, 1]} | {plansza[w, 2]} ");
if (w < 2)
{
Console.WriteLine("---+---+---");
}
}
Console.WriteLine();
char zwyciezca = ' ';
// --- wiersze ---
for (int w = 0; w < 3; w++)
{
if (plansza[w, 0] != ' ' &&
plansza[w, 0] == plansza[w, 1] &&
plansza[w, 1] == plansza[w, 2])
{
zwyciezca = plansza[w, 0];
}
}
// --- kolumny ---
for (int k = 0; k < 3; k++)
{
if (plansza[0, k] != ' ' &&
plansza[0, k] == plansza[1, k] &&
plansza[1, k] == plansza[2, k])
{
zwyciezca = plansza[0, k];
}
}
// --- przekatna glowna: [0,0] [1,1] [2,2] ---
if (plansza[0, 0] != ' ' &&
plansza[0, 0] == plansza[1, 1] &&
plansza[1, 1] == plansza[2, 2])
{
zwyciezca = plansza[0, 0];
}
// --- przekatna druga: [0,2] [1,1] [2,0] ---
if (plansza[0, 2] != ' ' &&
plansza[0, 2] == plansza[1, 1] &&
plansza[1, 1] == plansza[2, 0])
{
zwyciezca = plansza[0, 2];
}
if (zwyciezca == ' ')
{
Console.WriteLine("Brak zwyciezcy.");
}
else
{
Console.WriteLine($"Wygral gracz {zwyciezca}!");
}
X | O | O
---+---+---
O | X | O
---+---+---
X | O | X
Wygral gracz X!
Sprawdzenie != ' ' jest konieczne. Bez niego trzy puste pola w wierszu zostałyby uznane za wygraną spacji. To najczęstszy błąd w tym zadaniu i objawia się dopiero przy pustej planszy.
Przekątne mają stałe współrzędne, więc nie potrzebują pętli — wystarczą dwa warunki. Główna to indeksy równe ([0,0], [1,1], [2,2]), druga to takie, w których suma indeksów wynosi 2.
W przykładzie wygrywa X na przekątnej głównej: [0,0], [1,1] i [2,2] to trzy razy X. Sprawdź, że żaden wiersz ani kolumna nie daje trójki.
Wersja z pętlą po wierszach i kolumnach zamiast czterech osobnych bloków też jest możliwa i krótsza, ale wymaga sprytnego indeksowania. Przy planszy 3×3 czytelność wygrywa z pomysłowością.
Co trzeba zapamiętać
- Tablica dwuwymiarowa to
int[,]— jedne nawiasy, przecinek w środku. - Element wskazujemy dwoma numerami:
t[wiersz, kolumna]— zawsze w tej kolejności. GetLength(0)to liczba wierszy,GetLength(1)— kolumn. To metoda, z nawiasami.Lengthto liczba wszystkich komórek, czyli iloczyn wymiarów.- Przejście całości to dwie pętle zagnieżdżone; wewnątrz
Write, po pętli kolumn —WriteLine. - Suma wiersza: wiersz na zewnątrz. Suma kolumny: kolumna na zewnątrz.
- Zmienną na sumę zeruj wewnątrz pętli zewnętrznej.
- Wszystkie wiersze mają tyle samo kolumn — tablica jest prostokątna.
- Metody klasy
Array(Sort,IndexOf) orazstring.Joinnie działają na dwóch wymiarach. - Przy transpozycji nowa tablica ma zamienione wymiary.