8. Zoznamy

Už poznáme tieto typy údajov:

  • jednoduché: číselné (int a float), logické (bool)

  • postupnosti: znakov (str), riadkov (otvorený textový súbor), čísel (pomocou range())

Dátová štruktúra zoznam

  • v Pythone sa tento typ volá list

  • je to vlastne postupnosť hodnôt ľubovoľných typov

  • hovoríme, že typ zoznam sa skladá z prvkov

  • okrem názvu zoznam, môžeme používať aj názov tabuľka alebo pole (väčšinou pre zoznamy hodnôt rovnakého typu)

  • tento typ sa podobá na pole v iných jazykoch (napr. v Pascale je to dynamické pole, ktorého ale hodnoty musia byť rovnakého typu) - často im tak budeme hovoriť aj v Pythone

Zoznamy vytvárame vymenovaním prvkov v hranatých zátvorkách, v príklade ich hneď aj priradíme do rôznych premenných:

>>> teploty = [10, 13, 15, 18, 17, 12, 12]
>>> nakup = ['chlieb', 'mlieko', 'rozky', 'jablka']
>>> studenti = ['Juraj Janosik', 'Emma Drobna', 'Ludovit Stur', 'Pavol Habera', 'Margita Figuli']
>>> zviera = ['pes', 'Dunco', 8, 35.7, 'hneda']
>>> prazdny = []                                 # prázdny zoznam
>>> print(teploty)
[10, 13, 15, 18, 17, 12, 12]
>>> type(zviera)
<class 'list'>

Všimnite si, že niektoré z týchto zoznamov majú všetky prvky rovnakého typu (napr. všetky sú celé čísla alebo všetky sú reťazce).

Operácie so zoznamami

Základné operácie so zoznamami fungujú skoro presne rovnako, ako ich vieme používať so znakovými reťazcami:

  • indexovanie pomocou hranatých zátvoriek [ ] - je úplne rovnaké ako pri reťazcoch: indexom je celé číslo od 0 do počet prvkov zoznamu - 1, alebo je to záporné číslo, napr.

    >>> zviera[0]
    'pes'
    >>> nakup[1]
    'mlieko'
    >>> studenti[-1]
    'Margita Figuli'
    >>> for i in range(10):
            index = i % 4
            farba = ['red', 'blue', 'yellow', 'green'][index]
            print(farba)  # mohlo byť create_oval(..., fill=farba)
    
    red
    blue
    yellow
    green
    red
    blue
    yellow
    green
    red
    blue
    >>> ['red', 'blue', 'yellow'][2][4]
    'o'
    
  • zreťazovanie pomocou operácie + označuje, že vytvoríme nový väčší zoznam, ktorý bude obsahovať najprv prvky prvého zoznamu a za tým všetky prvky druhého zoznamu, napr.

    >>> nakup2 = ['zosity', 'pero', 'vreckovky']
    >>> nakup + nakup2
    ['chlieb', 'mlieko', 'rozky', 'jablka', 'zosity', 'pero', 'vreckovky']
    >>> studenti = studenti + ['Karel Capek']
    >>> studenti
    ['Juraj Janosik', 'Emma Drobna', 'Ludovit Stur', 'Pavol Habera', 'Margita Figuli', 'Karel Capek']
    >>> prazdny + prazdny
    []
    >>> [1] + [2] + [3, 4] + [] + [5]
    [1, 2, 3, 4, 5]
    
  • viacnásobné zreťazenie pomocou operácie * označuje, že daný zoznam sa navzájom zreťazí určený počet krát, napr.

    >>> jazyky = ['Python', 'Pascal', 'C++', 'Java', 'C#']
    >>> vela = 3 * jazyky
    >>> vela
    ['Python', 'Pascal', 'C++', 'Java', 'C#', 'Python', 'Pascal', 'C++', 'Java', 'C#', 'Python',
     'Pascal', 'C++', 'Java', 'C#']
    >>> sto_krat_nic = 100 * [None]
    >>> sto_krat_nic
    [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
     None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
     None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
     None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
     None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
     None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
     None, None, None, None, None, None, None, None, None, None]
    >>> prazdny * 1000
    []
    
  • zisťovanie prvku pomocou in označuje, či sa nejaká hodnota nachádza v danom zozname, napr.

    >>> 'Pavol Habera' in studenti
    True
    >>> 'pero' in nakup
    False
    >>> 'pascal' in jazyky
    False
    >>> prazdny in sto_krat_nic
    False
    >>> teploty = [10, 13, 15, 18, 17, 12, 12]
    >>> 18 in teploty
    True
    >>> [18, 17] in teploty
    False
    

    V poslednom príklade testujeme, či sa dvojprvkový zoznam [18, 17] nachádza niekde v zozname teploty. Lenže tento zoznam obsahuje len celé čísla a žiaden prvok nie je typu zoznam. Hoci pre znakové reťazce fungovalo hľadanie podreťazca, napr.

    >>> 'th' in 'Python'
    True
    

    pre zoznamy táto analógia nefunguje.

    Ešte si pripomeňte zápis negácie takéhoto testu, ktorý analogicky funguje pre reťazce aj zoznamy:

    >>> not 'Y' in 'Python'
    True
    >>> 'Y' not in 'Python'
    True
    >>> 'str' not in ['pon', 'uto', 'str', 'stv', 'pia', 'sob', 'ned']
    False
    >>> 'štv' not in ['pon', 'uto', 'str', 'stv', 'pia', 'sob', 'ned']
    True
    

Pripomeňme si, ako vyzerajú premenné a ich hodnoty v pamäti Pythonu. Urobme toto priradenie:

ab = [2, 3, 5, 7, 11]

Do pamäti mien (globálny menný priestor) pribudne jeden identifikátor premennej ab a tiež referencia na päťprvkový zoznam [2, 3, 5, 7, 11]. Tento zoznam môžeme v pamäti hodnôt vizualizovať ako päť vedľa seba položených škatuliek, pričom v každej je referencia na príslušnú hodnotu:

_images/08_01.png

Je dobre si uvedomiť, že momentálne máme v pamäti 6 premenných, jedna z nich je ab (je typu list) a zvyšných päť je ab[0], ab[1], ab[2], ab[3] a ab[4] (všetky sú typu int).

Prechádzanie prvkov zoznamu

Tzv. iterovanie najčastejšie pomocou for-cyklu. Napr.

>>> teploty
[10, 13, 15, 18, 17, 12, 12]
>>> for i in range(7):
        print(f'{i}. den {teploty[i]}')

0. den 10
1. den 13
2. den 15
3. den 18
4. den 17
5. den 12
6. den 12

Využili sme indexovanie prvkov zoznamu indexmi od 0 do 6. Ak nepotrebujeme pracovať s indexmi ale stačia nám samotné hodnoty, zapíšeme:

>>> for prvok in teploty:
        print(prvok, end=', ')

10, 13, 15, 18, 17, 12, 12,

Prípadne vieme využiť enumerate:

>>> for i, prvok in enumerate(teploty):
        print(f'{i + 1}. den {prvok}')

1. den 10
2. den 13
3. den 15
4. den 18
5. den 17
6. den 12
7. den 12

Môžeme zapísať aj výpočet priemernej teploty:

teploty = [10, 13, 15, 18, 17, 12, 12]

sucet = 0
for prvok in teploty:
    sucet += prvok
priemer = sucet / 7
print(f'priemerna teplota je {priemer:.1f}')    # formatovanie desatinneho cisla na jedno miesto
priemerna teplota je 13.9

Podobne vieme zistiť napr. maximálnu hodnotu v zozname:

mx = teploty[0]
for prvok in teploty:
    if mx < prvok:
        mx = prvok
print('najteplejsie bolo', mx, 'stupnov')
najteplejsie bolo 18 stupnov

Alebo zistenie počtu nadpriemerne teplých dní:

teploty = [10, 13, 15, 18, 17, 12, 12]

sucet = 0
for prvok in teploty:
    sucet += prvok
priemer = sucet / 7
pocet = 0
for prvok in teploty:
    if prvok > priemer:
        pocet += 1
print('pocet nadpriemerne teplych dni', pocet)
pocet nadpriemerne teplych dni 3

Z týchto jednoduchých príkladov môžeme zapísať šablóny, ktoré niečo zisťujú o prvkoch zoznamu.

šablóna na zisťovanie hodnôt v zozname

Prvá jej verzia napr. spočíta hodnoty prvkov (prvky by sme mohli napr. aj násobiť, alebo zreťazovať):

sucet = 0
for prvok in zoznam:
    sucet = sucet + prvok
print(sucet)

V druhej verzii šablóny budeme niečo o prvkoch zoznamu zisťovať, napr. minimálny prvok:

mn = zoznam[0]
for prvok in zoznam:
    if prvok < mn:
        mn = prvok
print(mn)

Zrejme podmienka aj priradenie budú závisieť od konkrétnej úlohy.

Zmena hodnoty prvku zoznamu

Dátová štruktúra zoznam je meniteľný typ (tzv. mutable) - môžeme meniť hodnoty prvkov zoznamu, napr. takto:

>>> studenti[3]
'Pavol Habera'
>>> studenti[3] = 'Janko Hrasko'
>>> studenti[2] = 'Guido van Rossum'
>>> studenti
['Juraj Janosik', 'Emma Drobna', 'Guido van Rossum', 'Janko Hrasko', 'Margita Figuli', 'Karel Capek']

Môžeme zmeniť hodnoty prvkov zoznamu aj v cykle, ale k prvkom musíme pristupovať pomocou indexov, napr. sme zistili, že náš teplomer ukazuje o 2 stupne menej ako je reálna teplota, preto opravíme všetky prvky zoznamu:

teploty = [10, 13, 15, 18, 17, 12, 12]
for i in range(7):
    teploty[i] = teploty[i] + 2
print(teploty)
[12, 15, 17, 20, 19, 14, 14]

Krajšie by sme to zapísali s využitím štandardnej funkcie len(), ktorá vráti počet prvkov zoznamu:

teploty = [10, 13, 15, 18, 17, 12, 12]
for i in range(len(teploty)):
    teploty[i] += 2
print(teploty)
[12, 15, 17, 20, 19, 14, 14]

Uvedomte si, že ak by sme prvky zoznamu neindexovali, ale prechádzali sme ich priamo cez premennú cyklu prvok:

teploty = [10, 13, 15, 18, 17, 12, 12]
for prvok in teploty:
    prvok += 2
print(teploty)

nebude to fungovať:

[10, 13, 15, 18, 17, 12, 12]

Samotný zoznam sa tým nezmení: menili sme len obsah premennej cyklu prvok, ale tým sa nezmení obsah zoznamu.

Je dobré si predstaviť, čo sa deje v pamäti pri zmene hodnoty prvku zoznamu. Zoberme si pôvodný pať prvkový zoznam prvočísel:

ab = [2, 3, 5, 7, 11]

Zmenou obsahu jedného prvku zoznamu sa zmení jediná referencia, všetko ostatné zostáva bez zmeny:

ab[2] = 55

dostávame:

_images/08_02.png

Priraďovaním do jedného prvku zoznamu sa tento zoznam modifikuje, hovoríme, že priradenie do prvku je mutable operácia. Ukážme šablónu, pomocou ktorej vieme v cykle priradiť do rôznych prvkov rôzne hodnoty.

šablóna na vytváranie zoznamu

Ak potrebujeme v cykle priradiť do prvkov zoznamu rôzne hodnoty, príslušné indexy zoznamu už musia existovať. Najlepšie takýto zoznam pripravíme jedným priradením a viacnásobným zreťazením, napr. pre n prvkov:

zoznam = [None] * n
for i in range(n):
    zoznam[i] = ... výpočet hodnoty
print(zoznam)

Napr. pre vytvorenie zoznamu prvých n druhých mocnín čísel od 0 do n-1:

n = int(input('zadaj n: '))
mocniny = [None] * n
for i in range(n):
    mocniny[i] = i * i
print(mocniny)
zadaj n: 14
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169]

Štandardné funkcie so zoznamami

Nasledovné funkcie fungujú nielen so zoznamami, ale s ľubovoľnou postupnosťou hodnôt. V niektorých prípadoch však nemajú zmysel a vyhlásia chybu (napr. číselný súčet prvkov znakového reťazca).

  • funkcia len(postupnosť) -> vráti počet prvkov postupnosti

  • funkcia sum(postupnosť) -> vypočíta číselný súčet prvkov postupnosti

  • funkcia max(postupnosť) -> vráti maximálny prvok postupnosti (t.j. jeho hodnotu)

  • funkcia min(postupnosť) -> vráti minimálny prvok postupnosti

Predchádzajúci príklad, v ktorom sme počítali priemernú, minimálnu aj maximálnu teplotu, prepíšeme:

teploty = [10, 13, 15, 18, 17, 12, 12]

sucet = sum(teploty)
maximum = max(teploty)
minimum = min(teploty)
priemer = sucet / len(teploty)
print(f'priemerna teplota je {priemer:.1f}')
print('minimalna teplota je', minimum)
print('maximalna teplota je', maximum)
priemerna teplota je 13.9
minimalna teplota je 10
maximalna teplota je 18

Čo sa dá zapísať úspornejšie, ale menej čitateľne aj takto:

teploty = [10, 13, 15, 18, 17, 12, 12]

print(f'priemerna teplota je {sum(teploty) / len(teploty):.1f}')
print('minimalna teplota je', min(teploty))
print('maximalna teplota je', max(teploty))

Funkcia list()

Už máme nejaké skúsenosti s tým, že v Pythone každý základný typ má definovanú svoju konverznú funkciu, pomocou ktorej sa dajú niektoré hodnoty rôznych typov prekonvertovať na daný typ. Napr.

  • int(3.14) -> vráti celé číslo 3

  • int('37') -> vráti celé číslo 37

  • str(22 / 7) -> vráti reťazec '3.142857142857143'

  • str(2 < 3) -> vráti reťazec 'True'

Podobne funguje aj funkcia list(hodnota):

  • parametrom musí byť iterovateľná hodnota, t.j. nejaká postupnosť, ktorá sa dá prechádzať (iterovať), napr. for-cyklom

  • funkcia list() túto postupnosť rozoberie na prvky a z týchto prvkov poskladá nový zoznam

  • parameter môže chýbať, vtedy vygeneruje prázdny zoznam

Napr.

>>> list(zviera)                            # kópia existujúceho zoznamu
['pes', 'Dunco', 8, 35.7, 'hneda']
>>> list(range(5, 16))
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
>>> list('Python')
['P', 'y', 't', 'h', 'o', 'n']
>>> list()                                  # prázdny zoznam
[]
>>> list(3.14)
...
TypeError: 'float' object is not iterable

Ak nejaký súbor obsahuje tieto riadky:

Aká
práca,

taká
pláca.

aj toto Python vidí ako postupnosť (otvorený súbor sa dá prechádzať for-cyklom ako postupnosť riadkov) a preto:

>>> list(open('subor.txt', encoding='utf-8'))
['Aká\n', 'práca,\n', '\n', 'taká\n', 'pláca.\n']

Rezy

Keď sme v znakovom reťazci potrebovali zmeniť nejaký znak, zakaždým sme museli vyrobiť kópiu reťazca, napr.

>>> retazec = 'Monty Python'
>>> retazec[4] = 'X'                   # takto sa to nedá
...
TypeError: 'str' object does not support item assignment
>>> retazec = retazec[:4] + 'X' + retazec[5:]
>>> retazec
'MontX Python'

Využili sme tu rezy (slice), t.j. získavanie podreťazcov. To isté sa dá použiť aj pri práci so zoznamami, lebo aj s nimi fungujú rezy, napr.

>>> jazyky
['Python', 'Pascal', 'C++', 'Java', 'C#']
>>> jazyky[1:3]
['Pascal', 'C++']
>>> jazyky[-3:]
['C++', 'Java', 'C#']
>>> jazyky[:-1]
['Python', 'Pascal', 'C++', 'Java']

Samozrejme, že pritom funguje aj určovanie kroku, napr.

>>> jazyky[1::2]
['Pascal', 'Java']
>>> jazyky[::-1]
['C#', 'Java', 'C++', 'Pascal', 'Python']

Uvedomte si, že takéto rezy nemenia obsah samotného zoznamu a preto hovoríme, že sú immutable.

Priraďovanie do rezu

Keď iba vyberáme nejaký podzoznam pomocou rezu, napr. zoznam[od:do:krok], takáto operácia s pôvodným zoznamom nič nerobí (len vyrobí úplne nový zoznam). Lenže my môžeme obsah zoznamu meniť aj takým spôsobom, že zmeníme len jeho nejakú časť. Takže rez zoznamu môže byť na ľavej strane priraďovacieho príkazu a potom na pravej strane priraďovacieho príkazu musí byť nejaká postupnosť (nemusí to byť zoznam). Priraďovací príkaz teraz túto postupnosť prejde, zostrojí z nej zoznam a ten vloží namiesto udaného rezu.

Preštudujte nasledovné príklady:

>>> zoz = list(range(0, 110, 10))
>>> zoz
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> zoz[3:6] = ['begin', 'end']                  # tri prvky sa nahradili dvoma
>>> zoz
[0, 10, 20, 'begin', 'end', 60, 70, 80, 90, 100]
>>> zoz[6:7] = [111, 222, 333]                   # jeden prvok sa nahradil tromi
>>> zoz
[0, 10, 20, 'begin', 'end', 60, 111, 222, 333, 80, 90, 100]
>>> abc = list('Python')
>>> abc
['P', 'y', 't', 'h', 'o', 'n']
>>> abc[2:2]                                     # rez dĺžky 0
[]
>>> abc[2:2] = ['dve', 'slova']                  # rez dĺžky 0 sa nahradí dvomi prvkami
>>> abc
['P', 'y', 'dve', 'slova', 't', 'h', 'o', 'n']
>>> prvo = [2, 3, 5, 7, 11]
>>> prvo[1:-1]
[3, 5, 7]
>>> prvo[1:-1] = []                              # rez dĺžky 3 sa nahradí žiadnymi prvkami
>>> prvo                                         # prvky sa takto vyhodili
[2, 11]

Pozor! Všetky tieto príklady modifikujú pôvodný zoznam, teda priraďovanie do rezu je mutable operácia.

Porovnávanie zoznamov

Zoznamy môžeme navzájom porovnávať (na rovnosť, alebo menší/väčší). Funguje to na rovnakom princípe ako porovnávanie znakových reťazcov:

  • postupne sa prechádzajú prvky jedného aj druhého zoznamu, kým sú rovnaké

  • ak je jeden so zoznamov kratší, tak ten sa považuje za menší ako ten druhý

  • ak pri postupnom porovnávaní prvkov nájde rôzne hodnoty, výsledok porovnania týchto dvoch rôznych hodnôt je výsledkom porovnania celých zoznamov

  • každé dva porovnávané prvky musí Python vedieť porovnať: na rovnosť je to bez problémov, ale relačné operácie < a > nebudú fungovať napr. pre porovnávanie čísel a reťazcov

Napr.

>>> [1, 2, 5, 3, 4] > [1, 2, 4, 8, 1000]
True
>>> [1000, 2000, 3000] < [1000, 2000, 3000, 0, 0]
True
>>> [1, 'ahoj'] == ['ahoj', 1]
False
>>> [1, 'ahoj'] < ['ahoj', 1]
...
TypeError: '<' not supported between instances of 'int' and 'str'

Zoznam ako parameter funkcie

Už predtým sme zisťovali priemernú teplotu zo zoznamu nameraných hodnôt. Teraz z toho vieme urobiť funkciu, napr.

def priemer(zoznam):
    sucet = 0
    pocet = 1
    for prvok in zoznam:
        sucet += prvok
        pocet += 1
    return sucet / pocet

Keďže pomocou štandardných funkcií sum() a len() vieme veľmi rýchlo zistiť súčet aj počet prvkov zoznamu, môžeme to zapísať elegantnejšie:

def priemer(zoznam):
    return sum(zoznam) / len(zoznam)

Ďalšia funkcia zisťuje počet výskytov nejakej konkrétnej hodnoty v zozname:

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

a naozaj funguje:

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

Zaujímavé je aj to, že funkcia funguje nielen pre zoznamy, ale pre ľubovoľnú postupnosť (iterovateľnú štruktúru), napr.

>>> pocet('bla-bla-bla', 'l')
3
>>> pocet('bla-bla-bla', 'la')     # v postupnosti znakov sa 'la' nenachádza
0

Pre znaky táto funkcia robí skoro to isté ako metóda count(), teda zápis:

>>> 'bla-bla-bla'.count('l')
3

naša funguje úplne rovnako ako funkcia pocet(). Aj pre zoznamy existuje metóda count, ktorá robí presne to isté ako naša funkcia pocet().

Metódy

Už vieme, že metódami voláme také funkcie, ktoré fungujú s nejakou hodnotou: za túto hodnotu dávame bodku (preto tzv. bodková notácia) a samotné meno funkcie s prípadnými parametrami. V prípade zoznamov to vyzerá takto:

zoznam.funkcia(parametre)

Pre zoznamy existujú tieto dve metódy, ktoré nemodifikujú ich obsah a preto vieme, že sú immutable.

metóda count()

Volanie metódy:

zoznam.count(hodnota)

vráti počet výskytov danej hodnoty v zozname. Táto metóda je immutable lebo nemení obsah zoznamu.

Metódu môžeme použiť nielen s premennou typu zoznam, napr.

>>> zoz = [1, 2, 3, 2, 1, 2]
>>> zoz.count(2)
3
>>> zoz.count(4)
0

Ale aj priamo s hodnotou zoznam, napr.

>>> [0, 1, 0, 0, 1, 0, 1, 0, 0].count(1)
3

Alebo s výrazom, ktorý je typu zoznam:

>>> ([3, 7] * 100 + [7, 8] * 50).count(7)
150

metóda index()

Volanie metódy:

zoznam.index(hodnota)

vráti index prvého výskytu danej hodnoty v zozname. Táto metóda je immutable lebo nemení obsah zoznamu. Funkcia spadne na chybe, ak sa daná hodnota v zozname nenachádza.

Aj túto metódu môžeme použiť rôznym spôsobom, napr. s premennou typu zoznam:

>>> farby = ['red', 'blue', 'red', 'blue', 'yellow']
>>> farby.index('blue')
1
>>> farby.index('green')
...
ValueError: 'green' is not in list

Pri používaní tejto metódy musíme dávať pozor, aby nám program nepadal, keď sa daná hodnota v zozname nenachádza. Napr. takto:

>>> farby = ['red', 'blue', 'red', 'blue', 'yellow']
>>> if 'green' in farby:
        index = farby.index('green')
    else:
        print('green sa v zozname nenachadza')
green sa v zozname nenachadza

Všetky ďalšie metódy, ktoré tu uvedieme, sú mutable, teda budú modifikovať samotný zoznam.

metóda append()

Volanie metódy:

zoznam.append(hodnota)

pridá na koniec zoznamu nový prvok - zoznam sa takto predĺži o 1. Táto metóda je mutable lebo mení obsah zoznamu. Funkcia nič nevracia, preto nemá zmysel priraďovať jej volanie do nejakej premennej (teda vracia hodnotu None).

Napr. volanie:

>>> ab = [2, 3, 5, 7, 11]
>>> ab.append(13)
>>> ab
[2, 3, 5, 7, 11, 13]

takto sa zmení vizualizácia pamäte pre premennú ab:

_images/08_03.png

Zrejme nemá zmysel volať túto metódu s hodnotou typu zoznam namiesto premennej. Hoci to funguje dobre, nemáme šancu zistiť, ako vyzerá daný zoznam s pridaným prvkom:

>>> [1, 2, 3].append(4)

Niekde v pamäti hodnôt sa vyrobil zoznam [1, 2, 3, 4], na ktorý ale nemáme žiadnu referenciu.

Vďaka tejto metóde sa oplatí naučiť aj druhý spôsob vytvárania zoznamu.

šablóna na vytváranie zoznamu pomocou append

Ak potrebujeme v cykle vytvárať nejaký zoznam rôznych hodnôt, pričom nemusíme poznať jeho výslednú dĺžku, môžeme takto elegantne využiť metódu append:

zoznam = []           # najprv je zoznam prázdny
for i in range(n):    # alebo for-cyklus pre inú postupnosť
    if ... nejaka_podmienka:
        nova_hodnota = ...
        zoznam.append(nova_hodnota)
print(zoznam)

Príklady:

  • zoznam hodnôt postupnosti čísel range(2, 100, 13):

    zoznam = []
    for i in range(2, 100, 13):
        zoznam.append(i)
    print(zoznam)
    
    [2, 15, 28, 41, 54, 67, 80, 93]
    

    v tomto konkrétnom prípade sa to dalo zapísať aj takto výrazne jednoduchšie:

    zoznam = list(range(2, 100, 13))
    print(zoznam)
    
  • zoznam reťazcov, ktoré sú vytvorené z mocnín 2 a obsahujú cifru 7:

    zoznam = []
    for i in range(30):
        hodnota = str(2 ** i)
        if '7' in hodnota:
            zoznam.append(hodnota)
    print(zoznam)
    
    ['32768', '131072', '1048576', '2097152', '16777216', '67108864', '134217728', '536870912']
    

metóda pop()

Volanie metódy:

zoznam.pop()

odoberie z konca zoznamu posledný prvok - zoznam sa takto skráti o 1. Táto metóda je mutable lebo mení obsah zoznamu. Funkcia vracia hodnotu odobratého prvku. Ak bol zoznam prázdny, funkcia nič nevracia ale spadne na chybe.

Napr.

>>> abc = ['raz', 'dva', 'tri']
>>> for i in range(4):
        abc.pop()

'tri'
'dva'
'raz'
...
IndexError: pop from empty list

metóda insert()

Volanie metódy:

zoznam.insert(index, hodnota)

pridá na dané miesto zoznamu nový prvok - zoznam sa takto predĺži o 1. Táto metóda je mutable lebo mení obsah zoznamu. Funkcia nič nevracia, preto nemá zmysel priraďovať jej volanie do nejakej premennej (teda vracia hodnotu None).

Napr.

>>> abc = ['raz', 'dva', 'tri']
>>> abc.insert(10, 'koniec')
>>> abc
['raz', 'dva', 'tri', 'koniec']
>>> abc.insert(2, 'stred')
>>> abc
['raz', 'dva', 'stred', 'tri', 'koniec']
>>> abc.insert(0, 'zaciatok')
>>> abc
['zaciatok', 'raz', 'dva', 'stred', 'tri', 'koniec']
>>> abc.insert(-1, 'predposledny')
>>> abc
['zaciatok', 'raz', 'dva', 'stred', 'tri', 'predposledny', 'koniec']

Uvedomte si, že zoznam.insert(len(zoznam), hodnota) pridáva vždy na koniec zoznamu, teda robí to isté ako zoznam.append(hodnota).

metóda pop() s indexom

Volanie metódy:

zoznam.pop(index)

odoberie zo zoznamu príslušný prvok (daný indexom) - zoznam sa takto skráti o 1. Táto metóda je mutable lebo mení obsah zoznamu. Funkcia vracia hodnotu odobratého prvku. Ak bol zoznam prázdny, funkcia nič nevracia ale spadne na chybe.

Napr.

>>> abc = ['raz', 'dva', 'tri', 'styri']
>>> abc.pop(7)
...
IndexError: pop index out of range
>>> abc.pop(-1)                      # to isté ako abc.pop()
'styri'
>>> abc.pop(0)                       # vyhadzuje prvý prvok
'raz'
>>> abc
['dva', 'tri']
>>> abc.pop(1)
'tri'
>>> abc.pop(0)
'dva'
>>> abc.pop(0)
...
IndexError: pop from empty list
>>> abc
[]

Posledné volanie metódy pop() sa snaží vybrať prvý prvok z prázdneho zoznamu - spôsobilo to vyvolanie správy o chybe.

metóda remove()

Volanie metódy:

zoznam.remove(hodnota)

odoberie zo zoznamu prvý výskyt prvku s danou hodnotou - zoznam sa takto skráti o 1. Táto metóda je mutable lebo mení obsah zoznamu. Funkcia nič nevracia (teda vracia None). Ak sa daná hodnota v zozname nenachádza, funkcia spadne na chybe.

Napr.

>>> abc = ['raz', 'dva', 'tri', 'dva']
>>> abc.remove('dva')
>>> abc
['raz', 'tri', 'dva']
>>> abc.remove('styri')
...
ValueError: list.remove(x): x not in list

metóda sort()

Volanie metódy:

zoznam.sort()

zmení poradie prvkov zoznamu tak, aby boli usporiadané vzostupne - zoznam takto nemení svoju dĺžku. Táto metóda je mutable lebo mení obsah zoznamu. Funkcia nič nevracia (teda vracia None). Ak sa prvky v zozname nedajú navzájom porovnávať (napr. sú tam čísla aj reťazce), funkcia spadne na chybe.

Napr.

>>> abc = ['raz', 'dva', 'tri', 'styri']
>>> abc.sort()
>>> abc
['dva', 'raz', 'styri', 'tri']
>>> post = list(reversed(range(10)))
>>> post
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> post.sort()
>>> post
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Častou začiatočníckou chybou býva priradenie výsledku tejto metódy (teda None) do premennej, napr.

>>> abc = ['raz', 'dva', 'tri', 'styri']
>>> abc = abc.sort()                       # vždy vráti None
>>> print(abc)
None

Takto si pokazíme referenciu na pekne utriedený zoznam.

Zoznam ako výsledok funkcie

Prvá funkcia vráti novo vytvorený zoznam rovnakých hodnôt:

def urob_zoznam(n, hodnota=0):
    return [hodnota] * n

Môžeme to použiť napr. takto (premenné sme nazvali pole, lebo sa to trochu podobá na pascalovské polia):

>>> pole1 = urob_zoznam(30)
>>> pole2 = urob_zoznam(25, None)
>>> pole3 = urob_zoznam(3, 'Python')
>>> pole1
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
>>> pole2
[None, None, None, None, None, None, None, None, None, None,
 None, None, None, None, None, None, None, None, None, None,
 None, None, None, None, None]
>>> pole3
['Python', 'Python', 'Python']

Ďalšia funkcia vytvorí z danej postupnosti nový zoznam:

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

Uvedomte si, že to robí skoro to isté ako volanie funkcie list():

>>> py = zoznam('Python')
>>> py
['P', 'y', 't', 'h', 'o', 'n']
>>> cisla = zoznam(range(3, 25, 4))
>>> cisla
[3, 7, 11, 15, 19, 23]

Funkcia pridaj() na základe nejakého zoznamu vytvorí nový zoznam, na koniec ktorého pridá nový prvok:

def pridaj(zoznam, hodnota):
    return zoznam + [hodnota]

Všimnite si, že pôvodný zoznam pritom ostal nezmenený:

>>> zoz = ['raz', 'dva', 'tri']
>>> novy = pridaj(zoz, 'styri')
>>> novy
['raz', 'dva', 'tri', 'styri']
>>> zoz
['raz', 'dva', 'tri']

Takejto funkcii budeme hovoriť, že je immutable, lebo nemení hodnotu žiadneho predtým existujúceho zoznamu.

Ak by sme túto funkciu zapísali takto:

def pridaj1(zoznam, hodnota):
    zoznam.append(hodnota)
    return zoznam

Volaním tejto funkcie by sme dostali veľmi podobné výsledky:

>>> zoz = ['raz', 'dva', 'tri']
>>> novy = pridaj1(zoz, 'styri')
>>> novy
['raz', 'dva', 'tri', 'styri']
>>> zoz
['raz', 'dva', 'tri', 'styri']

Ale zmenila sa pritom aj hodnota prvého parametra - premennej zoz typu zoznam. Táto funkcia je mutable - mení svoj parameter zoz typu list.

Dve premenné referencujú na ten istý zoznam

Už sme získali predstavu o tom, že priradenie zoznamu do premennej označuje, že sme v skutočnosti do premennej priradili referenciu na zoznam. Lenže na ten istý zoznam v pamäti môžeme mať viac referencií, napr.

>>> 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 (ale len pomocou mutable operácií!), zmení sa aj obsah premennej a:

_images/08_05.png

Zhrňme

Vkladanie do zoznamu

Videli sme viac rôznych spôsobov, ako môžeme pridať jednu hodnotu do zoznamu. Vkladanie nejakej hodnoty pred prvok s indexom i:

  • pomocou rezu (mutable):

    zoznam[i:i] = [hodnota]
    
  • pomocou metódy insert() (mutable):

    zoznam.insert(i, hodnota)
    
  • ak i == len(zoznam), pridávame na koniec (za posledný prvok), môžeme použiť metódu append() (mutable):

    zoznam.append(hodnota)
    

    to isté dosiahneme aj takto (mutable):

    zoznam += [hodnota]
    

Vo vašich programoch použijete ten zápis, ktorý sa vám bude najlepšie hodiť, ale zápis s rezom zoznam[i:i] je najmenej čitateľný a používa sa veľmi zriedkavo.

  • zrejme funguje aj (immutable):

    zoznam = zoznam[:i] + [hodnota] + zoznam[i:]
    

    toto priradenie nemodifikuje pôvodný zoznam, ale vytvára nový s pridanou hodnotou

Vyhadzovanie zo zoznamu

Aj vyhadzovanie prvku zo zoznamu môžeme robiť viacerými spôsobmi. Ak vyhadzujeme prvok na indexe i, môžeme zapísať:

  • pomocou rezu (mutable):

    zoznam[i:i+1] = []
    
  • pomocou príkazu del (mutable):

    del zoznam[i]
    
  • pomocou metódy pop(), ktorá nám aj vráti vyhadzovanú hodnotu (mutable):

    hodnota = zoznam.pop(i)
    
  • veľmi neefektívne pomocou metódy remove(), ktorá ako parameter očakáva nie index ale vyhadzovanú hodnotu (mutable):

    zoznam.remove(zoznam[i])
    

    tento spôsob je veľmi neefektívny (zbytočne sa hľadá prvok v zozname) a okrem toho niekedy môže vyhodiť nie i-ty prvok, ale prvok s rovnakou hodnotou, ktorý sa v zozname nachádza skôr ako na indexe i.

  • zrejme funguje aj (immutable):

    zoznam = zoznam[:i] + zoznam[i+1:]
    

    toto priradenie nemodifikuje pôvodný zoznam, ale vytvára nový bez prvku s daným indexom

Vyhodenie všetkých prvkov zo zoznamu

  • najjednoduchší spôsob (immutable):

    zoznam = []
    

    môžeme použiť len vtedy, keď nepotrebujeme uchovať referenciu na zoznam - toto priradenie nahradí momentálnu referenciu na zoznam referenciou na úplne nový zoznam; ak to použijeme vo vnútri funkcie, stratí sa tým referencia na pôvodný zoznam

Ďalšie spôsoby uchovávajú referenciu na zoznam:

  • všetky prvky zoznamu postupne vyhodíme pomocou while-cyklu (mutable):

    while zoznam:
        zoznam.pop()
    

    toto je zbytočne veľmi neefektívne riešenie

  • priradením do rezu (mutable):

    zoznam[:] = []
    

    je ťažšie čitateľné a menej pochopiteľné riešenie

  • metódou clear() (mutable):

    zoznam.clear()
    

    je asi najčitateľnejší zápis

Vytvorenie kópie zoznamu

Ak potrebujeme vyrobiť kópiu celého zoznamu, dá sa to urobiť:

  • pomocou cyklu:

    kopia = []
    for prvok in zoznam:
        kopia.append(prvok)
    
  • môžeme využiť aj rez:

    kopia = zoznam[:]
    
  • keďže funguje funkcia list(), môžeme zapísať:

    kopia = list(zoznam)
    


Cvičenia

L.I.S.T.

  1. Do premenných den a day priraď 7 názvov dní v týždni v slovenčine a v angličtine ('pondelok', … ). Vypíš tieto dva zoznamy každý do jedného riadku.

    • den = ...
      day = ...
      # vypis
      pondelok ...
      monday ...
      

  1. Napíš for-cyklus, pomocou ktorého sa do 7 riadkov postupne vedľa seba vypíšu slovenský a anglický názov dňa v týždni. Použi premenné den a day z úlohy (1).

    • pondelok monday
      ...
      

  1. Napíš funkciu vypis(zoznam), v ktorej parameter zoznam je trojprvkovým zoznamom. Tento zoznam má na začiatku dve celé čísla (označujú súradnice x a y) a za tým znakový reťazec. Funkcia vypíše do grafickej plochy na zadané súradnice daný reťazec.

    • napr.

      zoz = [200, 100, 'PYTHON']
      vypis(zoz)
      vypis([200, 130, 'programovanie'])
      

      vypíše dva texty: jeden na súradnice (200, 100) a druhý na (200, 130):

      _images/08_06.png

  1. V premennej recept je zoznam dĺžky 3 * n. Tento zoznam obsahuje takéto trojice: reťazec pre názov, číslo a názov jednotiek.

    • napr.

      ['cukor', 100, 'g', 'vajíčka', 5, 'ks', 'mlieko', 2, 'dcl']
      

      Napíš funkciu vypis_recept(zoznam), ktorá takýto zoznam vypíše do viacerých riadkov po troch, napr. tento zoznam by sa vypísal takto:

      cukor 100 g
      vajíčka 5 ks
      mlieko 2 dcl
      

  1. Napíš funkciu vypis_len_parne(zoznam), ktorá z daného zoznamu celých čísel vypíše do jedného riadku len párne čísla.

    • napr.

      >>> z = [11, 12, 13, 15, 16, 16, 17]
      >>> vypis_len_parne(z)
      12 16 16
      
  2. Napíš funkciu len_parne(zoznam), ktorá z daného zoznamu celých čísel vytvorí nový zoznam, ktorý bude obsahovať z pôvodného len párne čísla. Tento zoznam vráti (return) ako výsledok funkcie.

    • napr.

      >>> z = [11, 12, 13, 15, 16, 16, 17]
      >>> z1 = len_parne(z)
      >>> z1
      [12, 16, 16]
      

  1. Napíš funkciu vzostupne(zoznam), ktorá zistí, či sú prvky vstupného zoznamu usporiadané vzostupne. Funkcia vráti (return) True alebo False.

    • napr.

      >>> vzostupne([1, 5, 5, 8, 100])
      True
      >>> vzostupne(['pyton', 'python', 'pytliak'])
      False
      

  1. V nejakej obci je jediná ulica, na ktorej je n domov. Na miestnom úrade majú v zozname (typu list) pre každý dom zaznačený počet obyvateľov. Napíš program, ktorý zistí, koľko obyvateľov žije v celej obci, koľko domov je neobývaných a v koľkých domoch býva maximálny počet obyvateľov.

    • napr.

      domy = [4, 2, 0, 5, 0, 1, 5, 4]
      # vypíše
      počet obyvateľov je 21
      neobývaných domov je 2
      maximálny počet obyvateľov v dome je 5
      počet maximálnych domov je 2
      

  1. Napíš funkciu najdlhsie(zoznam), ktorá zo zoznamu slov vráti (return) najdlhšie slovo.

    • napr.

      >>> zoz = ['prvy', 'druhy', 'treti', 'stvrty', 'piaty']
      >>> najdlhsie(zoz)
      'stvrty'
      

  1. Napíš funkciu nahrad(zoznam), ktorá v danom zozname nahradí všetky výskyty nuly hodnotou None. Funkcia nič nevypisuje ani nevracia (return). Funkcia zmodifikuje pôvodný zoznam.

    • napr.

      >>> a = [1, 2, 0, 3, 0, 0, 4]
      >>> nahrad(a)
      >>> a
      [1, 2, None, 3, None, None, 4]
      

  1. Napíš funkciu sucet(zoznam), ktorá vypočíta súčet všetkých celých a desatinných čísel, ktoré sa nachádzajú v zadanom zozname. Hodnoty iných typov bude ignorovať. Funkcia vráti (return) tento súčet.

    • napr.

      >>> x = sucet(['123', -5, None, 3.141])
      >>> x
      -1.859
      

  1. Napíš funkciu citaj(), ktorá číta zo vstupu nejaké reťazce (pomocou input) a vyrobí z nich zoznam. Čítanie skončí pri prázdnom reťazci. Funkcia vráti (return) tento vytvorený zoznam.

    • napr.

      >>> zoz = citaj()
      zadaj: prvy
      zadaj: 123
      zadaj: druhy
      zadaj:
      >>> zoz
      ['prvy', '123', 'druhy']
      

  1. Zoznam obsahuje dvojice čísel [x, y, x, y, x, y, ...] - sú to súradnice nejakých bodov. Do grafického príkazu create_polygon môžeme súradnice vykresľovanej oblasti poslať aj v takomto zozname. Napíš funkciu poly(zoz, n, dx, dy), ktorá pomocou create_polygon nakreslí tento útvar náhodnou farbou, potom zväčší každé x v zozname o dx a y v zozname p dy a znovu to vykreslí náhodnou farbou. Takto sa postupne nakreslí n útvarov.

    • napr.

      utvar = [50, 50, 80, 150, 100, 90]
      poly(utvar, 5, 30, 5)
      

      nakreslí 5 farebných trojuholníkov, každý ďalší je posunutý oproti predchádzajúcemu o (30, 5):

      _images/08_07.png

  1. Zoznam obsahuje reťazce a čísla. Napíš funkciu spoj(zoznam, znak), ktorá spojí všetky prvky zoznamu do jedného reťazca. Zrejme z čísel najprv urobí reťazce. Medzi takto spájané reťazce vloží zadaný znak. Funkcia nič nevypisuje, ale vráti (return) jeden znakový reťazec.

    • napr.

      >>> spoj([12, 'a', 13], ' ')
      '12 a 13'
      >>> spoj(['Py', 't', 'hon'], '')
      'Python'
      

  1. Napíš funkciu vyhod(zoznam), ktorá z daného zoznamu vyhodí všetky prvky s hodnotou None. Funkcia nič nevypisuje ani nevracia. Funkcia modifikuje vstupný zoznam.

    • napr.

      >>> b = [None, 'None', 0, None]
      >>> vyhod(b)
      >>> b
      ['None', 0]
      

  1. Napíš funkciu nahodne(n), ktorá vráti n-prvkový zoznam náhodných čísel od 1 do 6. Funkcia nič nevypisuje len vráti (return) vygenerovaný zoznam.

    • napr.

      >>> nahodne(7)
      [5, 1, 1, 2, 6, 5, 2]
      

  1. Napíš funkciu najdlhsi_usek(zoznam), ktorá zo zoznamu z predchádzajúcej úlohy (16) zistí najdlhší úsek rovnakých hodnôt. Funkcia vypíše, aké číslo sa v úseku opakovalo a koľko krát.

    • napr.

      >>> x = nahodne(7)
      >>> x
      [5, 1, 1, 2, 6, 5, 2]
      >>> najdlhsi_usek(x)
      najdlhší úsek s číslom 1 má dĺžku 2
      
  2. Napíš funkciu pridaj(zoznam, nova), ktorá vloží na správne miesto novú hodnotu do utriedeného zoznamu. Napr. máme v zozname mená žiakov podľa abecedy, alebo výšky žiakov od najmenšieho po najväčšieho a pod. a potrebujeme na správne miesto pridať nového žiaka. Funkcia nič nevypisuje ani nevracia. Funkcia modifikuje vstupný zoznam.

    • napr.

      >>> ziaci = ['Adam', 'Eva', 'Paula']
      >>> pridaj(ziaci, 'Igor')
      >>> ziaci
      ['Adam', 'Eva', 'Igor', 'Paula']
      >>> vysky = [150, 155, 155, 162]
      >>> pridaj(vysky, 148)
      >>> vysky
      [148, 150, 155, 155, 162]
      

  1. Napíš funkciu vloz_medzi(zoznam), ktorá do zoznamu celých čísel medzi každé dva prvky vloží ich súčet. Funkcia nič nevypisuje len vráti (return) novo vytvorený zoznam (pôvodný zoznam nemení).

    • napr.

      >>> z1 = [1, 1]
      >>> z2 = vloz_medzi(z1)
      >>> z2
      [1, 2, 1]
      >>> z3 = vloz_medzi(z2)
      >>> z3
      [1, 3, 2, 3, 1]
      >>> vloz_medzi(z3)
      [1, 4, 3, 5, 2, 5, 3, 4, 1]
      


2. Domáce zadanie

L.I.S.T.

Napíšte pythonovský skript, ktorý bude definovať túto funkciu:

vypis(meno_suboru, sirka)

Funkcia vypis() dostáva ako prvý parameter meno textového súboru a dryhým parametrom je celé číslo, ktoré udáva šírku výpisu. Funkcia tento súbor prečíta a celý ho vypíše do textovej plochy (do konzoly) tak, že bude zarovnaný na danú šírku.

Textový súbor sa skladá z odsekov, ktoré sa skladajú zo slov. Odseky sú navzájom oddelené aspoň jedným púrázdnym riadkom. Slová v odesku sú navzákom oddelené aspoň jednou medzerou alebo koncom riadka.

Napr. "subor1.txt" sa skladá z týchto riadkov:

Ján Botto:
  Ž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.


  Tá ľalija smutno vzdychá:

  Hlávku moju tŕnie pichá
 a  nožičky  oheň  páli —
pomôžte mi v mojom žiali!

Tento súbor obsahuje 5 „odsekov“, pričom najkratší je druhý a má 3 „slová“. Najdlhší je tretí odsek má 20 slov.

Volanie vypis('subor1.txt', 20) vypíše:

Ján    Botto:   Ž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.

Tá   ľalija   smutno
vzdychá:

Hlávku   moju  tŕnie
pichá a nožičky oheň
páli  — pomôžte mi v
mojom žiali!

Pričom vypis('subor1.txt', 60) vypíše:

Ján Botto: Ž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.

Tá ľalija smutno vzdychá:

Hlávku  moju  tŕnie pichá a nožičky oheň páli — pomôžte mi v
mojom žiali!

Všimnite si, že všetky riadky v odseku okrem posledného sú zarovnané vpravo na zadanú šírku, pričom, ak by bola dĺžka takéhoto riadka kratšia ako zadaná šírka, medzi slová sú rovnomerne vložené medzery. Ak nejaký riadok obsahuje len jedno slovo, tak ani tento sa nezarovnáva na pravý okraj.

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

# 2. zadanie: zarovnaj
# autor: Janko Hraško
# datum: 5.11.2018

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