Pozn. ke kódu: obecné principy jsou nejdřív popsané slovně, ke konkrétním konstrukcím jsou pak ukázky reálného, spustitelného kódu v Pythonu a v C# vedle sebe – ve škole se běžně používají oba jazyky, princip řídicích struktur je ale ve všech běžných jazycích stejný.
Datové typy
- datový typ = určuje, jaké hodnoty může proměnná nabývat a jaké operace s ní lze provádět
- základní typy:
- celé číslo (integer, int) = čísla bez desetinné části; má omezený rozsah daný počtem bitů (přetečení – overflow, pokud je hodnota mimo rozsah)
- desetinné číslo (float, double) = čísla s desetinnou částí; uloženo s omezenou přesností, může vznikat zaokrouhlovací chyba
- znak (char) = jediný znak (písmeno, číslice, symbol), interně uložen jako číselný kód (např. ASCII/Unicode)
- řetězec (string) = posloupnost znaků, v mnoha jazycích je to vlastně pole znaků
- logická hodnota (boolean) = true/false, výsledek porovnání a logických operací
- pole (array) = uspořádaná kolekce hodnot stejného typu
- statické typování = typ proměnné je pevně daný a kontrolovaný při překladu (Java, C#) x dynamické typování = typ se určuje až za běhu programu (Python, JavaScript)
Proměnné a konstanty
- proměnná = pojmenované místo v paměti pro uložení hodnoty; má identifikátor (název), typ a hodnotu
- deklarace = oznámení, že proměnná existuje a jaký má typ; inicializace = přiřazení první hodnoty
- konstanta = pojmenovaná hodnota, kterou po nastavení nelze v průběhu programu změnit (používá se pro hodnoty, které se nemění – např. matematické konstanty, konfigurační údaje)
- platnost proměnné (scope) = část programu, ve které je proměnná viditelná a použitelná (lokální proměnná uvnitř funkce/bloku x globální proměnná v celém programu)
Řídicí struktury
- sekvence = základní řídicí struktura, příkazy se provádějí jeden po druhém v pořadí, v jakém jsou zapsané
- větvení (podmínka) = na základě splnění/nesplnění podmínky se provede jedna z několika větví programu
if / else if / elseswitch / case= větvení podle konkrétní hodnoty proměnné, vhodné při větším počtu možností- složené podmínky = kombinace více podmínek pomocí logických operátorů AND (a zároveň), OR (nebo), NOT (negace)
- cyklus (opakování) = opakované provádění bloku příkazů, dokud platí/neplatí daná podmínka
for= cyklus s pevným (předem známým) počtem opakování, typicky s řídicí proměnnou (čítačem)while= cyklus s podmínkou na začátku; pokud podmínka neplatí hned na začátku, tělo cyklu se nemusí provést ani jednoudo-while/repeat-until= cyklus s podmínkou na konci; tělo cyklu se provede vždy alespoň jednou
Větvení – if / else
Python:
vek = 20
ma_obcanku = True
if vek >= 18 and ma_obcanku:
print("může volit")
else:
print("nemůže volit")
C#:
int vek = 20;
bool maObcanku = true;
if (vek >= 18 && maObcanku)
{
Console.WriteLine("může volit");
}
else
{
Console.WriteLine("nemůže volit");
}
Větvení podle hodnoty – switch / case
Python (od verze 3.10 lze použít match, ve starším kódu se místo toho běžně používá řetězec if / elif):
den = 3
match den:
case 1:
print("pondělí")
case 2:
print("úterý")
case 3:
print("středa")
case _:
print("neznámý den")
C#:
int den = 3;
switch (den)
{
case 1:
Console.WriteLine("pondělí");
break;
case 2:
Console.WriteLine("úterý");
break;
case 3:
Console.WriteLine("středa");
break;
default:
Console.WriteLine("neznámý den");
break;
}
Cyklus s pevným počtem opakování – for
Python:
for i in range(10): # i postupně nabývá hodnot 0 až 9
print(i)
C#:
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
Cyklus s podmínkou na začátku – while
Python:
pocet = 0
while pocet < 5:
print(pocet)
pocet += 1
C#:
int pocet = 0;
while (pocet < 5)
{
Console.WriteLine(pocet);
pocet++;
}
Cyklus s podmínkou na konci – do-while
Python nemá vestavěný do-while, stejné chování (tělo se provede vždy alespoň jednou) se zapisuje pomocí while True a break na konci těla:
Python:
pocet = 0
while True:
print(pocet) # tělo se provede vždy alespoň jednou
pocet += 1
if not (pocet < 5):
break
C#:
int pocet = 0;
do
{
Console.WriteLine(pocet);
pocet++;
} while (pocet < 5);
Práce s textovým řetězcem
- základní operace nad řetězci:
- zjištění délky řetězce
- spojování (konkatenace) dvou a více řetězců
- získání podřetězce (substring) podle pozice a délky
- hledání znaku/podřetězce uvnitř řetězce
- nahrazení části řetězce
- převod velikosti písmen (na velká/malá)
- rozdělení řetězce na části podle oddělovače (split) a opačně spojení částí do řetězce (join)
- převod řetězce na číslo a naopak (parsování)
Python:
text = "Ahoj, světe!"
print(len(text)) # délka řetězce -> 12
pozdrav = text + " Jak se máš?" # spojování (konkatenace)
cast = text[6:11] # podřetězec (substring) -> "světe"
print("světe" in text) # hledání podřetězce -> True
nahrazeno = text.replace("Ahoj", "Nazdar")
velka = text.upper() # "AHOJ, SVĚTE!"
mala = text.lower() # "ahoj, světe!"
casti = text.split(", ") # rozdělení -> ["Ahoj", "světe!"]
spojeno = "-".join(casti) # spojení zpět -> "Ahoj-světe!"
cislo = int("42") # převod řetězce na číslo
retezec = str(42) # převod čísla na řetězec
C#:
string text = "Ahoj, světe!";
Console.WriteLine(text.Length); // délka řetězce -> 12
string pozdrav = text + " Jak se máš?"; // spojování (konkatenace)
string cast = text.Substring(6, 5); // podřetězec -> "světe"
Console.WriteLine(text.Contains("světe")); // hledání podřetězce -> True
string nahrazeno = text.Replace("Ahoj", "Nazdar");
string velka = text.ToUpper(); // "AHOJ, SVĚTE!"
string mala = text.ToLower(); // "ahoj, světe!"
string[] casti = text.Split(", "); // rozdělení -> ["Ahoj", "světe!"]
string spojeno = string.Join("-", casti); // spojení zpět -> "Ahoj-světe!"
int cislo = int.Parse("42"); // převod řetězce na číslo
string retezec = cislo.ToString(); // převod čísla na řetězec
Pole
- pole = pevně uspořádaná kolekce prvků stejného typu, přístup k prvku přes index
- indexování obvykle začíná od 0 (první prvek má index 0, poslední index
délka - 1) - pole může mít statickou velikost (pevně daná při vytvoření) nebo dynamickou (lze měnit za běhu, např. seznam/list)
- vícerozměrné pole (matice) = pole polí, přístup přes dva a více indexů (řádek, sloupec)
- typické operace nad polem: průchod (iterace) přes všechny prvky, součet/průměr prvků, nalezení maxima/minima, hledání prvku, řazení
Python (v Pythonu se místo pole typicky používá list, který má navíc dynamickou velikost):
znamky = [1, 2, 1, 3, 5]
print(znamky[0]) # přístup přes index -> 1
soucet = sum(znamky)
prumer = soucet / len(znamky)
for znamka in znamky: # průchod polem (iterace)
print(znamka)
# vícerozměrné pole (matice) 2x3 – seznam seznamů
matice = [[1, 2, 3], [4, 5, 6]]
print(matice[1][2]) # řádek 1, sloupec 2 -> 6
C#:
int[] znamky = { 1, 2, 1, 3, 5 };
Console.WriteLine(znamky[0]); // přístup přes index -> 1
int soucet = znamky.Sum(); // vyžaduje "using System.Linq;"
double prumer = (double)soucet / znamky.Length;
foreach (int znamka in znamky) // průchod polem (iterace)
{
Console.WriteLine(znamka);
}
// vícerozměrné pole (matice) 2x3
int[,] matice = { { 1, 2, 3 }, { 4, 5, 6 } };
Console.WriteLine(matice[1, 2]); // řádek 1, sloupec 2 -> 6
Typické algoritmy
- součet/průměr prvků pole = průchod polem s postupným přičítáním do pomocné proměnné
- hledání maxima/minima = průchod polem s průběžným porovnáváním s dosud nalezenou nejvyšší/nejnižší hodnotou
- lineární vyhledávání = postupné procházení prvků pole od začátku, dokud se nenajde hledaná hodnota nebo pole neskončí
- řazení (sorting) = uspořádání prvků pole podle klíče (vzestupně/sestupně); základní princip lze ukázat na tzv. bublinkovém řazení (bubble sort) – opakované porovnávání a prohazování sousedních prvků, dokud není pole seřazené
Ukázka postupně řeší na stejném poli všechny čtyři algoritmy z předchozích odrážek (součet/průměr, maximum, lineární vyhledávání, bublinkové řazení):
Python:
cisla = [5, 3, 8, 1, 9, 2]
# součet a průměr prvků pole
soucet = 0
for cislo in cisla:
soucet += cislo
prumer = soucet / len(cisla)
# hledání maxima
maximum = cisla[0]
for cislo in cisla:
if cislo > maximum:
maximum = cislo
# lineární vyhledávání hodnoty 8
hledana_hodnota = 8
nalezeno_na_indexu = -1
for i in range(len(cisla)):
if cisla[i] == hledana_hodnota:
nalezeno_na_indexu = i
break
# bublinkové řazení (bubble sort) vzestupně
for i in range(len(cisla) - 1):
for j in range(len(cisla) - 1 - i):
if cisla[j] > cisla[j + 1]:
cisla[j], cisla[j + 1] = cisla[j + 1], cisla[j]
print(cisla) # [1, 2, 3, 5, 8, 9]
C#:
int[] cisla = { 5, 3, 8, 1, 9, 2 };
// součet a průměr prvků pole
int soucet = 0;
foreach (int cislo in cisla)
{
soucet += cislo;
}
double prumer = (double)soucet / cisla.Length;
// hledání maxima
int maximum = cisla[0];
foreach (int cislo in cisla)
{
if (cislo > maximum)
{
maximum = cislo;
}
}
// lineární vyhledávání hodnoty 8
int hledanaHodnota = 8;
int nalezenoNaIndexu = -1;
for (int i = 0; i < cisla.Length; i++)
{
if (cisla[i] == hledanaHodnota)
{
nalezenoNaIndexu = i;
break;
}
}
// bublinkové řazení (bubble sort) vzestupně
for (int i = 0; i < cisla.Length - 1; i++)
{
for (int j = 0; j < cisla.Length - 1 - i; j++)
{
if (cisla[j] > cisla[j + 1])
{
int docasna = cisla[j];
cisla[j] = cisla[j + 1];
cisla[j + 1] = docasna;
}
}
}
Zobecnění, testování a ladění
- zobecnění řešení = algoritmus by neměl fungovat jen pro jeden konkrétní vstup, ale pro celou třídu podobných problémů (parametrizace vstupu, ošetření okrajových/hraničních případů – prázdný vstup, jediný prvek, záporná čísla apod.)
- testování programu:
- sestavení sady testovacích vstupů včetně hraničních hodnot (edge cases)
- porovnání skutečného výstupu s očekávaným
- typy chyb: syntaktická chyba (program se ani nespustí/nepřeloží), běhová chyba (runtime error – program spadne za běhu, např. dělení nulou), logická chyba (program běží, ale dává špatný výsledek)
- ladění (debugging) = hledání a oprava chyby; techniky: krokování programu (postupné spouštění příkaz po příkazu), sledování hodnot proměnných, dočasné výpisy (print/log), použití debuggeru s přerušovacími body (breakpoints)
Hodnocení a porovnávání algoritmů, časová a paměťová složitost
- pro stejný problém může existovat víc algoritmů řešení – liší se rychlostí, nároky na paměť a jednoduchostí zápisu
- asymptotická složitost = odhad, jak rychle roste čas (časová složitost) nebo paměť (paměťová složitost) potřebná pro algoritmus v závislosti na velikosti vstupu
n; zapisuje se pomocí tzv. O-notace (velké O) - základní třídy složitosti:
- O(1) – konstantní: doba běhu nezávisí na velikosti vstupu (např. přístup k prvku pole podle indexu)
- O(log n) – logaritmická: s každým krokem se prohledávaný prostor zmenší na polovinu (např. binární vyhledávání v seřazeném poli)
- O(n) – lineární: doba běhu roste přímo úměrně s velikostí vstupu (např. lineární vyhledávání, jeden průchod polem)
- O(n²) – kvadratická: typicky dva vnořené cykly přes vstup (např. bublinkové řazení, porovnání každého prvku s každým)
- při výběru algoritmu se hodnotí, jak se bude chovat pro malý i velký vstup – algoritmus s horší složitostí může být pro malá data v pořádku (je jednodušší na zápis), ale pro velká data se stane nepoužitelným
- vylepšení algoritmu = např. nahrazení lineárního vyhledávání binárním na seřazených datech změní složitost z O(n) na O(log n); cena za to je nutnost mít data předem seřazená