4. Podmienky


Podmienený príkaz

Pri programovaní často riešime situácie, keď sa má program na základe nejakej podmienky rozhodnúť medzi viacerými možnosťami. Napríklad, program má vypísať, či zadaný počet bodov stačí na známku z predmetu. Preto si najprv vyžiada číslo - získaný počet bodov, potom porovná túto hodnotu s požadovanou hranicou, napríklad 100 bodov a na základe toho vypíše, buď že je to dosť na známku, alebo nie je:

body = int(input('Zadaj získaný počet bodov: '))
if body >= 110:
    print(body, 'bodov je dostačujúci počet na známku')
else:
    print(body, 'bodov je málo na získanie známky')

Použili sme tu podmienený príkaz (príkaz vetvenia) if. Jeho zápis vyzerá takto:

if podmienka:    # ak podmienka platí, vykonaj 1. skupinu príkazov
    prikaz
    prikaz
    ...
else:            # ak podmienka neplatí, vykonaj 2. skupinu príkazov
    prikaz
    prikaz
    ...

V našom prvom príklade je v oboch skupinách príkazov len po jednom príkaze print(). Odsadenie skupiny príkazov (blok príkazov) má rovnaký význam ako vo for-cykle: budeme ich odsadzovať vždy presne o 4 medzery.

V pravidlách predmetu programovanie máme takéto kritériá na získanie známky:

  • známka A aspoň 150 bodov

  • známka B aspoň 140 bodov

  • známka C aspoň 130 bodov

  • známka D aspoň 120 bodov

  • známka E aspoň 110 bodov

  • známka Fx menej ako 110 bodov

Podmienka pre získanie známky A:

if body >= 150:
    print('za', body, 'bodov získavaš známku A')
else:
    ...

Ak je bodov menej ako 150, už to môže byť len horšia známka: dopíšeme testovanie aj známky B:

if body >= 150:
    print('za', body, 'bodov získavaš známku A')
else:
    if body >= 140:
        print('za', body, 'bodov získavaš známku B')
    else:
        ...

Všetky riadky v druhej skupine príkazov (za else) musia byť odsadené o 4 medzery, preto napríklad print(), ktorý vypisuje správu o známke B je odsunutý o 8 medzier. Podobným spôsobom zapíšeme všetky zvyšné podmienky:

body = int(input('Zadaj získaný počet bodov: '))
if body >= 150:
    print('za', body, 'bodov získavaš známku A')
else:
    if body >= 140:
        print('za', body, 'bodov získavaš známku B')
    else:
        if body >= 130:
            print('za', body, 'bodov získavaš známku C')
        else:
            if body >= 120:
                print('za', body, 'bodov získavaš známku D')
            else:
                if body >= 110:
                    print('za', body, 'bodov získavaš známku E')
                else:
                    print('za', body, 'bodov si nevyhovel a máš známku Fx')

Takéto odsadzovanie príkazov je v Pythone veľmi dôležité a musíme byť pritom veľmi presní. Príkaz if, ktorý sa nachádza vo vnútri niektorej vetvy iného if, sa nazýva vnorený príkaz if.

V Pythone existuje konštrukcia, ktorá uľahčuje takúto vnorenú sériu if-ov:

if podmienka_1:     # ak podmienka_1 platí, vykonaj 1. skupinu príkazov
    prikaz
    ...
elif podmienka_2:   # ak podmienka_1 neplatí, ale platí podmienka_2, ...
    prikaz
    ...
elif podmienka_3:   # ak ani podmienka_1 ani podmienka_2 neplatia, ale platí podmienka_3, ...
    prikaz
    ...
else:               # ak žiadna z podmienok neplatí, ...
    prikaz
    ...

Predchádzajúci program môžeme zapísať aj takto:

body = int(input('Zadaj získaný počet bodov: '))
if body >= 150:
    print('za', body, 'bodov získavaš známku A')
elif body >= 140:
    print('za', body, 'bodov získavaš známku B')
elif body >= 130:
    print('za', body, 'bodov získavaš známku C')
elif body >= 120:
    print('za', body, 'bodov získavaš známku D')
elif body >= 110:
    print('za', body, 'bodov získavaš známku E')
else:
    print('za', body, 'bodov si nevyhovel a máš známku Fx')

Ukážme ešte jedno riešenie tejto úlohy - jednotlivé podmienky zapíšeme ako intervaly:

body = int(input('Zadaj získaný počet bodov: '))
if body >= 150:
    print('za', body, 'bodov získavaš známku A')
if 140 <= body < 150:
    print('za', body, 'bodov získavaš známku B')
if 130 <= body < 140:
    print('za', body, 'bodov získavaš známku C')
if 120 <= body < 130:
    print('za', body, 'bodov získavaš známku D')
if 110 <= body < 120:
    print('za', body, 'bodov získavaš známku E')
if body < 110:
    print('za', body, 'bodov si nevyhovel a máš známku Fx')

V tomto riešení využívame to, že else-vetva v príkaze if môže chýbať, a teda pri neplatnej podmienke sa nevykoná nič:

if podmienka:        # ak podmienka platí, vykonaj skupinu príkazov
    prikaz
    prikaz
    ...
                     # ak podmienka neplatí, nevykonaj nič

Mohli by sme to zapísať aj takto:

if podmienka:        # ak podmienka platí, vykonaj skupinu príkazov
    prikaz
    prikaz
    ...
else:
    pass             # ak podmienka neplatí, nevykonaj nič

kde pass je tzv. prázdny príkaz, t.j. príkaz, ktorý nerobí nič. Môžeme ho použiť všade, kde chceme zapísať tzv. prázdny blok príkazov. Hoci je tento zápis správny, nezvykne sa takto používať.

Zrejme každý príkaz if po kontrole podmienky (a prípadnom výpise správy) pokračuje na ďalšom príkaze, ktorý nasleduje za ním (a má rovnaké odsadenie ako if). Okrem toho vidíme, že teraz sú niektoré podmienky trochu zložitejšie, lebo testujeme, či sa hodnota nachádza v nejakom intervale. (podmienku 140 <= body < 150 sme mohli zapísať aj takto 150 > body >= 140)

V Pythone môžeme zapisovať podmienky podobne, ako je to bežné v matematike:

body < 150

je menšie ako

body <= 100

je menšie alebo rovné

body == 100

rovná sa

body != 127

nerovná sa

body > 100

je väčšie ako

body >= 110

je väčšie alebo rovné

100 < body <= 110

je väčšie ako … a zároveň menšie alebo rovné …

a < b < c

a je menšie ako b a zároveň je b menšie ako c

Ukážme použitie podmieneného príkazu aj v grafickom programe. Začneme s programom, ktorý na náhodné pozície nakreslí 100 rôzne veľkých štvorčekov. Aj veľkosť týchto štvorčekov bude náhodné číslo od 1 do 30. Štvorčeky bude zafarbovať podľa takéhoto pravidla:

  • ak je ich veľkosť menšia alebo rovná 10, budú červené

  • inak, ak ich veľkosť menšia alebo rovná 20, budú modré

  • inak nebudú zafarbené, t.j. ich farba výplne bude ''

Zrejme v poslednej skupine štvorcov budú len tie, ktoré sú väčšie ako 20. Zapíšme program:

import tkinter
import random

canvas = tkinter.Canvas()
canvas.pack()

for i in range(100):
    x = random.randint(1, 350)
    y = random.randint(1, 230)
    a = random.randint(1, 30)
    if a <= 10:
        farba = 'red'
    elif a <= 20:
        farba = 'blue'
    else:
        farba = ''
    canvas.create_rectangle(x, y, x + a, y + a, fill=farba)

tkinter.mainloop()

Aj v tomto programe využívame sériu if - elif - else, rovnako ako pri prepočítavaní bodov na známky v predchádzajúcom príklade. Dostávame

../_images/z04_01.png

Pokračujme s grafickým programom, ktorý na náhodné pozície nakreslí 1000 malých krúžkov:

import tkinter
import random

canvas = tkinter.Canvas(bg='white', width=300, height=300)
canvas.pack()

for i in range(1000):
    x = random.randint(1, 300)
    y = random.randint(1, 300)
    farba = 'blue'
    canvas.create_oval(x - 5, y - 5, x + 5, y + 5, fill=farba, width=0)

tkinter.mainloop()
../_images/z04_02.png

Všimnite si, že krúžky sú bez obrysu (doteraz mali kruhy čierny tenký obrys). Je to vďaka pomenovanému parametru width=0, ktorým určujeme hrúbku obrysu, teda teraz bude 0.

Niektoré z týchto krúžkov zafarbíme tak, že tie z nich, ktoré sú v ľavej polovici plochy budú červené a zvyšné v pravej polovici (teda else vetva) budú modré:

import tkinter
import random

canvas = tkinter.Canvas(bg='white', width=300, height=300)
canvas.pack()

for i in range(1000):
    x = random.randint(1, 300)
    y = random.randint(1, 300)
    if x < 150:
        farba = 'red'
    else:
        farba = 'blue'
    canvas.create_oval(x - 5, y - 5, x + 5, y + 5, fill=farba, width=0)

tkinter.mainloop()
../_images/z04_03.png

Skúsme pridať ešte jednu podmienku: všetky bodky v spodnej polovici (y > 150) budú zelené, takže rozdelenie na červené a modré bude len v hornej polovici. Jedno z možných riešení:

import tkinter
import random

canvas = tkinter.Canvas(bg='white', width=300, height=300)
canvas.pack()

for i in range(1000):
    x = random.randint(1, 300)
    y = random.randint(1, 300)
    if y < 150:
        if x < 150:
            farba = 'red'
        else:
            farba = 'blue'
    else:
        farba = 'green'
    canvas.create_oval(x - 5, y - 5, x + 5, y + 5, fill=farba, width=0)

tkinter.mainloop()
../_images/z04_04.png

Podobne, ako sme to robili s intervalmi bodov pre rôzne známky, môžeme aj toto riešenie zapísať tak, že použijeme komplexnejšiu podmienku:

import tkinter
import random

canvas = tkinter.Canvas(bg='white', width=300, height=300)
canvas.pack()

for i in range(1000):
    x = random.randint(1, 300)
    y = random.randint(1, 300)
    if y < 150 and x < 150:
        farba = 'red'
    elif y < 150 and x >= 150:
        farba = 'blue'
    else:
        farba = 'green'
    canvas.create_oval(x - 5, y - 5, x + 5, y + 5, fill=farba, width=0)

tkinter.mainloop()
../_images/z04_05.png

Podmienky v Pythone môžu obsahovať logické operácie a tieto majú obvyklý význam z matematiky:

  • podmienka1 and podmienka2 … (a súčasne) znamená, že musia platiť obe podmienky

  • podmienka1 or podmienka2 … (alebo) znamená, že musí platiť aspoň jedna z podmienok

  • not podmienka … (neplatí) znamená, že daná podmienka neplatí

Otestovať rôzne kombinácie podmienok môžeme, napríklad takto:

>>> a = 10
>>> b = 7
>>> a < b
    False
>>> a >= b + 3
    True
>>> b < a < 2 * b
    True
>>> a != 7 and b == a - 3
    True
>>> a == 7 or b == 10
    False
>>> not a == b          # to isté ako  a != b
    True
>>> 1 == '1'
    False
>>> 1 < '2'             # nemôžeme takto porovnaváť čísla a znaky
    ...
    TypeError: unorderable types: int() < str()

Všimnite si, že podmienky, ktoré platia, majú hodnotu True a ktoré neplatia, majú False - sú to dve špeciálne hodnoty, ktoré Python používa ako výsledky porovnávania - tzv. logických výrazov. Sú logického typu, tzv. bool. Môžeme to skontrolovať:

>>> type(1 + 2)
    <class 'int'>
>>> type(1 / 2)
    <class 'float'>
>>> type('12')
    <class 'str'>
>>> type(1 < 2)
    <class 'bool'>

Aj logické hodnoty môžeme medzi sebou porovnávať. Zamyslite sa nad takouto podmienkou v if:

import tkinter
import random

canvas = tkinter.Canvas(bg='white', width=300, height=300)
canvas.pack()

for i in range(1000):
    x = random.randint(1, 300)
    y = random.randint(1, 300)
    if (y < 150) == (x < 150):
        farba = 'red'
    else:
        farba = 'blue'
    canvas.create_oval(x - 5, y - 5, x + 5, y + 5, fill=farba, width=0)

tkinter.mainloop()
../_images/z04_06.png

Logické operácie

Pozrime sa podrobnejšie na logické operácie and, or a not. Tieto operácie samozrejme fungujú pre logické hodnoty True a False.

Logický súčin a súčasne

A

B

A and B

False

False

False

True

False

False

False

True

False

True

True

True

Logický súčet alebo

A

B

A or B

False

False

False

True

False

True

False

True

True

True

True

True

Negácia neplatí

A

not A

False

True

True

False

Logické operácie fungujú nielen pre logický typ, ale aj pre skoro všetky ďalšie typy. V tomto prípade Python pre každý typ definuje prípady, ktoré on (Python) chápe ako False a zrejme všetky ostatné hodnoty tohto typu chápe ako True. Ukážme prípady pre doteraz známe typy, ktoré označujú logickú hodnotu False:

typ

False

True

int

x == 0

x != 0

float

x == 0.0

x != 0.0

str

x == ''

x != ''

Tejto tabuľke budeme rozumieť takto: keď bude Python niekde očakávať logickú hodnotu (napríklad v príkaze if) a bude tam namiesto toho celé číslo, tak, ak má hodnotu 0, pochopí to ako False a ak má hodnotu rôznu od 0, bude to pre neho znamenať True. Podobne aj s reťazcami: ak ako podmienku if uvedieme znakový reťazec, tento bude reprezentovať False, ak je prázdny a True, ak je neprázdny.

Napríklad:

pocet = int(input('zadaj: '))
if pocet:
    print('pocet je rôzny od 0')
else:
    print('pocet je 0')
meno = input('zadaj: ')
if meno:
    print('meno nie je prázdny reťazec')
else:
    print('meno je prázdny reťazec')

Logické operácie and, or a not majú v skutočnosti tiež trochu rozšírenú interpretáciu:

Môžeme upraviť aj tabuľku pravdivostných hodnôt:

A

B

A and B

False

hocičo

A

True

hocičo

B

Tabuľka:

A

B

A or B

False

hocičo

B

True

hocičo

A

Napríklad:

>>> 1 + 2 and 3 + 4       # keďže 1+2 nie je False, výsledkom je 3+4
    7
>>> 'ahoj' or 'Python'    # keďže 'ahoj' nie je False, výsledkom je 'ahoj'
    'ahoj'
>>> '' or 'Python'        # keďže '' je False, výsledkom je 'Python'
    'Python'
>>> 3 < 4 and 'kuk'       # keďže 3<4 nie je False, výsledkom je 'kuk'
    'kuk'
>>> False or True         # keďže False je False, výsledkom je True
    True
>>> 'False' or 'True'     # keďže 'False' nie je False, výsledkom je 'False'
    'False'

Podmienený príkaz sa často používa pri náhodnom rozhodovaní. Napríklad, hádžeme mincou (náhodné hodnoty 0 a 1) a ak padne 1, kreslíme náhodnú kružnicu, inak nakreslíme náhodný štvorec. Toto opakujeme 10-krát:

import tkinter
import random

canvas = tkinter.Canvas(bg='white', width=300, height=300)
canvas.pack()

for i in range(10):
    x = random.randint(1, 300)
    y = random.randint(1, 300)
    a = random.randint(5, 50)

    if random.randrange(2):         # t.j. random.randrange(2) != 0
        canvas.create_oval(x - a, y - a, x + a, y + a)
    else:
        canvas.create_rectangle(x - a, y - a, x + a, y + a)

tkinter.mainloop()
../_images/z04_07.png

Približne rovnaké výsledky by sme dostali, ak by sme hádzali kockou so 6 možnosťami (random.randint(1, 6)) a pre čísla 1, 2, 3 by sme kreslili kružnicu, inak štvorec.

Túto ideu môžeme využiť aj pre takúto úlohu: vygenerujte 1000 farebných štvorčekov - modré a červené, pričom ich pomer je 1:50, t.j. na 50 červených štvorčekov pripadne približne 1 modrý:

import tkinter
import random

canvas = tkinter.Canvas(bg='white', width=300, height=300)
canvas.pack()

for i in range(1000):
    x = random.randint(1, 300)
    y = random.randint(1, 300)
    if random.randrange(50):             # t.j. random.randrange(50) != 0
        farba = 'red'
    else:
        farba = 'blue'
    canvas.create_rectangle(x - 5, y - 5, x + 5, y + 5, fill=farba, width=0)

tkinter.mainloop()
../_images/z04_08.png

Delitele čísel

Nasledovný príklad zisťuje, akých deliteľov má zadané číslo:

cislo = int(input('Zadaj číslo: '))
pocet = 0
print('delitele:', end=' ')
for delitel in range(1, cislo + 1):
    if cislo % delitel == 0:        # mohli by sme zapísať aj  if not cislo % delitel:
        pocet += 1
        print(delitel, end=' ')
print()
print('počet deliteľov:', pocet)

Výstup môže byť napríklad takýto:

Zadaj číslo: 100
delitele: 1 2 4 5 10 20 25 50 100
počet deliteľov: 9

Malou modifikáciou tejto úlohy vieme vytvoriť ďalšie dva programy. Prvý zisťuje, či je zadané číslo prvočíslo:

cislo = int(input('Zadaj číslo: '))
pocet = 0
for delitel in range(1, cislo + 1):
    if cislo % delitel == 0:
        pocet += 1
if pocet == 2:
    print(cislo, 'je prvočíslo')
else:
    print(cislo, 'nie je prvočíslo')

Po spustení, napríklad:

Zadaj číslo: 101
101 je prvočíslo

Ďalší program zisťuje, či je nejaké číslo dokonalé, t.j. súčet všetkých deliteľov menších ako samotné číslo sa rovná samotnému číslu. Na základe tohto nájde (postupne preverí) všetky dokonalé čísla do 10000:

print('dokonalé čísla do 10000 sú', end=' ')
for cislo in range(1, 10001):
    sucet = 0
    for delitel in range(1, cislo):
        if cislo % delitel == 0:
            sucet += delitel
    if sucet == cislo:
        print(cislo, end=', ')
print()
print('=== viac ich už nie je ===')

Program vypíše:

dokonalé čísla do 10000 sú 6, 28, 496, 8128,
=== viac ich už nie je ===

Vráťme sa k programu, ktorý zisťuje, či je nejaké číslo prvočíslo:

cislo = int(input('Zadaj číslo: '))
pocet = 0
for delitel in range(1, cislo + 1):
    if cislo % delitel == 0:
        pocet += 1
if pocet == 2:
    print(cislo, 'je prvočíslo')

Tento for-cyklus prejde vždy cislo-krát prechodov - bude kontrolovať, či je cislo deliteľné premennou delitel. Asi by nám ale stačilo zistiť, či existuje aspoň jeden deliteľ, ktorý je väčší ako 1 a menší ako cislo. Ak taký nájdeme (t.j. platí cislo % delitel == 0), cyklus už ďalej nemusí preverovať zvyšné delitele. Predstavme si, že chceme zisťovať, či číslo 1000000 je prvočíslo. Tento náš program už pri treťom prechode cyklu (keď delitel má hodnotu 3) „vie“, že pocet je viac ako 2, a teda to určite nebude prvočíslo. Úplne zbytočne 999997-krát zisťuje, či nejaký deliteľ delí alebo nedelí naše cislo. V tomto prípade by sa vykonávanie cyklu mohlo prerušiť a dostali by sme oveľa rýchlejšie rovnako správny výsledok. Na prerušovanie for-cyklu využijeme špeciálny príkaz break, ktorý ale môžeme použiť len v tele cyklu.

Vylepšime cyklus na zisťovanie prvočísel s prerušením pomocou break:

cislo = int(input('Zadaj číslo: '))
pocet = 0
for delitel in range(1, cislo + 1):
    if cislo % delitel == 0:
        pocet += 1
        if pocet > 2:
            break
if pocet == 2:
    print(cislo, 'je prvočíslo')
else:
    print(cislo, 'nie je prvočíslo')

Otestujte zisťovaním, či je 1000000000 prvočíslo. Pomocou predchádzajúcej verzie programu (bez break) by sme sa výsledku nedočkali, ale teraz sa dozvieme veľmi rýchlo, že:

Zadaj číslo: 1000000000
1000000000 nie je prvočíslo

Všimnite si, že v tomto prípade vôbec nepotrebujeme premennú pocet, ktorá počítala počet deliteľov. Podľa hodnoty premennej delitel po skončení cyklu, vieme presne povedať, či to bolo prvočíslo alebo nie. Upravme:

cislo = int(input('Zadaj číslo: '))
for delitel in range(2, cislo + 1):
    if cislo % delitel == 0:
        break
if cislo > 1 and delitel == cislo:
    print(cislo, 'je prvočíslo')
else:
    print(cislo, 'nie je prvočíslo')

Toto môžeme zapísať ešte krajšie pomocou pomocnej premennej prvocislo, v ktorej si budeme pamätať, či sme pre dané číslo ešte nenašli žiadneho deliteľa (True) alebo sme už jedného našli (False), a teda to určite prvočíslo nebude:

cislo = int(input('Zadaj číslo: '))
prvocislo = True
for delitel in range(2, cislo):
    if cislo % delitel == 0:
        prvocislo = False
        break
if prvocislo and cislo > 1:
    print(cislo, 'je prvočíslo')
else:
    print(cislo, 'nie je prvočíslo')

V tomto prípade ale for-cyklus nemôže ísť od 1 do cislo, ale pôjde od 2 do cislo-1.

Pripomeňme si ešte úlohu zo začiatku prednášky, v ktorej sa z počtu bodov zisťuje výsledné hodnotenie. Prepíšme teraz sériu príkazov if do for-cyklu, v ktorom bude jediný if a prerušenie cyklu pomocou break:

body = int(input('Zadaj získaný počet bodov: '))
hranica = 150
for znamka in 'ABCDEF':
    if body >= hranica:
        break
    hranica -= 10

if znamka != 'F':
    print('za', body, 'bodov získavaš známku', znamka)
else:
    print('za', body, 'bodov si nevyhovel a máš známku Fx')

V tomto riešení vidíme, že vo for-cykle sa postupne prechádzajú všetky hranice (hodnoty 150, 140, 130, …) a pri prvej, ktorá vyhovuje (body >= hranica), sa z cyklu vyskakuje. V tom prípade bude v premennej cyklu znamka zodpovedajúce hodnotenie. Všimnite si, že v cykle pre známku F vyjde hranica 100. To znamená, že pre body od 100 do 109 sa vyskočí z cyklu (pomocou break) s hodnotou F v premennej znamka a pre body menšie ako 100 cyklus normálne skončí a v premennej znamka bude stále hodnota F.


Podmienený cyklus

V Pythone existuje konštrukcia cyklu, ktorá opakuje vykonávanie postupnosti príkazov v závislosti od nejakej podmienky:

while podmienka:              # opakuj príkazy, kým platí podmienka
    prikaz
    prikaz
    ...

Vidíme podobnosť s podmieneným príkazom if - vetvením. Tento nový príkaz postupne:

  • zistí hodnotu podmienky, ktorá je zapísaná za slovom while

  • ak má táto podmienka hodnotu False, blok príkazov, ktorý je telom cyklu, sa preskočí a pokračuje sa na nasledovnom príkaze za celým while-cyklom (podobne ako v príkaze if bez vetvy else), hovoríme, že sa ukončilo vykonávanie cyklu

  • ak má podmienka hodnotu True, vykonajú sa všetky príkazy v tele cyklu (v odsunutom bloku príkazov)

  • a znovu sa testuje podmienka za slovom while, t.j. celé sa to opakuje

Najprv zapíšeme pomocou tohto cyklu, to čo už vieme pomocou for-cyklu:

for i in range(1, 21):
    print(i, i * i)

Vypíše tabuľku druhých mocnín čísel od 1 do 20. Prepis na cyklus while znamená, že zostavíme podmienku, ktorá bude testovať, napríklad premennú i: tá nesmie byť väčšia ako 20. Samozrejme, že už pred prvou kontrolou premennej i v podmienke cyklu while, musí mať nejakú hodnotu:

i = 1
while i < 21:
    print(i, i * i)
    i += 1

V cykle sa vykoná print() a zvýši sa hodnota premennej i o jedna.

while-cykly sa ale častejšie používajú vtedy, keď zápis pomocou for-cyklu je príliš komplikovaný, alebo sa ani urobiť nedá.

Ukážeme to na programe, ktorý bude do jedného radu tesne vedľa seba kresliť stále sa zväčšujúce štvorce postupne so stranami 10, 20, 30, … Pritom bude dávať pozor, aby naposledy nakreslený štvorec „nevypadol“ z plochy - teda chceme skončiť skôr, ako by sme nakreslili štvorec, ktorý sa už celý nezmestí do grafickej plochy. Štvorce so stranou a budeme kresliť takto:

canvas.create_rectangle(x, 200, x + a, 200 - a)

vďaka čomu, všetky ležia na jednej priamke (y = 200). Keď teraz budeme posúvať x-ovú súradnicu vždy o veľkosť nakresleného štvorca, ďalší bude ležať tesne vedľa neho.

Program pomocou while-cyklu zapíšeme takto:

import tkinter

sirka = int(input('šírka plochy: '))

canvas = tkinter.Canvas(width=sirka)
canvas.pack()

x = 5
a = 10
while x + a < sirka:
    canvas.create_rectangle(x, 200, x + a, 200 - a, fill='white')
    x += a
    a += 10
# príkazy za cyklom

tkinter.mainloop()

Program pracuje korektne pre rôzne šírky grafickej plochy. Ak zväčšovanie strany štvorca a += 10 nahradíme a = 2 * a, program bude pracovať aj s takto zväčšovanými štvorcami (strany budú postupne 10, 20, 40, 80, …).

Zhrňme, ako funguje tento typ cyklu:

  1. vyhodnotí sa podmienka x + a < sirka, t.j. pravý okraj štvorca, ktorý práve chceme nakresliť, sa ešte celý zmestí do grafickej plochy

  2. ak je podmienka splnená (pravdivá), postupne sa vykonajú všetky príkazy, t.j. nakreslí sa ďalší štvorec so stranou a a potom sa posunie x (ľavý okraj budúceho štvorca) o veľkosť práve nakresleného štvorca a a tiež sa ešte zmení veľkosť budúceho štvorca a o 10

  3. po vykonaní tela cyklu sa pokračuje v 1. kroku, t.j. opäť sa vyhodnotí podmienka

  4. ak podmienka nie je splnená (nepravda), cyklus končí a ďalej sa pokračuje v príkazoch za cyklom

Uvedomte si, že podmienka nehovorí, kedy má cyklus skončiť, ale naopak - kým podmienka platí, vykonávajú sa všetky príkazy v tele cyklu. Teda, tento while-cyklus skončí vtedy, keď už podmienka platiť nebude, t.j. naplatí x + a < sirka, čo je to isté ako x + a >= sirka.

Pre šírku grafickej plochy 300 dostávame takýto obrázok:

../_images/z04_09.png

Konkrétne tento program s while-cyklom vieme jednoducho prepísať pomocou for-cyklu a break:

import tkinter

sirka = int(input('šírka plochy: '))

canvas = tkinter.Canvas(width=sirka)
canvas.pack()

x = 5
for a in range(10, sirka, 10):
    if x + a >= sirka:
        break
    canvas.create_rectangle(x, 200, x + a, 200 - a, fill='white')
    x += a

tkinter.mainloop()

Vyššie sme zostavili program, ktorý zisťoval, či je zadané číslo prvočíslo. Použili sme for-cyklus, v ktorom sme zadané číslo postupne delili všetkými číslami, ktoré sú menšie ako samotné číslo. Zistili sme, že na zisťovanie prvočísla nepotrebujeme skutočný počet deliteľov, ale malo by nám stačiť zistenie, či existuje aspoň jeden deliteľ. Keď sa vyskytne prvý deliteľ (t.j. platí cislo % delitel != 0), cyklus môžeme ukončiť a vyhlásiť, že číslo nie je prvočíslo. Ak ani jedno číslo nie je deliteľom nášho čísla, hodnota premennej delitel dosiahne cislo a to je situácia, keď cyklus tiež skončí (t.j. keď delitel == cislo, našli sme prvočíslo). Zapíšeme to while-cyklom:

cislo = int(input('Zadaj číslo: '))
delitel = 2
while delitel < cislo and cislo % delitel != 0:
    delitel = delitel + 1

if delitel == cislo:
    print(cislo, 'je prvočíslo')
else:
    print(cislo, 'nie je prvočíslo')

Do podmienky while-cyklu sme pridali novú časť. Operátor and tu má ten význam, že na to, aby sa cyklus opakoval, musia byť splnené obe časti. Uvedomte si, že cyklus skončí vtedy, keď prestane platiť zadaná podmienka, t.j. negácia podmienky (matematicky to upravíme):

  • not (delitel < cislo and cislo % delitel != 0)

  • not delitel < cislo or not cislo % delitel != 0

  • delitel >= cislo or cislo % delitel == 0

while-cyklus teda skončí vtedy, keď delitel >= cislo, alebo cislo % delitel == 0 (teda, že deliteľ by bol už zbytočne veľký alebo našli sme hodnotu, ktorá delí naše číslo).


Zisťovanie druhej odmocniny

Ukážeme, ako zistíme druhú odmocninu čísla aj bez volania funkcie math.sqrt(x), resp. umocňovaním na jednu polovicu x**0.5.

Prvé riešenie:

cislo = float(input('zadaj číslo:'))

x = 0
while x ** 2 < cislo:
    x += 1

print('odmocnina', cislo, 'je', x)

Takto nájdené riešenie môže byť dosť nepresné, lebo x zvyšujeme o 1, takže, napríklad odmocninu z 26 vypočíta ako 6. Skúsme zjemniť krok, o ktorý sa mení hľadané x:

cislo = float(input('zadaj číslo:'))

x = 0
while x ** 2 < cislo:
    x += 0.001

print('odmocnina', cislo, 'je', x)

Teraz dáva program lepšie výsledky, ale pre väčšiu zadanú hodnotu mu to trvá citeľne dlhšie - skúste zadať, napríklad 10000000. Keďže mu vyšiel výsledok približne 3162.278 a dopracoval sa k nemu postupným pripočítavaním čísla 0.001 k štartovému 1, musel urobiť vyše 3 miliónov pripočítaní a tiež toľkokrát testov vo while-cykle (podmienky x**2 < cislo). Pre toto je takýto algoritmus nepoužiteľne pomalý.

Využijeme inú ideu:

  • zvolíme si interval, v ktorom sa určite bude nachádzať hľadaný výsledok (hľadaná odmocnina), napríklad nech je to interval <1, cislo> (pre čísla väčšie ako 1 je aj odmocnina väčšia ako 1 a určite je menšia ako samotne cislo)

  • ako x (prvý odhad našej hľadanej odmocniny) zvolíme stred tohto intervalu

  • zistíme, či je druhá mocnina tohto x väčšia ako zadané cislo alebo menšia

  • ak je väčšia (x je už zbytočne veľké), tak upravíme predpokladaný interval, tak že jeho hornú hranicu zmeníme na x

  • ak je ale menšia, upravíme dolnú hranicu intervalu na x

  • tým sa nám interval zmenšil na polovicu

  • toto celé opakujeme, kým už nie je nájdené x dostatočne blízko k hľadanému výsledku, t.j. či sa nelíši od výsledku menej ako zvolený rozdiel (epsilon)

Zapíšme to:

cislo = float(input('zadaj číslo:'))

od = 0
do = cislo

x = (od + do) / 2

pocet = 0
while abs(x ** 2 - cislo) > 0.001:
    if x ** 2 > cislo:
        do = x
    else:
        od = x
    x = (od + do) / 2
pocet += 1

print('druhá odmocnina', cislo, 'je', x)
print('počet prechodov while-cyklom bol', pocet)

Ak spustíme program pre 10000000 dostávame:

zadaj číslo:10000000
druhá odmocnina 10000000 je 3162.2776600670477
počet prechodov while-cyklom bol 41

čo je výrazné zlepšenie oproti predchádzajúcemu riešeniu, keď prechodov while-cyklom (hoci jednoduchších) bolo vyše 3 milióny.


Nekonečný cyklus

Cyklus s podmienkou, ktorá má stále hodnotu True, bude nekonečný. Napríklad:

i = 0
while i < 10:
    i -= 1

Nikdy neskončí, lebo premenná i bude stále menšia ako 10. Takéto výpočty môžeme prerušiť stlačením klávesov Ctrl/C.

Aj nasledovný cyklus je úmyselne nekonečný:

while 1:
    pass

Pripomíname, že príkaz pass je prázdny príkaz, ktorý nerobí nič. V tomto príklade pass označuje prázdne telo cyklu, a teda tento cyklus bude nikdy nekončiaci.

Už vieme, že príkaz break môžeme použiť v tele for-cyklu a vtedy sa zvyšok cyklu nevykoná. Nasledovný príklad ilustruje použitie break aj vo while-cykle:

sucet = 0
while True:
    retazec = input('zadaj číslo: ')
    if retazec == '':
        break
    sucet += int(retazec)
print('súčet prečítaných čísel =', sucet)

V tomto príklade sa čítajú čísla zo vstupu, kým nezadáme prázdny reťazec: vtedy cyklus končí a program vypíše súčet prečítaných čísel. Napríklad:

zadaj číslo: 5
zadaj číslo: 17
zadaj číslo: 2
zadaj číslo:
súčet prečítaných čísel = 24

Cvičenia


  1. Napíš program, ktorý zadané číslo rozloží na prvočinitele (vyjadrí ho ako súčin prvočísel). Tento rozklad zapíš v tvare rovnosti s násobením:

    zadaj číslo: 60
    60 = 2 * 2 * 3 * 5
    
    zadaj číslo: 1001
    1001 = 7 * 11 * 13
    
    zadaj číslo: 37
    37 = 37
    

    V programe bude while-cyklus, v ktorom budeš postupne skúšať deliť zadané číslo rôznymi deliteľmi: ak je číslo týmto deliteľom deliteľné, tak ho vypíšeš a samotné číslo vydelíš týmto deliteľom, inak deliteľ zvýšiš o 1 a celé sa to opakuje.


  1. Napíš program, ktorý vypíše cifry zadaného čísla postupným delením desiatimi, teda vo while-cykle vypíšeš poslednú cifru (číslo % 10) a pritom ešte samotné číslo vydelíš 10. Súčasne každú túto cifru pripočítaš do počítadla cs (ciferný súčet). Môžeš dostať takýto výstup:

    zadaj číslo: 4132
    2
    3
    1
    4
    ciferný súčet = 10
    

    Všimni si, že cifry sú vypísané v opačnom poradí ako sú v zadanom čísle.

    Teraz vypíš cifry do grafickej plochy (zrejme sprava do ľava). Program jednotlivé cifry vypíše do farebných štvorcov. Napríklad, pre číslo 510726 by si mal dostať:

    ../_images/z04_c01.png

  1. Uprav predchádzajúci program tak, aby sa číslo vypísalo v osmičkovej sústave (zrejme namiesto delenia 10 budeš deliť 8). Pre číslo 1753 by si mal dostať:

    ../_images/z04_c02.png

    Tento výsledok môžeš porovnať s volaním štandardnej funkcie oct (resp. pomocou formátovacej šablóny):

    >>> oct(1753)
        '0o3331'
    >>> f'{1753:o}'
        '3331'
    

    Vyskúšaj upraviť tento program tak, aby vypisoval cifry v dvojkovej sústave. Aj tu môžeš pre kontrolu využiť štandardnú funkciu bin (resp. formátovaciu šablónu):

    >>> bin(479)
        '0b111011111'
    >>> f'{479:b}'
        '111011111'
    

  1. Nasledovný program vykresľuje farebné krúžky v štvorcovej sieti n x n a zafarbuje ich podľa podmienky v príkaze if:

    import tkinter
    
    canvas = tkinter.Canvas()
    canvas.pack()
    
    n = 13
    for i in range(n):
        for j in range(n):
            x = j * 20 + 100
            y = i * 20 + 12
            if i == 5:
                farba = 'red'
            else:
                farba = 'white'
            canvas.create_oval(x - 8, y - 8, x + 8, y + 8, fill=farba)
    
    tkinter.mainloop()
    

    A. Zmeň túto podmienku tak, aby sa nakreslil obrázok, v ktorom sa zafarbí stredný rad a stredný stĺpec (v programe nemeň iné príkazy, nepridávaj ďalšie):

    ../_images/z04_c03.png

    B. Zmeň túto podmienku tak, aby sa nakreslil obrázok, v ktorom sa zafarbia obe uhlopriečky:

    ../_images/z04_c04.png

    Programy by mali fungovať správne aj pri zmenenom rozmere n. Vyskúšaj napríklad n=10.


  1. Budem hrať takúto hru: kladiem vedľa seba do radu mince s náhodnými hodnotami z <1, 4>; skončím, keď ich súčet bude väčší alebo rovný danému k. Ak skončil so súčtom, ktorý je rovný k, vypíše text 'HURÁ' inak 'ŠKODA'. Napíš program, ktorý v grafickom režime túto hru odsimuluje 10-krát a zakreslí to pod seba, napríklad pre ˙˙k=21˙˙ môžeš dostať takýto obrázok:

    ../_images/z04_c06.png

  1. Využi program z prednášky, v ktorom sa vykresľovalo 1000 farebných bodiek (malé krúžky s polomerom 5 bez obrysu) podľa toho či mali x-ovú, alebo y-ovú súradnicu menšiu alebo väčšiu ako 150. Veľkosť grafickej plochy bola 300x300. Zmeň v tomto programe sériu príkazov if tak, aby kreslené bodky vytvorili uhlopriečky štvorca. Asi tu budeš vedieť využiť podmienky x < y alebo 300 - x < y:

    ../_images/z04_c08.png

  1. V ďalšej verzii bodkovacej úlohy vybodkuješ kruh. Program opäť nakreslí 4000 náhodných bodiek, ale tie z nich, ktoré majú vzdialenosť od (x0, y0) menšiu ako r, zafarbí na červeno, zvyšné na modro. Napríklad pre premenné:

    x0, y0 = 180, 130
    r = 110
    

    by si mal dostať takýto obrázok:

    ../_images/z04_c09.png

    Vzdialenosť dvoch bodov v rovine môžeš počítať podľa vzorca sqrt((x1-x2)**2 + (y1-y2)**2).

    Aj v ďalšej verzii tohto programu budeš generuovať náhodné bodky v 300 x 300. Bodiek teraz vygeneruj aspoň 10000 a zmenši ich na polomer 2. Červené bodky budú tie, pre ktoré platí x*x+y*y<=300*300, zvyšné budú modré. Program ma záver vypíše podiel počtu červených ku všetkým bodkám krát 4. Zamysli sa nad tým, prečo sa tento podiel blíži k číslu pi. Napríklad:

    ../_images/z04_c10.png

    vyšiel tento podiel 3.1436.


  1. Napíš program, ktorý v grafickej ploche najprv vygeneruje n náhodných bodiek (malé kruhy). Potom nakreslí čo najmenší obdĺžnik tak, aby sa v ňom nachádzali všetky nakreslené body. Napríklad, pre n=7 môžeš dostať takýto obrázok:


  1. Vlajku Nemecka môžeš do štandardnej veľkosti grafickej plochy nakresliť tak, že pomocou cyklu vygeneruješ 10000 náhodných súradníc x z intervalu <10, 350> a y z intervalu <10, 250>. Na vygenerované súradnice nakreslíš farebnú bodku (kruh s polomerom 5 bez obrysu). Farbu bodky zvolíš podľa y-ovej súradnice:

    • ak je y < 90 kresli čierny krúžok,

    • inak, ak je y < 170 kresli červený krúžok,

    • inak nakresli žltý (môže byť 'gold') krúžok.

    Mal by si dostať takýto obrázok:

    ../_images/z04_c12.png

    Teraz vytvor podobný program, ktorý nakreslí vlajku Českej republiky:

    ../_images/z04_c13.png
  2. Dvojkový logaritmus môžeme počítať pomocou funkcie math.log2(cislo), ale vieme na to použiť aj algoritmus z prednášky, ktorý delením intervalu na polovice počítal druhú odmocninu. Napíš program, ktorý to s nejakou presnosťou vypočíta takýmto algoritmom. Napríklad po spustení:

        zadaj číslo: 1000
        logaritmus 1000.0 je 9.965784382075071
    >>> import math
    >>> math.log2(1000)
        9.965784284662087
    

  1. Pomocou polygónu nakresli farebný trojuholník zadaný tromi jeho vrcholmi a, b, c. Potom do neho nakresli vpísaný trojuholník (jeho vrcholy sú v stredoch strán). Toto opakuj, kým sú všetky dĺžky strán trojuholníka aspoň 30. Trojuholníky vyfarbuj náhodnými farbami. Napríklad pre:

    a = 10, 100
    b = 250, 10
    c = 300, 250
    

    po spustení:

    ../_images/z04_c14.png

    Pomôcka: ak mám dva vrcholy (x1, y1) a (x2, y2), potom stred úsečky je x3, y3 = (x1+x2)/2, (y1+y2)/2


2. Týždenný projekt


Napíš pythonovský skript, ktorý najprv pomocou troch volaní príkazu input('?') postupne prečíta:

  1. jedno celé číslo n = int(input('?'))

  2. dve celé čísla (oddelene medzerou): sirka a vyska

  3. tri mená farieb (oddelene medzerou)

Potom do grafickej plochy nakreslí pyramídu zloženú z farebných obdĺžnikov veľksti (sirka, vyska) (pomocou``create_rectangle()``). Pyramída sa skladá z n poschodí: na vrchu pyramídy je jeden obdĺžnik, pod ním sú dva odsunuté vľavo o polovicu šírky, pod nimi sú tri opäť odsunuté vľavo. Tieto obdĺžniky budú zafarbené tromi farbami tak, že žiadne dva, ktoré sa dotýkajú, nemajú rovnakú farbu. Tieto tri farby obdĺžnikov sú zadefinované v treťom vstupe (input).

Napríklad, pre takýto vstup:

?4
?50 20
?green blue maroon

môžeš dostať takýto obrázok:

../_images/zp2_01.png

V tvojom riešení nepoužívaj žiaden iný modul okrem tkinter. Z Pythonu používaj len príkazy z prvých štyroch prednášok. Do grafického plátna (canvas) kresli len pomocou create_rectangle, do ktorého pošleš 4 čísla ako súradnice dvoch bodov a pomenovaný parameter fill. Je jedno, kde v plátne umiestniš nakreslenú pyramídu, môžeš predpokladať, že testovač má neobmedzené rozmery plochy.

Za tento projekt môžeš získať maximálne 3 body, pričom,

  • ak sú obdĺžniky umiestnené správne, ale nie je v poriadku niečo s farbami, tak iba 1 bod

  • ak sú obdĺžniky uniestnené správne a žiadne dva susediace nemajú rovnakú farbu, tak 2 body

  • ak vykresľuješ všetky obdĺžniky v takom poradí, že najprv sú všetky jednej farby, až potom všetky obdĺžniky druhej farby a na záver všetky obdĺžniky tretej farby, tak za správne riešenie dostaneš 3 body (je jedno v akom poradí farieb).

Tvoj odovzdaný program s menom riesenie.py musí začínať tromi riadkami komentárov (zmeň meno a dátum odovzdania):

# 2. zadanie: pyramida
# autor: Janko Hraško
# datum: 3.10.2024

Súbor riesenie.py odovzdávaj na úlohový server https://list.fmph.uniba.sk/, kde ho môžeš nechať otestovať ľubovoľný počet krát.