====== Den 4 ====== ===== Zpětná vazba ===== Moc nám pomůže, když vyplníte [[https://docs.google.com/forms/d/1ff1UjeZz6u0Rqdn1eryE5LTICIeYiuSNjY1Q4sn5Ip4 | 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ě [[https://cw.fel.cvut.cz/wiki/courses/pri-bootcamp/03#n-tice_-_teorie|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. /* V Pythonu můžete znak (textový řetězec) ''c'' vytisknout ''n''-krát pomocí ''print(n*str(c))''. */ [[courses:pri-bootcamp:solutions:day4#ukol_0|Ř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" ([[https://cs.wikipedia.org/wiki/Index_t%C4%9Blesn%C3%A9_hmotnosti|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 [[courses:pri-bootcamp:solutions:day4#ukol_1|Ř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]''. [[courses:pri-bootcamp:solutions:day4#ukol_2|Ř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''. [[courses:pri-bootcamp:solutions:day4#ukol_3|Ř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)''. [[courses:pri-bootcamp:solutions:day4#ukol_4|Ř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//). [[courses:pri-bootcamp:solutions:day4#ukol_11|Řešení (včetně rozšíření)]] \\ == Návrh na rozšíření 1.1 == 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. [[courses:pri-bootcamp:solutions:day4#ukol_11|Ř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 [[https://cs.wikipedia.org/wiki/Skal%C3%A1rn%C3%AD_sou%C4%8Din|(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 [[https://docs.python.org/3/tutorial/inputoutput.html|dokumentaci vstupu/výstupu (en)]] anebo [[https://docs.python.org/3/library/string.html#format-string-syntax | 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 [[courses:pri-bootcamp:solutions:day4#ukol_21|Řešení]] \\ == Návrh na rozšíření 2.1 == 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). [[courses:pri-bootcamp:solutions:day4#rozsireni_21|Ř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 [[courses:pri-bootcamp:solutions:day4#ukol_31|Řešení]] \\ == Návrh na rozšíření 3.1 == 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 [[courses:pri-bootcamp:solutions:day4#rozsireni_31|Řešení]] \\ == Návrh na rozšíření 3.2 == 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 [[courses:pri-bootcamp:solutions:day4#rozsireni_32|Řešení]] \\ == Návrh na rozšíření 3.3 == 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 [[courses:pri-bootcamp:solutions:day4#rozsireni_33|Řešení]] \\ == Návrh na rozšíření 3.4 == Upravte program tak, aby uživatel mohl po specifikaci velikosti a operace jednotlivé matice přímo zadat. [[courses:pri-bootcamp:solutions:day4#rozsireni_34|Ř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 funkci''sort()'', 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 [[https://cs.wikipedia.org/wiki/%C5%98azen%C3%AD_v%C3%BDb%C4%9Brem|zde]] [[courses:pri-bootcamp:solutions:day4#ukol_41|Ř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 [[https://en.wikipedia.org/wiki/Pascal's_triangle|Wikipedii]]). Například pro ''N=5'' program vypíše: 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 == Návrh na rozšíření 5.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 [[courses:pri-bootcamp:solutions:day4#ukol_5|Ř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. [[courses:pri-bootcamp:solutions:day4#ukol_12|Řešení]] ---- ==== Úkol 2 ==== Naimplementujte funkci **faktorial(x)**, která vypočítá **x!** pomocí rekurze. [[courses:pri-bootcamp:solutions:day4#ukol_22|Řešení]] ---- ==== Úkol 3 ==== Napiště funkci **fibonacci(x)**, která vrátí x-tý prvek fibonacciho posloupnosti. [[courses:pri-bootcamp:solutions:day4#ukol_32|Řešení]] ---- ==== Úkol 4 ==== Vytvořte matici (2D pole) 5x5. 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]] [[courses:pri-bootcamp:solutions:day4#ukol_42|Řešení]] ---- ==== Úkol 5 (Náročnější!) ==== Máte šachové pole 9x9 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 | [[courses:pri-bootcamp:solutions:day4#ukol_51|Řešení]] ---- ==== Úkol 6 (oddechový) ==== Choose you favourite [[http://www.devtopics.com/101-great-computer-programming-quotes/|Computer Quote]] 8-) ----