13. Dvojrozmerné tabuľky

Pythonovský zoznam list (blízky jednorozmerným poliam v iných programovacích jazykoch) slúži hlavne na uchovávanie nejakej postupnosti alebo skupiny údajov. V takejto štruktúre sa dajú uchovávať aj dvojrozmerné tabuľky ako zoznam zoznamov (opäť je to analógia k dvojrozmerným poliam). Dvojrozmerné údaje sa často vyskytujú, napríklad ako rôzne hracie plochy (štvorčekový papier pre piškvorky, šachovnica pre doskové hry, rôzne typy labyrintov), ale napríklad aj rastrové obrázky sú často uchovávané v dvojrozmernej tabuľke. Aj v matematike sa niekedy pracuje s dvojrozmernými tabuľkami čísel (tzv. matice).

Už vieme, že prvkami zoznamu môžu byť opäť postupnosti (zoznamy alebo n-tice). Práve táto vlastnosť nám poslúži pri reprezentácii dvojrozmerných tabuliek. Napr. takúto tabuľku (matematickú maticu 3x3):

1 2 3
4 5 6
7 8 9

môžeme v Pythone zapísať:

>>> m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Zoznam m má tri prvky: sú to tri riadky (zoznamy čísel - teda jednorozmerné polia). Našou prvou úlohou bude vypísať takýto zoznam do riadkov. Ale takýto jednoduchý výpis sa nám nie vždy bude hodiť:

>>> for riadok in m:
        print(riadok)
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

Častejšie to budeme robiť dvoma vnorenými cyklami. Zadefinujme funkciu vypis() s jedným parametrom dvojrozmernou tabuľkou:

def vypis(tab):
    for riadok in tab:
        for prvok in riadok:
            print(prvok, end=' ')
        print()

To isté vieme zapísať aj pomocou indexovania:

def vypis(tab):
    for i in range(len(tab)):
        for j in range(len(tab[i])):
            print(tab[i][j], end=' ')
        print()

Obe tieto funkcie sú veľmi častými šablónami pri práci s dvojrozmernými tabuľkami. Teraz výpis tabuľky vyzerá takto:

>>> vypis(m)
1 2 3
4 5 6
7 8 9

Vytváranie dvojrozmerných tabuliek

Pozrime, ako môžeme vytvárať nové dvojrozmerné tabuľky. Okrem priameho priradenia, napr.

>>> matica = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

ich môžeme poskladať z jednotlivých riadkov, napr.

>>> riadok1 = [1, 2, 3]
>>> riadok2 = [4, 5, 6]
>>> riadok3 = [7, 8, 9]
>>> matica = [riadok1, riadok2, riadok3]

Častejšie to ale bude pomocou nejakých cyklov. Závisí to od toho, či sa vo výslednej tabuľke niečo opakuje. Vytvorme dvojrozmernú tabuľku veľkosti 3x3, ktorá obsahuje samé 0:

>>> matica = []
>>> for i in range(3):
        matica.append([0, 0, 0])
>>> matica
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> vypis(matica)
0 0 0
0 0 0
0 0 0

Využili sme tu štandardný spôsob vytvárania jednorozmerného zoznamu pomocou metódy append(). Obsah ľubovoľného prvku matice môžeme zmeniť obyčajným priradením:

>>> matica[0][1] = 9
>>> matica[1][2] += 1
>>> vypis(matica)
0 9 0
0 0 1
0 0 0

Prvý index v [] zátvorkách väčšinou bude pre nás označovať poradové číslo riadka, v druhých zátvorkách je poradové číslo stĺpca. Už sme si zvykli, že riadky aj stĺpce sú číslované od 0.

Hoci pri definovaní matice sa zdá, že sa 3-krát opakuje to isté. Zapíšme to pomocou viacnásobného zreťazenia (operácia *) zoznamov:

>>> matica1 = [[0, 0, 0]] * 3

Opäť sa potvrdzuje, že je to veľmi nesprávny spôsob vytvárania prvkov zoznamu: zápis [0, 0, 0] označuje referenciu na trojprvkový zoznam, potom [[0, 0, 0]]*3 rozkopíruje túto jednu referenciu trikrát. Teda vytvorili sme zoznam, ktorý trikrát obsahuje referenciu na ten istý riadok. Presvedčíme sa o tom priradením do niektorých prvkov takéhoto zoznamu:

>>> matica1[0][1] = 9
>>> matica1[1][2] += 1
>>> vypis(matica1)
0 9 1
0 9 1
0 9 1

Uvedomte si, že zápis:

>>> matica1 = [[0, 0, 0]] * 3

v skutočnosti znamená:

>>> riadok = [0, 0, 0]
>>> matica1 = [riadok, riadok, riadok]

Zapamätajte si! Dvojrozmerné štruktúry nikdy nevytvárame tak, že viacnásobne zreťazujeme (násobíme) jeden riadok viackrát. Pritom

>>> matica2 = [[0] * 3, [0] * 3, [0] * 3]

je už v poriadku, lebo v tomto zozname sme vytvorili tri rôzne riadky.

Niekedy sa na vytvorenie „prázdnej“ dvojrozmernej tabuľky definuje funkcia:

def vyrob(pocet_riadkov, pocet_stlpcov, hodnota=0):
    vysl = []
    for i in range(pocet_riadkov):
        vysl.append([hodnota] * pocet_stlpcov)
    return vysl

Otestujme:

>>> a = vyrob(3, 5)
>>> vypis(a)
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
>>> b = vyrob(2, 6, '*')
>>> vypis(b)
* * * * * *
* * * * * *

Iný tvar zápisu tejto funkcie:

def vyrob(pocet_riadkov, pocet_stlpcov, hodnota=0):
    vysl = [0] * pocet_riadkov
    for i in range(pocet_riadkov):
        vysl[i] = [hodnota] * pocet_stlpcov
    return vysl

Je na programátorovi, ktorú formu zápisu tejto funkcie použije.

Niekedy potrebujeme do takto pripravenej tabuľky priradiť nejaké hodnoty, napr. postupným zvyšovaním nejakého počítadla:

def ocisluj(tab):
    poc = 0
    for i in range(len(tab)):
        for j in range(len(tab[i])):
            tab[i][j] = poc
            poc += 1

Všimnite si, že táto funkcia vychádza z druhej funkcie (šablóny) pre vypisovanie dvojrozmernej tabuľky: namiesto výpisu prvku (print()) sme do neho niečo priradili. Táto funkcia ocisluj() nič nevypisuje ani nevracia žiadnu hodnotu „len“ modifikuje obsah tabuľky, ktorá je parametrom tejto funkcie.

>>> a = vyrob(3, 5)
>>> ocisluj(a)
>>> vypis(a)
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14

Funkciu ocisluj môžeme zapísať aj takto jednoduchšie:

def ocisluj(tab):
    poc = 0
    for riadok in tab:
        for j in range(len(riadok)):
            riadok[j] = poc
            poc += 1

Zobrazenie obsahu dvojrozmernej tabuľky v grafickej ploche

Zapíšme takúto funkciu na výpis obsahu dvojrozmernej tabuľky:

import tkinter

def vykresli_text(tab):
    vel = 20
    for i, riadok in enumerate(tab):
        for j, prvok in enumerate(riadok):
            canvas.create_text(i*vel + 10, j*vel + 10, text=prvok)

Otestujeme:

t = vyrob(7, 11)
ocisluj(t)

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

Dostávame takýto výpis:

_images/13_01.png

Zaujímavejší výstup v grafickej ploche dostaneme, keď namiesto čísel budem vykresľovať farebné štvorčeky. Predpokladajme, že čísla v tabuľke sú len z nejakého malého intervalu, napr. sú to čísla z intervalu <0, 3>, potom môžeme každú hodnotu v tabuľke zakresliť jednou zo štyroch farieb, napr. 0 = 'white', 1 = 'black', 2 = 'red', 3 = 'blue'. Pre istotu pri priradení farby pre každý štvorček vypočítame zvyšok po delení počtom farieb, teda číslom 4:

import tkinter

def vykresli(tab, vel=20):
    farby = ('white', 'black', 'red', 'blue')
    for i, riadok in enumerate(tab):
        for j, prvok in enumerate(riadok):
            x, y = j * vel, i * vel
            farba = farby[prvok % len(farby)]
            canvas.create_rectangle(x, y, x + vel, y + vel,
                                    fill=farba, outline='light gray')

Keď teraz vykreslíme obsah tabuľky:

t = vyrob(7, 11)
ocisluj(t)

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

Dostávame takýto výpis:

_images/13_02.png

Aby sme takýto výpis mohli zavolať viackrát za sebou a zakaždým sa zobrazil aktuálny obsah tabuľky, trochu vylepšíme funkciu:

import tkinter

def vykresli(tab, vel=20):
    farby = ('white', 'black', 'red', 'blue')
    canvas.delete('all')
    for i, riadok in enumerate(tab):
        for j, prvok in enumerate(riadok):
            x, y = j * vel, i * vel
            farba = farby[prvok % len(farby)]
            canvas.create_rectangle(x, y, x + vel, y + vel,
                                    fill=farba, outline='light gray')
    canvas.update()

Otestujeme:

t = vyrob(7, 11)
ocisluj(t)

canvas = tkinter.Canvas()
canvas.pack()
vykresli(t)
input()

for i, riadok in enumerate(t):
    for j in range(len(riadok)):
        riadok[j] = i + 1
vykresli(t)

Najprv sa vykreslí predchádzajúci obrázok a po stlačení <Enter> v textovom okne a obrázok prekreslí:

_images/13_03.png

Ďalším testom najprv vytvoríme tabuľku veľkosti 11x11 s hodnotou 0 (zobrazila by sa ako biela tabuľka). Potom vyrobíme červený rámik, t.j. po obvode zapíšeme hodnoty 2 a na záver na obe uhlopriečky zapíšeme hodnoty 3, t.j. po vykreslení budú modré:

n = 11
t = vyrob(n, n)
canvas = tkinter.Canvas()
canvas.pack()
for i in range(n):
    for j in range(n):
        if i == 0 or i == n - 1 or j == 0 or j == n - 1:
            t[i][j] = 2
    t[i][i] = t[i][n - 1 - i] = 3
vykresli(t)

Nakreslí:

_images/13_04.png

Niekoľko príkladov práce s dvojrozmernými tabuľkami

  1. zvýšime obsah všetkých prvkov o 1:

    def zvys_o_1(tab):
        for riadok in tab:
            for i in range(len(riadok)):
                riadok[i] += 1
    

    Zrejme všetky prvky tejto tabuľky musia byť nejaké čísla, inak by funkcia spadla na chybe.

    >>> p = [[5, 6, 7], [0, 0, 0], [-3, -2, -1]]
    >>> zvys_o_1(p)
    >>> p
    [[6, 7, 8], [1, 1, 1], [-2, -1, 0]]
    
  2. podobný cieľ má aj druhá funkcia: hoci nemení samotnú tabuľku, vytvorí novú, ktorej prvky sú o jedna väčšie ako v pôvodnej tabuľke:

    def o_1_viac(tab):
        nova_tab = []
        for riadok in tab:
            novy_riadok = [0] * len(riadok)
            for i in range(len(riadok)):
                novy_riadok[i] = riadok[i] + 1
            nova_tab.append(novy_riadok)
        return nova_tab
    

    To isté trochu inak:

    def o_1_viac(tab):
        nova_tab = []
        for riadok in tab:
            novy_riadok = list(riadok)          # kopia povodneho riadka
            for i in range(len(novy_riadok)):
                novy_riadok[i] += 1
            nova_tab.append(novy_riadok)
        return nova_tab
    
  3. kópia dvojrozmernej tabuľky:

    def kopia(tab):
        nova_tab = []
        for riadok in tab:
            nova_tab.append(list(riadok))
        return nova_tab
    
  4. číslovanie prvkov tabuľky inak ako to robila funkcia cisluj(): nie po riadkoch ale po stĺpcoch. Predpokladáme, že všetky riadky sú rovnako dlhé:

    def ocisluj_po_stlpcoch(tab):
        poc = 0
        for j in range(len(tab[0])):
            for i in range(len(tab)):
                tab[i][j] = poc
                poc += 1
    

    Všimnite si, že táto funkcia má oproti ocisluj() vymenené len dva riadky for-cyklov.

    >>> a = vyrob(3, 5)
    >>> ocisluj_po_stlpcoch(a)
    >>> vypis(a)
    0 3 6 9 12
    1 4 7 10 13
    2 5 8 11 14
    
  5. spočítame počet výskytov nejakej hodnoty:

    def pocet(tab, hodnota):
        vysl = 0
        for riadok in tab:
            for prvok in riadok:
                if prvok == hodnota:
                    vysl += 1
        return vysl
    

    Využili sme tu prvú verziu funkcie (šablóny) pre výpis dvojrozmernej tabuľky. Ak si ale pripomenieme, že niečo podobné robí štandardná metóda count(), ale táto funguje len pre jednorozmerné zoznamy, môžeme našu funkciu vylepšiť:

    def pocet(tab, hodnota):
        vysl = 0
        for riadok in tab:
            vysl += riadok.count(hodnota)
        return vysl
    

    Otestujeme:

    >>> a = [[1, 2, 1, 2], [4, 3, 2, 1], [2, 1, 3, 1]]
    >>> pocet(a, 1)
    5
    >>> pocet(a, 4)
    1
    >>> pocet(a, 5)
    0
    
  1. funkcia zistí, či je nejaká matica (dvojrozmerný zoznam) symetrická, t. j. či sú prvky pod a nad hlavnou uhlopriečkou rovnaké, čo znamená, že má platiť matica[i][j] == matica[j][i] pre každé i a j:

    def symetricka(matica, hodnota):
        vysl = True
        for i in range(len(matica)):
            for j in range(len(matica[i])):
                if matica[i][j] != matica[j][i]:
                    vysl = False
        return vysl
    

    Hoci je toto riešenie správne, má niekoľko nedostatkov:

    • funkcia zbytočne testuje každú dvojicu prvkov matica[i][j] a matica[j][i] dvakrát, napr. či matica[0][2] == matica[2][0] aj matica[2][0] == matica[0][2], tiež zrejme netreba kontrolovať prvky na hlavnej uhlopriečke, či matica[i][i] == matica[i][i]

    • keď sa vo vnútornom cykle zistí, že sme našli dvojicu matica[i][j] a matica[j][i], ktoré sú navzájom rôzne, zapamätá sa, že výsledok funkcie bude False a ďalej sa pokračuje prehľadávať dvojrozmerný zoznam - toto je zrejme zbytočné, lebo výsledok je už známy - asi by sme mali vyskočiť z týchto cyklov; POZOR! príkaz break ale neurobí to, čo by sa nám tu hodilo:

    def symetricka(matica, hodnota):
        vysl = True
        for i in range(len(matica)):
            for j in range(len(matica[i])):
                if matica[i][j] != matica[j][i]:
                    vysl = False
                    break                     # vyskočí z cyklu
        return vysl
    

    Takéto vyskočenie z cyklu nám veľmi nepomôže, lebo vyskakuje sa len z vnútorného a ďalej sa pokračuje vo vonkajšom. Našťastie my tu nepotrebujeme vyskakovať z cyklu, ale môžeme priamo ukončiť celú funkciu aj s návratovou hodnotou False.

    Prepíšme funkciu tak, aby zbytočne dvakrát nekontrolovala každú dvojicu prvkov a aby sa korektne ukončila, keď nájde nerovnakú dvojicu:

    def symetricka(matica, hodnota):
        for i in range(1, len(matica)):
            for j in range(i):
                if matica[i][j] != matica[j][i]:
                    return False
        return True
    
  2. funkcia vráti pozíciu prvého výskytu nejakej hodnoty, teda dvojicu (riadok, stĺpec). Keďže budeme potrebovať poznať indexy konkrétnych prvkov zoznamu, použijeme šablónu s indexmi:

    def index(tab, hodnota):
        for i in range(len(tab)):
            for j in range(len(tab[i])):
                if tab[i][j] == hodnota:
                    return i, j
    

    Funkcia skončí, keď nájde prvý výskyt hľadanej hodnoty (prechádza po riadkoch zľava doprava):

    >>> a = [[1, 2, 1, 2], [1, 2, 3, 4], [2, 1, 3, 1]]
    >>> index(a, 3)
    (1, 2)
    >>> index(a, 5)
    >>>
    

Na tomto poslednom príklade vidíme, že naša funkcia index() v nejakom prípade nevrátila „nič“. My už vieme, že vrátila špeciálnu hodnotu None, ktorá sa ale v príkazovom režime nevypíše. Ak by sme výsledok volania funkcie vypísali príkazom print(), dozvieme sa:

>>> print(index(a, 5))
None
>>>

hodnota None

Táto špeciálna hodnota je výsledkom všetkých funkcií, ktoré nevracajú žiadnu hodnotu pomocou return. To znamená, že každá funkcia ukončená bez return v skutočnosti vracia None ako keby posledným príkazom funkcie bol:

return None

Túto hodnotu môžeme často využívať v situáciách, keď chceme nejako oznámiť, že napr. výsledok hľadania bol neúspešný. Tak ako to bolo v prípade našej funkcie index(), ktorá v prípade, že sa v tabuľke hľadaná hodnota nenašla, vrátila None. Je zvykom takýto výsledok testovať takto:

vysledok = index(tab, hodnota)
if vysledok is None:
    print('nenasiel')
else:
    riadok, stlpec = vysledok

Teda namiesto testu premenna == None alebo premenna != None radšej používame premenna is None alebo premenna is not None.

Tabuľky s rôzne dlhými riadkami

Doteraz sme predpokladali, že všetky riadky dvojrozmernej štruktúry majú rovnakú dĺžku. Niekedy sa ale stretáme so situáciou, keď riadky budú rôzne dlhé. Napr.

>>> pt = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], [1, 5, 10, 10, 5, 1]]
>>> vypis(pt)
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

Tento zoznam obsahuje prvých niekoľko riadkov Pascalovho trojuholníka. Našťastie funkciu vypis() (obe verzie) sme napísali tak, že správne vypíšu aj zoznamy s rôzne dlhými riadkami.

Niektoré tabuľky nemusia mať takto pravidelný tvar, napr.

>>> delitele = [[6, 2, 3], [13, 13], [280, 2, 2, 2, 5, 7], [1]]
>>> vypis(delitele)
6 2 3
13 13
280 2 2 2 5 7
1

Zoznam delitele má v každom riadku rozklad nejakého čísla (prvý prvok) na prvočinitele (súčin zvyšných prvkov).

Preto už pri zostavovaní funkcií musíme myslieť na to, že parametrom môže byť aj zoznam s rôznou dĺžkou riadkov. Zapíšme funkciu, ktorá nám vráti zoznam všetkých dĺžok riadkov danej dvojrozmernej štruktúry:

def dlzky(tab):
    vysl = []
    for riadok in tab:
        vysl.append(len(riadok))
    return vysl

Pre naše dva príklady zoznamov dostávame:

>>> dlzky(pt)
[1, 2, 3, 4, 5, 6]
>>> dlzky(delitele)
[3, 2, 6, 1]

Podobným spôsobom môžeme generovať nové dvojrozmerné štruktúry s rôznou dĺžkou riadkov, pre ktoré poznáme práve len tieto dĺžky:

def vyrob(dlzky, hodnota=0):
    vysl = []
    for dlzka in dlzky:
        vysl.append([hodnota] * dlzka)
    return vysl

Otestujeme:

>>> m1 = vyrob([3, 0, 1])
>>> m1
[[0, 0, 0], [], [0]]
>>> m2 = vyrob(dlzky(delitele), 1)
>>> vypis(m2)
1 1 1
1 1
1 1 1 1 1 1
1

Zamyslite sa, ako budú vyzerať tieto zoznamy:

>>> n = 7
>>> m3 = vyrob([n] * n)
>>> m4 = vyrob(range(n))
>>> m5 = vyrob(range(n, 0, -2))

Ukážme, ako vyzerá dvojrozmerná tabuľka s rôzne dlhými riadkami v grafickej ploche. Najprv vygenerujeme náhodný obsah tabuľky a potom každý riadok skrátime:

from random import randrange

n = 11
t = vyrob(n, n)
canvas = tkinter.Canvas()
canvas.pack()
for riadok in t:
    for i in range(n):
        riadok[i] = randrange(3)
vykresli(t)
input()
for i in range(n):
    t[i] = t[i][:i + 1]
vykresli(t)

Najprv bude:

_images/13_05.png

po stlačení <Enter>:

_images/13_06.png

Hoci, keby sme riadky tabuľky skracovali namiesto pôvodného cyklu takto:

for i in range(n):
    t[i] = t[i][:randrange(n)]

Mohli by sme dostať takéto zobrazenie (niektoré riadky tabuľky môžu byť prázdne):

_images/13_07.png

Hra LIFE

Informácie k tejto informatickej simulačnej hre nájdete na wikipedii

Pravidlá:

  • v nekonečnej štvorcovej sieti žijú bunky, ktoré sa rôzne rozmnožujú, resp. umierajú

  • v každom políčku siete je buď živá bunka, alebo je políčko prázdne (budeme označovať ako 1 a 0)

  • každé políčko má 8 susedov (vodorovne, zvislo aj po uhlopriečke)

  • v každej generácii sa s každým jedným políčkom:

    • ak je na políčku bunka a má práve 2 alebo 3 susedov, tak táto bunka prežije aj do ďalšej generácie

    • ak je na políčku bunka a má buď 0 alebo 1 suseda, alebo viac ako 3 susedov, tak bunka na tomto políčku do ďalšej generácie neprežije (umiera)

    • ak má prázdne políčko presne na troch susediacich políčkach živé bunky, tak sa tu v ďalšej generácii narodí nová bunka

Štvorcovú sieť s 0 a 1 budeme ukladať v dvojrozmernej tabuľke veľkosti n x n. V tejto tabuľke je momentálna generácia bunkových živočíchov. Na to, aby sme vyrobili novú generáciu, si pripravíme pomocnú tabuľku rovnakej veľkosti a do nej budeme postupne zapisovať bunky novej generácie. Keď už bude celá táto pomocná tabuľka hotová, prekopírujeme ju do pôvodnej tabuľky. Dvojrozmernú tabuľku budeme vykresľovať do grafickej plochy.

import tkinter
from random import randrange

def nahodne():
    siet = []
    for i in range(n):
        p = []
        for j in range(n):
            p.append(randrange(2))
        siet.append(p)
    return siet

def kresli(siet):
    d = 8
    canvas.delete('all')
    for i in range(n):
        for j in range(n):
            farba = ['white','black'][siet[i][j]]
            x, y = d * j, d * i
            canvas.create_rectangle(x, y, x + d, y + d,
                                    fill=farba, outline='light gray')
    canvas.update()

def pocet_susedov(siet, rr, ss):
    pocet = 0
    for r in rr - 1, rr, rr + 1:
        for s in ss - 1, ss, ss + 1:
            if 0 <= r < n and 0 <= s < n:
                pocet += siet[r][s]
    return pocet - siet[rr][ss]

def nova_generacia(siet):
    siet1 = []
    for i in range(n):
        siet1.append([0] * n)
    for i in range(n):
        for j in range(n):
            ps = pocet_susedov(siet, i, j)
            if ps == 3 or ps == 2 and siet[i][j]:
                siet1[i][j] = 1
    return siet1

canvas = tkinter.Canvas(width=600, height=500)
canvas.pack()

n = 50
siet = nahodne()
kresli(siet)
for i in range(1000):
    siet = nova_generacia(siet)
    kresli(siet)

Na tejto sérii obrázkov môžete sledovať, ako sa s nejakej náhodnej pozície postupne generujú ďalšie generácie:

_images/13_2.png _images/13_3.png _images/13_4.png _images/13_5.png _images/13_6.png _images/13_7.png _images/13_8.png _images/13_9.png

Namiesto náhodného obsahu môžeme vytvoriť prázdnu (vynulovanú) sieť, do ktorej priradíme:

siet[5][2] = siet[5][3] = siet[5][4] = siet[4][4] = siet[3][3] = 1

Dostávame takýto klzák (glider), ktorý sa pohybuje po ploche nejakým smerom:

_images/13_10.png _images/13_11.png _images/13_12.png _images/13_13.png _images/13_14.png

Všimnite si, že po 4 generáciách má rovnaký tvar, ale je posunutý o 1 políčko dole a vpravo.



Cvičenia

L.I.S.T.

  1. Z prednášky skopírujte tieto funkcie:

    • def vypis(tab):
          for riadok in tab:
              for prvok in riadok:
                  print(prvok, end=' ')
              print()
      
      def vyrob(pocet_riadkov, pocet_stlpcov, hodnota=0):
          vysl = []
          for i in range(pocet_riadkov):
              vysl.append([hodnota] * pocet_stlpcov)
          return vysl
      
    • Teraz zadefinuj dvojrozmernú tabuľku do premennej modra, ktorá bude mať rozmery 7x7 a bude obsahovať samé hodnoty 3


  1. Z prednášky skopíruj a ďalšiu funkciu:

    • import tkinter
      
      def vykresli(tab, vel=20):
          farby = ('white', 'black', 'red', 'blue')
          canvas.delete('all')
          for i, riadok in enumerate(tab):
              for j, prvok in enumerate(riadok):
                  x, y = j * vel, i * vel
                  farba = farby[prvok % len(farby)]
                  canvas.create_rectangle(x, y, x + vel, y + vel,
                                          fill=farba, outline='light gray')
          canvas.update()
      
    • skontroluj, či po vykreslení tabuľky modra z prvého príkladu, dostaneš takéto vykreslenie:

      _images/13_01c.png

  1. Do premennej b priraď dvojrozmernú tabuľku veľkosti 5x5, v ktorej budú len hodnoty 0 a 1 tak, aby vytvorili veľké písmeno B.

    • b = [...]
      
    • skontroluj pomocou funkcie vypis

    • skontroluj pomocou vykresli_text

    • skontroluj pomocou vykresli - mal by si dostať približne takýto výstup:

      _images/13_02c.png

  1. V tabuľke v premennej modra z druhej úlohy vynuluj 3x3 stredné prvky tak, aby si dostal takéto zobrazenie. Na nulovanie použi dva vnorené for-cykly.

    • for ...
          for ...
              modra[...] = 0
      
    • mal by si dostať takéto zobrazenie:

      _images/13_03c.png

  1. Napíš funkciu obdlznik(tab, r1, s1, r2, s2, hodnota), ktorá v dvojrozmernej tabuľke tab vyplní zadaný obdĺžnik hodnotou hodnota. Parametre r1, s1 označujú pozíciu riadka a stĺpca pravého horného prvku rohu obdĺžnika, parametre r2, s2 pozíciu ľavého dolného prvku rohu obdĺžnika. Funkcia nič nevracia ani nevypisuje. Funkcia modifikuje obsah tabuľky.

    • otestuj vyriešením predchádzajúcej úlohy pomocou funkcie obdlznik

    • zadefinuj tabuľku rozmerov 12x12 s hodnotami 0 a do nej na tri rôzne pozície tri obdĺžniky: jeden rozmerov 3x5 s hodnotami 1, druhý s rozmermi 5x5 s hodnotami 2 a tretí s rozmermi 9x4 s hodnotami 3; potom vykresli túto tabuľku pomocou vykresli


  1. Napíš funkciu sachovnica(n, h1, h2), ktorá vytvorí dvojrozmernú tabuľku veľkosti n x n. V tejto tabuľke sa budú striedať hodnoty h1 a h2 šachovnicovým spôsobom.

    • napr. pre

      s = sachovnica(10, 2, 3)
      vykresli(s)
      
    • by sme dostali:

      _images/13_04c.png

  1. Napíš funkciu mala_nasobilka(n), ktorá vytvorí dvojrozmernú tabuľku veľkosti n x n s malou násobilkou.

    • napr.

      >>> m = mala_nasobilka(7)
      >>> vypis(m)
      1 2 3 4 5 6 7
      2 4 6 8 10 12 14
      3 6 9 12 15 18 21
      4 8 12 16 20 24 28
      5 10 15 20 25 30 35
      6 12 18 24 30 36 42
      7 14 21 28 35 42 49
      
    • vykresli túto tabuľku do grafickej plochy, vyskúšaj aj väčšie n


  1. Ručne odkrokuj bez počítača tieto dve funkcie:

    • mali robiť to isté:

      def vyrob1(n):
          vysl = [0] * n
          riadok = [1]
          for i in range(n):
              vysl[i] = riadok
              riadok.append(riadok[-1] + 1)
          return vysl
      
      def vyrob2(n):
          vysl = [0] * n
          riadok = [1]
          for i in range(n):
              vysl[i] = riadok
              riadok = riadok + [riadok[-1] + 1]
          return vysl
      
      tab1 = vyrob1(5)
      tab2 = vyrob2(5)
      
    • funkcie dávajú rôzne výsledky, zamysli sa, v čom je chyba

    • obe funkcie môžeš potom odkrokovať na stránke http://pythontutor.com


  1. Do funkcie vykresli doplň ďalšie štyri farby (napr. zelenú, žltú, …). Vytvor dvojrozmernú tabuľku veľkosti 50x50 a zaplň ju náhodnými hodnotami od 0 do 7. Otestuj vykreslením do grafickej plochy - zmeň pritom veľkosť vykresľovaných štvorčekov, prípadne aj grafickej plochy, aby sa celý obrázok zmestil do canvas-u.


  1. Napíš funkciu otoc(tab), ktorá zo štvorcovej tabuľky (počet riadkov sa rovná počtu stĺpcov) vyrobí kópiu. V tejto kópii (dvojrozmernej tabuľky) budú všetky hodnoty , v ktorom sú prvky otočené o 90 stupňov

    • napr.

      >>> r1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
      >>> r2 = otoc(r1)
      >>> r2
      [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
      
    • otestuj s tabuľkou z úlohy (3), v ktorej bolo veľké písmeno B; mal by si dostať približne takýto výsledok:

      _images/13_05c.png
    • postupne otáčaj a znovu vykresľuj takto otočenú tabuľku, aby si dostal aj ďalšie dve otočenia


  1. Napíš funkciu kruh(tab, r, r1, s1, hodnota), ktorá bude podobne ako funkcia obdlznik z úlohy (5) vypĺňať nejakú oblasť dvojrozmernej tabuľky zadanou hodnotou hodnota. Touto oblasťou bude kruh s polomerom r a so stredom r1, s1. Funkcia by mohla fungovať tak, že postupne skontroluje všetky prvky tabuľky, či ich „vzdialenosť“ od stredu je menšia alebo rovná r a vtedy im zmení hodnoty. Funkcia nič nevracia ani nevypisuje. Funkcia modifikuje obsah tabuľky.

    • napr. po nakreslení štyroch kruhov v tabuľke 50x50 môžeme dostať:

      _images/13_06c.png

  1. Napíš funkciu zapis(tab, meno_suboru), ktorá zapíše obsah dvojrozmernej tabuľky do súboru s daným menom. Formát zápisu nech je rovnaký ako výpis tabuľky pomocou funkcie vypis

    • pre kontrolu zapíš do súborov tabuľky z úloh (3) a (11)


  1. Napíš funkciu citaj(meno_suboru), ktorá prečíta textový súbor (napr. vytvorený pomocou zapis z predchádzajúcej úlohy) a vytvorí z neho dvojrozmernú tabuľku celých čísel. Túto tabuľku vráti ako výsledok funkcie.

    • pre ľubovoľnú dvojrozmernú tabuľku celých čísel by malo fungovať:

      >>> tab1 = ... nejaká dvojrozmerná tabuľka
      >>> zapis(tab1, 'subor.txt')
      >>> tab2 = citaj('subor.txt')
      >>> tab1 == tab2
      True
      
    • obe funkcie otestuj aj pre tabuľky s rôzne dlhými riadkami, napr.

      >>> tab1 = [[1], [2, 3, 4], [5, 6], [7]]
      >>> zapis(tab1, 'subor.txt')
      >>> tab2 = citaj('subor.txt')
      >>> tab1 == tab2
      True
      

  1. Napíš funkciu do_radu(tab), ktorá vráti vytvorenú jednorozmernú tabuľku (zoznam list) z riadkov dvojrozmernej tabuľky tab. Použi na to jeden for-cyklus a metódu extend - štandardná funkcia, pomocou ktorej môžeme k nejakému zoznamu prilepiť na koniec naraz viac prvkov (na rozdiel od append, ktorý vie prilepiť len jeden prvok).

    • napr.

      >>> tab1 = [[1], [2, 3, 4], [5, 6], [7]]
      >>> zoz = do_radu(tab1)
      >>> zoz
      [1, 2, 3, 4, 5, 6, 7]
      >>> do_radu([['prvy'], [], ['druhy', 'treti']])
      ['prvy', 'druhy', 'treti']
      

  1. Napíš funkciu do_dvojrozmernej(postupnost, sirka), ktorá bude v istom zmysle fungovať naopak ako funkcia do_radu z predchádzajúceho príkladu: funkcia dostáva postupnosť nejakých hodnôt (napr. jednorozmerný zoznam) a vyrobí z nej dvojrozmernú tabuľku, v ktorej okrem posledného riadku majú všetky zadanú šírku. Posledný riadok môže byť kratší.

    • napr.

      >>> t1 = do_dvojrozmernej(range(10), 3)
      >>> vypis(t1)
      0 1 2
      3 4 5
      6 7 8
      9
      >>> t2 = do_dvojrozmernej(do_radu(t1), 5)
      >>> vypis(t2)
      0 1 2 3 4
      5 6 7 8 9
      >>> vypis(do_dvojrozmernej('programovanie', 5))
      p r o g r
      a m o v a
      n i e
      

  1. Spojazdni hru Life z prednášky a otestuj takéto bunkové organizmy:

    • do stredu plochy polož tesne vedľa seba k buniek (okrem toho je plocha prázdna) a sleduj, ako sa bude takýto bunkový organizmus vyvíjať pre rôzne k; vyskúšaj napr. rôzne k od 4 do 20