Lista

Tarkastellaan seuraavaa ongelmaa: Meteorologi haluaa ohjelman, johon hän voi syöttää kuukauden jokaisen päivän maksimilämpötilan. Kun lämpötilat on syötetty, ohjelman pitää tulostaa lämpötilojen keskiarvo ja syötetyt lämpötilat alkuperäisessä järjestyksessä. Oletetaan, että jokaisessa kuukaudessa on 30 päivää.

Jos ohjelman pitäisi tulostaa vain lämpötilojen keskiarvo, olisi helppo kerätä lämpötilojen summa yhteen muuttujaan ja laskea lopuksi keskiarvo tämän muuttujan avulla. Nyt kuitenkin halutaan tulostaa myös syötetyt lämpötilat. Tämän vuoksi jokaisen päivän lämpötila pitää tallentaa erikseen. Tähän asti opituilla välineillä tarvitsisimme kolmekymmentä eri muuttujaa lämpötilojen tallentamista varten. Meidän pitäisi myös kirjoittaa kolmekymmentä käskyä lämpötilojen lukemiseen ja toiset kolmekymmentä käskyä lämpötilojen tulostamiseen. Näin ohjelmasta tulisi tarpeettoman pitkä. Lisäksi sen muuttaminen toimimaan jollain muulla lämpötilamäärällä olisi hankalaa. Jos esimerkiksi haluaisimme ohjelman käsittelevän samalla tavalla koko vuoden lämpötilat, pitäisi ohjelmaan lisätä 335 uutta muuttujaa sekä saman verran uusia luku- ja tulostuskäskyjä.

Tällainen ongelma voidaan ratkaista selvästi helpommin käyttämällä listaa. Lista on tietorakenne, johon voi tallentaa useita arvoja. Näihin arvoihin pääsee käsiksi indeksin avulla. Jos on esimerkiksi lista lampotilat, niin listan viidenteen alkioon pääsee käsiksi kirjoittamalla lampotilat[4]. (Hakasuluissa oleva indeksi on 4 eikä 5 siksi, että listan ensimmäisen alkion indeksi on aina 0.)

Uusi lista luodaan esimerkiksi kirjoittamalla

lampotilat = []

Sijoituskäskyn oikealla puolella luodaan tyhjä lista ja sijoituskäsky yhdistää muuttujan lampotilat luotuun listaan.

Tämän jälkeen listan loppuun voi lisätä uuden arvon kirjoittamalla

lampotilat.append(arvo)

Uusi arvo lisätään aina vanhan listan loppuun niin, että listan pituus kasvaa yhdellä. Esimerkiksi ohjelma

def main():
    lampotilat = []
    lampotilat.append(15.0)
    lampotilat.append(22.5)
    lampotilat.append(-12.9)
    print(lampotilat)


main()

tulostaa

[15.0, 22.5, -12.9]

Tässä siis listaan lisättiin lukuja metodin append avulla. Kuten funktio, metodi on ohjelmakoodin osa, jolle on annettu oma nimi. Metodia kuitenkin kutsutaan eri tavalla kuin funktiota: lista, johon lisäys tehdään, ei olekaan metodin parametrina vaan se on kirjoitettu kutsuun ennen metodin nimeä ja erotettu metodin nimestä pisteellä. Metodien määrittely ja niiden kutsutapa liittyy olio-ohjelmoinnin piirteisiin, joihin tutustutaan tarkemmin kierroksella 9. Listojen ja myöhemmin merkkijonojen yhteydessä kuitenkin käytämme joitakin Pythonin valmiita metodeita.

Jos listassa on vähintään i+1 alkiota, pystyy indeksille i sijoittamaan uuden arvon kirjoittamalla

listamuuttuja[i] = arvo

tällöin indeksillä i oleva vanha arvo häviää ja ja se korvautuu sijoituskäskyn oikealla puolella olevalla arvolla.

Jos esimerkiksi edellisen ohjelman main-funktion loppuun lisätään rivi

lampotilat[1] = 32.0

on lista lampotilat rivin suorittamisen jälkeen

[15.0, 32.0, -12.9]

Seuraava animaatio vielä näyttää, mitä edellä annettujen käskyjen suorituksessa tapahtuu:

Tällaisella sijoituskäskyllä ei voi kuitenkaan kasvattaa listan kokoa eli hakasuluissa olevan indeksi saa olla korkeintaan listan alkioiden määrä vähennettynä yhdellä (esimerkiksi 2 silloin, kun listassa on 3 alkioita). Jos edellä esitetyn ohjelman main-funktion loppuun lisää käskyn

lampotilat[3] = 18.0

johtaa se ohjelman kaatumiseen ja seuraavaan virheilmoitukseen:

Traceback (most recent call last):
  File "lampotilat1.py", line 10, in <module>
    main()
  File "lampotilat1.py", line 7, in main
    lampotilat[3] = 18.0
IndexError: list assignment index out of range

Tässä viimeinen rivi “list assignment index out of range” kertookin aika suoraan, millaisesta virheestä on ollut kysymys eli että sijoituksessa listaan käytetty indeksi on ollut sallittujen rajojen ulkopuolella.

Listan alkioiden arvoja voidaan käyttää indeksoinnin avulla samalla tavalla kuin minkä tahansa muiden muuttujien arvoja, esimerkiksi

summa = lampotilat[1] + lampotilat[2]

laskee lampotilat-listassa indekseillä 1 ja 2 olevien alkioiden arvot yhteen ja sijoittaa näin saadun tuloksen muuttujan summa arvoksi. Tässäkin käytettyjen indeksien pitää olla listan indeksialueen sisällä.

Palataan sitten alkuperäiseen esimerkkiin: ohjelmaan, joka kirjaa kuukauden eri päivien lämpötilat ja sen jälkeen tulostaa nämä lämpötilat uudelleen sekä niiden keskiarvon.

Ohjelmassa pitää siis ensinnäkin luoda lista lämpötiloja varten. Tämä voidaan tehdä käskyllä

lampotilat = []

Sitten tarvitaan toistokäsky, joka pyytää käyttäjältä eri päivien lämpötilat ja lisää ne lampotilat-listaan. Toistokäsky tarvitsee tiedon siitä, montako lämpötilaa käyttäjältä luetaan, jotta se osaisi lopettaa lämpötilojen pyytämisen oikean määrän luettuaan. (Vaihtoehtoisesti ohjelma voidaan rakentaa niin, että käyttäjä ilmoittaa jollain sovitulla arvolla, että kaikki halutut lämpötilat on luettu). Otetaan tätä lämpötilojen määrää varten käyttöön vakio LKM, jolle siis annetaan arvo 30. Lisäksi while-käskyssä tarvitaan laskuri, joka pitää kirjaa siitä, kuinka monta lämpötilaa on jo luettu. Seuraavassa ohjelmassa käytetään siihen tarkoitukseen muuttujaa i. Lämpötilat lukeva ja listaan lisäävä while-käsky voi siis olla sitä edeltävine alustuksineen seuraava:

LKM = 30
i = 0
while i < LKM:
    lampo = float(input("Seuraava lampotila: "))
    lampotilat.append(lampo)
    i += 1

Tarkastellaan sitten sitä osaa ohjelmasta, joka laskee lämpötilojen keskiarvon. Kirjoitetaan toistokäsky, joka laskee listassa lampotilat olevien lämpötilojen summan. (Summan laskemisen voi käytännössä yhdistää samaan toistokäskyyn joko lämpötilojen lukemisen tai tulostamisen kanssa, mutta tässä se on esitetty aluksi selvyyden vuoksi erikseen.) Tarvitsemme muuttujan, johon summaa kerätään. Tämä muuttuja alustetaan aluksi nollaksi. Sen jälkeen käydään toistokäskyn avulla koko lista lampotilat läpi ja jokaisella kierroksella lisätään yksi alkio aikaisemmin laskettuun summaan:

summa = 0.0
i = 0
while i < LKM:
    summa += lampotilat[i]
    i += 1

Tässä siis i:n arvo vaihtelee eri kierroksilla niin, että ensimmäisellä kierroksella muuttujaan summa lisätään alkio lampotilat[0], toisella kierroksella lampotilat[1] ja niin edelleen, kunnes viimeisellä kierroksella summaan lisätään lampotilat[LKM - 1]. Listan viimeisen alkion indeksi on LKM - 1, vaikka listassa on LKM alkiota, koska ensimmäisen alkion indeksi on 0.

Katsotaan vielä alla olevan animaation avulla, mitä edellä annettujen käskyjen suorituksessa tapahtuu. Vakion LKM arvoksi on vaihdettu 3, jotta toistokäskyjen kierrosten määrä pysyisi kohtuullisena. Animaation koodin loppuun on myös lisätty summan tulostus.

Listan läpikäynti voidaan kirjoittaa myös for-käskyn avulla. Tämä yleensä onkin kätevämpi tapa silloin, kun listaan ei tarvitse enää lisätä uusia alkioita tai muuttaa listassa olevia arvoja, sillä for-käskyssä ohjelmoijan ei tarvitse ilmaista listan kokoa eikä pitää huolta indeksimuuttujan kasvattamisesta. Käskyn yleinen muoto on tällöin

for elementti in lista:
    tee jotain listan alkiolle elementti

Tässä muuttuja elementti saa vuorotellen arvokseen kunkin listan alkion niin, että ensimmäisellä kierroksella muuttujan elementti arvo on listan ensimmäinen alkio, toisella kierroksella toinen alkio jne. Listan lampotilat alkioiden summa voidaan laskea seuraavalla for-käskyllä:

summa = 0.0
for arvo in lampotilat:
    summa += arvo

Seuraava animaatio näyttää, miten for-käskyn suoritus etenee. Animaation lyhentämiseksi listan sisältö annetaan ohjelman alussa valmiina. Ohjelmaan on myös lisätty summan tulostus.

Seuraavaksi koko ohjelma, joka pyytää lämpötilat, tulostaa ne ja laskee ja tulostaa niiden keskiarvon. Lämpötilojen tulostus ja niiden summan laskeminen on yhdistetty samaan toistokäskyyn.

def main():
    LKM = 30
    lampotilat = []
    i = 0
    print("Anna", LKM, "lampotilaa.")
    while i < LKM:
        lampo = float(input("Seuraava lampotila: "))
        lampotilat.append(lampo)
        i += 1
    summa = 0.0
    print("Annetut lampotilat")
    for arvo in lampotilat:
        print(arvo)
        summa += arvo
    keskiarvo = summa / LKM
    print(f"Lampotilojen keskiarvo on {keskiarvo:.2f}.")


main()

Seuraavaksi esimerkki ohjelman toiminnasta. Vakion LKM arvoksi on vaihdettu 5, jotta esimerkkiajosta ei tulisi turhan pitkä.

Anna 5 lampotilaa.
Seuraava lampotila: 25.0
Seuraava lampotila: 12.0
Seuraava lampotila: -5.0
Seuraava lampotila: -10.0
Seuraava lampotila: 2.0
Annetut lampotilat
25.0
12.0
-5.0
-10.0
2.0
Lampotilojen keskiarvo on 4.80.

Jos käyttäjälle ei haluta tulostaa mitään tekstiä joka kerta, kun luetaan uusi lämpötila, jätetään input-käskyyn liittyvät sulut tyhjiksi. Näin on tehty alla olevassa ohjelman toisessa versiossa. Monessa harjoitustehtävässä käytetään tätä tapaa.

def main():
    LKM = 30
    lampotilat = []
    i = 0
    print("Anna", LKM, "lampotilaa.")
    while i < LKM:
        lampo = float(input())
        lampotilat.append(lampo)
        i += 1
    summa = 0.0
    print("Annetut lampotilat")
    for arvo in lampotilat:
        print(arvo)
        summa += arvo
    keskiarvo = summa / LKM
    print(f"Lampotilojen keskiarvo on {keskiarvo:.2f}.")


main()

Alla esimerkki tämän ohjelmaversion toiminnasta. Vakion LKM arvoksi on jälleen vaihdettu 5.

Anna 5 lampotilaa.
25.0
12.0
-5.0
-10.0
2.0
Lampotilojen keskiarvo on 4.80.

Kun uusi lista luodaan, sen ei tarvitse välttämättä olla tyhjä, vaan listaa luodessa voidaan samalla antaa listaan aluksi kuuluvat alkiot, esimerkiksi

numerolista = [2, 4, 6, 8]

luo listan, jossa on neljä kokonaislukua (2, 4, 6 ja 8 tässä järjestyksessä) ja liittää muuttujan numerolista luotuun listaan. Listan alkioihin päästään käsiksi indeksoinnin avulla, ja listaan voidaan myös lisätä uusia alkioita append-metodilla ihan samalla tavalla kuin tyhjänä luotuun listaan.

Aikaisemmassa lämpötilaesimerkissä kasvatettiin listan kokoa aina uutta lämpötilaa lisättäessä. Toinen vaihtoehto on luoda heti aluksi 30-alkioinen lista ja lisätä sijoituskäskyllä alkiot siihen jo olemassaoleville paikoille. Ei ole tosin mahdollista luoda listaa, jossa olisi 30 tyhjää paikkaa, mutta sen sijaan on mahdollista luoda 30-paikkainen lista, jonka jokaisessa alkiossa on aluksi arvo 0.0 seuraavasti:

lampotilat = [0.0] * 30

tai vakiota LKM käyttämällä

LKM = 30
lampotilat = [0.0] * LKM

Tällöin lämpötiloja luettaessa niitä ei enää lisätä listaan append-käskyllä, vaan lämpötilojen lisääminen voidaan tehdä while-käskyä käytettäessä indeksoimalla seuraavasti:

i = 0
while i < LKM:
    lampo = float(input("Seuraava lampotila: "))
    lampotilat[i] = lampo
    i += 1

Jos lämpötilat lisätään näin luotuun listaan for-käskyllä, pitää käskyssä käydä läpi listan indeksejä eikä itse alkioita esimerkiksi näin:

for i in range(LKM):
    lampo = float(input("Seuraava lampotila: "))
    lampotilat[i] = lampo