9. Zoznamy a n-tice (tuple)


Pripomeňme si z minulej prednášky, ako dve premenné referencujú na ten istý zoznam. Priraďme:

>>> a = [2, 3, 5, 7, 11]
>>> b = a
>>> b[3] = 'kuk'
>>> a
[2, 3, 5, 'kuk', 11]

Menili sme obsah premennej b (zmenili sme jej prvok s indexom 3), ale tým sa zmenil aj obsah premennej a. Totiž obe premenné referencujú na ten istý zoznam:

_images/08_04.png

Keď teraz meníme obsah premennej b pomocou mutable operácií, zmení sa aj obsah premennej a:

_images/08_05.png

Toto samozrejme platí aj vo funkciách a aj s parametrami funkcií.


Používanie zoznamov

Pozrime si takéto školské príklady, v ktorých dostávame chybné riešenie vďaka tomu, že si neuvedomíme, že dve premenné referencujú na ten istý zoznam:

  • napíšeme funkciu, ktorá ako parameter dostáva zoznam čísel a jej úlohou je z tohto zoznamu vypísať dva zoznamy - zoznam záporných a zoznam nezáporných čísel; elegantným riešením by mohlo byť:

    def vypis_dva_zoznamy(zoznam):
        zoz1 = zoz2 = []
        for prvok in zoznam:
            if prvok < 0:
                zoz1.append(prvok)
            else:
                zoz2.append(prvok)
        print('zaporne =', zoz1)
        print('nezaporne =', zoz2)
    

    Prekvapením môže byť otestovanie tejto funkcie:

    >>> vypis_dva_zoznamy([5, 7.3, 0, -3, 0.0, 1, -3.14])
    zaporne = [5, 7.3, 0, -3, 0.0, 1, -3.14]
    nezaporne = [5, 7.3, 0, -3, 0.0, 1, -3.14]
    

    Všimnite si priradenie zoz1 = zoz2 = []. Týmto priradením obe premenné získali referenciu v pamäti na ten istý zoznam. Keďže ďalej s oboma premenným pracujeme len pomocou mutable operácií (metóda append()), stále sa uchovávajú referencie na ten istý zoznam. Riešením by mohlo byť, buď opraviť úvodnú inicializáciu premenných napríklad na zoz1, zoz2 = [], [], alebo používanie immutable operácií (namiesto zoz1.append(prvok) použijeme zoz1 = zoz1 + [prvok]).

  • ďalšia funkcia z daného reťazca vytvorí kópiu, v ktorej ale vynechá všetky hodnoty, ktoré sú reťazcami (ponechá napríklad čísla):

    def zoznam_bez_retazcov(zoznam):
        kopia = zoznam
        for prvok in zoznam:
            if type(prvok) == str:
                kopia.remove(prvok)
        return kopia
    

    Riešenie je dostatočne čitateľné. Otestujme:

    >>> nejaky = [2, '+', 5, 'je', 7]
    >>> zoznam_bez_retazcov(nejaky)
    [2, 5, 7]
    >>> nejaky
    [2, 5, 7]
    >>> nejaky = [1, 'prvy', 'druhy', 'treti', 'stvrty']
    >>> zoznam_bez_retazcov(nejaky)
    [1, 'druhy', 'stvrty']
    >>> nejaky
    [1, 'druhy', 'stvrty']
    

    Prvý test dal dobrý výsledok, ale funkcia pokazila aj pôvodný zoznam. V druhom teste dokonca dostávame aj chybný výsledok aj pokazený vstupný zoznam. Aspoň trochu skúsenejší pythonista vidí problém v priradení kopia = zoznam. Premenná kopia referencuje ten istý zoznam ako je v parametri zoznam. Preto vyhadzovanie prvkov z premennej kopia ich bude vyhadzovať aj zo zoznam. Stačí opraviť úvodné priradenie napríklad na kopia = list(zoznam) a tým sa naozaj do kopia vyrobí nový zoznam. Teraz to už funguje, hoci skúsenejšiemu pythonistovi sa nemusí páčiť konštrukcia kopia.remove(prvok). Táto metóda vyhľadá v zozname kopia daný prvok a jeho prvý výskyt vyhodí. Skôr by tu (namiesto vyhadzovania) zapísal skladanie nového reťazca pomocou append() ale už bez reťazcov, napríklad:

    def zoznam_bez_retazcov(zoznam):
        novy = []
        for prvok in zoznam:
            if type(prvok) != str:
                novy.append(prvok)
        return novy
    
  • funkcia vypis má vypísať prvky zoznamu tak, aby v každom riadku (možno okrem posledného) bol presne zadaný počet prvkov; jedno zo študentských riešení vyzerá takto:

    def vypis(zoznam, pocet):
        p = 0
        while zoznam:
            print(zoznam.pop(0), end=' ')
            p += 1
            if p%pocet == 0:
                print()
    

    Hoci toto riešenie dáva správne výsledky, má jeden nepríjemný efekt: po výpise zoznamu zistíme, že obsah zoznamu sa zničil. Napríklad:

    >>> a = list(range(5, 20, 2))
    >>> a
    [5, 7, 9, 11, 13, 15, 17, 19]
    >>> vypis(a, 3)
    5 7 9
    11 13 15
    17 19
    >>> a
    []
    

    Táto funkcia, hoci len vypisovala prvky zoznamu, tento zoznam vyčistila nielen vo vnútri funkcie (vďaka zoznam.pop(0)), ale tým aj premennú, ktorá tento zoznam referencovala pred volaním funkcie. Mutable operácia pop na parameter vždy zmení obsah tohto parametra.

  • ďalšia funkcia bude zisťovať, či je daný zoznam usporiadaný vzostupne, t.j. či pre každú dvojicu prvkov v zozname, ktoré sú vedľa seba platí, že prvý z nich nie je väčší ako druhý; jedným z riešení je postupne prechádzať celým zoznamom a kontrolovať túto podmienku:

    def vzostupne(zoznam):
        for i in range(len(zoznam) - 1):
            if zoznam[i] > zoznam[i + 1]:
                return False
        return True
    

    Toto je korektné riešenie, ale môžeme to vyriešiť aj trochu inak: ak mám funkciu, ktorá vie preusporiadať prvky v zozname tak, že budú vo vzostupnom poradí, potom stačí prekontrolovať, či pôvodný zoznam má prvky v rovnakom poradí ako po preusporiadaní (utriedení). Začiatočníci chcú často využiť metódu sort a niekedy urobia takúto chybu:

    def vzostupne(zoznam):
        zoz1 = zoznam.sort()
        return zoz1 == zoznam
    

    Tu zrejme predpokladajú, že metóda sort z pôvodného zoznamu vyrobí vzostupne usporiadanú postupnosť a tú potom celú porovnajú s pôvodným zoznamom. Táto funkcia ale vždy vráti False, lebo metóda sort nevyrába novú usporiadanú postupnosť, ale preusporiada v pamäti (mutable) pôvodný zoznam a nič nevracia - v skutočnosti vráti hodnotu None. Tá sa potom priradí do premennej zoz1 a porovná s usporiadným zoznam (čo je zrejme False).

    Niektorí začiatočníci to chcú opraviť takto:

    def vzostupne(zoznam):
        zoz1 = zoznam
        zoz1.sort()
        return zoz1 == zoznam
    

    Teda asi najprv si chcú vytvoriť kópiu pôvodného zoznamu (aby si ho nepokazili triedením pomocou sort) a až túto kópiu budú triediť metódou sort. Idea nie je zlá, ale realizácia je opäť chybná: priradením zoz1 = zoznam nevzniká kópia, ale iba ďalšia referencia na ten istý zoznam. Ďalšie volanie metódy sort usporiada pôvodný zoznam a na koniec skontroluje, či sa rovná samému sebe (teda to vždy bude True).

    Už vieme, že kópia zoznamu sa najlepšie robí buď priradením zoz1 = zoznam[:] alebo zoz1 = list(zoznam). Ak ale namiesto metódy sort využijeme iný variant triedenia, riešenie sa ešte zjednoduší. Triediť môžeme pomocou funkcie sorted, ktorá dostane ako parameter ľubovoľnú iterovateľnú postupnosť (napríklad zoznam, reťazec, …) a vráti uzporiadaný zoznam. Pôvodná postupnosť sa pritom nemení (je to immutable). Našu funkciu môžeme teraz zapísať takto:

    def vzostupne(zoznam):
        zoz1 = sorted(zoznam)
        return zoz1 == zoznam
    

    alebo ešte úspornejšie:

    def vzostupne(zoznam):
        return sorted(zoznam) == zoznam
    
  • nasledovná funkcia by mala vyčistiť obsah zadaného zoznamu:

    def cisti(zoznam):
        zoznam = []
    

    Samozrejme, že to nefunguje:

    >>> ab = [1, 'dva', 3.14]
    >>> cisti(ab)
    >>> ab
    [1, 'dva', 3.14]
    

    Volanie funkcie cisti() vytvorí lokálnu premennú zoznam a ten dostáva hodnotu skutočného parametra, teda referenciu na zoznam [1, 'dva', 3.14]. Lenže priradenie zoznam = [] je immutable operácia, teda do lokálnej premennej zoznam priradíme novú referenciu na prázdny zoznam. Tým sa ale nezmení referencia pôvodnej premennej ab, ktorá bola skutočným parametrom volania funkcie cisti(). Riešením by asi bolo použitie nejakej mutable operácie, ktorá vyčistí obsah zoznamu, napríklad:

    def cisti(zoznam):
        zoznam.clear()
    

    Zapamätajte si, že priradenie do premennej pracuje vždy s lokálnou premennou - buď ju vytvorí (ak ešte neexistovala), alebo jej zmení hodnotu.

Predpokladáme, že takéto príklady vás presvedčia, že pri práci so zoznamami vo funkciách musíte byť veľmi ostražití a uvedomovať si dôsledky referencií na zoznamy.

S referenciami môžeme mať problémy nielen v situáciách, keď dve premenné odkazujú na ten istý zoznam, ale aj v prípadoch, keď zoznam obsahuje nejaký podzoznam, t.j. referenciu na iný zoznam. Vyskúšajme:

>>> x = ['prvy', [2, 3], 'styri']
>>> y = x[1]
>>> y
[2, 3]
>>> y.append('kuk')
>>> y
[2, 3, 'kuk']

Zatiaľ to vyzerá dobre: do premennej y sme priradili prvok zoznamu x s indexom 1. Vidíme, že je to dvojprvkový zoznam [2, 3] a preto do neho na koniec pridáme nejaký reťazec pomocou y.append('kuk'). Aj toto funguje dobre: v premennej y je teraz [2, 3, 'kuk']. Lenže teraz sa zmenil aj pôvodný zoznam x:

>>> x
['prvy', [2, 3, 'kuk'], 'styri']

Pekne tento efekt vidieť aj na ďalšom príklade:

>>> a = ['a']
>>> zoz = [a, a, a]
>>> zoz
[['a'], ['a'], ['a']]

My už vieme, že zoznam zoz má tri prvky a všetky tri sú referenciami na ten istý mutable objekt. Ak sa tento zmení mutable operáciou, zmenia sa obsahy všetkých troch prvkov zoznamu:

>>> a.insert(0, 'b')
>>> a
['b', 'a']
>>> zoz
[['b', 'a'], ['b', 'a'], ['b', 'a']]

Nielen začiatočníka prekvapí takáto chyba: plánujeme vytvoriť 100-prvkový zoznam, ktorý bude obsahovať len prázdne zoznamy []. Potom budeme niektoré z týchto zoznamov meniť, napríklad:

>>> zoz = [[]] * 100
>>> zoz[3].append(1)
>>> zoz[7].insert(0, 0)
>>> zoz[9] += [2]
>>> zoz
[[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2],
[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2],
[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2],
[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2],
[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2],
...]

Vidíme, že napriek tomu, že sme menili len niektoré tri prvky zoznamu zoz, malo to vplyv na všetkých 100 prvkov.

Pokračujme s týmto zoznamom. Zrejme platí:

>>> zoz[2] == zoz[3]
True
>>> zoz[3] = [0, 1, 2]
>>> zoz[2] == zoz[3]
True

Obsahy 2. a 3. prvkov sú rovnaké aj keď nemajú tú istú referenciu. Môžeme to vidieť, keď vymažeme obsah, napríklad 0. prvku:

>>> zoz[0].clear()
>>> zoz
[[], [], [], [0, 1, 2], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
[], []]

Všetky prvky s rovnakou referenciou sa vymazali okrem 3., ktorý mal rovnaký obsah, ale inú referenciu. V niektorých situáciách nám pomôže nová operácia is, pomocou ktorej vieme zistiť, či majú dva objekty identickú referenciu. Napríklad:

>>> zoz[1] = []
>>> zoz[0] == zoz[1]
True
>>> zoz[0] is zoz[1]
False
>>> zoz[0] is zoz[2]
True

Zmenili sme 1. prvok na prázdny zoznam, preto platí zoz[0] == zoz[1], ale sú to rôzne referencie, preto neplatí zoz[0] is zoz[1].

Zoznamy a reťazce

V Pythone existuje niekoľko metód, v ktorých zoznamy spolupracujú so znakovými reťazcami. Ukážeme dve reťazcové metódy, ktoré budeme odteraz veľmi intenzívne využívať.

metóda split()

metóda split()

Keďže je to reťazcová metóda, má tvar:

reťazec.split()

Metóda rozbije daný reťazec na samostatné reťazce a uloží ich do zoznamu (teda vracia zoznam reťazcov). Predpokladáme, že tieto podreťazce sú navzájom oddelené „medzerovými“ znakmi (medzera, znak konca riadku, tabulátor). V helpe (napríklad help(''.split)) sa môžete dozvedieť ďalšie možnosti tejto funkcie.

Najlepšie to pochopíte na niekoľkých príkladoch. Metóda split() sa často využíva pri rozdelení prečítaného reťazca zo vstupu (input() alebo subor.readline()) na viac častí, napríklad:

>>> ret = input('zadaj 2 čísla: ')
zadaj 2 čísla: 15 999
>>> zoz = ret.split()
>>> zoz
['15', '999']
>>> a, b = zoz
>>> ai, bi = int(zoz[0]), int(zoz[1])
>>> a, b, ai, bi
('15', '999', 15, 999)

Niekedy môžeme vidieť aj takýto zápis:

>>> meno, priezvisko = input('zadaj meno a priezvisko: ').split()
zadaj meno a priezvisko: Janko Hraško
>>> meno
'Janko'
>>> priezvisko
'Hraško'

Metóda split() môže dostať ako parameter oddeľovač, napríklad ak sme prečítali čísla oddelené čiarkami:

sucet = 0
for prvok in input('zadaj čísla: ').split(','):
    sucet += int(prvok)
print('ich súčet je', sucet)
zadaj čísla: 10,20,30,40
ich súčet je 100

metóda join()

metóda join()

Opäť je to reťazcová metóda. Má tvar:

oddeľovač.join(zoznam)

Metóda zlepí všetky reťazce z daného zoznamu reťazcov do jedného, pričom ich navzájom oddelí uvedeným oddeľovačom, t. j. nejakým zadaným reťazcom. Ako zoznam môžeme uviesť ľubovoľnú postupnosť (iterovateľný objekt) reťazcov.

Ukážme to na príklade:

>>> zoz = ['prvý', 'druhý', 'tretí']
>>> zoz
['prvý', 'druhý', 'tretí']
>>> ''.join(zoz)
'prvýdruhýtretí'
>>> '...'.join(zoz)
'prvý...druhý...tretí'
>>> list(str(2013))
['2', '0', '1', '3']
>>> '.'.join(list(str(2013)))
'2.0.1.3'
>>> '.'.join('Python')
'P.y.t.h.o.n'

Preštudujte:

>>> veta = 'kto druhemu jamu kope'
>>> ' '.join(veta[::-1].split()[::-1])
'otk umehurd umaj epok'

>>> ' '.join(input('? ').split()[::-1])
? viem ze neviem javu ale viem python
'python viem ale javu neviem ze viem'

n-tice (tuple)

Sú to vlastne len nemeniteľné (immutable) zoznamy. Pythonovský typ tuple dokáže robiť skoro všetko to isté ako list okrem mutable operácií. Takže to najprv zhrňme a potom si ukážeme, ako to pracuje:

  • operácia + na zreťazovanie (spájanie dvoch n-tíc)

  • operácia * na viacnásobné zreťazovanie (viacnásobné spájanie jednej n-tice)

  • operácia in na zisťovanie, či sa nejaká hodnota nachádza v n-tici

  • operácia indexovania [i] na zistenie hodnoty prvku na zadanom indexe

  • operácia rezu [i:j:k] na zistenie hodnoty nejakej podčasti n-tice

  • relačné operácie ==, !=, <, <=, >, >= na porovnávanie obsahu dvoch n-tíc

  • metódy count() a index() na zisťovanie počtu výskytov, resp. indexu prvého výskytu

  • prechádzanie n-tice pomocou for-cyklu (iterovanie)

  • štandardné funkcie len(), sum(), min(), max() ktoré zisťujú niečo o prvkoch n-tice

Takže n-tice, tak ako aj zoznamy sú štruktúrované typy, t.j. sú to typy, ktoré obsahujú hodnoty nejakých (možno rôznych) typov (sú to tzv. kolekcie):

  • konštanty typu n-tica uzatvárame do okrúhlych zátvoriek a navzájom oddeľujeme čiarkami

  • funkcia len() vráti počet prvkov n-tice, napríklad:

    >>> stred = (150, 100)
    >>> zviera = ('slon', 2013, 'gray')
    >>> print(stred)
    (150, 100)
    >>> print(zviera)
    ('slon', 2013, 'gray')
    >>> nic = ()
    >>> print(nic)
    ()
    >>> len(stred)
    2
    >>> len(zviera)
    3
    >>> len(nic)
    0
    >>> type(stred)
    <class 'tuple'>
    

Vidíte, že n-tica môže byť aj prázdna, označujeme ju () a vtedy má počet prvkov 0 (teda len(()) je 0). Ak n-tica nie je prázdna, hodnoty sú navzájom oddelené čiarkami.

n-tica s jednou hodnotou

n-ticu s jednou hodnotou nemôžeme zapísať takto:

>>> p = (12)
>>> print(p)
12
>>> type(p)
<class 'int'>
>>>

Ak zapíšeme ľubovoľnú hodnotu do zátvoriek, nie je to n-tica (v našom prípade je to len jedno celé číslo). Pre jednoprvkovú n-ticu musíme do zátvoriek zapísať aj čiarku:

>>> p = (12,)
>>> print(p)
(12,)
>>> len(p)
1
>>> type(p)
<class 'tuple'>
>>>

Pre Python sú dôležitejšie čiarky ako zátvorky. V mnohých prípadoch si Python zátvorky domyslí (čiarky si nedomyslí nikdy):

>>> stred = 150, 100
>>> zviera = 'slon', 2013, 'gray'
>>> p = 12,
>>> print(stred)
(150, 100)
>>> print(zviera)
('slon', 2013, 'gray')
>>> print(p)
(12,)

Uvedomte si rozdiel medzi týmito dvoma priradeniami:

>>> a = 3.14
>>> b = 3,14
>>> print(a, type(a))
3.14 <class 'float'>
>>> print(b, type(b))
(3, 14) <class 'tuple'>

Operácie s n-ticami

Operácie fungujú presne rovnako ako fungovali s reťazcami a zoznamami:

  • operácia + zreťazí dve n-tice, to znamená, že vyrobí novú n-ticu, ktorá obsahuje najprv všetky prvky prvej n-tice a za tým všetky prvky druhej n-tice; zrejme oba operandy musia byť n-tice: nemôžeme zreťazovať n-ticu s hodnotou iného typu

  • operácia * zadané číslo-krát zreťazí jednu n-ticu, to znamená, že vyrobí novú n-ticu, ktorá požadovaný-krát obsahuje všetky prvky zadanej n-tice; operácia viacnásobného zreťazovania má jeden operand typu n-tica a druhý musí byť celé číslo

  • operácia in vie zistiť, či sa nejaký prvok nachádza v n-tici

Napríklad:

>>> stred = (150, 100)
>>> zviera = ('slon', 2013, 'gray')
>>> stred + zviera
(150, 100, 'slon', 2013, 'gray')
>>> zviera + stred
('slon', 2013, 'gray', 150, 100)
>>> stred * 5
(150, 100, 150, 100, 150, 100, 150, 100, 150, 100)
>>> 50 * (1)
50
>>> 50 * (1,)
(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
>>> 50 * (1, 2)
(1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2)

n-tica môže obsahovať ako svoje prvky aj iné n-tice:

>>> stred = (150, 100)
>>> p = ('stred', stred)
>>> p
('stred', (150, 100))
>>> len(p)
2
>>> p = (stred, stred, stred, stred)
>>> p
((150, 100), (150, 100), (150, 100), (150, 100))
>>> 4 * (stred,)
((150, 100), (150, 100), (150, 100), (150, 100))
>>> 4 * (stred)                                     # čo je to isté ako 4 * stred
(150, 100, 150, 100, 150, 100, 150, 100)
>>> 4 * stred,
((150, 100, 150, 100, 150, 100, 150, 100),)

Operátorom in vieme zistiť, či sa nejaká hodnota nachádza v n-tici ako jeden jeho prvok. Takže:

>>> p = (stred, stred, stred, stred)
>>> p
((150, 100), (150, 100), (150, 100), (150, 100))
>>> stred in p
True
>>> 150 in p
False
>>> 150 in stred
True
>>> zviera = ('slon', 2013, 'gray')
>>> 2013 in zviera
True
>>> (2013, 'gray') in zviera
False

Funkcia tuple()

Vyrobí n-ticu z ľubovoľnej postupnosti (z iterovateľného objektu, t.j. takého objektu, ktorý sa dá prechádzať for-cyklom), napríklad zo znakového reťazca, zo zoznamu, vygenerovanej postupnosti celých čísel pomocou range(), ale iterovateľný je aj otvorený textový súbor. Znakový reťazec funkcia tuple() rozoberie na znaky:

>>> tuple('Python')
('P', 'y', 't', 'h', 'o', 'n')

Vytvorenie n-tice pomocou range():

>>> tuple(range(1, 16))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
>>> a = tuple(range(1000000))
>>> len(a)
1000000

Podobne môžeme skonštruovať n-ticu z textového súboru. Predpokladajme, že súbor obsahuje tieto 4 riadky:

prvy
druhy
treti
stvrty

potom

>>> with open('abc.txt') as t:
        obsah = tuple(t)

>>> obsah
('prvy\n', 'druhy\n', 'treti\n', 'stvrty\n')

Riadky súboru sa postupne stanú prvkami n-tice.

for-cyklus s n-ticami

for-cyklus je programová konštrukcia, ktorá postupne prechádza všetky prvky nejakého iterovateľného objektu. Doteraz sme sa stretli s iterovaním pomocou funkcie range(), prechádzaním prvkov reťazca str a zoznamu list, aj celých riadkov textového súboru. Ale už od 2. prednášky sme používali aj takýto zápis:

for i in 2, 3, 5, 7, 11, 13:
    print('prvocislo', i)

V tomto zápise už teraz vidíme n-ticu (tuple): 2, 3, 5, 7, 11, 13. Len sme tomu nemuseli dávať zátvorky. Keďže aj n-tica je iterovateľný objekt, Môžeme ju používať vo for-cykle rovnako ako iné iterovateľné typy. To isté, ako predchádzajúci príklad, by sme zapísali napríklad aj takto:

cisla = (2, 3, 5, 7, 11, 13)
for i in cisla:
    print('prvocislo', i)

Keďže teraz už vieme manipulovať s n-ticami, môžeme zapísať napríklad:

>>> rozne = ('retazec', (100, 200), 3.14, len)
>>> for prvok in rozne:
        print(prvok, type(prvok))

retazec <class 'str'>
(100, 200) <class 'tuple'>
3.14 <class 'float'>
<built-in function len> <class 'builtin_function_or_method'>

Tu vidíme, že prvkami n-tice môžu byť najrôznejšie objekty, hoci aj funkcie (tu je to štandardná funkcia len).

Pomocou operácií s n-ticami vieme zapísať aj zaujímavejšie postupnosti čísel, napríklad:

>>> for i in 10 * (1,):
        print(i, end=' ')

1 1 1 1 1 1 1 1 1 1
>>> for i in 10 * (1, 2):
         print(i, end=' ')

1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
>>> for i in 10 * tuple(range(10)):
         print(i, end=' ')

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
3 4 5 6 7 8 9
>>> for z in 'Python':
        print(z, end=' ')

P y t h o n
>>> for z in 'Python',:
        print(z, end=' ')

Python

Ďalej pomocou for-cyklu vieme n-tice skladať podobne, ako sme to robili so zoznamami. V nasledovnom príklade vytvoríme n-ticu zo všetkých deliteľov nejakého čísla:

cislo = int(input('zadaj cislo: '))
delitele = ()
for i in range(1, cislo+1):
    if cislo % i == 0:
        delitele = delitele + (i,)
print('delitele', cislo, 'su', delitele)

po spustení:

zadaj cislo: 124
delitele 124 su (1, 2, 4, 31, 62, 124)

Všimnite si, ako sme pridali jeden prvok na koniec n-tice: delitele = delitele + (i,). Museli, sme vytvoriť jednoprvkovú n-ticu (i,) a tú sme zreťazili s pôvodnou n-ticou delitele. Mohli sme to zapísať aj takto: delitele += (i,). Zrejme, keby sme toto riešili pomocou zoznamov, použili by sme metódu append().

Funkcia enumerate()

Už poznáme použitie štandardnej funkcie enumerate(). Vieme ju využiť aj pre n-tice, napríklad:

zoz = (2, 3, 5, 7, 9, 11, 13, 17, 19)
for ix, pr in enumerate(zoz):
    print(f'{ix}. prvocislo je {pr}')
0. prvocislo je 2
1. prvocislo je 3
2. prvocislo je 5
3. prvocislo je 7
4. prvocislo je 9
5. prvocislo je 11
6. prvocislo je 13
7. prvocislo je 17
8. prvocislo je 19

Funkcia enumerate() v skutočnosti z jednej ľubovoľnej postupnosti vygeneruje postupnosť dvojíc (ktoré sú už typu tuple). Túto postupnosť ďalej preliezame for-cyklom. Mohli by sme to zapísať aj takto:

zoz = (2, 3, 5, 7, 9, 11, 13, 17, 19)
for dvojica in enumerate(zoz):
    ix, pr = dvojica
    print(f'{ix}. prvocislo je {pr} ... dvojica = {dvojica}')

po spustení:

0. prvocislo je 2 ... dvojica = (0, 2)
1. prvocislo je 3 ... dvojica = (1, 3)
2. prvocislo je 5 ... dvojica = (2, 5)
3. prvocislo je 7 ... dvojica = (3, 7)
4. prvocislo je 9 ... dvojica = (4, 9)
5. prvocislo je 11 ... dvojica = (5, 11)
6. prvocislo je 13 ... dvojica = (6, 13)
7. prvocislo je 17 ... dvojica = (7, 17)
8. prvocislo je 19 ... dvojica = (8, 19)

Ak by sme skúmali výsledok z tejto funkcie, dozvedeli by sme sa:

>>> enumerate(zoz)
<enumerate object at 0x02F59B48>
>>> list(enumerate(zoz))
[(0, 2), (1, 3), (2, 5), (3, 7), (4, 9), (5, 11), (6, 13), (7, 17), (8, 19)]
>>> tuple(enumerate(zoz))
((0, 2), (1, 3), (2, 5), (3, 7), (4, 9), (5, 11), (6, 13), (7, 17), (8, 19))

čo sú naozaj postupnosti skutočných dvojíc.

Indexovanie

n-tice indexujeme rovnako ako sme indexovali zoznamy:

  • prvky postupnosti môžeme indexovať v [] zátvorkách, pričom index musí byť od 0 až po počet prvkov-1

  • pomocou rezu (slice) vieme indexovať časť n-tice (niečo ako podreťazec) tak, že [] zátvoriek zapíšeme aj dvojbodku:

    • ntica[od:do] n-tica z prvkov s indexmi od až po do-1

    • ntica[:do] n-tica z prvkov od začiatku až po prvok s indexom do-1

    • ntica[od:] n-tica z prvkov s indexmi od až po koniec n-tice

    • ntica[od:do:krok] n-tica z prvkov s indexmi od až po do-1, pričom berieme každý krok prvok

Niekoľko príkladov

>>> prvo = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
>>> prvo[2]
5
>>> prvo[2:5]
(5, 7, 11)
>>> prvo[4:5]
(11,)
>>> prvo[:5]
(2, 3, 5, 7, 11)
>>> prvo[5:]
(13, 17, 19, 23, 29)
>>> prvo[::2]
(2, 5, 11, 17, 23)
>>> prvo[::-1]
(29, 23, 19, 17, 13, 11, 7, 5, 3, 2)

Vidíme, že s n-ticami pracujeme veľmi podobne ako sme pracovali so znakovými reťazcami a so zoznamami. Keď sme ale do znakového reťazca chceli pridať jeden znak (alebo aj viac), museli sme to robiť rozoberaním a potom skladaním:

>>> prvo = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
>>> prvo = prvo[:5] + ('fuj',) + prvo[5:]
>>> prvo
(2, 3, 5, 7, 11, 'fuj', 13, 17, 19, 23, 29)

Pred 5-ty prvok vloží nejaký znakový reťazec.

Rovnako nemôžeme zmeniť ani hodnotu nejakého znaku reťazca obyčajným priradením:

>>> ret = 'Python'
>>> ret[2] = 'X'
...
TypeError: 'str' object does not support item assignment
>>> ret = ret[:2] + 'X' + ret[3:]
>>> ret
'PyXhon'
>>> ntica = (2, 3, 5, 7, 11, 13)
>>> ntica[2] = 'haha'
...
TypeError: 'tuple' object does not support item assignment
>>> ntica = ntica[:2] + ('haha',) + ntica[3:]
>>> ntica
(2, 3, 'haha', 7, 11, 13)

Všimnite si, že Python vyhlásil rovnakú chybu pre tuple ako pre str. Hovoríme, že ani reťazce ani n-tice nie sú meniteľné (teda sú immutable).

Porovnávanie n-tíc

Porovnávanie n-tíc je veľmi podobné ako porovnávanie reťazcov a zoznamov. Pripomeňme si, ako je to pri zoznamoch:

  • postupne porovnáva i-te prvky oboch zoznamov, kým sú rovnaké; pri prvej nerovnosti je výsledkom porovnanie týchto dvoch hodnôt

  • ak je pri prvej nezhode v prvom zozname menšia hodnota ako v druhom, tak prvý zoznam je menší ako druhý

  • ak je prvý zoznam kratší ako druhý a zodpovedajúce prvky sa zhodujú, tak prvý zoznam je menší ako druhý

Hovoríme tomu lexikografické porovnávanie.

Teda aj pri porovnávaní n-tíc sa budú postupne porovnávať zodpovedajúce si prvky a pri prvej nerovnosti sa skontroluje, ktorý z týchto prvkov je menší. Treba tu ale dodržiavať jedno veľmi dôležité pravidlo: porovnávať hodnoty napríklad na menší môžeme len vtedy, keď sú zhodných typu:

>>> 5 < 'a'
...
TypeError: unorderable types: int() < str()
>>> (1, 5, 10) < (1, 'a', 10)
...
TypeError: unorderable types: int() < str()
>>> (1, 5, 10) != (1, 'a', 10)
True

Najlepšie je porovnávať také n-tice, ktoré majú prvky rovnakého typu. Pri n-ticiach, ktoré majú zmiešané typy si musíme dávať väčší pozor

>>> ('Janko', 'Hrasko', 'Zilina') < ('Janko', 'Jesensky', 'Martin')
True
>>> (1, 2, 3, 4, 5, 5, 6, 7, 8) < tuple(range(1, 9))
True
>>> ('Janko', 'Hrasko', 2008) < ('Janko', 'Hrasko', 2007)
False

Viacnásobné priradenie

Tu len pripomenieme, ako funguje viacnásobné priradenie: ak je pred znakom priradenia = viac premenných, ktoré sú oddelené čiarkami, tak za znakom priradenia musí byť iterovateľný objekt, ktorý má presne toľko hodnôt, ako počet premenných. Iterovateľným objektom môže byť zoznam (list), n-tica (tuple), znakový reťazec (str), generovaná postupnosť čísel (range()) ale aj otvorený textový súbor (open()), ktorý má presne toľko riadkov, koľko je premenných v priradení.

Ak do jednej premennej priraďujeme viac hodnôt oddelených čiarkou, Python to chápe ako priradenie n-tice. Pozrite nasledovné priradenia.

Priradíme n-ticu:

>>> a1, a2, a3, a4 = 3.14, 'joj', len, (1, 3, 5)
>>> print(a1, a2, a3, a4)
3.14 joj <built-in function len> (1, 3, 5)
>>> a, b, c, d, e, f = 3 * (5, 7)
>>> print(a, b, c, d, e, f)
5 7 5 7 5 7

Priradíme vygenerovanú postupnosť 4 čísel:

>>> a, b, c, d = range(2, 6)
>>> print(a, b, c, d)
2 3 4 5

Priradíme znakový reťazec:

>>> d, e, f, g, h, i = 'Python'
>>> print(d, e, f, g, h, i)
P y t h o n

Priradíme riadky textového súboru:

>>> with open('dva.txt', 'w') as f:
        f.write('first\nsecond\n')

>>> with open('dva.txt') as subor:
        prvy, druhy = subor

>>> prvy
'first\n'
>>> druhy
'second\n'

Vieme zapísať aj takto:

>>> prvy, druhy = open('dva.txt')

Tento posledný príklad je veľmi umelý a v praxi sa asi priamo do premenných takto čítať nebude.

Viacnásobné priradenie používame napríklad aj na výmenu obsahu dvoch (aj viac) premenných:

>>> x, y, z = y, z, x

Aj v tomto príklade je na pravej strane priradenia (za =) n-tica: (y, z, x).

n-tica ako návratová hodnota funkcie

V Pythone sa dosť využíva to, že návratovou hodnotou funkcie môže byť n-tica, t.j. výsledkom funkcie je naraz niekoľko návratových hodnôt. Napríklad nasledovný príklad počíta celočíselné delenie a súčasne zvyšok po delení:

def zisti(a, b):
    return a // b, a % b

Funkciu môžeme použiť napríklad takto:

>>> podiel, zvysok = zisti(153, 33)
>>> print('podiel =', podiel, 'zvysok =', zvysok)
podiel = 4 zvysok = 21

Ak z výsledku takejto funkcie potrebujeme použiť len jednu z hodnôt, môžeme zapísať:

>>> print('zvysok =', zisti(153, 33)[1])

Ďalšia funkcia vráti postupnosť všetkých deliteľov nejakého čísla:

def delitele(cislo):
    vysl = ()
    for i in range(1, cislo+1):
        if cislo % i == 0:
            vysl = vysl + (i,)
    return vysl

Otestujeme:

>>> deli = delitele(24)
>>> print(deli)
(1, 2, 3, 4, 6, 8, 12, 24)
>>> if 2 in deli:
        print('parne')

parne
>>> print('sucet delitelov =', sum(deli))
sucet delitelov = 60
>>> print('je prvocislo =', len(delitele(int(input('zadaj cislo: '))))==2)
zadaj cislo: 11213
je prvocislo = True
>>> print('je prvocislo =', len(delitele(int(input('zadaj cislo: '))))==2)
zadaj cislo: 1001
je prvocislo = False

Príklad ukazuje, že keď je výsledkom n-tica, môžeme ju ďalej rôzne spracovať alebo testovať.

Ďalšie funkcie a metódy

S n-ticami vedia pracovať nasledovné štandardné funkcie:

  • len(ntica) - vráti počet prvkov n-tice

  • sum(ntica) - vypočíta súčet prvkov n-tice (všetky musia byť čísla)

  • min(ntica) - zistí najmenší prvok n-tice (prvky sa musia dať navzájom porovnať, nemôžeme tu miešať rôzne typy)

  • max(ntica) - zistí najväčší prvok n-tice (ako pri min() ani tu sa nemôžu typy prvkov miešať)

Na rozdiel od zoznamov a znakových reťazcov, ktoré majú veľké množstvo metód, n-tice majú len dve:

  • ntica.count(hodnota) - zistí počet výskytov nejakej hodnoty v n-tici

  • ntica.index(hodnota) - vráti index (poradie) v n-tici prvého (najľavejšieho) výskytu danej hodnoty, ak sa hodnota v n-tici nenachádza, metóda spôsobí spadnutie na chybe (ValueError: tuple.index(x): x not in tuple)

Ukážme tieto funkcie na malom príklade. V n-tici uložíme niekoľko nameraných teplôt a potom vypíšeme priemernú, minimálnu aj maximálnu teplotu:

>>> teploty = (14, 22, 19.5, 17.1, 20, 20.4, 18)
>>> print('počet nameraných teplôt: ', len(teploty))
počet nameraných teplôt:  7
>>> print('minimálna teplota: ', min(teploty))
minimálna teplota:  14
>>> print('maximálna teplota: ', max(teploty))
maximálna teplota:  22
>>> print('priemerná teplota: ', round(sum(teploty) / len(teploty), 2))
priemerná teplota:  18.71

Ďalej môžeme zistiť, kedy bola nameraná konkrétna hodnota:

>>> teploty.index(20)
4
>>> teploty.index(20.1)
...
ValueError: tuple.index(x): x not in tuple

Môžeme zistiť, koľko-krát sa nejaká konkrétna teplota vyskytla v našich meraniach:

>>> teploty.count(20)
1
>>> teploty.count(20.1)
0

n-tice a grafika

Nasledovný príklad predvedie použitie n-tíc v grafickom režime. Zadefinujeme niekoľko bodov v rovine a potom pomocou nich kreslíme nejaké farebné polygóny. Začnime takto:

a = (70, 150)
b = (200, 200)
c = (150, 250)
d = (120, 70)
e = (50, 220)

canvas = tkinter.Canvas()
canvas.pack()
canvas.create_polygon(a, b, c, d, fill='red')

Ak by sme chceli jedným priradením priradiť dva body do premenných a aj b, zapíšeme:

a, b = (100, 150), (180, 200)

čo je vlastne:

a, b = ((100, 150), (180, 200))

Polygónov môžeme nakresliť aj viac (zrejme väčšinou záleží na poradí ich kreslenia):

canvas.create_polygon(e, a, c, fill='green')
canvas.create_polygon(e, d, b, fill='yellow')
canvas.create_polygon(a, b, c, d, fill='red')
canvas.create_polygon(a, c, d, b, fill='blue')

Vidíme, že niektoré postupnosti bodov tvoria jednotlivé útvary, preto zapíšme:

utvar1 = e, a, c
utvar2 = e, d, b
utvar3 = a, b, c, d
utvar4 = a, c, d, b

canvas.create_polygon(utvar1, fill='green')
canvas.create_polygon(utvar2, fill='yellow')
canvas.create_polygon(utvar3, fill='red')
canvas.create_polygon(utvar4, fill='blue')

Volanie funkcie canvas.create_polygon() sa tu vyskytuje 4-krát, ale s rôznymi parametrami. Prepíšme to do for-cyklu:

utvar1 = e, a, c
utvar2 = e, d, b
utvar3 = a, b, c, d
utvar4 = a, c, d, b

for param in (utvar1, 'green'), (utvar2, 'yellow'), (utvar3, 'red'), (utvar4, 'blue'):
    utvar, farba = param
    canvas.create_polygon(utvar, fill=farba)

Dostávame to isté. Vo for-cykle sa najprv do premennej param priradí dvojica s dvoma prvkami: útvar a farba, a v tele cyklu sa táto premenná s dvojicou priradí do dvoch premenných utvar a farba. Potom sa zavolá funkcia canvas.create_polygon() s týmito parametrami.

Už sme videli aj predtým, že pre for-cyklus existuje vylepšenie: ak sa do premennej cyklu postupne priraďujú nejaké dvojice hodnôt a tieto by sa na začiatku tela rozdelili do dvoch premenných, môžeme priamo tieto dve premenné použiť ako premenné cyklu (ako keby viacnásobné priradenie). Podobnú ideu sme mohli vidieť pri použití enumerate(). Predchádzajúci príklad prepíšeme:

utvar1 = e, a, c
utvar2 = e, d, b
utvar3 = a, b, c, d
utvar4 = a, c, d, b

for utvar, farba in (utvar1, 'green'), (utvar2, 'yellow'), (utvar3, 'red'),(utvar4, 'blue'):
    canvas.create_polygon(utvar, fill=farba)

Pozrime sa na n-ticu, ktorá sa prechádza týmto for-cyklom:

>>> cyklus = (utvar1, 'green'), (utvar2, 'yellow'), (utvar3, 'red'), (utvar4, 'blue')
>>> cyklus
((((50, 220), (70, 150), (150, 250)), 'green') (((50, 220), (120, 70),
(200, 200)), 'yellow') (((70, 150), (200, 200), (150, 250), (120, 70)),
'red') (((70, 150), (150, 250), (120, 70), (200, 200)), 'blue'))

Vidíme, že n-tica v takomto tvare je dosť ťažko čitateľná, ale for-cyklus jej normálne rozumie. Zrejme by sme teraz mohli zapísať:

for utvar, farba in cyklus:
   canvas.create_polygon(utvar, fill=farba)

Zoznamy a grafika

Už vieme, že väčšina grafických príkazov, napríklad create_line(), create_polygon(), … akceptujú ako parametre nielen čísla, ale aj n-tice alebo aj zoznamy čísel, resp. zoznamy/dvojice čísel, napríklad:

import tkinter

canvas = tkinter.Canvas()
canvas.pack()
utvar = ((100, 50), (200, 120))
canvas.create_rectangle(utvar, fill='blue')
canvas.create_oval(utvar, fill='yellow')
utvar2 = list(utvar)                # z n-tice sa vyrobí zoznam
utvar2.append((170, 20))
canvas.create_polygon(utvar2, fill='red')

alebo môžeme generovať náhodnú krivku:

import tkinter, random

canvas = tkinter.Canvas(bg='white')
canvas.pack()
krivka = []
for i in range(30):
    krivka.append()
canvas.create_line(krivka)

Ak by sme chceli využiť grafickú funkciu coords(), ktorá modifikuje súradnice nakreslenej krivky, nemôžeme jej poslať zoznam súradníc (dvojíc čísel x a y), ale táto vyžaduje plochý zoznam (alebo n-ticu) čísel. Predchádzajúci príklad mierne zmeníme:

import tkinter, random

canvas = tkinter.Canvas(bg='white')
canvas.pack()
poly = canvas.create_polygon(0, 0, 0, 0, fill='yellow', outline='blue')
krivka = []
for i in range(100):
    bod = [random.randrange(350), random.randrange(250)]
    krivka.extend(bod)     # to isté ako    krivka += bod
    canvas.coords(poly, krivka)
    canvas.update()
    canvas.after(300)

Použili sme tu dvojicu nových príkazov pre grafickú plochu canvas:

canvas.update()
canvas.after(300)

Tieto dva príkazy označujú, že sa grafická plocha prekreslí (aktualizuje) pomocou metódy update() a potom sa na 300 milisekúnd pozdrží výpočet pomocou metódy after(). Vďaka tejto dvojici príkazov budeme vidieť postupné pridávanie náhodných bodov vytváranej krivky.

Okrem toho sme tu využili novú metódu extend(), ktorá k existujúcemu zoznamu prilepí na koniec nový zoznam. Napríklad:

>>> a = [1, 2, 3, 4]
>>> b = ['xx', 'yy']
>>> a.extend(b)
>>> a
[1, 2, 3, 4, 'xx', 'yy']

Zrejme to isté by sme dosiahli aj takto:

>>> a = [1, 2, 3, 4]
>>> b = ['xx', 'yy']
>>> a += b
>>> a
[1, 2, 3, 4, 'xx', 'yy']

Cvičenia

L.I.S.T.


  1. Na minulých cvičeniach sa riešila takáto úloha: Napíš funkciu cele(zoznam), ktorá z daného zoznamu vytvorí (a vráti) nový, v ktorom sa každý prvok previedol (pomocou funkcie int()) na celočíselnú hodnotu. Možné riešenie je:

    def cele(zoznam):
        vysl = []
        for prvok in zoznam:
            vysl.append(int(prvok))
        return vysl
    

    Zisti, či táto funkcia bude fungovať nielen pre zoznamy, ale aj pre iné iterovateľné typy:

    1. n-tica desatinných čísel a reťazcov

    2. reťazec cifier

    3. výsledok funkcie range

    4. otvorený súbor


  1. Na minulých cvičeniach sa riešia takáto úloha: Napíš funkciu zluc(zoz1, zoz2), ktorá z daných dvoch zoznamov zoz1 a zoz2 vyrobí (a vráti) nový, v ktorom budú prvky z pôvodných dvoch zoznamov na striedačku (najprv prvý prvok z prvého, potom prvý prvok z druhého, potom druhý z prvého, atď.). Možné riešenie je:

    def zluc(zoz1, zoz2):
        n = min(len(zoz1), len(zoz2))
        vysl = []
        for i in range(n):
            vysl += [zoz1[i], zoz2[i]]
        return vysl + zoz1[n:] + zoz2[n:]
    

    Táto funkcia nefunguje, ak je jeden z parametrov, napríklad n-tica alebo reťazec. Oprav funkciu tak, aby fungovalo aj:

    >>> zluc((1, 2, 3), 'python')
    [1, 'p', 2, 'y', 3, 't', 'h', 'o', 'n']
    >>> zluc('abc', range(5))
    ['a', 0, 'b', 1, 'c', 2, 3, 4]
    

  1. Napíš funkciu zdvoj(postupnost), ktorá vráti novú postupnosť. V nej bude každý prvok dvakrát za sebou. Ak je vstupná postupnosť typu zoznam, aj výsledok bude typu zoznam. Ak je typu n-tica, aj výsledok bude n-tica. Napríklad:

    >>> zdvoj((1, 2, 3, 4))
    (1, 1, 2, 2, 3, 3, 4, 4)
    >>> zdvoj([2, 3, 5])
    [2, 2, 3, 3, 5, 5]
    

  1. Napíš funkciu od_najvacsieho(zoznam), ktorá zistí (vráti True alebo False), či je daný zoznam (resp. n-tica) usporiadaný od najväčšieho po najmenší. Na riešenie použi metódu zoznam.sort() alebo funkciu sorted(). Funkcia by nemala zmeniť vstupný zoznam. Napríklad:

    >>> zoz = [5, 4, 2, 3]
    >>> od_najvacsieho(zoz)
    False
    >>> zoz
    [5, 4, 2, 3]
    >>> od_najvacsieho(('ccc', 'bbb', 'aaa'))
    True
    

  1. Napíš funkciu sort_y(zoznam), kde zoznam obsahuje dvojice (typ tuple) celých čísel. Tieto reprezentujú súradnice (x, y) nejakých bodov v rovine. Tento zoznam treba usporiadať od najmenšieho po najväčší, lenže podľa druhých prvkov dvojíc (y-ových súradníc). Uvedom si, že volanie zoznam.sort() by usporiadalo zoznam podľa prvých prvkov dvojíc (x-ových súradníc). Napríklad:

    >>> xy = [(100, 30), (200, 10), (300, 20)]
    >>> sort_y(xy)
    >>> xy
    [(200, 10), (300, 20), (100, 30)]
    

    Triediť môžeš takto: najprv v zozname v každej dvojici vymeníš x a y, potom normálne utriediš a nakoniec vrátiš všetky dvojice do pôvodného stavu.


  1. Napíš funkciu kruh(trojica), ktorá do grafickej plochy nakreslí modrý kruh. Parameter trojica je n-tica (tuple) troch celých čísel v tvare (r, x, y), t.j. polomer a súradnice stredu kruhu. Napríklad pre volanie:

    kruh((70, 200, 180))
    for k in (10, 80, 100), (30, 120, 100), (10, 160, 100), (30, 200, 100):
        kruh(k)
    

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

    _images/09_c01.png

  1. Napíš funkciu text(retazec), kde parameter retazec je znakový reťazec v tvare 'slovo x y'. Funkcia nakreslí do grafickej plochy zadané slovo na súradnice stredu (x, y). Tento text vypíše do rámika. Použi metódu split. Napríklad pre volanie:

    text('programujem 60 50')
    text('v 100 80')
    text('Pythone 100 110')
    text('na 100 140')
    text('matfyze 140 170')
    

    môžeme dostať nejaký takýto výpis:

    _images/09_c02.png

  1. V textovom súbore sú slová oddelené medzerou. Tento súbor treba prečítať a v každom riadku prevrátiť poradie slov. Dopíš funkciu prevrat_riadky(meno_suboru). Zrejme využiješ metódy split a join.

    def prevrat_riadky(meno_suboru):
        with open(meno_suboru, encoding='utf-8') as t:
            text = t.read()
        ...
        with open(meno_suboru, 'w', encoding='utf-8') as t:
            t.write(text)
    
    prevrat_riadky('subor.txt')
    

    Vstupný súbor:

    Žltá ľalija
    
    Stojí stojí mohyla
    Na mohyle zlá chvíľa
    na mohyle tŕnie chrastie
    a v tom tŕní chrastí rastie
    rastie kvety rozvíja
    jedna žltá ľalija
    

    by sa mal zmeniť takto:

    ľalija Žltá
    
    mohyla stojí Stojí
    chvíľa zlá mohyle Na
    chrastie tŕnie mohyle na
    rastie chrastí tŕní tom v a
    rozvíja kvety rastie
    ľalija žltá jedna
    

  1. Už asi vieš, že štandardná funkcia enumerate() vráti iterátor dvojíc. Ak vyskúšaš, napríklad:

    >>> tuple(enumerate('python'))
    ((0, 'p'), (1, 'y'), (2, 't'), (3, 'h'), (4, 'o'), (5, 'n'))
    

    vidíš, že funkcia rozoberie svoj parameter (napríklad reťazec) na prvky a potom z nich vytvorí postupnosť dvojíc (tuple), pomocou ktorých pôvodnú postupnosť očísluje. Napíš funkciu enum(postupnost), ktorá vytvorí rovnakú n-ticu dvojíc, ako by vytvorila tuple(enumerate(postupnost)) ale bez volania štandardnej funkcie enumerate.


  1. Python ponúka ešte jednu štandardnú funkciu zip. Táto funkcia, keď dostane nejaké dve postupnosti (napríklad zoznam, n-tica, reťazec, range, …), vytvorí z nich jednu novú postupnosť dvojíc (tuple): v každej takejto dvojici je jeden prvok z prvej a jeden z druhej postupnosti. Môžeš vyskúšať, napríklad:

    >>> list(zip('python', [2, 3, 5, 7]))
    [('p', 2), ('y', 3), ('t', 5), ('h', 7)]
    

    Zrejme, ak je jedna z týchto postupností kratšia, výsledok sa nastavil podľa nej. Napíš funkciu moj_zip(post1, post2), ktorá z dvoch postupností (iterovateľných objektov možno rôznej dĺžky) vytvorí jeden zoznam dvojíc.


  1. Prepíš funkciu enum z (9) úlohy tak, aby neobsahovala cyklus a využila tvoju funkciu moj_zip. Teda:

    def enum(postupnost):
        return ... moj_zip ...
    

  1. Napíš funkciu od_zip(zoznam), ktorá bude fungovať ako opak funkcie moj_zip. Funkcia dostáva zoznam dvojíc a vráti dva zoznamy prvých a druhých prvkov dvojíc. Napríklad:

    >>> z1, z2 = od_zip([(2, 'a'), ('h', 3), (5, 'o'), ('j', 7)])
    >>> z1
    [2, 'h', 5, 'j']
    >>> z2
    ['a', 3, 'o', 7]
    

  1. Napíš funkciu sipka(xy1, xy2), ktorá do grafickej plochy nakreslí šípku z bodu xy1 do bodu xy2. Oba tieto parametre sú dvojice čísel (tuple), t.j. súradnice bodov. Šípku kresli pomocou create_line, v ktorej využiješ ďalší pomenovaný parameter arrow='last'. Teraz nakresli štyri šípky, ktoré obkreslia takýto štvorec:

    canvas.create_rectangle(150, 50, 250, 150, fill='gold')
    

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

    _images/09_c03.png

  1. Napíš funkciu vektor(xy, dlzka, uhol), ktorá nakreslí šípku z bodu xy (dvojica súradníc) dĺžky dlzka, ktorá zviera s osou x zadaný uhol. Využi funkciu sipka z predchádzajúcej úlohy. Napríklad:

    xy = (150, 150)
    for u in range(0, 360, 10):
        vektor(xy, 100, u)
    

    by nakreslil:

    _images/09_c04.png

    Teraz pomocou tejto funkcie zapíš funkciu n_uholnik(n, d, xy). Táto nakreslí n vektorov na obvode n-uholníka so stranami d, ktorého jeden vrchol je xy (dvojica súradníc). Napríklad:

    n_uholnik(5, 70, (150, 50))
    

    nakreslí:

    _images/09_c05.png

3. Domáce zadanie

L.I.S.T.

Napíš pythonovský skript, ktorý bude definovať tieto funkcie a jednu globálnu premennú:

tab = []

def citaj(meno_suboru):
    ...

def pocet_vyskytov(slovo):
    ...
    return 0

def zoznam_najcastejsich():
    ...
    return [...]

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

def zoznam_najdlhsich():
    ...
    return [...]

def zoznam_najkratsich():
    ...
    return [...]

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

Funkcia citaj() dostáva ako parameter meno textového súboru. Funkcia tento súbor prečíta a do globálnej premennej tab si nejako zaznačí počet výskytov každého slova v súbore (najlepšie pre každé slovo ako dvojicu: reťazec a počet výskytov). Premenná tab by mala byť typu zoznam (list). Slová v súbore sú oddelené aspoň jednou medzerou alebo znakom konca riadkov. Nepoužívaj ďalšie globálne premenné ani ďalšie funkcie.

Ďalšie funkcie, ktoré by si mal naprogramovať, spracujú informácie v premennej tab a vrátia zoznam nejakých slov (možno aj prázdny):

  • funkcia pocet_vyskytov(slovo) vráti počet, koľkokrát sa dané slovo v texte objavilo

  • funkcia zoznam_najcastejsich() vráti zoznam slov (list), ktoré boli v texte najčastejšie - buď je to zoznam s jedným slovom, alebo je to zoznam viacerých slov, ak mali rovnaký počet výskytov (zrejme maximálny)

  • funkcia zoznam_s_poctom(n) vráti zoznam slov (list), ktoré sa v texte vyskytli presne n-krát - tento zoznam môže byť aj prázdny, keď v súbore nebolo ani jedno slovo s týmto počtom výskytov

  • funkcia zoznam_najdlhsich() vráti zoznam slov (list), ktoré boli v texte najdlhšie - buď je to zoznam s jedným slovom, alebo je to zoznam viacerých slov, ak majú rovnakú maximálnu dĺžku

  • funkcia zoznam_najkratsich() vráti zoznam slov (list), ktoré boli v texte najkratšie - buď je to zoznam s jedným slovom, alebo je to zoznam viacerých slov, ak majú rovnakú minimálnu dĺžku

  • funkcia zoznam_s_dlzkou(n) vráti zoznam slov (list), ktoré majú dĺžku presne n - tento zoznam môže byť aj prázdny, keď v súbore nebolo ani jedno slovo s touto dĺžkou

Žiadna z týchto funkcií nič nevypisuje, len vráti (return) nejaký zoznam slov, respektíve celé číslo. Funkcia citaj() nič nevracia.

Napríklad "text1.txt" sa skladá z týchto riadkov:

i see it i deduce it how do i know that you have been getting yourself very wet lately and that you have a most clumsy and careless servant girl
my dear holmes said i this is too much you would certainly have been burned had you lived a few centuries ago it is true that i had a country walk on thursday and came home in a dreadful mess but as i have changed my clothes i can't imagine how you deduce it as to mary jane she is incorrigible and my wife has given her notice but there again i fail to see how you work it out
he chuckled to himself and rubbed his long nervous hands together
it is simplicity itself said he my eyes tell me that on the inside of your left shoe just where the firelight strikes it the leather is scored by six almost parallel cuts obviously they have been caused by someone who has very carelessly scraped round the edges of the sole in order to remove crusted mud from it hence you see my double deduction that you had been out in vile weather and that you had a particularly malignant bootslitting specimen of the london slavey as to your practice if a gentleman walks into my rooms smelling of iodoform with a black mark of nitrate of silver upon his right forefinger and a bulge on the right side of his tophat to show where he has secreted his stethoscope i must be dull indeed if i do not pronounce him to be an active member of the medical profession

Tento súbor obsahuje 271 slov, pričom niektoré sa v texte opakujú. Po spustení takéhoto testu:

if __name__ == '__main__':
    citaj('text1.txt')
    print('pocet vyskytov "the":', pocet_vyskytov('the'))
    print('najcastejsie:', zoznam_najcastejsich())
    print('najdlhsie:', zoznam_najdlhsich())
    print('najkratsie:', zoznam_najkratsich())
    print('len s poctom 5:', zoznam_s_poctom(5))
    print('len s poctom 10:', zoznam_s_poctom(10))
    print('len s dlzkou 10:', zoznam_s_dlzkou(10))
    print('pocet roznych slov =', len(tab))

program vypíše:

pocet vyskytov "the": 8
najcastejsie: ['i']
najdlhsie: ['incorrigible', 'particularly', 'bootslitting']
najkratsie: ['i', 'a']
len s poctom 5: ['have', 'is']
len s poctom 10: ['i']
len s dlzkou 10: ['simplicity', 'carelessly', 'forefinger', 'profession']
pocet roznych slov = 161

Prvý riadok tohto testu if __name__ == '__main__': označuje, že telo tohto príkazu (podmienka je pravdivá) sa vykoná jedine v prípade, že program spúšťaš pomocou Run (F5). Ak tvoj program bude spúšťať testovač, tieto príkazy sa nevykonajú (testovač neobľubuje príkaz print).

Tvoj odovzdaný program s menom riesenie.py musí začínať tromi riadkami komentárov:

# 3. zadanie: najcastejsie
# autor: Janko Hraško
# datum: 10.11.2019

Projekt riesenie.py odovzdávaj na úlohový server https://list.fmph.uniba.sk/ najneskôr do 23:00 7. novembra, kde ho môžeš nechať otestovať. Testovač bude spúšťať tvoje funkcie s rôznymi textovými súbormi, ktoré si môžeš stiahnuť z L.I.S.T.u. Odovzdať projekt aj ho testovať môžeš ľubovoľný počet krát. Môžeš zaň získať 10 bodov.