Przejdź do treści
School IT / Przestrzeń do nauki
← C# od podstawLekcja 24 z 58

Klasa Array i jej metody

W poprzedniej lekcji wszystko robiliśmy ręcznie — i dobrze, bo teraz wiesz, co się dzieje pod spodem. Ale sortowanie napisane od zera to trzydzieści linii, a gotowa metoda to jedna. Ta lekcja jest o narzędziach, które .NET daje ci za darmo.

C# Array Sort 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Posortujesz tablicę rosnąco i malejąco jedną instrukcją
  • Znajdziesz element metodą IndexOf zamiast pisać własną pętlę
  • Skopiujesz tablicę i zmienisz jej rozmiar
  • Porównasz zawartość dwóch tablic
  • Rozpoznasz, które metody zmieniają tablicę, a które zwracają nową
TEORIA

Czym jest klasa Array

Array to klasa z biblioteki .NET, w której zebrano gotowe operacje na tablicach. Jej metody wywołujemy przez nazwę klasy, a tablicę podajemy jako pierwszy argument:

Program.cs
int[] liczby = { 42, 17, 93, 8, 55, 23 };

Array.Sort(liczby);                     // przez nazwe klasy
Console.WriteLine(string.Join(", ", liczby));

Console.WriteLine(liczby.Length);       // ale Length juz przez zmienna
wynik w konsoli
8, 17, 23, 42, 55, 93
6

Dwa sposoby zapisu i skąd ta różnica

Array.Sort(liczby) to metoda statyczna — należy do klasy, nie do konkretnej tablicy, więc tablicę trzeba jej podać. liczby.Length i liczby.Clone() należą już do samej tablicy. Na razie wystarczy zapamiętać, które jest które; pełne wyjaśnienie przyjdzie w bloku o klasach i metodach statycznych.

Najważniejsze rozróżnienie w tej lekcji

Część metod zmienia tablicę na miejscu i nie zwraca nic (Sort, Reverse, Fill, Clear). Część zwraca wynik, a tablicy nie rusza (IndexOf, BinarySearch). Pomylenie tych dwóch grup to najczęstszy błąd: zapis liczby = Array.Sort(liczby); nie skompiluje się, bo Sort nie zwraca tablicy.

TEORIA

Sortowanie

Program.cs
int[] liczby = { 42, 17, 93, 8, 55, 23 };

Array.Sort(liczby);                     // rosnaco - zmienia tablice!
Console.WriteLine($"Rosnaco:  {string.Join(", ", liczby)}");

Array.Reverse(liczby);                  // odwraca kolejnosc
Console.WriteLine($"Malejaco: {string.Join(", ", liczby)}");
wynik w konsoli
Rosnaco:  8, 17, 23, 42, 55, 93
Malejaco: 93, 55, 42, 23, 17, 8

Podstawowe Array.Sort(tablica) sortuje rosnąco według domyślnego porównywania. Przeciążenia pozwalają podać własną regułę porównania, także malejącą. Żeby uzyskać porządek malejący, sortujemy, a potem odwracamy — dwie instrukcje, jedna po drugiej.

Sort niszczy pierwotną kolejność

Po Array.Sort(liczby) oryginalna kolejność jest bezpowrotnie stracona. Jeśli jest ci jeszcze potrzebna (bo np. druga tablica opisuje te same obiekty), posortuj kopię:

Program.cs
int[] oryginal = { 42, 17, 93, 8, 55, 23 };
int[] posortowana = oryginal[..];       // kopia z lekcji 23

Array.Sort(posortowana);

Console.WriteLine($"Oryginal:    {string.Join(", ", oryginal)}");
Console.WriteLine($"Posortowana: {string.Join(", ", posortowana)}");
wynik w konsoli
Oryginal:    42, 17, 93, 8, 55, 23
Posortowana: 8, 17, 23, 42, 55, 93

Sortowanie napisów

Program.cs
string[] imiona = { "Zofia", "Adam", "Ewa", "Bartek", "Celina" };

Array.Sort(imiona);
Console.WriteLine(string.Join(", ", imiona));
wynik w konsoli
Adam, Bartek, Celina, Ewa, Zofia

Polskie znaki w sortowaniu

Napisy sortują się według reguł kultury systemu, więc na polskim systemie „ą” trafi zaraz za „a”, a nie na koniec alfabetu. To zwykle jest to, czego chcemy. Gdyby wynik miał być identyczny na każdym komputerze (np. dane wysyłane dalej), użyj Array.Sort(imiona, StringComparer.Ordinal) — wtedy porównywane są numery znaków, a wielkie litery wypadają przed małymi.

Sortowanie dwóch tablic naraz

To rozwiązuje problem z poprzedniej lekcji: dwie tablice opisujące te same obiekty. Podana wersja Sort przestawia elementy w obu tablicach jednakowo:

Program.cs
string[] uczniowie = { "Kowalski", "Nowak", "Zielinski", "Adamczyk" };
int[] punkty = { 68, 91, 45, 77 };

Array.Sort(punkty, uczniowie);          // klucz, potem wartosci

for (int i = 0; i < punkty.Length; i++)
{
    Console.WriteLine($"{uczniowie[i],-12}{punkty[i],5}");
}
wynik w konsoli
Zielinski      45
Kowalski       68
Adamczyk       77
Nowak          91

Pierwsza tablica to klucz — według niej odbywa się sortowanie. Druga jedzie za nią, więc nazwiska nadal pasują do punktów. Obie muszą mieć ten sam rozmiar.

TEORIA

Wyszukiwanie

Program.cs
int[] liczby = { 42, 17, 93, 8, 55, 23 };

int gdzie = Array.IndexOf(liczby, 55);
Console.WriteLine($"55 stoi na indeksie {gdzie}");

int brak = Array.IndexOf(liczby, 100);
Console.WriteLine($"100 -> {brak}");

if (Array.IndexOf(liczby, 8) >= 0)
{
    Console.WriteLine("Osemka jest w tablicy.");
}
wynik w konsoli
55 stoi na indeksie 4
100 -> -1
Osemka jest w tablicy.

To dokładnie ta sama pętla, którą pisaliśmy w lekcji 23 z tą samą konwencją: −1 oznacza „nie znaleziono”. Metoda LastIndexOf szuka od końca.

Sprawdzaj >= 0, nie != -1

Oba zapisy działają, ale >= 0 lepiej opisuje intencję („dostaliśmy poprawny indeks”) i nie zawiedzie, gdyby kiedyś zmieniła się wartość oznaczająca brak. Nigdy natomiast nie pisz if (Array.IndexOf(...)) — indeks to liczba, a nie wartość logiczna; kompilator zgłosi CS0029.

BinarySearch — szybciej, ale tylko na posortowanej

Program.cs
int[] liczby = { 42, 17, 93, 8, 55, 23 };

Array.Sort(liczby);                     // WARUNEK KONIECZNY
Console.WriteLine(string.Join(", ", liczby));

int gdzie = Array.BinarySearch(liczby, 55);
Console.WriteLine($"55 stoi na indeksie {gdzie}");
wynik w konsoli
8, 17, 23, 42, 55, 93
55 stoi na indeksie 4
CechaIndexOfBinarySearch
Wymaga posortowanianietak
Sposób działaniasprawdza po koleidzieli zakres na pół (jak zgadywanie liczby z lekcji 16)
Milion elementówdo miliona porównańokoło 20 porównań
Gdy nie znajdzie-1liczba ujemna, ale nie zawsze −1
Przy duplikatachpierwsze wystąpieniedowolne z wystąpień

BinarySearch na nieposortowanej tablicy nie zgłasza błędu

Po prostu zwraca bzdurę — najczęściej „nie znalazłem”, choć element tam jest. To wyjątkowo podstępna pomyłka, bo program działa i nie skarży się. Sortuj przed użyciem albo zostań przy IndexOf.

TEORIA

Kopiowanie i zmiana rozmiaru

Program.cs
int[] zrodlo = { 1, 2, 3, 4, 5 };

// pelna kopia
int[] cel = new int[5];
Array.Copy(zrodlo, cel, 5);             // ile elementow przepisac
Console.WriteLine(string.Join(", ", cel));

// tylko fragment - do tego wygodniejszy jest zakres z lekcji 23
int[] fragment = zrodlo[1..4];
Console.WriteLine(string.Join(", ", fragment));
wynik w konsoli
1, 2, 3, 4, 5
2, 3, 4

Array.Resize — pozorna zmiana rozmiaru

Tablica ma stały rozmiar, więc Resize tak naprawdę tworzy nową tablicę, przepisuje do niej zawartość i podmienia zmienną. Dlatego wymaga słowa ref:

Program.cs
int[] liczby = { 1, 2, 3 };

Array.Resize(ref liczby, 5);            // powiekszenie
Console.WriteLine(string.Join(", ", liczby));

Array.Resize(ref liczby, 2);            // pomniejszenie - nadmiar PRZEPADA
Console.WriteLine(string.Join(", ", liczby));
wynik w konsoli
1, 2, 3, 0, 0
1, 2

Po co ref

Metoda nie zmienia zawartości tablicy, tylko podmienia adres zapisany w zmiennej. Bez ref zmieniłaby swoją własną kopię adresu i po powrocie do programu nic by się nie stało. Do słowa ref wrócimy w bloku o funkcjach.

Nie powiększaj tablicy w pętli

Każde wywołanie Resize tworzy nową tablicę i przepisuje wszystko od nowa. Dodanie tysiąca elementów po jednym oznacza tysiąc przepisań i pół miliona kopiowań. Gdy liczba elementów rośnie w trakcie działania, właściwym narzędziem jest List<T> z bloku o C# nowoczesnym.

TEORIA

Pozostałe przydatne metody

Program.cs
// wypelnienie jedna wartoscia
int[] punkty = new int[5];
Array.Fill(punkty, 10);
Console.WriteLine(string.Join(", ", punkty));

// wypelnienie napisow - ratunek na puste elementy z lekcji 23
string[] imiona = new string[3];
Array.Fill(imiona, "");
Console.WriteLine($"Dlugosc pierwszego: {imiona[0].Length}");

// wyczyszczenie fragmentu (od indeksu 1, dwa elementy)
Array.Clear(punkty, 1, 2);
Console.WriteLine(string.Join(", ", punkty));

// pusta tablica bez tworzenia obiektu
int[] nic = Array.Empty<int>();
Console.WriteLine($"Rozmiar: {nic.Length}");
wynik w konsoli
10, 10, 10, 10, 10
Dlugosc pierwszego: 0
10, 0, 0, 10, 10
Rozmiar: 0

Array.Clear nie usuwa elementów — przywraca im wartość domyślną. Rozmiar tablicy zostaje bez zmian, bo zmienić go nie można.

Minimum, maksimum i suma — bez pętli

Klasa Array ich nie ma, ale są w LINQ: liczby.Max(), liczby.Min(), liczby.Sum(), liczby.Average(). Działają od razu, bo przestrzeń System.Linq jest dołączana automatycznie. Poznasz je porządnie w bloku o C# nowoczesnym; tutaj warto wiedzieć, że istnieją, ale w zadaniach z tej lekcji pisz pętle — na egzaminie zwykle chodzi o pokazanie algorytmu.

TEORIA

Porównywanie tablic

Z poprzedniej lekcji wiesz, że == porównuje adresy. Oto trzy sposoby na porównanie zawartości:

Program.cs
int[] a = { 1, 2, 3 };
int[] b = { 1, 2, 3 };

// 1. operator == - porownuje ADRESY
Console.WriteLine($"a == b        : {a == b}");

// 2. gotowa metoda - porownuje ZAWARTOSC
Console.WriteLine($"SequenceEqual : {a.SequenceEqual(b)}");

// 3. recznie, petla
bool takieSame = a.Length == b.Length;

for (int i = 0; i < a.Length && takieSame; i++)
{
    if (a[i] != b[i])
    {
        takieSame = false;
    }
}

Console.WriteLine($"Recznie       : {takieSame}");
wynik w konsoli
a == b        : False
SequenceEqual : True
Recznie       : True

Sprawdzenie rozmiaru na początku

W wersji ręcznej najpierw porównujemy długości. Bez tego pętla po krótszej tablicy uznałaby, że {1, 2} i {1, 2, 3} są takie same — a nie są. Ta sama pułapka wraca przy porównywaniu napisów i list.

METODY WBUDOWANE

Zestawienie metod

MetodaZwracaDziałanie
Array.Sort(t)nic Zmienia tablicę. Sortuje rosnąco. Wersja Array.Sort(klucze, wartosci) przestawia dwie tablice jednakowo.
Array.Reverse(t)nic Zmienia tablicę. Odwraca kolejność elementów. Razem z Sort daje porządek malejący.
Array.IndexOf(t, w)int Indeks pierwszego wystąpienia wartości albo -1. Tablicy nie zmienia. LastIndexOf szuka od końca.
Array.BinarySearch(t, w)int Szybkie wyszukiwanie, ale wyłącznie na tablicy posortowanej. Wynik ujemny oznacza brak.
Array.Copy(z, c, n)nic Przepisuje n pierwszych elementów z tablicy z do c. Tablica docelowa musi już istnieć i być dość duża.
Array.Resize(ref t, n)nic Podmienia tablicę na nową o rozmiarze n. Nadmiarowe elementy przepadają, brakujące dostają wartość domyślną. Wymaga ref.
Array.Fill(t, w)nic Zmienia tablicę. Wpisuje tę samą wartość do wszystkich elementów.
Array.Clear(t, i, n)nic Zmienia tablicę. Przywraca wartości domyślne n elementom od indeksu i. Rozmiaru nie zmienia.
Array.Empty<T>()tablica Pusta tablica danego typu. Przydatna jako „nic nie znaleziono” bez tworzenia nowego obiektu.
t.SequenceEqual(u)bool Czy obie tablice mają te same elementy w tej samej kolejności. Pochodzi z LINQ, ale działa bez dopisywania using.
t.Clone()object Kopia tablicy; wynik wymaga rzutowania: (int[])t.Clone().
string.Join(sep, t)string Skleja elementy w jeden napis z separatorem — najprostszy sposób wypisania całej tablicy.

Jak zapamiętać, która metoda co zwraca

Reguła praktyczna: jeśli metoda przestawia zawartość (Sort, Reverse, Fill, Clear), zmienia tablicę i nie zwraca nic. Jeśli szuka (IndexOf, BinarySearch) — zwraca liczbę i niczego nie rusza. Resize jest wyjątkiem: podmienia całą tablicę, dlatego potrzebuje ref.

PRZYKŁAD Z OMÓWIENIEM

Wyniki egzaminu

Program analizuje wyniki dziesięciu uczniów: liczy statystyki, wskazuje najlepszych i sprawdza, kto zdał. Wszystko, co da się zrobić gotową metodą, robimy gotową metodą.

Program.cs
const int ProgZdania = 50;

string[] uczniowie = { "Kowalski", "Nowak", "Wisniewski", "Wojcik", "Kowalczyk",
                       "Kaminski", "Lewandowski", "Zielinski", "Szymanski", "Wozniak" };
int[] wyniki = { 68, 91, 45, 77, 52, 88, 34, 95, 61, 73 };

// --- statystyki: jedno przejscie ---
int suma = 0;
int zdanych = 0;

foreach (int w in wyniki)
{
    suma += w;

    if (w >= ProgZdania)
    {
        zdanych++;
    }
}

double srednia = (double)suma / wyniki.Length;
double procentZdanych = (double)zdanych / wyniki.Length * 100;

// --- kopie do sortowania, zeby nie stracic kolejnosci ---
int[] posortowane = wyniki[..];
Array.Sort(posortowane);

int najgorszy = posortowane[0];
int najlepszy = posortowane[^1];

// mediana: srednia dwoch srodkowych, bo elementow jest parzyscie
double mediana = (posortowane[4] + posortowane[5]) / 2.0;

// --- ranking: sortujemy dwie tablice naraz ---
string[] rankingNazwiska = uczniowie[..];
int[] rankingPunkty = wyniki[..];

Array.Sort(rankingPunkty, rankingNazwiska);   // rosnaco
Array.Reverse(rankingPunkty);                 // teraz malejaco
Array.Reverse(rankingNazwiska);               // OBIE trzeba odwrocic!

// --- wypisanie ---
Console.WriteLine("=== WYNIKI EGZAMINU ===");
Console.WriteLine();
Console.WriteLine($"{"Lp.",-5}{"Nazwisko",-16}{"Punkty",8}  Status");
Console.WriteLine(new string('-', 40));

for (int i = 0; i < rankingPunkty.Length; i++)
{
    string status = rankingPunkty[i] >= ProgZdania ? "zdany" : "niezdany";
    Console.WriteLine($"{i + 1,-5}{rankingNazwiska[i],-16}{rankingPunkty[i],8}  {status}");
}

Console.WriteLine(new string('-', 40));
Console.WriteLine();
Console.WriteLine($"{"Srednia:",-20}{srednia,8:F2}");
Console.WriteLine($"{"Mediana:",-20}{mediana,8:F2}");
Console.WriteLine($"{"Najlepszy wynik:",-20}{najlepszy,8}");
Console.WriteLine($"{"Najgorszy wynik:",-20}{najgorszy,8}");
Console.WriteLine($"{"Zdalo:",-20}{zdanych,8}");
Console.WriteLine($"{"Zdawalnosc:",-20}{procentZdanych,7:F1}%");
Console.WriteLine();
Console.WriteLine($"Podium: {string.Join(", ", rankingNazwiska[..3])}");
wynik w konsoli
=== WYNIKI EGZAMINU ===

Lp.  Nazwisko          Punkty  Status
----------------------------------------
1    Zielinski             95  zdany
2    Nowak                 91  zdany
3    Kaminski              88  zdany
4    Wojcik                77  zdany
5    Wozniak               73  zdany
6    Kowalski              68  zdany
7    Szymanski             61  zdany
8    Kowalczyk             52  zdany
9    Wisniewski            45  niezdany
10   Lewandowski           34  niezdany
----------------------------------------

Srednia:               68,40
Mediana:               70,50
Najlepszy wynik:          95
Najgorszy wynik:          34
Zdalo:                     8
Zdawalnosc:            80,0%

Podium: Zielinski, Nowak, Kaminski

Omówienie

FragmentCo robi i dlaczego tak
int[] posortowane = wyniki[..]; Kopia przed sortowaniem. Bez niej Array.Sort przestawiłby oryginalną tablicę i przestałaby ona pasować do tablicy nazwisk — wyniki przypisałyby się do niewłaściwych osób. To najgroźniejszy błąd w tym programie, bo nic się nie wysypie, a dane będą fałszywe.
posortowane[^1] Ostatni element posortowanej tablicy to największa wartość. Sortowanie daje minimum i maksimum „przy okazji”, bez osobnej pętli.
(posortowane[4] + posortowane[5]) / 2.0 Mediana przy parzystej liczbie elementów to średnia dwóch środkowych. Dla dziesięciu wyników są to indeksy 4 i 5. Dzielimy przez 2.0, a nie przez 2 — inaczej byłoby to dzielenie całkowite i 141 / 2 dałoby 70 zamiast 70,5.
Array.Sort(rankingPunkty, rankingNazwiska) Sortowanie z kluczem: punkty decydują, nazwiska jadą za nimi. Obie tablice muszą być kopiami, bo obie zostaną przestawione.
dwa razy Array.Reverse Uwaga: trzeba odwrócić obie tablice. Odwrócenie tylko punktów rozjechałoby je z nazwiskami — i znowu: program by nie protestował, tylko skłamał.
rankingNazwiska[..3] Trzy pierwsze elementy posortowanej malejąco tablicy, czyli podium. Zakres z lekcji 23 zastępuje pętlę.
(double)zdanych / wyniki.Length * 100 Rzutowanie przed dzieleniem — bez niego 8 / 10 dałoby 0, a zdawalność wyszłaby zerowa.

Sprawdź rachunek

Suma wyników: 68 + 91 + 45 + 77 + 52 + 88 + 34 + 95 + 61 + 73 = 684, więc średnia to 68,40. Po posortowaniu: 34, 45, 52, 61, 68, 73, 77, 88, 91, 95 — środkowe to 68 i 73, mediana (68 + 73) / 2 = 70,50. Zdało 8 z 10, czyli 80,0%. Zwróć uwagę, że mediana jest wyższa od średniej — bo dwa bardzo słabe wyniki ciągną średnią w dół, a mediany prawie nie ruszają.

CZĘSTE BŁĘDY

Na czym się najczęściej potykamy

BłądCo się dzieje i jak poprawić
liczby = Array.Sort(liczby); CS0029 — Sort nie zwraca tablicy, tylko zmienia tę podaną. Poprawnie: Array.Sort(liczby);.
liczby.Sort() CS1061 — tablica nie ma metody Sort. To metoda klasy Array: Array.Sort(liczby). (Typ List<T> ma własną Sort() — stąd zamieszanie.)
sortowanie oryginału zamiast kopii Bezpowrotna utrata pierwotnej kolejności. Jeśli druga tablica opisuje te same obiekty, dane przestają do siebie pasować — i nic tego nie zasygnalizuje.
odwrócenie tylko jednej z dwóch tablic Nazwiska rozjeżdżają się z wynikami. Array.Reverse trzeba wywołać dla każdej tablicy osobno.
BinarySearch bez sortowania Zwraca błędny wynik i nie zgłasza problemu. Zawsze sortuj wcześniej.
Array.Resize(liczby, 5) bez ref CS1620 — argument musi być przekazany ze słowem ref.
Array.Copy do zbyt małej tablicy ArgumentException przy uruchomieniu. Tablica docelowa musi istnieć i pomieścić kopiowane elementy.
a == b dla tablic Porównuje adresy. Do zawartości: a.SequenceEqual(b) albo pętla — z porównaniem długości na początku.
Array.Clear jako „usuwanie” Nie usuwa elementów, tylko wpisuje wartości domyślne. Rozmiar tablicy jest niezmienny.
Array.Resize w pętli Kwadratowo rosnąca liczba kopiowań. Gdy rozmiar ma rosnąć, użyj List<T>.
ZADANIA

Zadania

ZAD 1Trzy porządki★☆☆

Mając tablicę liczb, wypisz ją w trzech postaciach: oryginalnej, posortowanej rosnąco i posortowanej malejąco. Oryginał ma pozostać nietknięty.

ZAD 2Czy tablica jest posortowana★☆☆

Sprawdź, czy tablica jest już posortowana rosnąco — bez jej sortowania i bez tworzenia kopii.

ZAD 3Usunięcie elementu★★☆

Napisz program, który „usuwa” z tablicy element o podanym indeksie. Skoro rozmiaru zmienić się nie da, utwórz nową tablicę mniejszą o jeden.

ZAD 4Ranking sprzedaży★★☆

Mając tablicę nazw produktów i tablicę sprzedanych sztuk, wypisz ranking malejąco wraz z udziałem procentowym każdego produktu w całości sprzedaży.

ZAD 5Scalanie dwóch posortowanych tablic★★★

Mając dwie posortowane rosnąco tablice, utwórz z nich jedną — też posortowaną. Zrób to bez sortowania wyniku: przechodź obie tablice jednocześnie i za każdym razem bierz mniejszy element.

ZAD 6Usunięcie duplikatów★★★

Z tablicy liczb usuń powtórzenia, zachowując kolejność pierwszych wystąpień. Wynik ma być tablicą o dokładnie takim rozmiarze, ile jest różnych wartości.

PODSUMOWANIE

Co trzeba zapamiętać

  • Array.Sort(tablica) i Array.Reverse(tablica) zmieniają kolejność na miejscu, nie zwracają nowej tablicy.
  • Sort ma też przeciążenia z własnym porównywaniem. Nie gwarantuje stabilności.
  • IndexOf i BinarySearch zwracają wynik wyszukiwania; liczba ujemna oznacza brak.
  • BinarySearch wymaga porządku zgodnego z porównywaniem użytym podczas wyszukiwania.
  • Array.Resize zmienia referencję przez ref i w razie zmiany długości tworzy nową tablicę. Częste powiększanie lepiej zastąpić List<T>.
  • SequenceEqual porównuje elementy w kolejności; == porównuje tożsamość tablic.
  • Sortuj kopię, gdy pierwotna kolejność jest nadal potrzebna.
Postęp zapisuje się w tej przeglądarce.