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.
Czego się dziś nauczysz
- Posortujesz tablicę rosnąco i malejąco jedną instrukcją
- Znajdziesz element metodą
IndexOfzamiast 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ą
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:
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
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.
Sortowanie
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)}");
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ę:
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)}");
Oryginal: 42, 17, 93, 8, 55, 23
Posortowana: 8, 17, 23, 42, 55, 93
Sortowanie napisów
string[] imiona = { "Zofia", "Adam", "Ewa", "Bartek", "Celina" };
Array.Sort(imiona);
Console.WriteLine(string.Join(", ", imiona));
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:
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}");
}
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.
Wyszukiwanie
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.");
}
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
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}");
8, 17, 23, 42, 55, 93
55 stoi na indeksie 4
| Cecha | IndexOf | BinarySearch |
|---|---|---|
| Wymaga posortowania | nie | tak |
| Sposób działania | sprawdza po kolei | dzieli zakres na pół (jak zgadywanie liczby z lekcji 14) |
| Milion elementów | do miliona porównań | około 20 porównań |
| Gdy nie znajdzie | -1 | liczba ujemna, ale nie zawsze −1 |
| Przy duplikatach | pierwsze wystąpienie | dowolne 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.
Kopiowanie i zmiana rozmiaru
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));
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:
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));
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.
Pozostałe przydatne metody
// 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}");
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.
Porównywanie tablic
Z poprzedniej lekcji wiesz, że == porównuje adresy. Oto trzy sposoby na porównanie zawartości:
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}");
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.
Zestawienie metod
| Metoda | Zwraca | Dział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.
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ą.
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])}");
=== 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
| Fragment | Co 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ą.
Na czym się najczęściej potykamy
| Błąd | Co 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
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)}");
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.
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;
}
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.
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)}");
}
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.
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}");
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.
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)}");
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.
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}.");
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.
Co trzeba zapamiętać
- Metody klasy
Arraywywołujemy przez nazwę klasy:Array.Sort(t), a niet.Sort(). Sort,Reverse,FilliClearzmieniają tablicę i nic nie zwracają.IndexOfiBinarySearchzwracają 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 potemReverse. Array.Sort(klucze, wartosci)przestawia dwie tablice jednakowo — aleReversetrzeba wywołać dla każdej osobno.BinarySearchna nieposortowanej tablicy zwraca bzdurę bez ostrzeżenia.Array.Resizewymagaref, tworzy nową tablicę i nie nadaje się do pętli.- Zawartość porównujemy przez
SequenceEqualalbo pętlą —==porównuje adresy.