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

Array.Sort sortuje tylko rosnąco. Ż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 17

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 17 — 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 14)
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 17
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 17
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 17 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); CS0029Sort 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.

pokaż rozwiązanie
int[] liczby = { 42, 17, 93, 8, 55, 23 };

int[] rosnaco = liczby[..];
Array.Sort(rosnaco);

int[] malejaco = rosnaco[..];
Array.Reverse(malejaco);

Console.WriteLine($"Oryginal: {string.Join(", ", liczby)}");
Console.WriteLine($"Rosnaco:  {string.Join(", ", rosnaco)}");
Console.WriteLine($"Malejaco: {string.Join(", ", malejaco)}");
wynik w konsoli
Oryginal: 42, 17, 93, 8, 55, 23
Rosnaco:  8, 17, 23, 42, 55, 93
Malejaco: 93, 55, 42, 23, 17, 8

Trzy tablice, dwie kopie. Druga kopia powstaje z już posortowanej, więc wystarczy ją odwrócić — nie trzeba sortować drugi raz.

Gdyby zabrakło pierwszej kopii, Array.Sort(liczby) zmieniłoby oryginał i pierwsza linia wypisałaby już posortowane wartości.

ZAD 2Czy tablica jest posortowana★☆☆

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

pokaż rozwiązanie
int[] a = { 1, 3, 5, 7, 9 };
int[] b = { 1, 3, 2, 7, 9 };

Console.WriteLine($"a posortowana: {CzyPosortowana(a)}");
Console.WriteLine($"b posortowana: {CzyPosortowana(b)}");

// --- sprawdzenie w petli (bez metody wygladaloby tak samo) ---
static bool CzyPosortowana(int[] t)
{
    for (int i = 1; i < t.Length; i++)
    {
        if (t[i] < t[i - 1])
        {
            return false;          // znaleziona para w zlej kolejnosci
        }
    }

    return true;
}
wynik w konsoli
a posortowana: True
b posortowana: False

Wystarczy porównać każdy element z poprzednim — stąd start pętli od 1, tak jak przy skokach temperatur w lekcji 17.

Jeden kontrprzykład przesądza sprawę, więc od razu zwracamy false. Gdybyśmy jeszcze nie znali metod, ten sam efekt daje flaga i break.

To pierwsza w kursie własna metoda (static bool) — traktuj ją na razie jako zapowiedź. Pełne omówienie w bloku o funkcjach.

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.

pokaż rozwiązanie
int[] liczby = { 10, 20, 30, 40, 50 };

Console.Write($"Ktory element usunac (0-{liczby.Length - 1})? ");
bool ok = int.TryParse(Console.ReadLine(), out int doUsuniecia);

if (!ok || doUsuniecia < 0 || doUsuniecia >= liczby.Length)
{
    Console.WriteLine("Nie ma takiego elementu.");
}
else
{
    int[] mniejsza = new int[liczby.Length - 1];
    int pozycja = 0;

    for (int i = 0; i < liczby.Length; i++)
    {
        if (i == doUsuniecia)
        {
            continue;                   // ten pomijamy
        }

        mniejsza[pozycja] = liczby[i];
        pozycja++;
    }

    Console.WriteLine($"Przed: {string.Join(", ", liczby)}");
    Console.WriteLine($"Po:    {string.Join(", ", mniejsza)}");
}
wynik w konsoli
Ktory element usunac (0-4)? 2
Przed: 10, 20, 30, 40, 50
Po:    10, 20, 40, 50

Dwa liczniki, nie jeden. i chodzi po tablicy źródłowej, pozycja po docelowej. Po pominięciu jednego elementu przestają być równe — i o to chodzi. Użycie i jako indeksu w nowej tablicy zostawiłoby dziurę i wyszło poza zakres.

continue pomija cały obieg, więc pozycja też się nie zwiększy. To poprawne: do nowej tablicy nic nie trafiło.

Alternatywa z gotowych metod: Array.Copy dwa razy (fragment przed i fragment po), albo połączenie zakresów. Wersja z pętlą jest czytelniejsza i pokazuje mechanizm.

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.

pokaż rozwiązanie
string[] produkty = { "Zeszyt A5", "Dlugopis", "Blok", "Linijka", "Gumka" };
int[] sztuki = { 120, 340, 85, 45, 210 };

// suma PRZED sortowaniem - kolejnosc nie ma tu znaczenia
int razem = 0;

foreach (int s in sztuki)
{
    razem += s;
}

// sortujemy kopie, obie tablice naraz
string[] nazwy = produkty[..];
int[] ilosci = sztuki[..];

Array.Sort(ilosci, nazwy);
Array.Reverse(ilosci);
Array.Reverse(nazwy);

Console.WriteLine($"{"Lp.",-5}{"Produkt",-14}{"Sztuk",8}{"Udzial",10}");
Console.WriteLine(new string('-', 37));

for (int i = 0; i < ilosci.Length; i++)
{
    double udzial = (double)ilosci[i] / razem;
    Console.WriteLine($"{i + 1,-5}{nazwy[i],-14}{ilosci[i],8}{udzial,10:P1}");
}

Console.WriteLine(new string('-', 37));
Console.WriteLine($"{"RAZEM",-19}{razem,8}");
wynik w konsoli
Lp.  Produkt          Sztuk    Udzial
-------------------------------------
1    Dlugopis           340    42,5 %
2    Gumka              210    26,3 %
3    Zeszyt A5          120    15,0 %
4    Blok                85    10,6 %
5    Linijka             45     5,6 %
-------------------------------------
RAZEM                    800

Suma liczona jest przed sortowaniem, bo i tak nie zależy od kolejności — a po sortowaniu trzeba by pamiętać, której tablicy użyć.

Format P1 z lekcji 11 sam mnoży przez 100 i dokleja znak procenta — do klamry podajemy ułamek, czyli ilosci[i] / razem, a nie gotowy procent.

Rachunek: 120 + 340 + 85 + 45 + 210 = 800. Stąd 340 / 800 = 42,5%, 210 / 800 = 26,3%, 120 / 800 = 15,0%, 85 / 800 = 10,6% i 45 / 800 = 5,6%.

Linia oddzielająca ma 37 znaków, bo tyle liczą razem kolumny: 5 + 14 + 8 + 10.

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.

pokaż rozwiązanie
int[] a = { 1, 4, 7, 10 };
int[] b = { 2, 3, 8, 9, 12, 15 };

int[] wynik = new int[a.Length + b.Length];

int ia = 0;      // gdzie jestesmy w tablicy a
int ib = 0;      // gdzie jestesmy w tablicy b
int iw = 0;      // gdzie piszemy w wyniku

// dopoki obie tablice maja jeszcze elementy
while (ia < a.Length && ib < b.Length)
{
    if (a[ia] <= b[ib])
    {
        wynik[iw] = a[ia];
        ia++;
    }
    else
    {
        wynik[iw] = b[ib];
        ib++;
    }

    iw++;
}

// jedna z tablic sie skonczyla - reszte drugiej przepisujemy
while (ia < a.Length)
{
    wynik[iw] = a[ia];
    ia++;
    iw++;
}

while (ib < b.Length)
{
    wynik[iw] = b[ib];
    ib++;
    iw++;
}

Console.WriteLine($"a:      {string.Join(", ", a)}");
Console.WriteLine($"b:      {string.Join(", ", b)}");
Console.WriteLine($"scalone: {string.Join(", ", wynik)}");
wynik w konsoli
a:      1, 4, 7, 10
b:      2, 3, 8, 9, 12, 15
scalone: 1, 2, 3, 4, 7, 8, 9, 10, 12, 15

Trzy niezależne liczniki — po jednym na każdą tablicę. To ten sam pomysł co w zadaniu z usuwaniem, tylko rozbudowany.

Dwie pętle „dosypujące” są konieczne. Pierwsza pętla kończy się, gdy którakolwiek tablica się wyczerpie — a druga wciąż ma elementy. Bez tych pętli w wyniku zostałyby zera. Wykona się dokładnie jedna z nich; druga ma od razu fałszywy warunek.

<=, a nie <, przy porównaniu: przy równych wartościach bierzemy z pierwszej tablicy. Dla samych liczb to bez znaczenia, ale przy sortowaniu obiektów decyduje o tym, czy sortowanie jest stabilne.

To serce algorytmu sortowania przez scalanie, jednego z najszybszych. Wrócimy do niego w lekcji 21.

Prześledź na kartce pierwsze kroki: 1 < 2 → bierzemy 1 z a; 4 > 2 → bierzemy 2 z b; 4 > 3 → bierzemy 3 z b; 4 < 8 → bierzemy 4 z a.

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.

pokaż rozwiązanie
int[] liczby = { 5, 3, 5, 9, 3, 1, 9, 5, 7 };

// --- przejscie 1: zbieramy unikaty do tablicy roboczej ---
int[] robocza = new int[liczby.Length];    // na pewno wystarczy
int ile = 0;

foreach (int x in liczby)
{
    bool juzJest = false;

    for (int i = 0; i < ile; i++)          // tylko po juz zebranych!
    {
        if (robocza[i] == x)
        {
            juzJest = true;
            break;
        }
    }

    if (!juzJest)
    {
        robocza[ile] = x;
        ile++;
    }
}

// --- przycinamy do wlasciwego rozmiaru ---
int[] unikaty = robocza[..ile];

Console.WriteLine($"Wejscie:  {string.Join(", ", liczby)}");
Console.WriteLine($"Unikaty:  {string.Join(", ", unikaty)}");
Console.WriteLine($"Bylo {liczby.Length}, zostalo {unikaty.Length}.");
wynik w konsoli
Wejscie:  5, 3, 5, 9, 3, 1, 9, 5, 7
Unikaty:  5, 3, 9, 1, 7
Bylo 9, zostalo 5.

Tablica robocza o maksymalnym rozmiarze to standardowe obejście problemu „nie wiem, ile będzie elementów”. Gdy wszystkie wartości są różne, unikatów jest tyle co wejścia — więc taka tablica na pewno wystarczy. Na końcu przycinamy ją zakresem [..ile].

Wewnętrzna pętla idzie do ile, nie do robocza.Length. Dalsza część tablicy roboczej to jeszcze same zera — porównywanie z nimi uznałoby, że zero już jest w zbiorze, i wyrzuciłoby prawdziwe zero z danych.

break po znalezieniu duplikatu: dalej szukać nie ma po co.

Ten algorytm sprawdza każdy element ze wszystkimi wcześniejszymi, więc przy dużych tablicach robi się wolny. Szybsza wersja: posortować kopię i porównywać tylko sąsiadów — ale wtedy tracimy pierwotną kolejność. Docelowo służy do tego typ HashSet<T> albo metoda Distinct() z LINQ.

PODSUMOWANIE

Co trzeba zapamiętać

  • Metody klasy Array wywołujemy przez nazwę klasy: Array.Sort(t), a nie t.Sort().
  • Sort, Reverse, Fill i Clear zmieniają tablicę i nic nie zwracają.
  • IndexOf i BinarySearch zwracają indeks i tablicy nie ruszają; -1 (lub liczba ujemna) oznacza brak.
  • Sortuj kopię, jeśli pierwotna kolejność jest jeszcze potrzebna.
  • Porządek malejący to Sort, a potem Reverse.
  • Array.Sort(klucze, wartosci) przestawia dwie tablice jednakowo — ale Reverse trzeba wywołać dla każdej osobno.
  • BinarySearch na nieposortowanej tablicy zwraca bzdurę bez ostrzeżenia.
  • Array.Resize wymaga ref, tworzy nową tablicę i nie nadaje się do pętli.
  • Zawartość porównujemy przez SequenceEqual albo pętlą — == porównuje adresy.