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íklad, 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íklad:

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

ich môžeme poskladať z jednotlivých riadkov, napríklad:

>>> 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 = [None] * pocet_riadkov         # None alebo ľubovoľná iná hodnota
    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íklad 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íklad sú to čísla z intervalu <0, 3>, potom môžeme každú hodnotu v tabuľke zakresliť jednou zo štyroch farieb, napríklad 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)          # kópia pôvodného 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 pôvodnému ocisluj() vymenené 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
    
  6. 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íklad č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ť zvyšok matice - 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
    
  7. 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íklad 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íklad:

>>> 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íklad:

>>> 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:

import random

n = 11
t = vyrob(n, n)
canvas = tkinter.Canvas()
canvas.pack()
for riadok in t:
    for i in range(n):
        riadok[i] = random.randint(0, 2)
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 urobí:

    • 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
import random

def nahodne():
    tab = []
    for i in range(n):
        p = []
        for j in range(n):
            p.append(random.randint(0, 1))
        tab.append(p)
    # tab[5][2] = tab[5][3] = tab[5][4] = tab[4][4] = tab[3][3] = 1
    return tab

def kresli(tab):
    d = 8
    canvas.delete('all')
    for i in range(n):
        for j in range(n):
            farba = ['white','black'][tab[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(tab, 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 += tab[r][s]
    return pocet - tab[rr][ss]

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

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

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

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:

tab[5][2] = tab[5][3] = tab[5][4] = tab[4][4] = tab[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íruj 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ť len čísla 3. Premennú skontroluj pomocou funkcie vypis.


  1. Z prednášky skopíruj aj ď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 obrázok:

    _images/13_c01.png

  1. Do premennej b, f a g priraď dvojrozmerné tabuľky veľkosti 5x5, v ktorých budú len hodnoty 0, 1, 2 a 3 tak, aby sa vytvorili veľké písmená B, F a G:

    b = [[0, 1, 1, 1, 0],
         [0, 1, 0, 0, 1],
         ...
        ]
    f = [...]
    g = [...]
    
    • skontroluj pomocou funkcie vypis

    • skontroluj pomocou vykresli - v tomto jednom obrázku sú vykreslené všetky tri tabuľky:

    _images/13_c02.png

  1. Napíš funkciu zvys(tab, o_kolko=1), ktorá každý prvok v dvojrozmernej tabuľke zvýši o zadanú hodnotu o_kolko. Pri zvyšovaní hodnoty funkcia ešte urobí aj zvyšok po delení 4. Otestuj tabuľky z predchádzajúcej úlohy, napríklad:

    >>> zvys(b)
    >>> vykresli(b)
    >>> zvys(g, 3)
    >>> vykresli(g)
    

  1. V tabuľke v premennej modra z prvej ú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 obrázok:

    _images/13_c03.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íklad, pre

    s = sachovnica(10, 2, 3)
    vykresli(s)
    

    by si mal dostať:

    _images/13_c04.png

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

    >>> 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 ale 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 dve farby (napríklad zelenú a žltú). Vytvor dvojrozmernú tabuľku veľkosti 50x50 a zaplň ju náhodnými hodnotami od 0 do 5. Otestuj vykreslením do grafickej plochy - zmeň pritom veľkosť vykresľovaných štvorčekov (parameter vel) tak, 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íklad:

    >>> r1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    >>> r2 = otoc(r1)
    >>> r2
    [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
    

    Otestuj s tabuľkami z úlohy (3), napríklad pre tabuľku, v ktorej bolo veľké písmeno B, by si mal dostať takýto výsledok:

    _images/13_c05.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 spoj(tab1, tab2), ktorá z dvoch daných tabuliek vytvorí novú tabuľku. Predpokladáme, že obe tabuľky majú rovnaký počet riadkov. Spojenie dvoch tabuliek znamená, že v novej tabuľke bude v každom riadku najprv príslušný riadok z prvej tabuľky a za tým z druhej tabuľky. Napríklad, pre tabuľky s písmenami dostaneme:

    bf = spoj(b, f)
    zvys(g, 3)
    bfg = spoj(bf, g)
    vykresli(bfg)
    
    _images/13_c07.png

  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íklad rôzne k od 4 do 20


  1. Napíš funkciu kruh(tab, r, r1, s1, hodnota), ktorá bude podobne ako funkcia obdlznik z úlohy (6) 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íklad po nakreslení štyroch kruhov v tabuľke 50x50 môžeme dostať:

    _images/13_c06.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, napríklad (3), (11) a (12).


  1. Napíš funkciu citaj(meno_suboru), ktorá prečíta textový súbor (napríklad 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
    

    Funkcie zapis a citaj otestuj aj pre tabuľky s rôzne dlhými riadkami, napríklad:

    >>> 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íklad:

    >>> 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íklad 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íklad:

    >>> 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