Osa 4

Lisää merkkijonoista ja listoista

Olemme käyttäneet aiemmin []-syntaksia merkkijonon osajonon erottamiseen:

mjono = "esimerkki"
print(mjono[3:7])
Esimerkkitulostus

merk

Sama syntaksi toimii myös listoissa, ja voimme erottaa sen avulla listan osan:

lista = [3,4,2,4,6,1,2,4,2]
print(lista[3:7])
Esimerkkitulostus

[4, 6, 1, 2]

Lisää erottamisesta

Itse asiassa []-syntaksi toimii hyvin samalla periaatteella kuin range-funktio, eli voimme antaa sille myös askeleen:

mjono = "esimerkki"
print(mjono[0:7:2])
lista = [1,2,3,4,5,6,7,8]
print(lista[6:2:-1])
Esimerkkitulostus

eiek [7, 6, 5, 4]

Jos emme anna jotain arvoa, oletuksena koko sisältö valitaan mukaan. Tämän avulla voimme tehdä seuraavan lyhyen ohjelman, joka kääntää merkkijonon:

mjono = input("Kirjoita merkkijono: ")
print(mjono[::-1])
Esimerkkitulostus

Kirjoita merkkijono: esimerkki ikkremise

Varoitus: globaalin muuttujan käyttö funktion sisällä

Funktioiden sisällä on mahdollista määritellä muuttujia, mutta tämän lisäksi funktio näkee sen ulkopuolella pääohjelmassa määritellyt muuttujat. Tälläisia muuttujia sanotaan globaaleiksi muuttujiksi.

Globaalien muuttujien käyttämistä funktioista käsin ei useimmiten pidetä hyvänä asiana muun muassa siksi, että ne saattavat johtaa ikäviin bugeihin.

Seuraavassa on esimerkki funktiosta, joka käyttää "vahingossa" globaalia muuttujaa:

def tulosta_vaarinpain(nimet: list):
    # käytetään vahingossa parametrin sijaan globaalia muuttujaa nimilista
    i = len(nimilista) - 1
    while i >= 0:
        print(nimilista[i])
        i -= 1

# globaali muuttuja
nimilista = ["Antti", "Emilia", "Erkki", "Margaret"]
tulosta_vaarinpain(nimilista)
print()
tulosta_vaarinpain(["Tupu", "Hupu", "Lupu"])
Esimerkkitulostus

Margaret Erkki Emilia Antti

Margaret Erkki Emilia Antti

Vaikka funktiota kutsutaan oikein, se tulostaa aina globaalissa muuttujassa nimilista olevat nimet.

Kaikki funktioita testaava koodi on kirjoitettava erillisen lohkon sisälle, jotta TMC-testit hyväksyisivät koodin. Edellinen esimerkki siis tulisi toteuttaa seuraavasti:

def tulosta_vaarinpain(nimet: list):
    # käytetään vahingossa parametrin sijaan globaalia muuttujaa nimilista
    i = len(nimilista) - 1
    while i>=0:
        print(nimilista[i])
        i -= 1

# kaikki funktiota testaava koodi tämän lohkon sisälle
if __name__ == "__main__":
    # globaali muuttuja
    nimilista = ["Antti", "Emilia", "Erkki", "Margaret"]
    tulosta_vaarinpain(nimilista)
    print()
    tulosta_vaarinpain(["Tupu", "Hupu", "Lupu"])

Nyt myös globaalin muuttujan määrittely on siirtynyt if-lohkoon.

TMC-testit suoritetaan aina siten, että mitään if-lohkon sisällä olevaa koodia ei suoriteta. Tämän takia funktio ei voi edes teoriassa toimia, sillä se viittaa muuttujaan nimilista, jota ei testejä suoritettaessa ole lainkaan olemassa.

Loading

Merkkijonoa ei voi muuttaa

Merkkijonoilla ja listoilla on paljon yhteistä, ja useimmat operaatiot toimivat samalla tavalla sekä merkkijonoille että listoille. Kuitenkin erona on, että merkkijonoa ei voi muuttaa. Esimerkiksi seuraava koodi ei toimi tarkoitetulla tavalla:

mjono = "esimerkki"
mjono[0] = "a"

Koska merkkijonoa ei voi muuttaa, ohjelman suoritus aiheuttaa virheen:

Esimerkkitulostus

TypeError: 'str' object does not support item assignment

Samankaltainen virhe seuraa, jos yritetään esimerkiksi järjestää merkkijonoa järjestykseen sort-metodilla.

Vaikka merkkijonoa ei voi muuttaa, voimme silti sijoittaa merkkijonon paikalle toisen merkkijonon.

Onkin tärkeää huomata ero seuraavien esimerkkien välillä:

lista = [1,2,3]
lista[0] = 10
4 4 1
mjono = "Moi"
mjono = mjono + "!"
4 4 2

Ensimmäisessä esimerkissä listan sisältö muuttuu. Toisessa esimerkissä alkuperäinen merkkijono korvataan toisella merkkijonolla. Alkuperäinen merkkijono jää muistiin, mutta siihen ei enää ole viittausta, joten sitä ei voi enää käyttää ohjelmassa.

Tähän palataan tarkemmin ensi viikolla, kun viittauksia listoihin käsitellään tarkemmin.

Lisää metodeita

Metodin count avulla voidaan laskea osajonon esiintymien määrä. Metodi toimii samaan tapaan sekä merkkijonon että listan kanssa. Esimerkiksi näin:

mjono = "Vesihiisi sihisi hississä"
print(mjono.count("si"))

lista = [1,2,3,1,4,5,1,6]
print(lista.count(1))
Esimerkkitulostus

5 3

Huomaa, että metodi count ei laske päällekkäisiä esiintymiä. Esimerkiksi metodin mukaan merkkijonossa aaaa esiintyy kaksi kertaa osajono aa, vaikka oikeastaan esiintymiä olisi kolme, jos päällekkäiset esiintymät sallitaan.

Metodin replace avulla voidaan muodostaa uusi merkkijono, jossa tietty merkkijono on korvattu toisella merkkijonolla. Esimerkiksi:

mjono = "Moi kaikki"
uusi = mjono.replace("Moi", "Hei")
print(uusi)
Esimerkkitulostus

Hei kaikki

Metodi korvaa kaikki merkkijonon esiintymät:

lause = "hei heilan löysin minä heinikosta hei"
print(lause.replace("hei", "HEI"))
Esimerkkitulostus

HEI HEIlan löysin minä HEInikosta HEI

Tyypillinen virhe replace-metodia käytettäessä on unohtaa, että merkkijonot ovat muuttumattomia:

mjono = "Python on kivaa"

# Korvataan alijono, muttei tallenneta tulosta mihinkään...
mjono.replace("Python", "Java")
print(mjono)
Esimerkkitulostus

Python on kivaa

Jos vanhaa jonoa ei tarvita, voidaan uusi jono sijoittaa samaan muuttujaan:

mjono = "Python on kivaa"

# Korvataan alijono, tallennetaan tulos samaan muuttujaan
mjono = mjono.replace("Python", "Java")
print(mjono)
Esimerkkitulostus

Java on kivaa

Loading
Loading
Loading
Loading

Laajemman ohjelman tekeminen

Tämän osan huipentaa ensimmäinen hieman laajempi ohjelma, jota tehdessäsi pääset soveltamaan kaikkea tähän asti opeteltua.

Sääntö numero yksi isompaa tai oikeastaan mitä tahansa ohjelmaa tehdessä on se, että ei kannata yrittää ratkaista kaikkia ongelmia yhtä aikaa. Ohjelma kannattaa rakentaa pienistä paloista kuten sopivista apufunktioista, ja kunkin palan toimivuus kannattaa varmistaa ennen kun alkaa rakentaa seuraavaa palaa. Jos näin ei tee, on aika varmaa että edessä on suuri kaaos.

Isompaa ohjelmaa rakentaessa on järkevää testailla ohjelman funktioita aluksi erillään pääohjelmasta. Yksi helppo tapa on tehdä myös pääohjelmasta oma funktio, esimerkiksi nimeltään main, jonka ohjelman funktioiden ulkopuoleinen osa käynnistää. Esimerkiksi seuraavaa tehtävää voitaisiin ruveta lähestymään näin:

def main():
    pisteet = []
    # ohjelman koodi tänne

main()

Näin ohjelman apufunktioita on mahdollista testata ilman pääohjelman suorittamista:

# apufunktio, joka laskee arvosanan pisteiden perusteella
def arvosana(pisteet):
    # koodia

def main():
    pisteet = []
    # ohjelman koodi tänne

# kommentoidaan pääohjelma pois
#main()

# testataan apufunktiota
pistemaara = 35
tulos = arvosana(pistemaara)
print(tulos)

Tiedon välittäminen funktiosta toiseen

Jos ohjelma koostuu useista funktioista, nousee esiin kysymys, miten tietoa siirretään funktiosta toiseen.

Seuraavassa on esimerkki ohjelmasta, joka lukee käyttäjältä joukon kokonaislukuarvoja. Sen jälkeen ohjelma tulostaa arvot ja tekee niille vielä "analyysin". Ohjelma on jaettu kolmeen erilliseen funktioon:

def lue_kayttajalta(maara: int):
    print(f"Syötä {maara} lukua:")
    luvut = []

    for i in range(maara):
        luku = int(input("Anna luku: "))
        luvut.append(luku)

    return luvut

def tulosta(luvut: list):
    print("Luvut ovat: ")
    for luku in luvut:
        print(luku)

def analysoi(luvut: list):
    keskiarvo = sum(luvut) / len(luvut)
    return f"Lukuja yhteensä {len(luvut)}, keskiarvo {keskiarvo}, pienin {min(luvut)} ja suurin {max(luvut)}"

# funktioita käyttävä "pääohjelma"
syotteet = lue_kayttajalta(5)
tulosta(syotteet)
analyysin_tulos = analysoi(syotteet)
print(analyysin_tulos)

Esimerkkisuoritus:

Esimerkkitulostus

Syötä 5 lukua: Anna luku: 10 Anna luku: 34 Anna luku: -32 Anna luku: 99 Anna luku: -53 Luvut ovat: 10 34 -32 99 -53 Lukuja yhteensä 5, keskiarvo 11.6, pienin -53 ja suurin 99

Perusperiaatteena ohjelmassa on se, että pääohjelma "tallentaa" ohjelman käsittelemän tiedon eli tässä tapauksessa käyttäjän syöttämät luvut muuttujassa syotteet.

Jos lukuja on tarve käsitellä jossain funktiossa, ne välitetään sinne parametrina. Näin tapahtuu funktioissa tulosta ja analysoi. Jos taas funktio tuottaa tietoa, jota muut ohjelman osat tarvitsevat, palauttaa funktio datan return-komennolla. Näin tekevät käyttäjän syötteen lukeva funktio lue_kayttajalta sekä analyysin tekevä funktio analysoi.

Olisi periaatteessa mahdollista, että funktiot käyttäisivät suoraan "pääohjelman" globaalia muuttujaa syotteet. Se ei kuitenkaan ole järkevää, sillä jos funktiot pystyvät muuttamaan globaalia muuttujaa, voi ohjelmassa alkaa tapahtua jotain hallitsematonta, varsinkin kun funktioiden määrä kasvaa.

Tiedon välitys funktioihin ja niistä ulos on siis järkevintä hoitaa parametrien ja paluuarvojen avulla.

Jos haluaisimme tehdä edellisen esimerkin ohjelman siten, että sen pääohjelma eriytettäisiin omaan funktioon main, siirrettäisiin ohjelman käsittelemä data pääohjelmaa edustavan funktion sisäiseksi muuttujaksi:

# pääohjelmaa edustava funktio
def main():
    syotteet = lue_kayttajalta(5)
    tulosta(syotteet)
    analyysin_tulos = analysoi(syotteet)

    print(analyysin_tulos)

# ohjelman käynnistys
main()
Loading
Loading...
:
Loading...

Log in to view the quiz

Vastaa lopuksi osion loppukyselyyn:

Loading...
:
Loading...

Log in to view the quiz