Array.Sort a LINQList<T>, cykly a metody z minulých hodin — nebo jen prohlížečTenhle blok je jen pro vás — dětem ho nepromítejte.
| Fáze | Co se děje | Čas |
|---|---|---|
| Rozjezd | Seřaďte živě pět dětí podle výšky. Kolik porovnání to dalo? | 10 min |
| Graf v konzoli | Blok 2–3, pole jako sloupce | 10 min |
| Bubble sort | Blok 4 + ukázka 2, počitadla na tabuli | 20 min |
| Merge a quick | Bloky 5–6 + ukázky 3–4 | 25 min |
| Hotové řazení | Blok 7 + ukázka 5 | 10 min |
| Cvičení | Děti pracují samy, vy obcházíte | 55 min |
| Závěr | Porovnání počtu porovnání u všech tří algoritmů | 10 min |
Rozjezd bez počítače je tady nejdůležitější část hodiny. Postavte pět dětí do řady a nechte je seřadit se podle výšky tak, že smí porovnávat jen dvojice sousedů a prohazovat je. Třída počítá, kolikrát se porovnávalo.
Pak to samé „chytře": rozdělte je na dvě skupinky, každou seřaďte zvlášť a nakonec skupinky slijte dohromady. Děti samy uvidí, že to bylo rychlejší. Až potom sáhněte na klávesnici.
Cílem není memorovat algoritmy, ale mít v hlavě obrázek: hloupé řazení porovnává pořád dokola totéž, chytré si rozdělí práci.
Array.Sort (introsort). Stačí „je to chytrá směs
quicksortu a dalších algoritmů, napsaná profesionály".Jak pracovat se smíšenou úrovní. Cvičení 1 zvládne každý. Cvičení 2 a 3 jsou rekurzivní a jsou nejtěžší — slabším stačí doplnit slučování, zbytek jim nechte předepsaný. Cvičení 4 a 5 jsou nejvděčnější, protože tam se konečně používají hotové nástroje.
Každé řazení na světě stojí na dvou operacích:
pole[0] > pole[1]),Prohození potřebuje dočasnou proměnnou. Kdybys napsal rovnou
pole[0] = pole[1];, původní sedmička se nenávratně ztratí — přepsal bys ji.
Představ si dvě sklenice: než přelít, musíš mít třetí prázdnou.
Algoritmy se liší jen v tom, které dvojice a v jakém pořadí porovnávají. Bubble sort porovnává sousedy, merge sort slévá hotové poloviny, quicksort porovnává s vybraným prvkem. Ale pod tím je pořád totéž.
C# umí prohodit dvě hodnoty i bez dočasné proměnné pomocí n-tice (tu znáš z minulé hodiny):
int[] pole = { 7, 3 };
(pole[0], pole[1]) = (pole[1], pole[0]);
Console.WriteLine(string.Join(" ", pole)); // 3 7
Vpravo se nejdřív spočítá celá dvojice a teprve pak se rozdá doleva. Je to kratší a hlavně se u toho nedá splést pořadí.
Aby bylo řazení vidět, musí být pole vidět. V konzoli na to máme dva způsoby:
string.Join(" ", pole) slepí prvky mezerami.
Hodí se, když chceš vypsat stav po každém průchodu pod sebe.new string('█', hodnota) vyrobí tolik plných
čtverečků, jaká je hodnota. Menší číslo = kratší sloupec.{hodnota,3} znamená „zarovnej číslo na tři znaky doprava", takže svislítka
jsou hezky pod sebou i u dvouciferných čísel.
Tohle je konzolová obdoba grafu: jakmile se pole seřadí, sloupce se srovnají do schodů. A to je přesně ten moment, kdy řazení „uvidíš".
7 3 9 1 5 porovnej 7 a 3 → prohoď
3 7 9 1 5 porovnej 7 a 9 → nech
3 7 9 1 5 porovnej 9 a 1 → prohoď
3 7 1 9 5 porovnej 9 a 5 → prohoď
3 7 1 5 9 konec 1. průchodu, devítka je doma
Bubble sort (bublinkové řazení) projde pole a porovná každé dva sousedy. Když jsou ve špatném pořadí, prohodí je. A pak to celé opakuje.
Po prvním průchodu je největší číslo úplně vpravo — probublalo tam jako bublina v limonádě. Po druhém průchodu je na místě druhé největší, a tak dál.
Proto se vnitřní cyklus může pokaždé o kousek zkrátit:
int[] pole = { 7, 3, 9, 1, 5 };
for (int i = 0; i < pole.Length - 1; i++)
{
for (int j = 0; j < pole.Length - 1 - i; j++)
{
// tady se porovnají pole[j] a pole[j + 1]
}
}
To - i znamená „konec pole už je hotový, tam nechoď".
Kolik práce to dá? U pole s 10 prvky asi 45 porovnání. U 100 prvků už skoro 5 000. U 1 000 prvků skoro půl milionu. Deset prvků navíc stojí desetkrát víc práce — a to je ten problém.
Když v celém průchodu nedošlo ani k jednomu prohození, je pole už seřazené a nemá smysl pokračovat:
int[] pole = { 7, 3, 9, 1, 5 };
for (int i = 0; i < pole.Length - 1; i++)
{
bool prohozeno = false;
// ...vnitřní cyklus; při každém prohození nastav prohozeno = true;
if (!prohozeno) break; // nic se nehýbalo → pole je hotové
}
Na už seřazeném poli pak bubble sort skončí po jediném průchodu. Je to hezká ukázka toho, že i hloupý algoritmus se dá vyladit — ale rychlým se z toho nestane.
[7 3 9 1 5 8]
/ \
[7 3 9] [1 5 8]
/ \ / \
[7] [3 9] [1] [5 8]
| |
[3 9] [5 8]
\ / \ /
[3 7 9] [1 5 8]
\ /
[1 3 5 7 8 9] ← slévání zpátky
Merge sort má jediný nápad: dvě seřazené hromádky jdou slít dohromady velice rychle. Stačí se pořád dívat jen na dva vrchní prvky a brát menší z nich.
Algoritmus proto:
Rozdělování končí u jednoprvkových kousků — jeden prvek je totiž seřazený sám o sobě. Tomu se říká dno rekurze a bez něj by program běžel donekonečna.
Pro pole 1 000 prvků udělá merge sort asi 10 000 porovnání místo půl milionu. Padesátkrát míň práce, a čím větší pole, tím větší náskok.
Máš [3 7 9] a [1 5 8]. Díváš se na první prvky obou: 3 a 1.
Menší je 1 → jde ven a v pravé hromádce se posuneš dál. Pak porovnáš 3 a 5,
ven jde 3. A tak dál, dokud jedna hromádka nedojde; zbytek druhé se prostě přilepí.
Pointa: každý prvek se podívá jen jednou. Proto je slévání tak levné.
[7 3 9 1 5 8] pivot = 9
menší: [7 3 1 5 8] stejné: [9] větší: []
[7 3 1 5 8] pivot = 1
menší: [] stejné: [1] větší: [7 3 5 8]
[7 3 5 8] pivot = 5
menší: [3] stejné: [5] větší: [7 8]
Quicksort si vybere jeden prvek — pivot — a rozdělí podle něj pole na tři hromádky: menší než pivot, rovné pivotu, větší než pivot.
Pak stejným způsobem seřadí hromádku menších a hromádku větších
a všechno slepí za sebe: menší + stejné + větší. Žádné slévání se neřeší,
protože hromádky už jsou ve správném pořadí.
Volba pivota rozhoduje o všem. Když pivot rozdělí pole zhruba na polovic, je quicksort ještě rychlejší než merge sort. Když padne pokaždé na nejmenší prvek (třeba u už seřazeného pole a pivota zleva), zdegeneruje na bubble sort.
Proto se pivot obvykle bere ze středu nebo náhodně — my použijeme střed:
var data = new List<int> { 7, 3, 9, 1, 5, 8 };
int pivot = data[data.Count / 2];
Console.WriteLine($"Pivot je {pivot}.");
V praxi si řazení nikdy nepíšeš sám. .NET má tři nástroje:
Array.Sort(pole) — seřadí pole na místě, původní pořadí je pryč.seznam.Sort() — totéž pro List<T>, taky na místě..OrderBy(...) — z LINQ; nezmění původní seznam, vrátí nový seřazený.
Do závorky píšeš, podle čeho se má řadit.Zápis j => j.Length se čte „pro každou položku j vezmi její délku".
Je to nejkratší způsob, jak říct „řaď podle tohohle".
| Chci… | Použij |
|---|---|
| seřadit pole čísel | Array.Sort(pole) |
| seřadit seznam na místě | seznam.Sort() |
| nový seřazený seznam | seznam.OrderBy(x => x).ToList() |
| řadit podle vlastnosti | .OrderBy(p => p.cena) |
| od největšího | .OrderByDescending(p => p.cena) |
| druhé kritérium | .OrderBy(a).ThenBy(b) |
Ze stejného důvodu, proč se ve škole počítá zpaměti, i když existuje kalkulačka.
Až budeš řešit úlohu, na kterou hotový nástroj není, budeš potřebovat umět
přemýšlet jako algoritmus — rozdělit problém, porovnat, poskládat zpátky.
A hlavně: díky dnešku víš, že Array.Sort není magie, jen dobře napsaný kód.
Co je uvnitř Array.Sort. Od .NET Core 2.1 je to introsort: začne
quicksortem, při příliš velké hloubce rekurze přepne na heapsort a malé úseky
dodělá insertion sortem. Je to nestabilní řazení.
Stabilita. Array.Sort a List<T>.Sort nejsou stabilní — prvky se
stejným klíčem mohou změnit vzájemné pořadí. LINQ OrderBy stabilní je.
U inventáře v cvičení 5 to je vidět: při řazení podle vzácnosti zůstane
u OrderBy původní pořadí předmětů uvnitř skupiny.
Porovnávání vlastních typů. List<T>.Sort() bez parametru vyžaduje,
aby prvky uměly IComparable. U n-tic to funguje (porovnává se položka
po položce), u vlastních tříd (16. hodina) ne — tam se předává
Comparison<T>, tedy seznam.Sort((a, b) => a.cena.CompareTo(b.cena)).
CompareTo vrací tři stavy: záporné číslo (a je menší), nulu (rovnost),
kladné číslo (a je větší). Prohozením a a b otočíte směr řazení.
Dětem stačí vzor „menší → a.CompareTo(b), větší → b.CompareTo(a)".
Měření času. Stopwatch v prohlížeči funguje, ale čísla jsou v desítkách
milisekund nepřesná (WASM, jiné rozlišení časovače). Proto ve cvičeních měříme
hlavně počet porovnání — to je stejné číslo na každém počítači a nedá se
zpochybnit.
Pozor na rekurzi v prohlížeči. Merge i quicksort jsou rekurzivní.
Na polích do tisíce prvků je to v pohodě; kdo zkusí sto tisíc prvků
s pivotem zleva na seřazených datech, může narazit na StackOverflowException.
█ za # nebo ▓.for od konce).Co si z toho odnést: graf je jen new string('█', hodnota) a řazení uvidíš
podle toho, jak se sloupce postupně srovnávají do schodů.
Co si z toho odnést: po každém průchodu je vpravo o jedno číslo „doma". A ten sloupeček počtů porovnání je jediné, co dnes opravdu porovnáváme.
MergeSort).Co si z toho odnést: nejvíc odsazené řádky jsou nejmenší hromádky. Postupují zdola nahoru — čím výš, tím větší a hotovější kousky pole.
data[0] (první prvek) a pusť to na seřazeném poli.Co si z toho odnést: quicksort nic nesluje — hromádky jsou už ve správném
pořadí, stačí je slepit za sebe pomocí AddRange.
OrderByDescending podle ceny — co se změní?.Where(p => p.cena > 50)).Co si z toho odnést: OrderBy vyrobí nový seřazený seznam, Sort
přerovná ten původní. Když se ti pořadí „záhadně" změnilo, sáhl jsi na Sort.
Unhandled exception. System.IndexOutOfRangeException:
Index was outside the bounds of the array.
Klasika bubble sortu. Vnitřní cyklus sáhne na pole[j + 1], takže j
smí dojít nejvýš k pole.Length - 2. Podmínka proto musí být
j < pole.Length - 1 - i, ne j < pole.Length.
Když si nejste jistí, vypište si j a j + 1 před porovnáním — hned uvidíte,
kde to přeteče.
Druhý zdroj téhle chyby je GetRange v merge sortu: druhá půlka se bere jako
data.GetRange(stred, data.Count - stred), tedy „od středu, tolik prvků,
kolik zbývá". Napsat tam data.Count je nejčastější překlep hodiny.
| Hláška / projev | Co to znamená | Jak to opravit |
|---|---|---|
IndexOutOfRangeException |
vnitřní cyklus jde o jeden moc daleko | j < pole.Length - 1 - i |
ArgumentException u GetRange |
špatný počet prvků | GetRange(stred, data.Count - stred) |
StackOverflowException |
rekurze bez dna | if (data.Count <= 1) return data; |
| pole se neseřadí | prohození bez dočasné proměnné | (a, b) = (b, a) nebo tři řádky s docasny |
| pole je seřazené pozpátku | obrácené porovnání | > řadí vzestupně, < sestupně |
OrderBy nic neudělal |
výsledek se zahodil | ulož ho: pole = pole.OrderBy(...).ToList(); |
List.Sort() hlásí InvalidOperationException |
prvky se neumí porovnat | předej pravidlo: Sort((a, b) => a.cena.CompareTo(b.cena)) |
| quicksort nikdy neskončí | pivot zůstává v hromádce „menší" | rovné prvky dávej do samostatné hromádky stejne |
Zadání:
Napiš bubble sort tak, aby bylo po každém průchodu vidět, co se v poli stalo.
- Dokonči metodu
Vykreslia vypiš pole před řazením💡 Poradit
Projdi pole cyklemforeacha každou hodnotu vypiš jako sloupec:Console.WriteLine($"{hodnota,3} │{new string('█', hodnota)}");{hodnota,3}zarovná číslo na tři znaky, aby byla svislítka pod sebou.
Řádek čísel vyrobíš pomocístring.Join(" ", pole).- Napiš vnitřní cyklus: porovnej každé dva sousedy a prohoď je
💡 Poradit
Cyklus musí skončit o jedno dřív, protože saháš i napole[j + 1]:for (int j = 0; j < pole.Length - 1; j++)
Uvnitř:if (pole[j] > pole[j + 1]) (pole[j], pole[j + 1]) = (pole[j + 1], pole[j]);- Obal ho vnějším cyklem a po každém průchodu vypiš stav pole
💡 Poradit
Vnější cyklus:for (int i = 0; i < pole.Length - 1; i++)
Vnitřní se smí pokaždé zkrátit, konec pole je už hotový:for (int j = 0; j < pole.Length - 1 - i; j++)
Za vnitřním cyklem (ale ještě uvnitř vnějšího) vypišstring.Join(" ", pole).- Přidej počitadla
porovnaniaprohozenia vypiš je na konci💡 Poradit
Obě proměnné si připrav před cykly:int porovnani = 0;porovnani++;patří hned předif— porovnání proběhne vždycky.prohozeni++;patří dovnitřif— prohodí se jen někdy.- Zastav řazení dřív, když se v průchodu nic neprohodilo
💡 Poradit
Na začátku každého průchodu nastavbool prohozenoVPruchodu = false;
Při každém prohození ji přepni natrue.
Na konci průchodu:if (!prohozenoVPruchodu) break;
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3, pole se seřadí a je vidět každý průchod |
| Rozšíření | Body 4–5, počitadla a předčasné ukončení |
| Výzva | Vypsat graf po každém průchodu, ne jen na konci |
Očekávaný výsledek: pole 9 4 7 1 6 3 se seřadí na 1 3 4 6 7 9
za 15 porovnání a 11 prohození, přičemž poslední průchod se už neudělá.
Kde se zaseknou:
j < pole.Length. Nejčastější chyba hodiny. Program spadne na
IndexOutOfRangeException, protože poslední porovnání sáhne za konec pole.
Nechte je to přečíst z hlášky — je naprosto srozumitelná.pole[j] = pole[j+1]; a pak
pole[j+1] = pole[j];, dostane dvě stejná čísla. Ukažte to na dvou sklenicích.porovnani++ uvnitř if počítá prohození,
ne porovnání. Krásný důvod, proč se počitadla kontrolují ručním počítáním
na malém poli.break. Bez něj to funguje, jen se udělá o průchod víc.
Ať si vypíšou počet porovnání s ním i bez něj.Kdo je hotový dřív: ať volá Vykresli po každém průchodu — vznikne
animace řazení. A ať zkusí pole seřazené pozpátku: { 9, 7, 6, 4, 3, 1 }.
Otázka na závěr cvičení: „Proč se vnitřní cyklus s každým průchodem zkracuje?" Odpověď: protože největší čísla už probublala na konec a jsou na svém místě.
Zadání:
Celý merge sort stojí na jedné jediné dovednosti: slít dvě seřazené hromádky. Tu si nejdřív vyrob.
- Napiš hlavní cyklus metody
Slij: dokud mají obě hromádky prvky, ber menší z vrchu💡 Poradit
Potřebuješ dva ukazatele:int i = 0;do levé aint j = 0;do pravé hromádky.while (i < leva.Count && j < prava.Count)
Uvnitř porovnej vrchy a ten menší přidej do výsledku:if (leva[i] <= prava[j]) { vysledek.Add(leva[i]); i++; }else { vysledek.Add(prava[j]); j++; }- Dolij zbytek hromádky, která ještě něco má, a
Slijvyzkoušej💡 Poradit
Když jedna hromádka dojde, druhá je celá větší — stačí ji přilepit:while (i < leva.Count) { vysledek.Add(leva[i]); i++; }while (j < prava.Count) { vysledek.Add(prava[j]); j++; }
Bez těchhle dvou řádků ti na konci budou chybět čísla.- Doplň
MergeSort: dno rekurze, rozdělení na půlky a volání sebe sama💡 Poradit
Dno rekurze:if (data.Count <= 1) return data;— jeden prvek je seřazený sám o sobě.
Rozdělení:int stred = data.Count / 2;var leva = MergeSort(data.GetRange(0, stred));var prava = MergeSort(data.GetRange(stred, data.Count - stred));
Druhý parametrGetRangeje počet prvků, ne index konce.- Vypisuj každé slévání s odsazením podle hloubky rekurze
💡 Poradit
Přidej metodě parametrint hloubkaa při rekurzi předávejhloubka + 1.
Odsazení vyrobíš stejně jako sloupce:string odsazeni = new string(' ', hloubka * 2);
Vypiš to až po slití:$"{odsazeni}[{string.Join(" ", leva)}] + [{string.Join(" ", prava)}] → [{string.Join(" ", spojene)}]"- Přidej počitadlo porovnání a srovnej ho s bubble sortem na stejných datech
💡 Poradit
Proměnnouint porovnani = 0;si dej úplně nahoru, ať na ni obě metody vidí.porovnani++;patří dovnitř hlavního cyklu veSlij, hned předif.
Na bubble sort si napiš metodu, která vrátí jen počet porovnání — dovnitř si udělej kopii dat:var kopie = new List<int>(vstup);
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–2, Slij správně slije dvě hromádky |
| Rozšíření | Body 3–4, funkční merge sort s výpisem slévání |
| Výzva | Bod 5 a odpověď na otázku, kdo vyhraje na 1 000 číslech |
Očekávaný výsledek: pole se seřadí na 1 2 3 4 6 7 8 9, merge sort
udělá 17 porovnání, bubble sort 28. Rozdíl je malý schválně — pointa je
v posledních dvou vypsaných řádcích o tisíci číslech.
Kde se zaseknou:
GetRange(stred, data.Count). Druhý parametr je počet prvků, ne index
konce. Hlásí se ArgumentException.StackOverflowException přijde okamžitě.
Ptejte se: „Kdy už není co dělit?"Kdo je hotový dřív: ať do DATA přidá dalších osm čísel a spočítá, o kolik
řádků výpis naroste. Nebo ať Slij upraví tak, aby řadila sestupně.
Otázka na závěr cvičení: „Proč je slévání dvou seřazených hromádek tak levné?" Odpověď: protože se na každý prvek koukneme jen jednou — nikdy se nevracíme.
Zadání:
Quicksort nic nesluje. Vybere si jedno číslo a podle něj celé pole rozhází na tři hromádky.
- V metodě
QuickSortvyber pivot ze středu a připrav tři prázdné hromádky💡 Poradit
Pivot bereme ze středu, protože zleva by to na seřazených datech dopadlo špatně:int pivot = data[data.Count / 2];
Hromádky jsou obyčejné seznamy:var mensi = new List<int>();,var stejne = new List<int>();,var vetsi = new List<int>();- Rozděl všechny prvky do hromádek a vypiš je
💡 Poradit
Projdi data cyklemforeacha každé číslo pošli do správné hromádky:if (hodnota < pivot) mensi.Add(hodnota);else if (hodnota > pivot) vetsi.Add(hodnota);else stejne.Add(hodnota);
Rovné prvky musí jít dostejne, jinak by se pivot vracel pořád dokola.- Doplň dno rekurze, rekurzivní volání a slep výsledek
💡 Poradit
Dno rekurze patří úplně nahoru:if (data.Count <= 1) return data;
Slepení se dělá metodouAddRange, a to přesně v tomhle pořadí:vysledek.AddRange(QuickSort(mensi, hloubka + 1));vysledek.AddRange(stejne);vysledek.AddRange(QuickSort(vetsi, hloubka + 1));- Přidej odsazení podle hloubky a počitadlo porovnání
💡 Poradit
string odsazeni = new string(' ', hloubka * 2);a při rekurzi předávejhloubka + 1.porovnani++;dej dovnitřforeach— každý prvek se s pivotem porovnává.
Proměnnouint porovnani = 0;si připrav nahoře, ať na ni metoda vidí.- Porovnej pivot ze středu a pivot zleva na už seřazeném poli
💡 Poradit
Přidej metodě parametrbool pivotZlevaa hned za výběrem pivota:if (pivotZleva) pivot = data[0];
Před každým během vynuluj počitadlo:porovnani = 0;
Pusť obě varianty nanew List<int> { 1, 2, 3, 4, 5, 6, 7, 8 }a porovnej čísla i odsazení výpisu.
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–2, rozdělení na tři hromádky se vypíše |
| Rozšíření | Body 3–4, funkční quicksort s odsazeným výpisem |
| Výzva | Bod 5 a vysvětlení, proč pivot zleva na seřazených datech propadne |
Očekávaný výsledek: na náhodných datech 19 porovnání, na seřazených datech s pivotem ze středu 17 porovnání, ale s pivotem zleva 35 — a výpis se zláme do schodiště. To schodiště je celá pointa cvičení.
Kde se zaseknou:
mensi. Pivot se pak dostane do rekurze znovu
a program běží donekonečna (v prohlížeči se prostě zasekne). Proto je
hromádka stejne povinná.AddRange. Když se stejne přilepí až na konec, výsledek
je skoro seřazený — a to je zákeřné, protože to na první pohled vypadá dobře.Kdo je hotový dřív: ať spočítá i maximální hloubku rekurze (parametr
hloubka si stačí zapamatovat do proměnné maximum). U pivota zleva na
seřazených datech vyjde hloubka rovná počtu prvků.
Otázka na závěr cvičení: „Proč je pivot ze středu lepší než pivot zleva?" Odpověď: dělí pole na dvě podobně velké části, takže rekurze je mělká.
Zadání:
Teď to zkus naostro. Tisíc dvě stě čísel — a uvidíš, kde je hranice mezi „hloupým" a „hotovým".
- Vyrob pole
POCETnáhodných čísel pomocínew Random(42)a vypiš prvních deset💡 Poradit
Číslo v závorce je semínko — se stejným semínkem dostaneš pokaždé stejná čísla.var generator = new Random(42);data[k] = generator.Next(1, 1000);vrátí číslo od 1 do 999.
Prvních deset vypíšeš přes LINQ:string.Join(" ", data.Take(10))- Napiš metodu
BubbleSort, která pole seřadí a vrátí počet porovnání💡 Poradit
Je to přesně ten kód z cvičení 1, jen zabalený do metody s návratovou hodnotou:int BubbleSort(int[] data) { ... return porovnani; }
Pouštěj ho na kopii, ať máš pořád i původní data:int[] kopie = puvodni.ToArray();- Seřaď druhou kopii pomocí
Array.Sorta ověř, že výsledky jsou stejné💡 Poradit
Array.Sort(kopieB);seřadí pole rovnou na místě, nic nevrací.
Shodu ověříš jedním voláním z LINQ:bool stejne = kopieA.SequenceEqual(kopieB);
Kdyby vyšlofalse, máš v bubble sortu chybu.- Změř obě řazení pomocí
Stopwatch💡 Poradit
Nahoru přidejusing System.Diagnostics;var stopky = Stopwatch.StartNew();…stopky.Stop();
Výsledek přečteš jakostopky.Elapsed.TotalMillisecondsa zaokrouhlíš:$"{stopky.Elapsed.TotalMilliseconds:F1} ms"- Vypiš pomocí LINQ pět největších čísel a pět nejmenších
💡 Poradit
puvodni.OrderByDescending(x => x).Take(5)vrátí pět největších.puvodni.OrderBy(x => x).Take(5)vrátí pět nejmenších.
Obojí pak vypiš přesstring.Join(" ", ...). Všimni si, že původní pole se nezměnilo.
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3, obě metody dají stejný výsledek |
| Rozšíření | Bod 4, změřené časy obou řazení |
| Výzva | Bod 5 a tabulka časů pro 300, 600 a 1 200 čísel |
Očekávaný výsledek: bubble sort udělá kolem 719 000 porovnání, Array.Sort
je řádově rychlejší. Přesné časy se liší podle počítače i prohlížeče — proto
je důležitější to obrovské číslo porovnání než milisekundy.
Kde se zaseknou:
puvodni, má pak
Array.Sort seřazená data a kontrola shody je bezcenná. ToArray() je tu
klíčové.using System.Diagnostics; uprostřed souboru. Direktivy using musí být
úplně nahoře, jinak CS1529.Array.Sort přiřazený do proměnné. int[] x = Array.Sort(pole); se
nepřeloží — metoda nic nevrací, řadí na místě.POCET. Kdo si nastaví 50 000, bude v prohlížeči čekat
minuty. Doporučená horní hranice je 3 000.Kdo je hotový dřív: ať udělá tabulku pro 300, 600 a 1 200 čísel. Při zdvojnásobení počtu vyskočí počet porovnání zhruba čtyřikrát — to je ta „druhá mocnina" názorně.
Otázka na závěr cvičení: „Proč měříme počet porovnání, a ne jenom čas?" Odpověď: čas závisí na počítači, počet porovnání je na každém stroji stejný.
Zadání:
Hrdina nasbíral hromadu věcí a batoh je v nepořádku. Zařiď, ať si ho umí srovnat podle čehokoliv.
- Vytvoř inventář jako seznam n-tic a vypiš ho jako tabulku
💡 Poradit
Typ napiš celý:var inventar = new List<(string nazev, int cena, string vzacnost)>();
Předmět přidáš jako trojici:("Lektvar zdraví", 25, "běžný")
V metoděVypiszarovnej názvy:polozka.nazev.PadRight(22)a cenu{polozka.cena,4}.- Seřaď kopii inventáře bubble sortem podle ceny
💡 Poradit
Kopii vyrobíš takhle:var kopie = new List<(string nazev, int cena, string vzacnost)>(inventar);
Porovnává se jen jedna položka n-tice:if (kopie[j].cena > kopie[j + 1].cena)
Prohození funguje úplně stejně jako u čísel:(kopie[j], kopie[j + 1]) = (kopie[j + 1], kopie[j]);- To samé jedním řádkem přes
OrderByDescendinga vypiš tři nejcennější💡 Poradit
inventar.OrderByDescending(p => p.cena).Take(3).ToList()
Zápisp => p.cenaznamená „u každé položky se dívej na její cenu".OrderBypůvodní seznam nemění — vrací nový.- Seřaď podle vzácnosti a uvnitř každé skupiny podle ceny sestupně
💡 Poradit
Abeceda by dala špatné pořadí, tak si vzácnostem přiděl čísla:var PORADI = new Dictionary<string, int> { ["legendární"] = 0, ["vzácný"] = 1, ["běžný"] = 2 };
Pak se řadí dvakrát za sebou:inventar.OrderBy(p => PORADI[p.vzacnost]).ThenByDescending(p => p.cena).ToList()- Vypiš souhrn: celkovou hodnotu, nejdražší předmět a počty podle vzácnosti
💡 Poradit
Součet i maximum umí LINQ:inventar.Sum(p => p.cena)ainventar.Max(p => p.cena).
Nejdražší předmět dostaneš jako první z seřazeného seznamu:var nejdrazsi = inventar.OrderByDescending(p => p.cena).First();
Počty spočítej cyklem přesPORADI.Keysainventar.Count(p => p.vzacnost == klic).
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–2, inventář se vypíše a seřadí podle ceny |
| Rozšíření | Body 3–4, LINQ a řazení podle dvou kritérií |
| Výzva | Bod 5 plus filtr „ukaž jen věci dražší než 50 zlaťáků" |
Očekávaný výsledek: čtyři vypsané tabulky (původní pořadí, podle ceny, tři nejcennější, podle vzácnosti) a souhrn: 7 předmětů, 615 zlaťáků, nejdražší Dračí šupina.
Kde se zaseknou:
.ToList(). OrderBy vrací IEnumerable, ale metoda Vypis
chce List. Hláška CS1503 je poměrně dlouhá — ukažte, kde v ní je jádro.List<(string nazev, int cena, string vzacnost)>
se opakuje pořád dokola. Zmiňte, že v 16. hodině to nahradí jedna třída
Predmet a bude z toho List<Predmet>.predmet.cena = 0; uvnitř foreach nejde —
n-tice v seznamu se musí přepsat celá: seznam[i] = (nazev, novaCena, vzacnost);Kdo je hotový dřív: ať přidá filtr inventar.Where(p => p.cena > 50).ToList()
a ať zkusí seřadit podle poměru cena/vzácnost — tedy vymyslet vlastní
pravidlo a obhájit ho.
Otázka na závěr cvičení: „Kdy se vyplatí OrderBy a kdy Sort?"
Odpověď: OrderBy když chci původní pořadí zachovat, Sort když ho chci přepsat.
Vyrob program „Žebříček hrdinů". Připrav si pět hrdinů jako n-tice (jméno, úroveň, zlato) a pak:
- vypiš hrdiny tak, jak jsi je zadal,
- seřaď je vlastním bubble sortem podle úrovně od nejvyšší,
- totéž udělej jedním řádkem přes
OrderByDescendinga ověř, že vyšlo to samé,- přidej druhé kritérium: při stejné úrovni rozhoduje zlato (
ThenByDescending),- a ke každému hrdinovi dokresli sloupec z
█podle jeho úrovně.Přines soubor
Program.csnebo snímek obrazovky s výstupem.
Nejcennější bod je ten třetí — porovnání vlastního řadicího kódu s LINQ.
Dítě si má na vlastní oči ověřit, že dvacet řádků bubble sortu a jeden řádek
OrderByDescending dají stejný výsledek. To je celá pointa dnešní hodiny.
Na začátku příští hodiny promítněte dva tři žebříčky a zeptejte se,
co se stane, když mají dva hrdinové stejnou úroveň. Odtud je krok
k ThenByDescending a k pojmu stabilní řazení.
Kdo nemá doma .NET, může úkol udělat rovnou tady v prohlížeči a poslat zkopírovaný výstup.
Toto je jedna z možných variant, jak mohl úkol dopadnout. Tvoje řešení se může lišit a to je v pořádku.
Všechno, co jsme dnes použili, je popsané na Microsoft Learn. Odkazy jsou anglicky — klidně si je nech přeložit prohlížečem, kód je stejný v každém jazyce.
| Téma | Odkaz |
|---|---|
Array.Sort |
learn.microsoft.com — Array.Sort Method |
List<T>.Sort |
learn.microsoft.com — List<T>.Sort Method |
OrderBy a ThenBy |
learn.microsoft.com — Enumerable.OrderBy |
| Řazení v LINQ | learn.microsoft.com — Sorting Data (LINQ) |
Random |
learn.microsoft.com — Random Class |
Stopwatch |
learn.microsoft.com — Stopwatch Class |
| N-tice | learn.microsoft.com — Tuple types |
Dnešek nebyl o tom naučit se nazpaměť tři algoritmy. Byl o jedné myšlence: stejný úkol se dá vyřešit chytře nebo hloupě, a ten rozdíl je vidět v číslech.
Array.Sort, List.Sort a LINQ OrderBy a víš, čím se liší.Tohle je přesně to, co dělá informatik. Ne že by znal víc příkazů — ale že se umí zeptat „kolik práce to doopravdy dá?" a odpověď si ověřit.
Příště se podíváme na to, jak si všechny ty seřazené inventáře a žebříčky uložit do souboru, aby nezmizely, když program skončí.