12. Riešenie úloh s for-cyklom

Príkaz opakovania s vymenovanými hodnotami

Ukážeme aj iný spôsob opakovania príkazov pomocou for-cyklu.

V tomto program opakujeme vykonávanie jedného príkazu print s rôznymi hodnotami premennej n:

n = 11
print(n, '*', n, '=', n*n)
n = 111
print(n, '*', n, '=', n*n)
n = 1111
print(n, '*', n, '=', n*n)
n = 11111
print(n, '*', n, '=', n*n)
n = 111111
print(n, '*', n, '=', n*n)

Dostávame takýto výpis:

11 * 11 = 121
111 * 111 = 12321
1111 * 1111 = 1234321
11111 * 11111 = 123454321
111111 * 111111 = 12345654321

Zrejme príkaz for-cyklu, tak ako ho poznáme, tu teraz nevyužijeme. Doteraz sme poznali tento cyklus tak, že opakoval jedne alebo viac príkazov (blok príkazov), pričom premenná cyklu nadobúdala hodnoty z nejakého rozsahu od 0 do počet-1. Lenže tu by sme potrebovali, aby premenná cyklu namiesto toho nadobúdala postupne hodnoty 11, 111, 1111, 11111, 111111.

Príkaz for-cyklu môže mať v Pythone ešte aj takýto tvar:

for premenná in postupnosť_hodnôt:
    príkaz
    príkaz
    ...

Aj v tomto prípade sa bude niekoľko krát opakovať vykonávanie odsunutého bloku príkazov, pričom premenná cyklu bude nadobúdať hodnoty z danej postupnosti.

V našom programe sa opakuje vykonávanie jediného riadku:

print(n, '*', n, '=', n*n)

pre postupnosť 11, 111, 1111, 11111, 111111, čo môžeme elegantne zapísať:

for n in 11, 111, 1111, 11111, 111111:
    print(n, '*', n, '=', n*n)

Teda nemiesto range(počet) môžeme jednoducho vymenovať hodnoty, ktoré chceme, aby premenná cyklu nadobudla.

Teraz trochu pozmeňme program, ktorý počítal súčty druhých mocnín, takto:

cislo = 0
for i in 2, 3, 5, 7, 11, 13, 17:
    cislo = cislo + i
print('vysledok =', cislo)

Mohli by ste tento program prečítať ako výpočet súčtu niekoľkých čísel - náhodou sú to prvočísla do 17. Výsledok bude:

vysledok = 58

Takúto konštrukciu využijeme najčastejšie, keď budeme potrebovať vykonať nejaké príkazy (výpočty) pre nejakú konkrétnu skupinu hodnôt.

Ďalší program kreslí 5 obdĺžnikov, pričom sa im mení iba ich x-ová súradnica:

import tkinter

platno = tkinter.Canvas()
platno.pack()

x = 10
platno.create_rectangle(x, 100, x+50, 200, fill='blue')
x = 70
platno.create_rectangle(x, 100, x+50, 200, fill='blue')
x = 140
platno.create_rectangle(x, 100, x+50, 200, fill='blue')
x = 200
platno.create_rectangle(x, 100, x+50, 200, fill='blue')
x = 280
platno.create_rectangle(x, 100, x+50, 200, fill='blue')

Dostávame takúto kresbu:

_images/12_01.png

Aj v tomto programe sa opakuje len jeden príkaz:

platno.create_rectangle(x, 100, x+50, 200, fill='blue')

pričom sa mení hodnota premennej x. Aj z nej urobíme premennú cyklu a zadefinujeme aj postupnosť hodnôt:

import tkinter

platno = tkinter.Canvas()
platno.pack()

for x in 10, 70, 140, 200, 280:
    platno.create_rectangle(x, 100, x+50, 200, fill='blue')

Vo všetkých týchto programoch vidíte spôsob, ako môžeme z postupnosti príkazov vyrobiť cyklus: musíme najprv odhaliť, aká časť programu sa opakuje a ako pritom využijeme premennú cyklu. Tiež sa pritom musíme rozhodnúť, či použijeme vymenovanie hodnôt, alebo zápis range(počet).

Ukážme teraz riešenie úlohy, v ktorej chceme vedľa seba nakresliť 10 štvorčekov so stranou 20. Medzi samotnými štvorčekmi ale chceme malú medzeru veľkosti 3. Riešenie bez cyklu by mohlo vyzerať napr. takto:

import tkinter

platno = tkinter.Canvas()
platno.pack()

y = 100
x = 5
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 28
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 51
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 74
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 97
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 120
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 143
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 166
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 189
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 212
platno.create_rectangle(x, y, x+20, y+20, fill='red')

a grafická plocha vyzerá takto:

_images/12_02.png

Prerobiť to na for-cyklus s vymenovaním hodnôt je už teraz hračka:

import tkinter

platno = tkinter.Canvas()
platno.pack()

y = 100
for x in 5, 28, 51, 74, 97, 120, 143, 166, 189, 212:
    platno.create_rectangle(x, y, x+20, y+20, fill='red')

Ale už to nebude tak jednoduché, keby sme chceli využiť funkciu range, do ktorej chceme nastaviť počet kreslených štvorčekov.

Vráťme sa k pôvodnej verzii bez cyklu a pozrime, ako sa mení hodnota premennej x:

x = 5
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 28
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 51
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = 74
...

Každá ďalšia hodnota x je presne o 23 väčšia ako predchádzajúca (20 je veľkosť štvorčeka a 3 je medzera medzi nimi). Teda platí, že:

nové_x = staré_x + 23

Už vieme, že to môžeme zapísať:

x = x + 23

Teda vypočítaj hodnotu pravej strany priraďovacieho príkazu x + 23 a túto novú hodnotu priraď späť do premennej x. Takže časť programu bez cyklu môžeme zapásať:

x = 5
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = x + 23
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = x + 23
platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = x + 23
...

Tu vidíme, že v cykle sa stále opakujú dva príkazy:

platno.create_rectangle(x, y, x+20, y+20, fill='red')
x = x + 23

a to sa robí presne 10-krát (resp. toľko, koľko potrebujeme štvorčekov). Prepíšeme to na for-cuklus:

import tkinter

platno = tkinter.Canvas()
platno.pack()

y = 100
x = 5
for x in range(10):
    platno.create_rectangle(x, y, x+20, y+20, fill='red')
    x = x + 23

Toto riešenie je už teraz tak flexibilné, že zvládne nakresliť ľubovoľný počet štvorčekov, ktoré budú nakreslené v jednom rade.

Vylepšená postupnosť range

Už vieme, že zápis range(5) nahrádza postupnosť celých čísel 0, 1, 2, 3, 4. Môžeme to použiť aj všeobecne s nejakou premennou napr. n, kde range(n) označuje postupnosť začínajúcu 0 a končiacu n-1 (pre nezáporné n je počet prvkov tejto postupnosti presne n). Uvedomte si, že range(0) označuje postupnosť nulovej dĺžky (tzv. prázdna postupnosť), teda cyklus:

print('start')
for i in range(0):
    print(i)
print('koniec')

neprejde ani raz, teda program vypíše len riadok pred cyklom a po skončení cyklu:

start
koniec

Niekedy by sa nám mohlo hodiť, keby premenná cyklu nezačínala nulou, ale nejakou inou celočíselnou hodnotou, napr. 1. Predstavme si, že potrebujeme vypočítať súčin čísel 1 * 2 * 3 * ... * n (faktoriál čísla n). Veľmi výhodne by sa to počítalo pomocou cyklu, v ktorom budeme nejakú hodnotu fakt násobiť ďalším číslom cislo. Predpokladajme, že číslo bude postupne 1, 2, 3, …, n. Teda cyklus bude vyzerať nejako takto:

fakt = 1
for cislo in ...........:
    fakt = fakt * cislo
print(fakt)

Je jasne, že v hlavičke for-cyklu nemôžeme zadať range(n), lebo to by znamenalo, že medzi číslami by bola aj 0 a tou určite nechceme násobiť.

Našťastie existuje druhý variant volania range, ktorý umožňuje zadať aj počiatočnú hodnotu postupnosti:

range(od, do)

označuje postupnosť, ktorá začína hodnotou od, každá ďalšia hodnota je o 1 väčšia ako predchádzajúca a táto postupnosť končí vtedy, keď by nasledovné hodnota bola väčšia alebo rovná do (teda maximálne do-1). Ukážme to na niekoľkých príkladoch:

range(7)              # 0, 1, 2, 3, 4, 5, 6
range(0, 7)           # 0, 1, 2, 3, 4, 5, 6
range(1, 7)           # 1, 2, 3, 4, 5, 6
range(5, 7)           # 5, 6
range(7, 7)           # prázdna postupnosť
range(9, 7)           # prázdna postupnosť
range(-2, 7)          # -2, -1, 0, 1, 2, 3, 4, 5, 6

Z tohoto vidíme, že keď budeme potrebovať postupnosť od 1 do n, musíme ju zapísať range(1, n+1).

Teraz už môžeme zapísať podprogram faktorial, ktorý pre dané n vypočíta a vypíše faktoriál tohto čísla:

def faktorial(n):
    fakt = 1
    for cislo in range(1, n+1):
        fakt = fakt * cislo
    print('faktorial', n, '=', fakt)

Tento podprogram môžeme otestovať takýmto cyklom:

for n in range(11):
    faktorial(n)

a dostaneme tento výpis:

faktorial 0 = 1
faktorial 1 = 1
faktorial 2 = 2
faktorial 3 = 6
faktorial 4 = 24
faktorial 5 = 120
faktorial 6 = 720
faktorial 7 = 5040
faktorial 8 = 40320
faktorial 9 = 362880
faktorial 10 = 3628800

Zápis range má ešte jedno vylepšenie:

range(od, do, krok)

Ak má tri parametre, potom tretí parameter krok označuje hodnotu, o ktorú sa bude kaˇždý nasledovný člen zvyšovať. Napr. ak je krok 2, tak prvý člen postupnosti je od, druhý od+2, tretí od+4, atď. až po maximálnu hodnotu, ktorá nebude väčšia ako horná hranica do. Napr.

range(0, 7)           # 0, 1, 2, 3, 4, 5, 6
range(0, 7, 1)        # 0, 1, 2, 3, 4, 5, 6
range(0, 7, 2)        # 0, 2, 4, 6
range(1, 7, 2)        # 1, 3, 5
range(-3, 7, 2)       # -3, -1, 1, 3, 5

príklady …

Úlohy

  1. Vypíšte prvých 30 mocnín čísla 2 v tvare:

    0 1
    1 2
    2 4
    3 8
    4 16
    5 32
    6 64
    ...
    

    V programe nepoužívajte umocňovanie (napr. 2**6 = 64), ale v cykle predchádzajúci výsledok vynásobíte 2.

  1. Napíšte program, ktorý postupne vypočíta mocniny 11 a vypíše ich pod seba do grafickej plochy. V programe nepoužívajte umocňovanie, ale postupne predchádzajúci výsledok násobite 11. Dostanete takýto obrázok:

    _images/12_03.png

  1. Napíšte program, ktorý nakreslí desať tesne vedľa seba položených štvorcov, veľkosti ich strán sú postupne 20, 22, 24, … Dostanete takýto obrázok:

    _images/12_04.png

Zhrnutie

čo sme sa naučili:

  • pomocou príkazu for môžeme opakovať vykonávanie jedného alebo viacerých príkazov (tzv. blok príkazov)

  • všetky riadky bloku príkazov musia byť odsunuté o 4 medzery vpravo

  • for-cyklus používa premennú cyklu, ktorá pri každom prechode postupne nadobúda hodnoty buď z vymenovanej postupnosti, alebo s udanej postupnosti pomocou range

dôležité zásady:

  • meno premennej cyklu zvoľte tak, aby čo najlepšie vyjadrovalo hodnotu, ktorá sa v cykle bude automaticky meniť

  • for-cyklus s vymenovanými hodnotami používajte len vtedy, keď tieto hodnoty netvoria nejakú pravideľnú postupnosť a nie je predpoklad, že v budúcnosti bude treba zovšeobecňovať tento cyklus - inak radšej použite konštrukciu range