Lisää listoista
Lisää listoista
Viime kerralla käsiteltiin lähes yksinomaan listoja, joissa alkiot ovat kokonaislukuja. Listoihin voi kuitenkin tallentaa minkä tahansa tyyppisiä arvoja. Esimerkiksi voimme tallentaa listaan merkkijonoja:
nimet = ["Maija", "Liisa", "Pekka"]
print(nimet)
nimet.append("Kalle")
print(nimet)
print("Listalla nimiä:", len(nimet))
print("Nimet aakkosjärjestyksessä:")
nimet.sort()
for nimi in nimet:
print(nimi)
['Maija', 'Liisa', 'Pekka'] ['Maija', 'Liisa', 'Pekka', 'Kalle'] Listalla nimiä: 4 Nimet aakkosjärjestyksessä: Kalle Liisa Maija Pekka
Samalla tavalla listalle voidaan tallentaa liukulukuja:
mittaukset = [-2.5, 1.1, 7.5, 14.6, 21.0, 19.2]
for mittaus in mittaukset:
print(mittaus)
keskiarvo = sum(mittaukset) / len(mittaukset)
print("Keskiarvo:", keskiarvo)
-2.5 1.1 7.5 14.6 21.0 19.2 Keskiarvo: 10.15
Muistutus: globaalin muuttujan käytön sudenkuoppa
Kuten olemme nähneet, funktioiden sisällä on mahdollista 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"])
Margaret Erkki Emilia Antti
Margaret Erkki Emilia Antti
Vaikka funktiota kutsutaan oikein, se tulostaa aina globaalissa muuttujassa nimilista olevat nimet.
Kuten olemme nähneet, 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 huomioida. Tämän takia funktio ei voi edes teoriassa toimia, sillä se viittaa muuttujaan nimilista
mitä ei testejä suoritettaessa ole ollenkaan olemassa.
Varoitus: parametrin ylikirjoittaminen ja liian aikainen return
Ennen tämän osan tehtäviin menemistä on syytä kiinnittää huomiota pariin potentiaaliseen ongelmalähteeseen. Tarkastellaan funktiota, joka kertoo löytyykö parametrina oleva luku listalta:
def luku_listalla(luvut: list, luku: int):
for luku in luvut:
if luku == luku:
return True
else:
return False
Funktio palauttaa jostain syystä aina True
. Syynä tälle on se, että for-silmukka ylikirjoittaa parametrin luku
arvon, ja tämän takia if-lauseen ehto on aina tosi.
Ongelmasta päästään eroon nimeämällä parametri uudelleen:
def luku_listalla(luvut: list, etsittava_luku: int):
for luku in luvut:
if luku == etsittava_luku:
return True
else:
return False
Nyt if-lauseen ehto on kunnossa. Funktiossa on kuitenkin uusi ongelma, se ei näytä edelleenkään toimivan. Esim. seuraava kokeilu tuo esiin bugin:
on = luku_listalla([1, 2, 3, 4], 3)
print(on) # tulostuu False
Vika on nyt siinä että funktiosta poistutaan liian aikaisin. Funktio tarkistaa ainoastaan ensimmäisen luvun ja riippuen sen arvosta palauttaa heti joko arvon True
tai False
. Lopullista tuomiota, eli tietoa siitä että luku ei ole listalla ei voi kuitenkaan antaa ennen kuin kaikki luvut on tarkastettu. Komento return False
pitääkin siirtää silmukan ulkopuolelle:
def luku_listalla(luvut: list, etsittava_luku: int):
for luku in luvut:
if luku == etsittava_luku:
return True
return False
Tarkastellaan vielä yhtä virheellistä esimerkkiä:
def luvut_erisuuret(luvut: list):
# apumuuttuja, johon kerätään ne luvut jotka on jo tarkastettu
luvut = []
for luku in luvut:
# joko luku on nähty?
if luku in luvut:
return False
luvut.append(luku)
return True
on = luvut_erisuuret([1, 2, 2])
print(on) # tulostuu True
Funktio siis yrittää testata ovatko kaikki listan alkiot erisuuria. Se kuitenkin palauttaa aina arvon True
.
Ongelmana on jälleen se, että funktio vahingossa ylikirjottaa parametrinsa arvon. Funktio yrittää käyttää muuttujaa luvut
pitämään kirjaa jo vastaan tulleista luvuista ja tämä ylikirjoittaa parametrin. Lääke ongelmaan on muuttujan uudelleennimeäminen:
def luvut_erisuuret(luvut: list):
# apumuuttuja, johon kerätään ne luvut jotka on jo tarkastettu
havaitut_luvut = []
for luku in luvut:
# joko luku on nähty?
if luku in havaitut_luvut:
return False
havaitut_luvut.append(luku)
return True
on = luvut_erisuuret([1, 2, 2])
print(on) # tulostuu False
Nämä kuten oikeastaan kaikki koodia vaivaavat ongelmat selviävät debuggerilla tai visualisaattorilla, jonka käytön tärkeyttä ei voi olla korostamatta liikaa.
Sisäkkäiset listat
Listan alkiot voivat olla myös listoja:
lista = [[5, 2, 3], [4, 1], [2, 2, 5, 1]]
print(lista)
print(lista[1])
print(lista[1][0])
[[5, 2, 3], [4, 1], [2, 2, 5, 1]] [4, 1] 4
Mihin voimme käyttää listoja jonka sisällä on listoja?
Voisimme esimerkiksi esittää henkilön tiedot listana, jossa ensimmäisenä alkiona on henkilön nimi, toisena ikä ja kolmantena kengännumero:
["Anu", 10, 26]
Vastaavasti joukko henkilöitä on lista, joka sisältää yksittäisiä henkilöä kuvaavia listoja:
henkilot = [["Anu", 10, 26], ["Petteri", 7, 22], ["Emilia", 32, 37], ["Antti", 39, 44]]
for henkilo in henkilot:
nimi = henkilo[0]
ika = henkilo[1]
kenka = henkilo[2]
print(f"{nimi}: ikä {ika} vuotta, kengännumero {kenka}")
Anu: ikä 10 vuotta, kengännumero 26 Petteri: ikä 7 vuotta, kengännumero 22 Emilia: ikä 32 vuotta, kengännumero 37 Antti: ikä 39 vuotta, kengännumero 44
Huomaa, miten for
-lause käy läpi henkilöt yksitellen, eli toiston lohko-osassa muuttuja henkilo
saa yksi kerrallaan arvokseen kutakin henkilöä esittävän listan.
Lista ei ole välttämättä paras Pythonin tietorakenne henkilön tietojen esittämiseen. Tutustumme pian sanakirjaan, joka on usein luontevampi tapa hoitaa vastaava tilanne.
Matriisit
Sisäkkäisten listojen avulla voidaan myös esittää matriisi eli kaksiulotteinen taulukko.
Esimerkiksi matriisi
voitaisiin mallintaa kaksiulotteisena listana näin:
matriisi = [[1, 2, 3], [3, 2, 1], [4, 5, 6]]
Koska matriisi on lista listoja, matriisin alkioihin viitataan käyttämällä peräkkäisiä hakasulkuja. Ensimmäinen indeksi viittaa riviin ja toinen sarakkeeseen. Niinpä esimerkiksi m[0][1]
tarkoittaa ensimmäisen rivin toista alkiota (kun muistetaan, että indeksointi alkaa nollasta).
matriisi = [[1, 2, 3], [3, 2, 1], [4, 5, 6]]
print(matriisi[0][1])
matriisi[1][0] = 10
print(matriisi)
2 [[1, 2, 3], [10, 2, 1], [4, 5, 6]]
Voimme käydä läpi matriisin rivit for
-silmukalla. Esimerkiksi seuraava koodi tulostaa matriisin rivit allekkain:
matriisi = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for rivi in matriisi:
print(rivi)
[1, 2, 3] [4, 5, 6] [7, 8, 9]
Seuraava koodi puolestaan tulostaa matriisin alkiot yksitellen kahden for
-silmukan avulla:
matriisi = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for rivi in matriisi:
print("uusi rivi")
for alkio in rivi:
print(alkio)
uusi rivi 1 2 3 uusi rivi 4 5 6 uusi rivi 7 8 9
Sisäkkäisiä listoja käyttävän koodin visualisointi
Jos sisäkkäisiä listoja käsittelevät ohjelmat tuntuvat hankalalta ymmärtää, kannattaa ehdottomasti havainnollistaa niitä Python Tutorin visualisaattorilla. Seuraavassa kuva edellisen esimerkin visualisoinnista:
Kuten kuva paljastaa, 3x3-matriisi koostuu teknisesti ottaen neljästä listasta. Ensimmäinen lista edustaa koko matriisia ja sen alkioina on erillisiä rivejä edustavat listat.
Kuva havainnollistaa jo sitä seikkaa, josta puhumme tarkemmin seuraavassa osassa: moniulotteisessa listassa listat eivät ole todellisuudessa sisäkkäin, vaan matriisia edustava lista "viittaa" jokaista riviä edustavaan listaan.
Kuvassa tulostus on edennyt matriisin toiselle riville, johon muuttuja rivi
parhaillaan viittaa. Muuttuja alkio
kertoo sen alkion, jonka kohdalla tulostus on menossa. Muuttujan arvo on nyt keskimmäisen rivin keskimmäinen eli 5.
Lisää matriisin käsittelyä
Matriisin yksittäisten rivien käsittely on helppoa, riittää että valitaan haluttu rivi. Esimerkiksi seuraava funktio laskee halutun rivin alkioiden summan:
def rivin_alkioiden_summa(matriisi, rivi_nro: int):
# tarkasteluun valitaan yksi rivi
rivi = matriisi[rivi_nro]
summa = 0
for alkio in rivi:
summa += alkio
return summa
m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]
summa = rivin_alkioiden_summa(m, 1)
print(summa) # tulostuu 33 (saadaan laskemalla 9 + 1 + 12 + 11)
Jos taas haluttaisiin laskea tietyn sarakkeen eli "pystyrivin" alkioiden summa, tilanne olisi jo monimutkaisempi:
def sarakkeen_alkioiden_summa(matriisi, sarake_nro: int):
# summaan lisätään kaikkien rivien halutussa kohdassa oleva alkio
summa = 0
for rivi in matriisi:
summa += rivi[sarake_nro]
return summa
m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]
summa = sarakkeen_alkioiden_summa(m, 2)
print(summa) # tulostuu 39 (saadaan laskemalla 3 + 12 + 9 + 15)
Tarkasteltava sarake siis koostuu jokaisen rivin paikassa 2 olevasta alkiosta.
Näidenkin ohjelmien toiminta kannattaa ehdottomasti käydä läpi visualisaattorilla!
Matriisissa olevan yksittäisen arvon vaihtaminen on helppoa. Riittää että valitaan matriisin sisältä oikea rivi ja sen sisältä sarake:
def vaihda_arvoon(matriisi, rivi_nro: int, sarake_nro: int, arvo: int):
# haetaan oikea rivi
rivi = matriisi[rivi_nro]
# ja sen sisältä oikea kohta
rivi[sarake_nro] = arvo
m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]
print(m)
vaihda_arvoon(m, 2, 3, 1000)
print(m)
[[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]] [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 1000], [2, 9, 15, 1]]
Mikäli halutaan muuttaa matriisin sisältöä silmukan sisällä, ei ole mahdollista käyttää "normaalia" for-silmukkaa, sillä muutettaessa sisältöä on pakko tietää muutettavien alkioiden indeksit.
Tämä taas onnistuu while
-silmukalla tai for
-silmukalla hyödyntämällä range
-funktiota iteroinnissa. Esimerkiksi seuraava koodi kasvattaa jokaista matriisin alkiota yhdellä:
m = [[1,2,3], [4,5,6], [7,8,9]]
for i in range(len(m)):
for j in range(len(m[i])):
m[i][j] += 1
print(m)
[[2, 3, 4], [5, 6, 7], [8, 9, 10]]
Ulompi silmukka käy range
-funktion avulla läpi arvot nollasta matriisin pituuteen (eli matriisin rivien määrään) ja sisempi silmukka jokaisen rivin alkiot nollasta rivin pituuteen.
Kaksiulotteinen taulukko pelin tietorakenteena
Matriisi sopii hyvin monien pelien tietorakenteeksi. Esim. sudokun ruudukko
voitaisiin esittää seuraavana matriisina:
sudoku = [
[9, 0, 0, 0, 8, 0, 3, 0, 0],
[0, 0, 0, 2, 5, 0, 7, 0, 0],
[0, 2, 0, 3, 0, 0, 0, 0, 4],
[0, 9, 4, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 7, 3, 0, 5, 6, 0],
[7, 0, 5, 0, 6, 0, 4, 0, 0],
[0, 0, 7, 8, 0, 3, 9, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 3],
[3, 0, 0, 0, 0, 0, 0, 0, 2]
]
Arvolla nolla siis kuvataan tilanne, jossa ruutu on vielä tyhjä.
Seuraavassa vielä yksinkertainen versio sudokun tulostavasta metodista:
def tulosta(sudoku):
for rivi in sudoku:
for ruutu in rivi:
if ruutu > 0:
print(f" {ruutu}", end="")
else:
print(" _", end="")
print()
tulosta(sudoku)
Tulostus näyttää seuraavalta:
9 _ _ _ 8 _ 3 _ _
_ _ _ 2 5 _ 7 _ _
_ 2 _ 3 _ _ _ _ 4
_ 9 4 _ _ _ _ _ _
_ _ _ 7 3 _ 5 6 _
7 _ 5 _ 6 _ 4 _ _
_ _ 7 8 _ 3 9 _ _
_ _ 1 _ _ _ _ _ 3
3 _ _ _ _ _ _ _ 2
Vastaavalla tavalla on mahdollista kuvata moni tuttu peli (esim. shakki, miinaharava, laivan upotus, mastermind, ...) matriisina. Pelistä riippuu, mikä on sopiva tapa "koodata" pelin tilanne matriisiin.
Log in to view the quiz