V>tvení, aneb nechť padne rozhodnutí

O čem si budeme povídat?

Třetím z našich základních stavebních kamenů je v>tvení nebo také podmín>ný příkaz. Jde jednoduše o pojmy, které popisují schopnost provést jednu z n>kolika možných posloupností příkazů (jednu z v>tví) a to v závislosti na n>jaké podmínce.

Dříve, v dob> programování v assembleru, bylo v>tvení realizováno nejjednodušším možným způsobem — použitím instrukce JUMP. Program v tomto míst> doslova skočil na určenou adresu v pam>ti. Obvykle to bylo podmín>no tím, že výsledkem předchozí instrukce byla nula. I když nebylo možné použít jiný způsob realizace podmín>ného příkazu, byly takto napsány úžasn> složité programy. To potvrzovalo správnost Dijkstrových tvrzení o minimálních požadavcích potřebných pro programování. Když se objevily vyšší programovací jazyky, objevila se i nová podoba instrukce JUMP pod názvem GOTO. V jazyce QBASIC, který byl dodáván na instalačních CD ROM starších verzí Windows (před XP), lze GOTO stále používat. Pokud máte QBASIC nainstalován, můžete si vyzkoušet následující úsek kódu:

10 PRINT "Začínáme na řádku 10"
20 J = 5
30 IF J < 10 GOTO 50
40 Print "Tento řádek se nevytiskne"
50 STOP

Povšimn>te si, že dokonce i u tak krátkého programu trvá n>kolik sekund, než přijdete na to co se stane. Kód nemá žádnou strukturu. Musíte si ji b>hem čtení doslova vytvořit. U velkých programů to začne být prakticky nemožné. Z tohoto důvodu v>tšina moderních programovacích jazyků — včetn> jazyků Python, VBScript a JavaScript — buď příkazy skoku JUMP nebo GOTO nemají, nebo vás od jejich používání odrazují. Takže co bychom vlastn> místo nich m>li použít?

Příkaz if

Intuitivn> nejzřejm>jší podobou podmín>ného příkazu je konstrukce if, then, else. Sleduje logiku anglické v>ty v tom smyslu, že if (jestliže) je n>jaká boolovská podmínka spln>na (o boolovských podmínkách se zmíníme dále v textu), then (pak) se provede blok příkazů, v opačném případ> (nebo else (jinak)) se provede jiný blok.

Python

V jazyce Python vypadá zápis takto:

import sys  # jen proto, abychom mohli program ukončit
print "Začínáme zde"
j = 5
if j > 10:
    print "Toto se nikdy nevytiskne"
else:
    sys.exit()

Takový zápis se ve srovnání s předchozím příkazem s GOTO lépe čte a je srozumiteln>jší. Za slovo if můžeme samozřejm> dosadit libovolnou podmínku testu za předpokladu, že ji lze vyhodnotit jako True nebo False — to znamená jako boolovskou hodnotu. Zkuste zm>nit operátor > na < a pozorujte, co se stane.

VBScript

Zápis v jazyce VBScript vypadá podobn>:

<script type="text/vbscript">
MsgBox "Začínáme zde"
Dim J
J = 5
If J > 10 Then
    MsgBox "Toto se nikdy nevytiskne."
Else
    MsgBox "Konec programu."
End If
</script>

Vždyť je to tém>ř shodné, že ano? Hlavní rozdíl spočívá v použití End If pro označení konce konstrukce.

A ješt> v JavaScript

V jazyce JavaScript nalezneme samozřejm> příkaz if také:

<script type="text/javascript">
var j;
j = 5;
if (j > 10){
           document.write("Toto se nikdy nevytiskne.");
           }
else {
     document.write("Konec programu.");
     }
</script>

Povimn>te si, že JavaScript používá uvnitř částí if a else pro vymezení bloku kódu složené závorky. Boolovský test je rovn>ž uzavřen v závorkách. Klíčové slovo then se zde nepoužívá. Co se týká stylu, složené závorky můžeme umístit na libovolnou pozici. Rozhodl jsem se, že je zarovnám pod sebe prost> proto, abych zdůraznil strukturu bloku. Pokud se v bloku nachází jen jediný řádek (jako v našem případ>), můžeme závorky úpln> vynechat. Potřebujeme je jen v případech, kdy mají ohraničit skupinu řádků, které patří do jednoho bloku.

Boolovské výrazy

Možná si ješt> vzpomínáte, že jsme se v kapitole o datech zmínili o datovém typu boolean. Řekli jsme si, že má pouze dv> hodnoty: True a False. Boolovské prom>nné vytváříme velmi zřídka[1], ale dočasné boolovské hodnoty často vznikají jako výsledek vyhodnocení výrazů. Výrazem rozumíme kombinaci prom>nných a hodnot, spojených operátory s cílem vyprodukovat výslednou hodnotu. V následujícím příkladu

if x < 5:
  print x

je zápis x < 5 výrazem. Pokud je x menší než 5, bude jeho výsledkem hodnota True. Pokud je x v>tší nebo rovno 5, bude výsledkem výrazu hodnota False.

Výrazy mohou být libovoln> složité s tím, že výsledkem jejich vyhodnocení musí být nakonec vždy jediná hodnota. V případ> v>tvení musí být výsledkem pravdivostní hodnota True nebo False. Nicmén>, definice t>chto dvou pravdivostních hodnot se jazyk od jazyka liší. V mnoha jazycích je hodnota false[2] ztotožn>na s hodnotou 0 nebo s hodnotou vyjadřující neexistenci (té se často říká NULL, Nil nebo None). Takže v boolovském kontextu bude například prázdný seznam nebo prázdný řet>zec vyhodnocen jako False. Takovým způsobem se chová i Python. To znamená, že například můžeme využít cyklu while pro zpracování seznamu, které má skončit v okamžiku, kdy je seznam prázdný:

while seznam:
    # Proveď n>jakou operaci, která vede ke zkrácení seznamu.

V příkazu if můžeme tento obrat použít k testování prázdnosti seznamu, aniž bychom použili funkci len():

if seznam:
    # n>co zde ud>lej (seznam je prázdný)

Boolovské výrazy můžeme kombinovat pomocí boolovských operátorů. Často tím můžeme zmenšit počet příkazů if. Uvažujme následující příklad:

if hodnota > maximum:
    print "Hodnota je mimo rozsah!"
else:
    if hodnota < minimum:
        print "Hodnota je mimo rozsah!"

Povšimn>te si, že blok provád>ného kódu je v obou případech shodný. Zkombinováním obou testů do jednoho dosáhneme úspory práce jak pro počítač, tak pro nás:

if (hodnota > maximum) or (hodnota < minimum):
   print "Hodnota je mimo rozsah!"

Oba testy jsme spojili operátorem or (nebo, čili logickým součtem). Dostáváme jediný výraz. Python nejdříve vyhodnotí výraz uzavřený v první dvojici závorek, potom výraz v druhých závorkách a nakonec vypočtené hodnoty zpracuje do podoby jediné hodnoty — True nebo False.

Poznámka překladatele: Výše uvedený odstavec chápejte spíše z obecného pohledu. Python ve skutečnosti zpracovává boolovský výraz zleva doprava a skončí v okamžiku, kdy už následující části výrazu nemohou ovlivnit výsledek. Říká se tomu zkrácené vyhodnocování výrazu. Pokud v uvedeném příkladu získáváme vyhodnocením první závorky hodnotu True, pak při použití operátoru or nemá výsledek vyhodnocování druhé závorky na celkový výsledek vliv. Proto se vůbec neprovádí.

Zkráceného vyhodnocování boolovských výrazů se v>tšinou spíš výhodn> využívá. Mohou však nastat případy, kdy díky tomuto jevu vzniká obtížn>ji odhalitelná chyba. Pokud bychom v míst> druhé části výrazu volali funkci, která vrací boolovský výsledek, ale krom> toho má n>jaký vedlejší efekt (například n>co vypisuje), pak musíme myslet na to, že se také nemusí vůbec zavolat. Na dosažení zmín>ného vedlejšího efektu proto nemůžeme při vyhodnocování výrazu spoléhat.

Pokud o provád>ných testech uvažujeme v pojmech přirozeného jazyka, velmi často používáme spojky jako a (anglicky and), nebo (or), negace (ne, není, anglicky not). V takovém případ> je velmi pravd>podobné, že se nám místo více jednoduchých testů podaří zapsat jeden složený.

Poznámka překladatele: Pokud uvažujeme v českém jazyce, pak vám doporučuji, abyste si operátor and překládali jako a zároveň. Pouhé a může vést k chybám, kdy tuto spojku můžeme chápat ve významu nebo. Překlad a zároveň zdůrazní význam operátoru and a pomůže nám snadn>ji vytvořit mentální obraz situace.

Zřet>zení příkazů if

Příkazy if/then/else můžeme do sebe vnořovat. V jazyce Python to můžeme vyjádřit následovn>:

# Předpokládáme, že cena byla předem stanovena...
cena = int(raw_input(u"Kolik to stojí? "))
if cena == 100:
    print u"Vezmu si to." 
else:
    if cena > 500:
        print u"Tak to nechci ani náhodou!"
    else:
        if cena > 200:
            print u"Co kdybyste přihodil zdarma podložku pod myš?"
        else:
            print u"Neočekávaná cena."

Poznámka 1: V prvním příkazu if jsme pro test na rovnost použili operátor == (tj. zdvojený znak =). Jednoduchý znak = se používá pro přiřazování hodnot prom>nným. Při programování v Pythonu (a také v C a v C++) patří použití jednoduchého = v míst>, kde bychom cht>li použít == k nejčast>jším chybám. Python vás v takovém případ> našt>stí varuje, že jste se dopustili syntaktické chyby. N>kdy se ale musíte pořádn> podívat, než si všimnete, o co vlastn> jde.

Poznámka 2: Za povšimnutí stojí ješt> jeden detail. Testy v>tší než provádíme od nejv>tší hodnoty k nejmenší. Kdybychom postupovali obrácen> a začali bychom testem cena > 200, pak bychom se nikdy nedostali k testu cena > 500. Při používání po sob> jdoucích testů menší než musíme naopak začít testovat na nejmenší hodnotu a postupovat sm>rem k hodnotám vyšším. Jde o další past, do které se můžeme při troše nepozornosti snadno chytit.

VBScript & JavaScript

Příkazy if můžeme řet>zit i v jazycích VBScript a JavaScript. Postup je zcela zřejmý. Proto si to ukážeme jen na příkladu v jazyce VBScript:

<script type="text/vbscript">
DIM Cena
cena = InputBox("Kolik to stojí?")
cena = CInt(cena)
If cena = 100 Then
   MsgBox "Vezmu si to." 
Else:
    if cena > 500 Then
        MsgBox "Tak to nechci ani náhodou!"
    else:
        if cena > 200 Then
            MsgBox "Co kdybyste přihodil zdarma podložku pod myš?"
        else:
            MsgBox "Neočekávaná cena."
	End If
    End If
End If
</script>

Za zmínku zde stojí jedin> to, že ke každému příkazu if musíme uvést odpovídající příklaz End If. Poznamenejme ješt>, že pro převod řet>zcové hodnoty na celočíselnou jsme použili funkci CInt().

Příkazy typu Case

S používáním zanořených příkazů if/else souvisí jedna potíž. Postupné odsazování způsobí, že se zdrojový text rychle roztáhne přes celou šířku stránky. Posloupnost zanořených if/else/if/else… však patří k tak b>žným konstrukcím, že n>které jazyky poskytují speciální způsob v>tvení.

Zmín>né speciální konstrukce se často označují jako příkazy case nebo switch. V jazyce JavaScript vypadá příkaz switch následovn>:

<script type="text/javascript">
function vypoctiPlochu() {
   var tvar, sirka, delka, plocha;
   tvar   = document.plocha.tvar.value;
   sirka  = parseInt(document.plocha.sirka.value);
   delka  = parseInt(document.plocha.delka.value);
   switch (tvar) {
       case 'ctverec': 
           plocha = delka * delka;
           alert("Plocha tvaru " + tvar + " = " + plocha);
           break;
       case 'obdelnik': 
           plocha = delka * sirka;
           alert("Plocha tvaru " + tvar + " = " + plocha);
           break;
       case 'trojuhelnik':
           plocha = delka * sirka / 2;
           alert("Plocha tvaru " + tvar + " = " + plocha);
           break;
       default: alert("Neznámý tvar: " + tvar)
       };
    }
</script>

<form name="plocha">
Délka:  <input type="text" name="delka">
Šířka:  <input type="text" name="sirka">
Tvar:   <select name="tvar" size="1" onChange="vypoctiPlochu()">
           <option value="ctverec">čtverec
           <option value="obdelnik">obdélník
           <option value="trojuhelnik">trojúhelník
        </select>
</form>

Detaily jsou zachyceny v rámci HTML kódu formuláře. Jakmile si uživatel vybere tvar, zavolá se naše funkce. Na prvních řádcích se vytvářejí lokální prom>nné a podle potřeby se řet>zce převád>jí na čísla. Zajímá nás úsek, který je vyznačen tučn>. Podle vybraného tvaru se v n>m vybírá příslušná akce. Povšimn>te si kulatých závorek kolem identifikátoru tvar za klíčovým slovem switch. Jsou povinné — musí být uvedeny. Mohlí byste předpokládat, že bloky kódu uvnitř case by m>ly být uzavřeny do složených závorek, ale není tomu tak. Místo toho jsou ukončovány příkazem break. Nicmén> celá sada příkazů case, která odpovídá části switch, již je svázána do podoby bloku jedním párem složených závorek.

Povšimn>te si, že poslední podmínka v příkladu má podobu default. V této části se zachytí všechny případy, které se nezachytily v předchozích částech case.

Vyzkoušejte si, zda byste um>li výše uvedený příklad rozšířit tak, aby pracoval i s kruhem. Do HTML formuláře nezapomeňte přidat novou volbu a do příkazu switch přidejte další variantu case.

Příkaz Select Case v jazyce VBScript

Verzi příkazu pro výb>r jedné z n>kolika variant nalezneme i v jazyce VBScript:

<script type="text/vbscript">
Dim tvar, delka, sirka, CTVEREC, OBDELNIK, TROJUHELNIK
CTVEREC = 0
OBDELNIK = 1
TROJUHELNIK = 2
tvar  = CInt(InputBox("Čtverec(0), obdélník(1) nebo trojúhelník(2)?"))
delka = CDbl(InputBox("Délka?"))
sirka  = CDbl(InputBox("Šířka?"))
Select Case tvar 
  Case CTVEREC
    plocha = delka * delka
    MsgBox "Plocha = "  & plocha 
  Case OBDELNIK
    plocha = delka * sirka
    MsgBox "Plocha = " & plocha
  Case TROJUHELNIK
    plocha = delka * sirka / 2
    MsgBox "Plocha = " & plocha
  Case Else 
    MsgBox "Neznámý tvar"
End Select
</script>

Na n>kolika prvních řádcích se od uživatele získávají data a převád>jí se na správný typ — stejn>, jako tomu bylo u příkladu v jazyce JavaScript. Tučn> vyznačená část Select znázorňuje konstrukci typu case, jak se používá v jazyce VBScript. Zasebou uvedené příkazy Case vždy ukončují blok předchozího. Celou konstrukci Select uzavírá příkaz End Select. Nalezneme zde také část Case Else, ve které se (jako v části default jazyka JavaScript) zachytí vše, co nebylo zpracováno dříve uvedenými částmi Case.

Za zmínku stojí ješt> použití symbolických konstant místo čísel. Prom>nné zapsané velkými písmeny CTVEREC, OBDELNIK a TROJUHELNIK jsou zde jen kvůli tomu, aby se zdrojový text snadn>ji četl. Použití prom>nných zapsaných velkými písmeny je předepsáno pouze konvencí. Dáváme tím najevo, že bychom je nem>li chápat jako b>žné prom>nné, ale jako prom>nné udržující konstantní hodnoty. Jazyk VBScript vám ale dovolí pojmenovat si prom>nné podle své libosti.

case v jazyce Python

Python explicitní konstrukci typu case nepodporuje. Místo toho nabízí kompromis v podob> if/elif/else:

menu = """
Vyberte si tvar (1-3):
   1) Ctverec
   2) Obdelnik
   3) Trojuhelnik
"""   
tvar = int(raw_input(menu))
if tvar == 1:
    strana = float(raw_input("Strana: "))
    print "Plocha ctverce = ", strana ** 2
elif tvar == 2:
    delka = float(raw_input("Delka: "))
    sirka = float(raw_input("Sirka: "))
    print "Plocha obdelniku = ", delka * sirka   
elif tvar == 3:
    zakladna = float(raw_input("Zakladna: "))
    vyska =    float(raw_input("   Vyska: "))
    print "Plocha trojuhelniku = ", zakladna * vyska / 2   
else: 
    print "Neplatny tvar. Zkute to znovu"

Poznámka překladatele: Abychom se zatím vyhnuli problémům s českými znaky, použili jsme texty bez diakritických znamének. Způsob řešení, kdy používáme i české znaky s diakritikou, můžete nalézt v dalších kapitolách.

Povšimn>te si použití elif a skutečnosti, že se (v porovnání s příkladem se zanořenými if) nem>ní odsazení, které je v Pythonu tak důležité. Za zmínku stojí i to, že oba zápisy — jak poslední zápis, tak dříve uvedený zápis využívající vnořených konstrukcí if/else — jsou funkčn> shodné. Zápis využívající elif zvyšuje čitelnost v případech, kdy použijeme v>tší množství testů. V koncové v>tvi else se zachytí všechny případy, které nebyly zachyceny v předchozích testech. Odpovídá to použití default v JavaScript nebo Case Else v jazyce VBScript.

O n>co t>žkopádn>jší podobu stejné konstrukce naleznete i v jazyce VBScript. Konstrukce ElseIf...Then se používá naprosto stejným způsobem, jako elif v jazyce Python. Ale setkáte se s ní zřídka, protože použití alternativního příkazu Select Case je jednodušší.

Teď to dáme všechno dohromady

Až dosud byly mnohé z našich příkladů velmi abstraktní. Na záv>r se podívejme na příklad, který používá tém>ř vše, co jsme se zatím naučili. Uvedeme si b>žnou programovací techniku, konkrétn> zobrazení menu pro řízení uživatelského vstupu.

Zde máme kód, za kterým následuje krátká diskuse.

menu = """
Vyberte si tvar (1-3):
   1) Ctverec
   2) Obdelnik
   3) Trojuhelnik
   
   4) Konec
"""   
tvar = int(raw_input(menu))
while tvar != 4:
    if tvar == 1:
        strana = float(raw_input("Strana: "))
        print "Plocha ctverce = ", strana ** 2
    elif tvar == 2:
        delka = float(raw_input("Delka: "))
        sirka = float(raw_input("Sirka: "))
        print "Plocha obdelniku = ", delka * sirka   
    elif tvar == 3:
        zakladna = float(raw_input("Zakladna: "))
        vyska =    float(raw_input("   Vyska: "))
        print "Plocha trojuhelniku = ", zakladna * vyska / 2   
    else: 
        print "Neplatny tvar. Zkute to znovu"    
    tvar = int(raw_input(menu))

K předchozímu příkladu jsme přidali pouhé tři řádky (označeny tučn>), ale tato jednoduchá úprava výrazn> zvýšila použitelnost našeho programu. Dopln>ním volby Konec a přidáním cyklu jsme uživateli umožnili pokračovat ve výpočtech ploch různých tvarů až do doby, kdy získá všechny potřebné informace. Program již nemusí pokaždé ručn> znovu a znovu spoušt>t. Krom> již zmín>ných řádků jsme přidali pouze jeden řádek s raw_input(menu), který slouží k opakovanému výb>ru tvaru. To znamená že uživatel může volit různé tvary a na záv>r také činnost programu ukončit.

Program tedy uživateli vytváří iluzi, že ví, co uživatel potřebuje. Na základ> jeho volby se chová různým způsobem a správn> provede odpovídající činnost. Uživateli se v podstat> zdá, že postup řídí, zatímco ve skutečnosti má řízení v rukou programátor, který předvídal, jak mají vypadat všechny platné vstupy a jak má na n> program reagovat. Projevovaná inteligence tedy patří programátorovi, nikoliv stroji. Počítače jsou ve své podstat> hloupé!

Povšimn>te si jak snadno můžeme svůj program zdokonalit přidání pouhých pár řádků a zkombinováním posloupností (bloků pro výpočet plochy), cyklů (zde cyklus while) a podmín>ných příkazů (konstrukce if/elif/else). Jde o tři z Dijkstrových základních programátorských stavebních kamenů. Pokud zvládnete všechny tři, můžete teoreticky naprogramovat cokoliv. Ale můžeme se naučit ješt> n>kolik technik, které nám programování dále usnadní. Takže zatím m>jte ješt> trochu strpení.

Zm>ny v kolekci b>hem provád>ní cyklu

Když jsme se bavili o cyklech, zmínili jsme se o tom, že úprava kolekce b>hem průchodu cyklem, konkrétné rušení prvků v procházené kolekci, nemusí být zcela jednoduché. Ale nevysv>tlili jsme proč! Důvodem pro tento odklad byla skutečnost, že jsme museli nejdříve vysv>tlit pojem v>tvení. Vraťme se tedy k řešení problému.

Pokud potřebujeme m>nit obsah kolekce b>hem jejího zpracování (bez kopírování do jiné kolekce), můžeme k tomu využít vlastností cyklu while. Při použití konstrukce while totiž přímo pracujeme s obsahem indexové prom>nné. Srovnejte to se situací, kdy se použije cyklus for, který indexovou prom>nnou upravuje automaticky. Podívejme se, jak můžeme ze seznamu vypustit všechny prvky s nulovou hodnotou:

seznam = [1, 2, 3, 0, 4, 5, 0]
index = 0
while index < len(seznam):
    if seznam[index] == 0:
        del seznam[index]
    else: 
        index += 1
print seznam

Povšimn>te si, že v případ> odstraňování prvku neprovádíme zvyšování indexu. Spoléháme na to, že se při smazání položky vše posune, takže původní hodnota indexu bude poté ukazovat na další prvek kolekce. Zvyšování indexu se tedy provádí jen v jedné v>tvi konstrukce if/else. Při podobných obratech se můžeme velice snadno dopustit chyby, proto vždy funkčnost pečliv> otestujte.

V Pythonu můžeme používat jistou sadu funkcí, které byly přímo navrženy pro manipulaci s obsahy senamů. Seznámíme se s nimi v rámci tématu Funkcionální programování, tedy v části pro pokročilé.

Zapamatujte si

Pokud vás napadne, co by se dalo na překladu této kapitoly vylepšit, zašlete e-mail odklepnutím Tím budou do dopisu automaticky vloženy informace o tomto HTML dokumentu.

$Id: cztutbranch.html,v 1.12 2005/10/20 20:52:03 petr Exp $