20. Funkcie a parametre

Parametre funkcií

Chceli by sme zapísať funkciu, ktorá by počítala súčin ľubovoľného počtu nejakých čísel. Aby sme ju mohli volať s rôznym počtom parametrov, využijeme náhradné hodnoty:

def sucin(a=1, b=1, c=1, d=1, e=1):
    return a * b * c * d * e

Túto funkciu môžeme volať aj bez parametrov, ale nefunguje viac ako 5 parametrov:

>>> sucin(3, 7)
21
>>> sucin(2, 3, 4)
24
>>> sucin(2, 3, 4, 5, 6)
720
>>> sucin(2, 3, 4, 5, 6, 7)
...
TypeError: sucin() takes from 0 to 5 positional arguments but 6 were given
>>> sucin()
1
>>> sucin(13)
13

Ak chceme použiť aj väčší počet parametrov, môžeme využiť zoznam, resp. ľubovoľnú postupnosť:

def sucin(postupnost):
    vysl = 1
    for prvok in postupnost:
        vysl *= prvok
    return vysl

Teraz to funguje pre ľubovoľný počet čísel, ale musíme ich uzavrieť do hranatých alebo okrúhlych zátvoriek:

>>> sucin([3, 7])
21
>>> sucin([2, 3, 4, 5, 6])
720
>>> sucin((2, 3, 4, 5, 6, 7))
5040
>>> sucin(range(2, 8))
5040
>>> sucin(range(2, 41))
815915283247897734345611269596115894272000000000

Namiesto zoznamu môžeme ako parameter poslať aj range(2, 8), t.j. ľubovoľnú štruktúru, ktorá sa dá rozobrať pomocou for-cyklu.

Zbalené a rozbalené parametre

Predchádzajúce riešenie stále nerieši náš problém: funkciu s ľubovoľným počtom parametrov. Na toto slúži tzv. zbalený parameter (po anglicky packing):

  • pred menom parametra v hlavičke funkcie píšeme znak * (zvyčajne je to posledný parameter)

  • pri volaní funkcie sa všetky zvyšné parametre zbalia do jednej n-tice (typ tuple)

Otestujme:

def test(prvy, *zvysne):
    print('prvy =', prvy)
    print('zvysne =', zvysne)

po spustení:

>>> test('jeden', 'dva', 'tri')
prvy = jeden
zvysne = ('dva', 'tri')
>>> test('jeden')
prvy = jeden
zvysne = ()

Funkcia sa môže volať s jedným alebo aj viac parametrami. Prepíšme funkciu sucin() s použitím jedného zbaleného parametra:

def sucin(*ntica):            # zbalený parameter
    vysl = 1
    for prvok in ntica:
        vysl *= prvok
    return vysl

Uvedomte si, že teraz jeden parameter ntica zastupuje ľubovoľný počet parametrov a Python nám do tohto parametra automaticky zbalí všetky skutočné parametre ako jednu n-ticu (tuple). Otestujeme:

>>> sucin()
1
>>> sucin(3, 7)
21
>>> sucin(2, 3, 4, 5, 6, 7)
5040
>>> sucin(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
479001600
>>> sucin(range(2, 13))
...
TypeError: unsupported operand type(s) for *=: 'int' and 'range'

V poslednom príklade vidíte, že range(...) tu nefunguje: Python tento jeden parameter zbalí do jednoprvkovej n-tice a potom sa s týmto range() bude chcieť násobiť, čo samozrejme nefunguje.

Teraz sa pozrime na ďalší príklad, ktorý ilustruje možnosti práce s parametrami funkcií. Napíšme funkciu, ktorá dostáva dva alebo tri parametre a nejako ich vypíše:

def pis(meno, priezvisko, rok=2018):
    print(f'volam sa {meno} {priezvisko} a narodil som sa v {rok}')

Napr.

>>> pis('Janko', 'Hrasko', 2014)
volam sa Janko Hrasko a narodil som sa v 2014
>>> pis('Juraj', 'Janosik')
volam sa Juraj Janosik a narodil som sa v 2018

Malá nepríjemnosť nastáva vtedy, keď máme takéto hodnoty pripravené v nejakej štruktúre:

>>> p1 = ['Janko', 'Hrasko', 2014]
>>> p2 = ['Juraj', 'Janosik']
>>> p3 = ['Monty', 'Python', 1968]
>>> pis(p1)
...
TypeError: pis() missing 1 required positional argument: 'priezvisko'

Túto funkciu nemôžeme volať s trojprvkovým zoznamom, ale musíme prvky tohto zoznamu rozbaliť, aby sa priradili do príslušných parametrov, napr.

>>> pis(p1[0], p1[1], p1[2])
volam sa Janko Hrasko a narodil som sa v 2014
>>> pis(p2[0], p2[1])
volam sa Juraj Janosik a narodil som sa v 2018

Takáto situácia sa pri programovaní stáva dosť často: v nejakej štruktúre (napr. v zozname) máme pripravené parametre pre danú funkciu a my potrebujeme túto funkciu zavolať s rozbalenými prvkami štruktúry. Na toto slúži rozbaľovací operátor, pomocou ktorého môžeme ľubovoľnú štruktúru poslať ako skupinu parametrov, pričom sa automaticky rozbalia (a teda prvky sa priradia do formálnych parametrov). Rozbaľovací operátor pre parametre je opäť znak * a používa sa takto:

>>> pis(*p1)              # je to isté ako pis(p1[0], p1[1], p1[2])
volam sa Janko Hrasko a narodil som sa v 2014
>>> pis(*p2)              # je to isté ako pis(p2[0], p2[1])
volam sa Juraj Janosik a narodil som sa v 2018

Takže, všade tam, kde sa očakáva nie jedna štruktúra ako parameter, ale veľa parametrov, ktoré sú prvkami tejto štruktúry, môžeme použiť tento rozbaľovací operátor (po anglicky unpacking argument lists).

Tento operátor môžeme využiť napr. aj v takýchto situáciách:

>>> print(range(10))
range(0, 10)
>>> print(*range(10))
0 1 2 3 4 5 6 7 8 9
>>> print(*range(10), sep='...')
0...1...2...3...4...5...6...7...8...9
>>> param = (3, 20, 4)
>>> print(*range(*param))
3 7 11 15 19
>>> dvenasto = 2 ** 100
>>> print(dvenasto)
1267650600228229401496703205376
>>> print(*str(dvenasto))
1 2 6 7 6 5 0 6 0 0 2 2 8 2 2 9 4 0 1 4 9 6 7 0 3 2 0 5 3 7 6
>>> print(*str(dvenasto), sep='-')
1-2-6-7-6-5-0-6-0-0-2-2-8-2-2-9-4-0-1-4-9-6-7-0-3-2-0-5-3-7-6
>>> p = [17, 18, 19, 20, 21]
>>> [*p[3:], *range(5), *p]
[20, 21, 0, 1, 2, 3, 4, 17, 18, 19, 20, 21]

Pripomeňme si funkciu sucin(), ktorá počítala súčin ľubovoľného počtu čísel - tieto sa spracovali jedným zbaleným parametrom. Teda funkcia očakáva veľa parametrov a niečo z nich vypočíta. Ak ale máme jednu štruktúru, ktorá obsahuje tieto čísla, môžeme použiť rozbaľovací operátor:

>>> cisla = [7, 11, 13]
>>> sucin(cisla)              # zoznam [7, 11, 13] sa násobí 1
[7, 11, 13]
>>> sucin(*cisla)             # sucin(cisla[0], cisla[1], cisla[2])
1001
>>> sucin(*range(2, 11))
3628800

Parameter s meniteľnou hodnotou

Teraz trochu odbočíme od zbalených a rozbalených parametrov. Ukážme veľký problém, ktorý nás môže zaskočiť v situácii, keď náhradnou hodnotou parametra je meniteľný typ (mutable). Pozrime na túto nevinne vyzerajúcu funkciu:

def pokus(a=1, b=[]):
    b.append(a)
    return b

Očakávame, že ak neuvedieme druhý parameter, výsledkom funkcie bude jednoprvkový zoznam s prvkom prvého parametra. Skôr, ako to otestujeme, vypíšme, ako túto našu funkciu vidí help():

>>> help(pokus)
Help on function pokus in module __main__:

pokus(a=1, b=[])

a teraz test:

>>> pokus(2)
[2]

Zatiaľ je všetko v poriadku. Ale po druhom spustení:

>>> pokus(7)
[2, 7]

Vidíme, že Python si tu nejako pamätá aj naše prvé spustenie tejto funkcie. Znovu pozrime help():

>>> help(pokus)
Help on function pokus in module __main__:

pokus(a=1, b=[2, 7])

A vidíme, že sa dokonca zmenila hlavička našej funkcie pokus(). Mali by sme teda rozumieť, čo sa tu vlastne deje:

  • Python si pre každú funkciu pamätá zoznam všetkých náhradných hodnôt pre formálne parametre funkcie, tak ako sme ich zadefinovali v hlavičke (môžete si pozrieť premennú pokus.__defaults__)

  • ak sú v tomto zozname len nemeniteľné hodnoty (immutable), nevzniká žiaden problém

  • problémom sú meniteľné hodnoty (mutable) v tomto zozname: pri volaní funkcie, keď treba použiť náhradnú hodnotu, Python použije hodnotu z tohto zoznamu (použije referenciu na túto štruktúru) - keď tomuto parametru ale v tele funkcie zmeníme obsah, zmení sa tým aj hodnota v zozname náhradných hodnôt (pokus.__defaults__)

Z tohto pre nás vyplýva, že radšej nikdy nebudeme definovať náhradnú hodnotu parametra ako meniteľný objekt. Funkciu pokus by sme mali radšej zapísať takto:

def pokus(a=1, b=None):
    if b is None:
        b = []
    b.append(a)
    return b

A všetko by fungovalo tak, ako sme očakávali.

Skúsení programátori vedia túto vlastnosť využiť veľmi zaujímavo. Napr. do funkcie posielame nejaké hodnoty a funkcia nám oznamuje, či už sa taká vyskytla, alebo ešte nie:

def kontrola(hodnota, bola=set()):
    if hodnota in bola:
        print(hodnota, 'uz bola')
    else:
        bola.add(hodnota)
        print(hodnota, 'OK')

a test:

>>> kontrola(7)
7 OK
>>> kontrola(17)
17 OK
>>> kontrola(-7)
-7 OK
>>> kontrola(17)
17 uz bola
>>> kontrola(7)
7 uz bola

Veľmi pekným využitím tejto nečakanej vlastnosti parametra s meniteľnou náhradnou hodnotou je zrýchlenie výpočtu fibonacciho postupnosti. Už sme sa stretli s rekurzívnou verziou, ktorá je pre väčšie hodnoty nepoužiteľne pomalá:

def fib(n):
    if n < 2:
        return n
    return fib(n-2) + fib(n-1)

Vyskúšajte napr. fib(40).

Tu by mohol pomôcť jeden parameter navyše, vďaka ktorému by si funkcia mohla pamätať všetky doteraz vypočítané hodnoty. Zapíšme:

def fib(n, pamat={}):
    if n in pamat:
        return pamat[n]
    if n < 2:
        vysl = n
    else:
        vysl = fib(n-2) + fib(n-1)
    pamat[n] = vysl
    return vysl

Aj táto funkcia je rekurzívna, len si vie zapamätať niečo navyše. Takému spôsobu riešenia úlohy, pri ktorom vieme využiť až predtým vypočítané a zapamätané medzivýsledky, hovoríme memoizácia a budete sa to učiť vo vyšších ročníkoch.

Zbalené pomenované parametre

Pozrime sa na túto funkciu:

def vypis(meno, vek, vyska, vaha, bydlisko):
    print('volam sa', meno)
    print('    vek =', vek)
    print('    vyska =', vyska)
    print('    vaha =', vaha)
    print('    bydlisko =', bydlisko)

otestujeme:

>>> vypis('Janko Hrasko', vek=5, vyska=7, vaha=0.3, bydlisko='Pri poli')
volam sa Janko Hrasko
    vek = 5
    vyska = 7
    vaha = 0.3
    bydlisko = Pri poli

Radi by sme aj tu dosiahli podobnú vlastnosť parametrov, ako to bolo pri zbalenom parametri, ktorý do jedného parametra dostal ľubovoľný počet skutočných parametrov. V tomto prípade by sme ale chceli, aby sa takto zbalili všetky vlastnosti vypisovanej osoby ale aj s príslušnými menami týchto vlastností. V tomto prípade nám pomôžu zbalené pomenované parametre (po anglicky keyword argument packing): namiesto viacerých pozičných parametrov, uvedieme jeden s dvomi hviezdičkami **:

def vypis(meno, **vlastnosti):
    print('volam sa', meno)
    for k, h in vlastnosti.items():
        print('    ', k, '=', h)

Tento zápis označuje, že ľubovoľný počet pomenovaných parametrov sa zbalí do jedného parametra a ten vo vnútri funkcie bude typu slovník (asociatívne pole dict). Uvedomte si ale, že v slovníku sa nezachováva poradie dvojíc:

>>> vypis('Janko Hrasko', vek=5, vyska=7, vaha=0.3, bydlisko='Pri poli')
volam sa Janko Hrasko
     vyska = 7
     vaha = 0.3
     bydlisko = Pri poli
     vek = 5

Ďalší príklad tiež ilustruje takýto zbalený slovník:

import tkinter

def kruh(r, x, y):
    canvas.create_oval(x-r, y-r, x+r, y+r)

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

kruh(50, 100, 100)

Funkcia kruh() definuje nakreslenie kruhu s daným polomerom a stredom, ale nijako nevyužíva ďalšie parametre na definovanie farieb a obrysu kruhu. Doplňme do funkcie zbalené pomenované parametre:

def kruh(r, x, y, **param):
    canvas.create_oval(x-r, y-r, x+r, y+r)

Toto označuje, že kruh() môžeme zavolať s ľubovoľnými ďalšími pomenovanými parametrami, napr. kruh(..., fill='red', width=7). Tieto parametre ale chceme ďalej poslať do funkcie create_oval(). Určite sem nemôžeme poslať param, lebo toto je premenná typu dict a create_oval() s tým pracovať nevie. Tu by sa nám zišlo premennú param rozbaliť do viacerých pomenovaných parametrov: Rozbaľovací operátor pre pomenované parametre sú dve hviezdičky **, teda zapíšeme:

def kruh(r, x, y, **param):
    canvas.create_oval(x-r, y-r, x+r, y+r, **param)

a teraz funguje aj

kruh(50, 100, 100)
kruh(30, 150, 100, fill='red')
kruh(100, 200, 200, width=10, outline='green')

Takýto rozbaľovací parameter by sme vedeli využiť aj v predchádzajúcom príklade s funkciou vypis():

>>> p1 = {'meno':'Janko Hrasko', 'vek':5, 'vyska':7, 'vaha':0.3, 'bydlisko':'Pri poli'}
>>> vypis(**p1)
volam sa Janko Hrasko
     vaha = 0.3
     vek = 5
     vyska = 7
     bydlisko = Pri poli
>>> p2 = {'vek':25, 'narodeny':'Terchova', 'popraveny':'Liptovsky Mikulas'}
>>> vypis('Juraj Janosik', **p2)
volam sa Juraj Janosik
     popraveny = Liptovsky Mikulas
     vek = 25
     narodeny = Terchova

Funkcia ako hodnota

v Pythone sú aj funkcie objektami a môžeme ich priradiť do premennej, napr.

>>> def fun1(x): return x * x
>>> fun1(7)
49
>>> cojaviem = fun1
>>> cojaviem(8)
64

Funkcie môžu byť prvkami zoznamu, napr.

>>> def fun2(x): return 2*x + 1
>>> def fun3(x): return x // 2
>>> zoznam = [fun1, fun2, fun3]
>>> for f in zoznam:
        print(f(10))
100
21
5

Funkciu môžeme poslať ako parameter do inej funkcie, napr.

>>> def urob(fun, x):
        return fun(x)
>>> urob(fun2, 3.14)
7.28

Funkcia (teda referencia na funkciu) môže byť aj prvkom slovníka. Pekne to ilustruje príklad s korytnačkou:

def vykonaj():
    t = turtle.Turtle()
    p = {'fd': t.fd, 'rt': t.rt, 'lt': t.lt}
    while True:
        prikaz, parameter = input('> ').split()
        p[prikaz](int(parameter))

a funguje napr.

>>> vykonaj()
> fd 100
> lt 90
> fd 50
> rt 60
> fd 100

Anonymné funkcie

Často sa namiesto jednoriadkovej funkcie, ktorá počíta jednoduchý výraz a tento vráti ako výsledok (return) používa špeciálna konštrukcia lambda. Tá vygeneruje tzv. anonymnú funkciu, ktorú môžeme priradiť do premennej alebo poslať ako parameter do funkcie, napr.

>>> urob(lambda x: 2*x + 1, 3.14)
7.28

Tvar konštrukcie lambda je nasledovný:

lambda parametre: výraz

Tento zápis nahrádza definovanie funkcie:

def anonymne_meno(parametre):
    return vyraz

Môžeme zapísať napr.

lambda x: x % 2==0              # funkcia vráti True pre párne číslo
lambda x, y: x ** y             # vypočíta príslušnú mocnimu čísla
lambda x: isinstance(x, int)    # vráti True pre celé číslo

Mapovacie funkcie

Ideu funkcie ako parametra najlepšie ilustruje takáto funkcia mapuj():

def mapuj(fun, postupnost):
    vysl = []
    for prvok in postupnost:
        vysl.append(fun(prvok))
    return vysl

Funkcia aplikuje danú funkciu (prvý parameter) na všetky prvky nejakej postupnosti (zoznam, n-tica, …) a z výsledkov poskladá nový zoznam, napr.

>>> mapuj(fun1, (2, 3, 7))
[4, 9, 49]
>>> mapuj(list, 'Python'))
[['P'], ['y'], ['t'], ['h'], ['o'], ['n']]
>>> mapuj(lambda x: [x] * x, range(1, 6))
[[1], [2, 2], [3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5, 5]]

V Pythone existuje štandardná funkcia map(), ktorá robí skoro to isté ako naša funkcia mapuj() ale s tým rozdielom, že map() nevracia zoznam, ale niečo ako generátorový objekt, ktorý môžeme použiť ako prechádzanú postupnosť vo for-cykle, alebo napr. pomocou list() ho previesť na zoznam, napr.

>>> list(map(int, str(2 ** 30)))
[1, 0, 7, 3, 7, 4, 1, 8, 2, 4]

Vráti zoznam cifier čísla 2**30.

Podobná funkcii mapuj() je aj funkcia filtruj(), ktorá z danej postupnosti (iterovateľný objekt) vyrobí nový zoznam, ale nechá v ňom len tie prvky, ktoré spĺňanú nejakú podmienku. Podmienka je definovaná funkciou, ktorá je prvým parametrom:

def filtruj(fun, postupnost):
    vysl = []
    for prvok in postupnost:
        if fun(prvok):
            vysl.append(prvok)
    return vysl

Napr.

>>> def podm(x):               # zistí, či je číslo párne
        return x % 2==0
>>> list(range(1, 20, 3))
[1, 4, 7, 10, 13, 16, 19]
>>> mapuj(podm, range(1, 20, 3))
[False, True, False, True, False, True, False]
>>> filtruj(podm, range(1, 20, 3))
[4, 10, 16]

Podobne ako pre mapuj() existuje štandardná funkcia map(), aj pre filtruj() existuje štandardná funkcia filter() - tieto dve funkcie ale nevracajú zoznam (list) ale postupnosť, ktorá sa dá prechádzať for-cyklom alebo poslať ako parameter do funkcie, kde sa očakáva postupnosť.

Ukážkovým využitím funkcie map() je funkcia, ktorá počíta ciferný súčet nejakého čísla:

def cs(cislo):
    return sum(map(int, str(cislo)))
>>> cs(1234)
10

Generátorová notácia

Veľmi podobná funkcii map() je generátorová notácia (po anglicky list comprehension):

  • je to spôsob, ako môžeme elegantne vygenerovať nejaký zoznam pomocou for-cyklu a nejakého výrazu

  • do hranatých zátvoriek [...] nezapíšeme prvky zoznamu, ale predpis, akým sa majú vytvoriť

  • základný tvar je tohto zápisu:

    [vyraz for i in postupnost]
    
  • kde výraz najčastejšie obsahuje premennú cyklu a postupnosť je ľubovoľná štruktúra, ktorá sa dá prechádzať for-cyklom (napr. list, set, str, range(), riadky otvoreného súboru, ale aj výsledok map() a filter() a pod.)

  • táto notácia môže používať aj vnorené cykly ale aj podmienku if, vtedy je to v takomto tvare:

    [vyraz for i in postupnost if podmienka]
    

    alebo

    [vyraz for i in postupnost for j in postupnost]
    

    alebo

    [vyraz for i in postupnost for j in postupnost if podmienka]
    

    a podobne

  • generátorová notácia s podmienkou nechá vo výsledku len tie prvky, ktoré spĺňajú danú podmienku

Niekoľko príkladov:

>>> [i ** 2 for i in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

je to isté ako:

>>> vysl = []
>>> for i in range (1, 11):
        vysl.append(i ** 2)
>>> vysl
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Vnorené cykly:

>>> [i * j for i in range(1, 5) for j in range(1, 5)]
[1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, 4, 8, 12, 16]

je to isté ako:

>>> vysl = []
>>> for i in range(1, 5):
        for j in range(1, 5):
            vysl.append(i * j)
>>> vysl
[1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, 4, 8, 12, 16]

Ďalšie cykly:

>>> [[i * j for i in range(1, 5)] for j in range(1, 5)]
[[1, 2, 3, 4], [2, 4, 6, 8], [3, 6, 9, 12], [4, 8, 12, 16]]
>>> [[i * j] for i in range(1, 5) for j in range(1, 5)]
[[1], [2], [3], [4], [2], [4], [6], [8], [3], [6], [9], [12], [4], [8], [12], [16]]

Generátorová notácia s podmienkou:

>>> [i for i in range(100) if cs(i) == 5]        # cs() vypočíta ciferný súčet
[5, 14, 23, 32, 41, 50]

Tento zápis nájde všetky čísla z intervalu <0, 99>, ktorých ciferný súčet je 5. Doteraz by sme to zapisovali takto:

>>> vysl = []
>>> for i in range(100):
        if cs(i) == 5:
            vysl.append(i)
>>> vysl
[5, 14, 23, 32, 41, 50]

Generátorová notácia ako parameter do join (nepoužili sme tu hranaté zátvorky):

>>> ''.join(2 * znak for znak in 'python')
'ppyytthhoonn'

Pomocou tejto konštrukcie by sme vedeli zapísať aj mapovacie funkcie:

def mapuj(fun, zoznam):
    return [fun(prvok) for prvok in zoznam]

def filtruj(fun, zoznam):
    return [prvok for prvok in zoznam if fun(prvok)]

Všimnite si, že aj funkcia filtruj() využíva if, ktorý je vo vnútri generátorovej notácie.



Cvičenia

L.I.S.T.

Zbalené a rozbalené parametre

  1. Napíš funkciu max(), ktorá môže mať ľubovoľný počet parametrov. Funkcia zistí maximálnu hodnotu. Ak je prázdny počet parametrov, funkcia vyvolá chybu 'TypeError: max expected 1 arguments, got 0'. Nepoužívaj štandardnú funkciu max.

    • napr.

      >>> max(9, 13, 11)
      13
      >>> max(*'python')
      'y'
      >>> max()
      ...
      TypeError: max expected 1 arguments, got 0
      

  1. Napíš funkciu max() podobnú z úlohy (1), ktorá v prípade, že má zadaný iba jeden parameter a tento je list, tuple alebo set, vráti maximálnu hodnotu z tejto štruktúry. Inak bude pracovať ako v úlohe (1).

    • napr.

      >>> max(9, 13, 11)
      13
      >>> p = (9, 13, 11)
      >>> max(p)
      13
      >>> zoz = list('python')
      >>> max(zoz)
      'y'
      >>> max([])
      ...
      TypeError: max expected 1 arguments, got 0
      
    • nepoužívaj štandardnú funkciu max (môžeš otestovať, ako sa v týchto prípadoch správa štandardná funkcia max)


  1. Napíš funkciu zisti s ľubovoľným počtom parametrov, ktorá zistí (vráti True), či aspoň jeden z parametrov je n-tica (typ tuple).

    • napr.

      >>> zisti(1, 2, 3)
      False
      >>> t = zisti(())
      >>> t
      True
      

  1. Napíš funkciu spoj s ľubovoľným počtom parametrov, pričom všetky sú typu list. Výsledkom funkcie je zreťazenie všetkých týchto parametrov.

    • napr.

      >>> spoj(['a', 1], [], [('b', 2)])
      ['a', 1, ('b', 2)]
      >>> spoj()
      []
      

  1. Napíš funkciu vypis(postupnost), ktorá pomocou print vypíše všetky prvky danej postupnosti (zoznam, n-tica, …) do jedného riadka. Nepouži for-cyklus.

    • napr.

      >>>vypis([123, 'ahoj', (50, 120), 3.14])
      123 ahoj (50, 120) 3.14
      

Funkcie ako parametre

  1. Napíš funkciu retazec(zoznam). Funkcia vráti znakový reťazec, ktorý reprezentuje prvky zoznamu. Prvky zoznamu budú v reťazci oddelené znakom bodkočiarka. Nepouži žiadne cykly, ale namiesto toho štandardnú funkciu map a metódu join.

    • napr.

      >>> r = retazec([123, 'ahoj', (50, 120), 3.14])
      >>> r
      "123; 'ahoj'; (50, 120); 3.14"
      

  1. Napíš funkciu aplikuj, ktorej parametrami sú nejaké funkcie, okrem posledného parametra, ktorým je nejaká hodnota. Funkcia postupne zavolá všetky tieto funkcie s danou hodnotou, pričom každú ďalšiu funkciu aplikuje na predchádzajúci výsledok. Napr. aplikuj(f1, f2, f3, x) vypočíta f3(f2(f1(x))). Funkcia by mala pracovať pre ľubovoľný nenulový počet parametrov.

    • napr.

      >>> def rev(x): return x[::-1]
      >>> aplikuj(str, rev, int, 1074)
      4701
      >>> aplikuj(abs, lambda x: x+1, -17)
      18
      

  1. Do funkcie max() z úlohy (2) pridaj na koniec parameter key s náhradnou hodnotou None. Teraz bude funkcia pracovať takto:

    • v prípade, že key má hodnotu None, bude pracovať rovnako ako v úlohe (2)

    • inak predpokladáme, že key je daná funkcia s jedným parametrom, vďaka tejto funkcii bude max hľadať taký prvok x, pre ktorý je key(x) maximálny

    • zapíš:

      def max(*postupnost, key=None):
          ...
      
    • napr.

      >>> max(9, 13, 11)
      13
      >>> p = (9, 13, 11)
      >>> max(p)
      13
      >>> max(9, 13, 11, key=str)
      9
      
    • nepoužívaj štandardnú funkciu max (aj štandardná funkcia max funguje presne takto, môžeš to otestovať)


  1. Napíš funkciu najdlhsi(*retazec), ktorá pre ľubovoľný počet reťazcov vráti najdlhší z nich.

    • napr.

      >>> najdlhsi('a', '', 'bc', 'd', 'ef')
      'bc'
      >>> najdlhsi(*'mam rad programovanie v pythone'.split())
      'programovanie'
      
    • vyrieš najprv pomocou for-cyklu vo funkcii:

      def najdlhsi(*retazec):
          ...
          for ...
              ...
          return ...
      
    • vyrieš pomocou štandardnej funkcie max() a parametra key:

      def najdlhsi(*retazec):
          return max(...)
      
    • otestuj funkciu na textovom súbore: otvor nejaký textový súbor a zavolaj túto funkciu tak, aby ti vrátila najdlhší riadok z tohto súboru


  1. Napíš funkciu map2(fun, param1, param2), ktorá bude pracovať podobne ako funkcia mapuj() z prednášky, len funkcia fun očakáva dva parametre: jeden z postupnosti param1 a druhý z postupnosti param2. Ak majú tieto postupnosti rôznu dĺžku, tak berie len počet kratšej z nich. Nepouži štandradnú funkciu map.

    • napr.

      >>> def f(x, y): return x * y
      >>> map2(f, 'python', range(1, 6))
      ['p', 'yy', 'ttt', 'hhhh', 'ooooo']
      >>> map2(f, ('a', 4, (1, 2)), [3, 5, 2])
      ['aaa', 20, (1, 2, 1, 2)]
      

  1. Funkcia kruh() z prednášky funguje aj bez určovania farby výple

    • funkcia:

      def kruh(r, x, y, **param):
          canvas.create_oval(x-r, y-r, x+r, y+r, **param)
      
    • doplň funkciu tak, aby každý takto kreslený kruh bol vyplnený buď farbou udanou v parametroch alebo bude červený

    • napr.

      >>> kruh(30, 50, 100, width=3, fill='blue')   # modrý
      >>> kruh(100, 100, 100, outline='blue')       # červený
      

  1. Funkcia vykonaj() z prednášky spadne pri chybnom mene príkazu alebo chybnom parametri.

    • funkcia:

      def vykonaj():
          t = turtle.Turtle()
          p = {'fd': t.fd, 'rt': t.rt, 'lt': t.lt}
          while True:
              prikaz, parameter = input('> ').split()
              p[prikaz](int(parameter))
      
    • oprav ju tak, aby nespadla, ale vypísala sa o tom správa a ďalej sa pokračovalo

    • využi metódu get() pre slovník, ktorá vyrieši situáciu so zle zadaným menom príkazu tak, že sa zavolá anonymná funkcia, ktorá vypíše správu (napr. lambda: print('chybne meno prikazu', ...))

    • napr.

      >>> vykonaj()
      > fd 100
      > bk 50
      chybne meno prikazu 'bk'
      > rt 90
      > lt 45x
      chybny parameter '45x'
      >
      

Generátorová notácia

  1. Napíš funkciu mocniny(n), ktorá vráti zoznam druhých mocnín čísel od 1 do n.

    • napr.

      >>> mocniny(4)
      [1, 4, 9, 16]
      

  1. Napíš funkciu prevrat_slova(veta), ktorá vráti zadanú vetu tak, že každé slovo v nej bude otočené.

    • napr.

      >>> prevrat_slova('isiel macek do malacek')
      'leisi kecam od kecalam'
      
    • pokús sa celú funkciu zapísať len do jedného riadka (len jeden return)


  1. Napíš funkciu nahodne(n), ktorá vygeneruje n-prvkový zoznam náhodných čísel z intervalu <0, 2*n-1>. Použi generátorovú notáciu.

    • zapíš:

      def nahodne(n):
          return [...]
      

      napr.

      >>> nahodne(4)
      [5, 7, 2, 5]
      

  1. Napíš funkciu matica(n, m, hodnota=0), ktorá vygeneruje dvojrozmerný zoznam n riadkov po m stĺpcov. Prvkami matice budú zadané hodnoty. Použi generátorovú notáciu.

    • zapíš:

      def matica(n, m, hodnota=0):
          return [...]
      

      napr.

      >>> matica(3, 4, 1)
      [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
      

  1. Napíš funkciu matica_nahodne(n, m, rozsah=2), ktorá vygeneruje dvojrozmerný zoznam n riadkov po m stĺpcov. Prvkami matice budú háhodné hodnoty z rozsahu <0, rozsah>. Použi generátorovú notáciu.

    • zapíš:

      def matica(n, m, rozsah=2):
          return [...]
      

      napr.

      >>> matica(3, 4, 3)
      [[1, 0, 2, 1], [0, 0, 2, 1], [0, 2, 2, 1]]
      

  1. Napíš funkciu zadaj(text), ktorá si najprv vypýta so zadaným textom nejaký vstup (input()) a potom ho prerobí na zoznam celých čísel. V prípade chyby nespadne, ale vráti prázdny zoznam.

    • napr.

      >>> zoz = zadaj('zadaj cisla: ')
      zadaj cisla: 6 73 -8
      >>> zoz
      [6, 73, -8]
      >>> zoz = zadaj('zadaj: ')
      zadaj: 6 73 a -8
      >>> zoz
      []
      

  1. Už poznáme štandardnú funkciu enumerate(), ktorá z danej postupnosti vracia postupnosť dvojíc. Napíš vlastnú funkciu enumerate(postupnost), ktorá vytvorí takýto zoznam dvojíc (list s prvkami tuple): prvým prvkom bude poradové číslo dvojice a druhým prvkom prvok zo vstupnej postupnosti.

    • napr.

      def enumerate(postupnost):
          return ...
      
      >>> enumerate('python')
      [(0, 'p'), (1, 'y'), (2, 't'), (3, 'h'), (4, 'o'), (5, 'n')]
      

  1. Napíš funkciu zip(p1, p2), ktorá z dvoch postupností rovnakých dĺžok vytvorí zoznam zodpovedajúcich dvojíc, t.j. zoznam v ktorom prvým prvkom bude dvojica prvých prvkov postupností, druhým prvkom dvojica druhých prvkov, …

    • napr.

      >>> zip('python', [2, 3, 5, 7, 11, 13])
      [('p', 2), ('y', 3), ('t', 5), ('h', 7), ('o', 11), ('n', 13)]
      
    • pokús sa to zapísať tak, aby to fungovala aj pre postupnosti rôznych dĺžok: vtedy vytvorí len toľko dvojíc, koľko je prvkov v kratšej z týchto postupností, napr.

      >>> zip('python', [2, 3, 5, 7, 11])
      [('p', 2), ('y', 3), ('t', 5), ('h', 7), ('o', 11)]
      
    • pokús sa to zapísať tak, aby funkcia fungovala pre ľubovoľný počet ľubovoľne dlhých postupností, napr.

      >>> zip('python', [2, 3, 5, 7, 11], 'abcd')
      [('p', 2, 'a'), ('y', 3, 'b'), ('t', 5, 'c'), ('h', 7, 'd')]
      
    • v Pythone existuje štandardná funkcia zip(), ktorá funguje skoro presne ako táto posledná verzia funkcie, len jej výsledkom nie je zoznam, ale opäť postupnosť (dá sa prechádzať for-cyklom, alebo vypísať pomocou print(*zip(...)), alebo previesť na zoznam pomocou list(zip(...)))

    • funkciu zip() (štandardnú alebo tú vašu) môžeme použiť aj vo for-cykle, napr.

      >>> ''.join(x*y for x, y in zip('python', [2, 3, 2, 1, 3, 2]))
      'ppyyytthooonn'
      


9. Domáce zadanie

L.I.S.T.

Robot Mravec

Máme cvičeného malého robota mravca, ktorý sa pohybuje po štvorcovej sieti a pritom vie pred sebou tlačiť malé kocky. Mravec poslúcha na povely 'l' (vľavo), 'p' (vpravo), 'h' (hore), 'd' (dole), pričom sa v danom smere posunie na susedné políčko štvorcovej siete. Ak sa v danom smere nachádza kocka, tak ju pred sebou v tomto smere potlačí. Ak je v danom smere tesne za sebou viac kociek, tak ich tlačí všetky. Mravec z plochy nikdy nevyjde, hoci kocky, ktoré pred sebou tlačí, z plochy vypadnúť môžu.

Na každej kocke je zapísané jedno písmeno. Samotná štvorcová sieť vie indikovať, či sa na niektorých špeciálnych políčkach nachádzajú kocky s písmenami a vie zistiť množinu písmen na týchto kockách.

Zadanie štvorcovej siete s počiatočným rozložením kociek je v textovom súbore. V prvých riadkoch tohto súboru sa nachádzajú riadky štvorcovej siete (všetky sú rovnako dlhé), pričom špeciálne políčka sú označené znakom '+' a zvyšné sú označené znakom '.'. Za štvorcovou sieťou je v súbore jeden riadok prázdny a za tým nasleduje postupnosť súradníc kociek s písmenami - v každom ďalšom riadku je trojica: písmeno a dve celé čísla. Táto trojica označuje písmeno na kocke a jej pozíciu v ploche: riadok a stĺpec (číslujeme od 0).

Naprogramujte triedu Mravec:

class Mravec:
    def __init__(self, meno_suboru):
        ...

    def __str__(self):
        return ''

    def start(self, riadok, stlpec):
        ...

    def rob(self, prikazy):
        ...

    def zisti(self):
        return set()

kde

  • init prečíta súbor - mravec tam zatiaľ nie je

  • start položí mravca na zadaný riadok a stĺpec

  • __str__ vráti znakovú reprezentáciu plochy: pozíciu mravca zapíšte znakom '@' a špeciálne políčka, na ktorých sa nenachádza ani mravec ani kocka, zapíšte znakom '+', ostatné políčka zapíšte znakmi písmen, resp. znakom '.'

  • rob dostáva jeden povel, alebo postupnosť za sebou nasledujúcich povelov, pričom povel je jedno z písmen 'l', 'p', 'h' alebo 'd'; mravec sa postupne pohybuje v daných smeroch, pričom pred sebou môže tlačiť kocky; povely, ktoré sa nedajú vykonať, ignoruje

  • metóda zisti vráti množinu písmen na špeciálnych políčkach hracej plochy

Napr. pre súbor:

.....
..+.+
.++..
.....

D 2 2
C 2 1
A 1 1
B 1 3

takýto test:

if __name__ == '__main__':
    m = Mravec('subor1.txt')
    print(m)
    print('zisti =', m.zisti())
    m.start(1, 0)
    m.rob('pp')
    print(m)
    print('zisti =', m.zisti())
    m.rob('dl')
    print(m)
    print('zisti =', m.zisti())

vypíše:

.....
.A+B+
.CD..
.....
zisti = {'D', 'C'}
.....
..@AB
.CD..
.....
zisti = {'B', 'D', 'C'}
.....
..+AB
C@+..
..D..
zisti = {'B'}

Z úlohového servera L.I.S.T. si stiahnite kostru programu riesenie9.py. Pozrite si testovacie dáta v súboroch 'subor1.txt', 'subor2.txt', 'subor3.txt', …, ktoré bude používať testovač.

Váš odovzdaný program s menom riesenie9.py musí začínať tromi riadkami komentárov:

# 9. zadanie: mravec
# autor: Janko Hraško
# datum: 17.12.2018

Projekt riesenie9.py (bez dátových súborov) odovzdávajte na úlohový server https://list.fmph.uniba.sk/ najneskôr do 23:00 23. decembra, kde ho môžete nechať otestovať. Testovač bude spúšťať vašu funkciu s rôznymi vstupmi. Odovzdať projekt aj ho testovať môžete ľubovoľný počet krát. Môžete zaň získať 10 bodov.