Warning
This page is located in archive. Go to the latest version of this course pages. Go the latest version of this page.

Den 4

Zpětná vazba

Moc nám pomůže, když vyplníte dotazník, díky předem!
Podle garantů některých oborů je zpětná vazba důležitější, než sex.

Teorie funkce

Funkce (někdy též metoda) je uzavřený, znovupoužitelný blok kódu (příkazů), který umí splnit nějaký jasně definovaný dílčí úkol (například načtení a ošetření vstupu, vykreslení řádky, výpočet obsahu trojuhelníku, atd). Definovanou funkci pak můžeme kdekoliv v našem programu zavolat jedním příkazem, což nám ušetří psaní, usnadní hledání chyb a celkově zpřehlední kód. Definice funkcí se vyplatí zejména v případech, kdy dílčí úkol potřebujeme vykonávat na více různých místech programu.

Definice funkce v Pythonu

Každá funkce má název, kterým funkci budeme později volat. Funkce může, nebo nemusí mít návratovou hodnotu (funkce na výpočet obsahu trojúhelníku ji mít bude, funkce sloužící pro opakovaný tisk textového řetězce ne). Vstupní argumenty (též proměnné, či parametry) jsou taková data (proměnné), se kterými funkce pracuje. Vstupních argumentů může mít funkce libovolné množství a mohou být libovolných datových typů.

Zajímavost: Python umožňuje, aby funkce vracela více hodnot najednou v podobě n-tice (tuple).

Funkci v Pythonu definujeme pomocí klíčovího slova def, jménem funkce, kulatými závorkami obsahujícími argumenty a znakem :, například def jmeno_funkce():. Následují příkazy v těle funkce, které je v Pythonu podobně jako v případě cyklů nebo podmínek odsadit tabulátorem. Má-li funkce návratovou hodnotu, je funkce zakončena klíčovým slovem return následované výrazem nebo proměnnou, kterou má funkce vracet, například return 5. Níže si ukážeme příkady některých funkcí.

Příklad funkce bez vstupních argumentů:

def pozdrav():
    print("Hello Bootcamp!")

Příklad funkce bez vstupních argumentů, ale s návratovou hodnotou:

import math
def pi():
    return math.pi

Příklad funkce se vstupními parametry a návratovou hodnotou:

def pythagoras(a, b):
    c = math.sqrt(a*a + b*b)
    return c

Je možné vracet návratovou hodnotu na vícero místech:

def soucet_listu(l):
    if len(l) == 0:
        return 0
    else:
        soucet = 0
        for i in range(len(l)):
            soucet += l[i]
        return soucet

Je důležité, aby funkce byly nejprve definovány a teprve pak volány, jak si ukážeme níže na ucelenějším ukázkovém kódu.

Příklady definic a volání funkcí

Následující kód:

import math
 
def say_hello():    
    print("Dobry den, vitejte u vykladu funkci.")
 
pythagoras() # skonci chybou
 
def pythagoras(a, b):
    c = math.sqrt(a*a + b*b)
    return c
 
say_hello()
 
strana_a = 3
strana_b = 4
strana_c = pythagoras(strana_a, strana_b)
 
print("Odvesna trojuhelniku se stranami " + str(strana_a) + " a " + str(strana_b) + " je " + str(strana_c))

vypíše:

Dobry den, vitejte u vykladu funkci.
Odvesna trojuhelniku se stranami 3 a 4 je 5

Dekompozice

How do you eat an elephant? – One bite at a time.
uknown author

Dekompozice nebo-li rozložení problému na podproblémy je základní princip, jak přistupovat ke složitějším úlohám v programování a dovednost, která usnadňuje řešení náročných úloh. Existují dva základní přístupy: od problému k jednotliým pod problémům (někdy označovaný jako shora-dolů) a od dílčích kroků k obecnému cíli (někdy označovaný jako zdola-nahoru). Oba přístupy se odlišují jak svou náročností, tak typem zadání, ke kterému jsou vhodné, proto si ukážeme, jak pomocí prvního analytického přístupu rozdělit úlohu na následující úlohu domečku na jednotlivé podproblémy.

Uvažujme následující zadání.

Úkol X. Načtěte na vstupu liché číslo N větší nebo rovno 5 a vypište na konzole domeček o odpovídající velikosti. Pokud číslo není liché nebo je menší než 5, vypište vhodnou chybovou hlášku. Vzorový výstup pro N=5:

  X  
 X X
XXXXX
X   X
X   X
x   X
XXXXX

Zamyslíme-li se nad samotným tvarem, vidíme na první pohled, že domeček se skládá ze zdí a ze střechy, na druhý, že zdi mají tvar čtverce. Pokud se podíváme na zadání, vidíme, že musíme načítat od uživatele nějaký vstup, kontrolovat, jestli je vstup platný a pak vykreslovat domeček. Spojením těchto dvou kategorií, dostáváme první nástřel dekompozice:

# 1. nacist vstup
# 2. zkontrolovat vstup
#    '-> je-li chybne, vypsat hlasku
#    '-> je-li validni, pokracovat 
# 3. vykreslit domecek
#    3.1 vykreslit strechu
#    3.2 vykreslit steny 

Nyní můžeme použít tyto kroky a rozhodnout, jaké kroky je vhodné zabalit do funkcí. Podobně jako v případě identifikace jednotlivých podúkolů, i zde může být více podobně vhodných způsobů, my ale zvolíme funkci pro každý číslovaný bod, čímž dostatneme následující funkce:

# nacte input od uzivatele a vrati ho
def load_input():
    pass
 
# zkontroluje velikost (argument 'size'),
# '-> je-li v poradku vrati True
# '-> je-li špatně, informuje o tom uživatele vypisem a vrati False
def check_size(size):
    pass
 
# vykresli steny domecku o velikosti 'size'
def print_walls(size):
    pass
 
# vykresli strechu domecku o velikosti "size"
def print_roof(size):
    pass
 
# vykresli cely domecek
def print_house(size):
    print_roof(size)
    print_walls(size)

Klíčové slovo pass v Pythonu mimojiné značí prázdné tělo funkce.

Hlavní část programu volající funkce pak bude vypadat takto:

# 1. nacist vstup
size = load_input()
# 2. zkontrolovat vstup
#    '-> je-li chybne, vypsat hlasku
#    '-> je-li validni, pokracovat 
is_size_valid = check_size(size)
# 3. vykreslit domecek
if is_size_valid:
    print_house(size)

Zaměřme se nyní na funkce tisknoucí domeček (print_roof(size) a print_walls(size)), ktereré mají pro vstup size=5 vytisknout:

  X  
 X X
a
XXXXX
X   X
X   X
x   X
XXXXX

Budeme-li domeček rovnou tisknout, pak musíme tisknout jeden znak za druhý, řádek po řádku. Zaměříme-li se na jednotlivé řádky, uvidíme, že se skládají z opakujících se znaků (velmi dobře viditelné v případě stěn) a že by se nám mohla hodit funkce, která vytiskne znak c několikrát za sebe, přidáme ji tedy do funkcí k implementaci:

def repeat_char_n_times(c, n):
    pass

S těmito funkcemi máme jasně dané konkrétní kroky, které musíme učinit, abychom byli schopni vykreslit domeček; každý krok je jasně vymezený a reltivně přímočarý, takže teď je na vás takto předkrájeného slona sníst a dokončit jednotlivé funkce.

Řešení

Úkoly na funkce

Úkol 1

Definujte dvě funkce sloužící k výpočtu a posouzení hodnoty BMI (body mass index).

  • První funkce přijme dva parametry - hmotnost člověka a jeho výšku - a vrátí hodnotu BMI takového člověka.
  • Druhá funkce přijme jako parametr hodnotu BMI. Pokud je BMI < 18.5, vypíše “Podvaha”, pro BMI v intervalu <18.5, 25> vypíše “Zdrava vaha” a pro BMI > 25 vypíše “Nadvaha” (zdroj).

Zkombinujte obě funkce tak, že uživatel je nejprve dotázán na hmotnost a výšku a následně je vypsána hodnota BMI a kategorie, do níž BMI spadá.

BMI = hmotnost[kg] / výška[m] 2

Řešení


Úkol 2

Vytvořte funkci swap(), které je předán jako parametr seznam čísel a dva číselné indexy. Funkce v seznamu prohodí čísla na zadaných pozicích.

Například:

numbers = [5, 7, 6, 8]
swap(numbers, 1, 2)
print(numbers)
vypíše: [5, 6, 7, 8].

Řešení


Úkol 3

Vytvořte funkci smallest_number(), které je předán jako parametr seznam čísel. Funkce v seznamu nalezne nejmenší číslo a vrátí jeho index (pozici). Předpokládejme, že nejmenší říslo se v seznamu nachází pouze jednou.

Například:

numbers = [7, 6, 5, 8]
i = smallest_number(numbers)
print(i)
vypíše: 2.

Řešení


Úkol 4

Použijte funkci smallest_number() z předchozího úkolu a vytvořte funkci sort_list(), které je předán jako parametr seznam čísel. Funkce čísla v seznamu seřadí od nejmenšího po největší.

Pokud nevíte, jak postupovat, napovíme, že se vám může hodit funkce pop(i).

Řešení

Rozsáhlejší úlohy

Úkol 1

Vytvořte knihovnu funkcí (modul) jednoduché pokladny.

V souboru pokladna.py definujte následující funkce:

  • Funkce read_input(), která od uživatele postupně načítá ceny jednotlivých položek (kladná čísla). Načítání uživatel ukončí zadáním čísla 0. Funkce pak vrátí načtené částky uložené v poli.
  • Funkce get_sum(array), která jako vstupní parametr přijme pole s čísly (načtenými částkami) a vrátí jejich součet.
  • Funkce get_avg(array), která spočítá průměrnou cenu na jednu položku z částek uložených v poli.
  • Funkce print_receipt(array), která vypíše jednotlivé položky útraty a následně informace o útratě uložené v poli, které umíme pomocí výše zadaných funkcí zjistit.
  • Funkce serve_custormer(), která nejprve načte všechny vstupy a pak vypíše účtenku

Abychom si vyzkoušeli vyzkoušeli pracovat i s funkcemi vlastního modulu, vytvořte ve stejné složce, kde je uložený soubor pokladna.py, jiný soubor (například main.py).

Import modulu můžeme provést několika způsoby:

První (a preferovaná možnost) je importovat modul jako celek se všemi funkcemi:

import pokladna
 
bill = pokladna.read_input()

Jak vidíte, pokud chcete zavolat nějakou funkci z modulu, použitjeme příkazem ve tvaru nazev_modulu.nazev_funkce(parametry).

Druhá možnost je importovat z modulu přímo některé konkrétní funkci, ne celý modul. Kupříkladu pouze funkci read_input():

from pokladna import read_input
 
bill = read_input()

V tomto případě při volání funkce neuvádíme název modulu, v němž je funkce definována, což může v případě, že importujeme více modulů s funkcemi stejného jména, vést k nepříjemným situacím.

Další možnost umožňuje importovat všechny funkce v souboru pokladna.py pomocí *, což znamená všechny funkce:

from pokladna import *
bill = read_input

Při tvorbě větších programů je běžné vytvářet si pro určité typy úkonů knihovny funkcí a pak si tyto funkce jinde v projektu pouze volat.
Díky tomuto konceptu se mohou projekty dělit do menších souborů a udržovat přehledné.
Zároveň nám většina programovacích jazyků nabízí velké množství vlastních knihoven s funkcemi, jejichž využíváním si mnohdy můžeme výrazně ušetřit práci (v Pythonu jsme se zatím setkali s knihovnou math a random).

Řešení (včetně rozšíření)


Rozšiřte knihovnu pokladna.py o následující funkce:

  • Funkce get_max(array), která v poli najde nejvyšší částku.
  • Funkce get_min(array), která v poli najde nejmenší částku.
  • Funkce array_contains(array, number) která vrátí True nebo False podle toho, jestli pole obsahuje nějakou konkrétní částku (číslo).

Nezapomeňte příslušně opravit i funkci print_receipt(array), aby brala v potaz nové funkce.

Řešení


Úkol 2

Napište program, který se uživatele dotáže na délku vektorů, následně náhohodně vygeneruje dva vektory danné délky a spočítá jejich skalární součin, oba vygenerované vektory i výsledek následně vypíše.

Skalární součin (Wikipedia) je součet součinů jednotlivých prvků ve vektoru:

$\vec{a} \cdot \vec{b} = \left[ a_1, a_2, \dots, a_N \right] \cdot \left[ b_1, b_2, \dots, b_N \right] = a_1 \cdot b_1 + a_2 \cdot b_2 + \dots + \ a_N \cdot b_N $

tedy například:

$\left[1, 2, 4 \right] \cdot \left[ 3, 2, -1 \right] = 1 \cdot 3 + 2 \cdot 2 + 4 \cdot (-1) = 3 + 4 - 4 = 3$

Pro větší přehlednost vypistujte čísla naformátovaná na pevný počet desetinných míst, kupříkladu pro 2 desetinná místa:

printf("Cislo x na dve desetinna mista: {:.2f}".format(x))

Pokud navíc chcete, fixní celkovou délku čísla x, například na 5 znaků (2 za desetinnou čárkou, 1 jako desetinná tečka, a zbývajících 5-(2+1)=2 před desetinnou čárkou), můžete použít:

printf("{:5.2f}".format(x))

Další informace o formátování výpisu v dokumentaci vstupu/výstupu (en) anebo dokumentaci k formátování (en) .

Například pro N=2 může program vypsat:

[ -3.51  9.04 ] . [  8.90  2.29 ] = -10.53

Řešení


Upravte program tak, aby namísto generování dvou náhodných vektorů umožnil uživateli postupně načíst oba vektory (a případně ošetřil jejich různou délku).

Řešení


Úkol 3

Napište program, který se uživatele dotáže na velikost čtvercových matic, následně náhohodně vygeneruje dvě matice příslušné velikosti a spočítá jejich maticový součet, obě vygenerované matice i výsledek následně vypíše.

Matice je neformálně řečeno dvourozměrný vektor. Čtvercová matice je taková matice, která má oba rozměry stejně velké. Maticový součet pro čtvercové matice $A$ a $B$ je:

\begin{equation*} A + B = \begin{bmatrix} a_{1,1} & a_{1,2} \\ a_{2,1} & a_{2,2} \end{bmatrix} + \begin{bmatrix} b_{1,1} & b_{1,2} \\ b_{2,1} & b_{2,2} \end{bmatrix} = \begin{bmatrix} a_{1,1}+b_{1,1} & a_{1,2}+b_{1,2} \\ a_{2,1}+b_{2,1} & a_{2,2}+b_{2,2} \end{bmatrix} \end{equation*}

Pro větší přehlednost vypistujte čísla naformátovaná na pevný počet desetinných míst (například 2).

Například pro velikost matic N=2 může program vypsat:

  -2.80   7.42
   9.10   6.66
+
   1.08  -4.47
   6.29  -5.22
=
  -1.71   2.95
  15.40   1.44

Řešení


Upravte program tak aby kromě maticového sčítání provedl i maticové odčítání.

Maticový rozdíl pro čtvercové matice $A$ a $B$ je:

\begin{equation*} A - B = \begin{bmatrix} a_{1,1} & a_{1,2} \\ a_{2,1} & a_{2,2} \end{bmatrix} - \begin{bmatrix} b_{1,1} & b_{1,2} \\ b_{2,1} & b_{2,2} \end{bmatrix} = \begin{bmatrix} a_{1,1}-b_{1,1} & a_{1,2}-b_{1,2} \\ a_{2,1}-b_{2,1} & a_{2,2}-b_{2,2} \end{bmatrix} \end{equation*}

Například pro velikost matic N=2 může program vypsat:

   4.73  -8.12
  -2.89   9.45
-
  -6.29  -7.93
   4.86   4.04
=
  11.02  -0.19
  -7.75   5.41

Řešení


Upravte program tak aby kromě maticového sčítání a odčítání provedl i násobení prvků matice číslem.

Násobení matice číslem pro čtvercovou matici $A$ a číslo $k$ je:

\begin{equation*} k \cdot A = k \cdot \begin{bmatrix} a_{1,1} & a_{1,2} \\ a_{2,1} & a_{2,2} \end{bmatrix} = \begin{bmatrix} k \cdot a_{1,1} & k \cdot a_{1,2} \\ k \cdot a_{2,1} & k \cdot a_{2,2} \end{bmatrix} \end{equation*}

Například pro velikost matic N=2 může program vypsat:

   0.45
*
   9.18  -7.09
   5.33   5.66
=
   4.11  -3.18
   2.39   2.54

Řešení


Spojte předchozí operace a vytvořte maticovou kalkulačku, která se nejprve uživatele dotáže na velikost matice a následně na operaci (+/-/*).

Příklad průběhu programu:

Zadej velikost matice: 2
Zadejte operaci (+/-/*): *
   0.63
*
   2.82  -1.19
   0.48   2.90
=
   1.77  -0.75
   0.30   1.82

Řešení


Upravte program tak, aby uživatel mohl po specifikaci velikosti a operace jednotlivé matice přímo zadat.

Řešení


Úkol 4

Dobře zmámá funkce sort() seřadí prvky seznamu vzestupně podle velikosti. Napište funkci selection_sort(), která pomocí algoritmu Selection sort (viz výklad na cvičení) seznam seřadí danný seznam. Vaše funkce nemůže používat funkcisort(), ale můžete ji použít ke kontrole správnosti vašeho řešení.

Rychlejší z vás si mohou Selection sort nastudovat sami – například zde

Řešení


Úkol 5

Vytvořte program, který který od uživatele zjistí velikost N a následně vypíše do konzole Pascalův trojúhelník (Různé algoritmy výpočtu najdete na Wikipedii).

Například pro N=5 program vypíše:

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1

Upravte program tak, aby výstup tvořil obvyklou pyramidu, tedy například pro N=7, program vypíše:

       1
      1 1
     1 2 1
    1 3 3 1
   1 4 6 4 1
 1 5 10 10 5 1
1 6 15 20 15 6 1

Řešení


Teorie rekurze

“To iterate is human, to recurse divine.”

(L. Peter Deutsch)´

Nejjednodušší případ rekurzivní funkce, je taková funkce, která musí k vypočítání výsledku zavolat sebe sama. Například:

def odecitej(x):
    return odecitej(x-1)

Pokud vám stále ještě není jasné co je to rekurze, přečtěte si tuto větu ještě jednou… ;-)

Funkce odecitej má ale jednu malou vadu. Nikdy nevrátí výsledek. Proto je potřeba mít v každé rekurzivní funkci takzvaný “base case”, který ukončí rekurzivní volání.

def odecitej_do_nuly(x):
    if x == 0:     # base case
        return 0   # konec rekurze
    return odecitej_do_nuly(x-1)

Funkce odecitej_do_nuly má stále ještě jeden problém. Odhalíte jaký?


Úkoly na rekurzi

Při řešení následujících úkolů nesmíte použít klíčová slova for ani while!

Úkol 1

Vytvořte pole obsahujicí deset náhodných čísel pomocí rekurze.

Řešení


Úkol 2

Naimplementujte funkci faktorial(x), která vypočítá x! pomocí rekurze.

Řešení


Úkol 3

Napiště funkci fibonacci(x), která vrátí x-tý prvek fibonacciho posloupnosti.

Řešení


Úkol 4

Vytvořte matici (2D pole) 5×5.

matice = [          # matice 2x2
    [1,2],
    [3,4],
    ]
matice[1][1]        # vrati 4
matice[0][1]        # vrati 2

Vyplňte tuto matici čísly od 24 do 0 funkcí jméno_funkce(matice), a poté vypište matici na standartní výstup.

[[24, 23, 22, 21, 20], [19, 18, 17, 16, 15], [14, 13, 12, 11, 10], [9, 8, 7, 6, 5], [4, 3, 2, 1, 0]]

Řešení


Úkol 5 (Náročnější!)

Máte šachové pole 9×9 a figurku jezdce na pozici (4,4). Označte všechny políčka šachovnice, na které jezdec může doskákat do tří tahů, hodnotou 1 a ostatní políčka hodnotou nula. Vypiště šachovnici na standartní výstup.

Jezdec nemusí využít všechny své tahy.
K vypsání můžete použít for cyklus.

Výstup programu:

001010100
010101010
100111001
011101110
101010101
011101110
100111001
010101010
001010100

Stav sachovnice po jednotlivých tazích (namísto 0 a 1 jsou zde kvůli názornosti použité mezery a znak 'X'):

# prvni tah    # druhy tah    # treti tah
|         |    |         |    |  X X X  |    
|         |    |         |    | X X X X |    
|         |    |   X X   |    |X  XXX  X|    
|         |    |  X   X  |    | XXX XXX |    
|    X    | -> |    X    | -> |X X X X X|    
|         |    |  X   X  |    | XXX XXX |    
|         |    |   X X   |    |X  XXX  X|    
|         |    |         |    | X X X X |    
|         |    |         |    |  X X X  |

Řešení


Úkol 6 (oddechový)

Choose you favourite Computer Quote 8-)


courses/pri-bootcamp/04.txt · Last modified: 2023/09/11 11:54 by riskoana