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ä

Kuten olemme nähneet, funktioiden sisällä on mahdollsita määritellä muuttujia. Kannattaa myös huomata se, että funktio näkee sen ulkopuolella, eli pääohjelmassa määritellyt muuttujat. Tälläisia muuttujia sanotaan globaaleiksi muuttujiksi.

Globalien 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 tulosaa aina globaalissa muuttujassa nimilista olevat nimet.

Kuten olemme nähneet, kaikki funktioita testaava koodi on kirjoitettava erillisen lohkon sisälle, jotta TMC-testit hyä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-lohkoon sisällä olevaa koodia ei huomioida. Tämän takia funktio ei voi edes teoriassa toimia, sillä se viittaa muuttujaan nimilista mitä ei testejä suoritettaessa ole ollenkaan 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 puhutaan listojen käytöstä funktioiden parametreina ja paluuarvoina.

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 oikestaan mitä tahansa ohjelmaa tehdessä on se, että ei kannata yrittää ratkaista kaikkia ongelmia yhtäaikaa. Ohjelma kannattaa rakentaa pienistä paloista, esim. 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ärkevintä testailla ohjelman funktioita aluksi erillään ns. "pääohjelmasta". Yksi tapa, joka tekee tämän helpoksi, on tehdä myös pääohjelmasta oma funktio, esimerkiksi nimeltään main, jonka ohjelman funktioiden ulkopuoleinen osa käynnistää. Esim. seuraavaa tehtävää voitaisiin ruveta lähestymään näin:

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

main()

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

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

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

# kommentoidaan pääohjelma pois
#main()

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

Tiedon välittäminen funktiosta toiseen

Jos ohjelma koostuu useista funktioista, nousee esiin kysymys miten tieoa 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 = []

    i = maara
    while i>0:
        luku = int(input("anna luku: "))
        luvut.append(luku)
        i -= 1

    return luvut

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

def analysoi(luvut: list):
    ka = sum(luvut) / len(luvut)
    return f"lukuja yhtensä {len(luvut)}, kesikarvo {ka}, pienin {min(luvut)} ja suurin {max(luvut)}"

# funktioita käyttävä  "pääohjelma"
syoteet = 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 yhtensä 5, kesikarvo 11.6, pienin- 53 ja suurin 99

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

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, palautta funktio datan returnilla. 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 syoteet. Se ei kuitenkaan ole ollenkaan järkevää, sillä jos usea funktio pääsee sorkkimaan 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äsittelmä data pääohjelmaa edustavan funktion sisäiseksi muuttujaksi:

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

    i = maara
    while i>0:
        luku = int(input("anna luku: "))
        luvut.append(luku)
        i -= 1

    return luvut

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

def analysoi(luvut: list):
    ka = sum(luvut) / len(luvut)
    return f"lukuja yhtensä {len(luvut)} kesikarvo {ka} pienin{min(luvut)} ja suurin {max(luvut)}"

# pääohjelmaa edustava funktio
def main():
    syoteet = 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