Ohjelmoinnin perusteet Python-kielellä

Huomautus lukijalle

Tämä materiaali on tarkoitettu Suomalaisten lukioiden yhteiskäyttöön. Materiaali pohjautuu Helsingin yliopistossa tietojenkäsittelytieteen laitoksella keväällä 2011 järjestetyn Ohjelmoinnin perusteet -kurssin materiaaliin. Korjaus- ja parannusehdotuksia saa ja tuleekin lähettää sähköpostitse osoitteeseen arto.vihavainen@helsinki.fi. Materiaalia saa käyttää vapaasti lukiokurssien järjestämiseen ja itseopiskeluun, muu käyttö vaatii luvan.

Kevään 2011 ohjelmointikurssien materiaaliin on vaikuttaneet useat tahot: Arto Vihavainen, Matti Paksula, Matti Luukkainen, Antti Laaksonen, Pekka Mikkola, Juhana Laurinharju, Martin Pärtel.

Lue materiaalia siten, että teet samalla itse kaikki lukemasi esimerkit. Esimerkkeihin kannattaa tehdä pieniä muutoksia ja tarkkailla, miten muutokset vaikuttavat ohjelman toimintaan. Äkkiseltään voisi luulla, että esimerkkien tekeminen myös itse ja niiden muokkaaminen hidastaa opiskelua. Tämä ei kuitenkaan pidä ollenkaan paikkansa. Ohjelmoimaan ei ole vielä tietääksemme kukaan ihminen oppinut lukemalla (tai esim. luentoa kuuntelemalla). Oppiminen perustuu oleellisesti aktiiviseen tekemiseen ja rutiinin kasvattamiseen, ja esimerkkien tekeminen ja erityisesti erilaisten omien kokeilujen tekeminen on parhaita tapoja "sisäistää" luettua tekstiä.

Pyri myös tekemään tai ainakin yrittämään tehtäviä sitä mukaa kuin luet tekstiä. Jos et osaa heti tehdä jotain tehtävää, älä masennu, sillä saat ohjelmointikurssillasi vinkkejä myös muilta tehtävän tekemiseen. Yksi parhaista ohjelmoijan ominaisuuksista onkin se, että osaa pyytää ja antaa apua.

Tekstiä ei ole tarkoitettu vain kertaalleen luettavaksi. Joudut varmasti myöhemmin palaamaan jo aiemmin lukemiisi kohtiin tai aiemmin tekemiisi tehtäviin. Tämä teksti ei sisällä kaikkea oleellista ohjelmointiin liittyvää. Itse asiassa ei ole olemassa mitään kirjaa josta löytyisi kaikki oleellinen. Eli joudut joka tapauksessa ohjelmoijan urallasi etsimään tietoa myös omatoimisesti. Harjoitukset sisältävät jo jonkun verran ohjeita, mistä suunnista ja miten hyödyllistä tietoa on mahdollista löytää.

Ensimmäinen ohjelma

Ohjelmoinnin aloittaminen

Ohjelmointi aloitetaan seuraavasti. Talleta oheinen koodi tiedostoon nimeltä hei.py

print "Hei maailma!"

Suoritus

Mene komentorivillä kansioon, johon tallensit tiedoston hei.py. Aja komento

python hei.py

Näet tulosteen:

Hei maailma

Olet aloittanut ohjelmoinnin.

Apua! Mikä ihmeen komentorivi?

Komentorivityökalua (cmd) voi ajatella tekstipohjaisena kansionhallintajärjestelmänä. Löydät sen Windows XP:ssä valitsemalla Start -> Run, ja kirjoittamalla cmd aukeavaan ikkunaan. Komennolla cd pääsee navigoimaan kansioiden sisälle. Jos tallensit hei.py-tiedoston kansioon "Ohjelmani", kirjoita

cd Ohjelmani

Ja pääset ohjelmakansioon. Kirjoita täällä komento python hei.py ja katso mitä tapahtuu.

Pythonin asentaminen

Jos saat virheviestin, joka sanoo ettei komento python ole tunnettu, sinun tulee asentaa Python koneellesi. Tee seuraavasti

  1. Mene osoitteeseen http://www.python.org/download/
  2. Paina kohdasta "Python 2.7.2 Windows Installer"
  3. Käynnistä lataamasi tiedosto, jolloin Python asentuu.

Ohjelma asentaa Pythonin hakemistoon C:\Python27\, joka kelpaa hyvin.

Pygame-kirjaston asentaminen

Tarvitset myös myöhempää varten Pygame-kirjaston, jonka saat tekemällä seuraavat askeleet

  1. Mene osoitteeseen http://pygame.org/download.shtml
  2. Paina otsikon Windows kohdasta pygame-1.9.2a0.win32-py2.7.msi
  3. Käynnistä lataamasi tiedosto, jolloin Pygame asentuu.

Ohjelmointiympäristö

Huom! (päivitys, kesä 2012) Vaikka tässä materiaalissa puhutaan NetBeans-ohjelmointiympäristöstä, tällä hetkellä kannattaa käyttää Aptana Studio-ohjelmointiympäristöä. Lyhyt johdanto aiheeseen löytyy sivulta http://www.cs.helsinki.fi/group/linkki/materiaali/peliohjelmointi/johdanto.html.

Nykyaikainen ohjelmointi tapahtuu yleensä ohjelmointiympäristössä. Ohjelmointiympäristö sisältää joukon ohjelmoijaa auttavia aputoimintoja. Ohjelmointiympäristö ei rakenna ohjelmaa ohjelmoijan puolesta, mutta se muunmuassa vinkkaa helpoista virheistä ohjelmakoodissa ja auttaa ohjelmoijaa hahmottamaan ohjelman rakennetta.

Annamme vinkkejä tällä kurssilla NetBeans-nimisen ohjelmointiympäristön käyttöön. NetBeans on ilmainen ohjelmisto, voit ladata ja asentaa sen omalle koneellesi täältä painamalla Download-nappulaa. Asenna lataamasi ohjelma. Uusimman NetBeans-ympäristön Python-tuki on vielä keskeneräinen, joten käytämme kurssilla hieman vanhempaa versiota NetBeansista.

Ohjelmoinnin aloitus ohjelmointiympäristössä

Ohje ohjelmoinnin aloittamiseen NetBeans-ohjelmointiympäristössä löytyy täältä.

Jatkossa julkaisemme pikkuhiljaa lisää ohjeita NetBeansin käyttöön. Vaikka ohjelma tuntuisi nyt sekavalta, älä hätäile. NetBeansin käyttöön saa ohjausta pajassa, ohjelma on loppujenlopuksi hyvin helppokäyttöinen. Perusteet opit 5 minuutissa, ja kurssin myötä opit koko ajan hieman lisää ja toukokuussa olet jo todellinen NetBeans "poweruser".

Ohjelma ja lähdekoodi

Lähdekoodi

Ohjelma muodostuu lähdekoodista. Tietokone suorittaa lähdekoodissa olevia komentoja pääsääntöisesti ylhäältä alaspäin ja vasemmalta oikealle. Lähdekoodi talletetaan tekstimuodossa ja suoritetaan jollakin tavalla.

Komennot

Varsinaisesti ohjelma muodostuu komennoista, jotka annetaan lähdekoodissa. Tietokone suorittaa eri operaatioita, eli toimintoja, komentojen perusteella. Esimerkiksi merkkijonon, eli tekstin, "Hei maailma"-tulostuksessa tärkein komento on print.

print "Hei maailma"

Komento print siis tulostaa sille annetun merkkijonon. Komento tulostaa rivin, eli kun annettu rivi on tulostettu, tulostaa komento myös rivinvaihdon.

Kääntäjä ja tulkki

Tietokone ei ymmärrä käyttämäämme ohjelmointikieltä suoraan. Siksi tarvitsemme lähdekoodin ja tietokoneen väliin kääntäjän tai tulkin. Ohjelmoidessamme komentoriviltä, komento python hei.py käynnistää python-tulkin joka suorittaa hei.py-tiedostossa olevan koodin.

Käyttäessämme ohjelmointiympäristöä, ohjelmointiympäristö hoitaa python-tulkin käynnistämisen puolestamme. Valitessamme ohjelman suorittamisen, ohjelmointiympäristö kääntää ja suorittaa ohjelman valmiiksi. Ohjelmointiympäristöt usein suorittavat ohjelman tulkkaamista myös ohjelmakoodia kirjoittaessa, jolloin yksinkertaiset virheet voidaan huomata jo ennalta. Esimerkiksi seuraavassa kuvassa olemme unohtaneet toisen merkkijonon lopettavan lainausmerkin. NetBeans alleviivaa koodin, ja ilmoittaa näin virheestä.

Huom! Virheiden löytäminen automaattisesti ei ole helppoa, joten virheviestit eivät aina ole oikeassa. Jos näet virheviestin, ohjelmassa on pulma -- mutta ei aina näytetyssä kohdassa.

Komennon osia

Komennot koostuvat osista.

Puolipiste

Puolipisteellä ; erotetaan komennot toisistaan. Tulkki ei siis ole kiinnostunut lähdekoodissa olevista riveistä, vaan voimme kirjoittaa koko ohjelman yhdelle riville.

Puolipisteen avulla voimme kertoa komennon loppuneen ja seuraavan komennon alkavan.

Esimerkki puolipisteiden käytöstä

print "Olipa"; print "kerran"; print "porkkana";
Olipa
kerran
porkkana

Vaikka kääntäjä ja tulkki eivät tarvitse rivinvaihtoja, on niiden käyttö hyvin tärkeää muita ihmisiä ajatellen. Selkeä lähdekoodin osien erottelu vaatii rivinvaihtojen käyttöä. Kannattaa pitäytyä säännössä "Yksi rivi komentoa kohden". Tämä helpottaa ohjelmakoodin myöhempää lukemista, sekä auttaa myös kaveriasi lukemaan lähdekoodiasi.

Vaikka pythonissa puolipisteiden käyttö ei ole pakollista, käytämme niitä tässä oppaassa.

Kommentit

Lähdekoodin kommentit ovat kätevä tapa merkitä asioita itselle ja muille muistiin. Kommentti on mikä tahansa rivi, joka alkaa merkillä #. Myös kaikki teksti samalla rivillä, joka tulee kommenttimerkin jälkeen tulkitaan kommentiksi.

Esimerkki kommenttien käytöstä

# Tulostamme tekstin "Hei!"
print "Hei!";

print "!ieH!"; # Lisäämme samalle riville tekstiä.

# print "tätä riviä ei suoriteta koska se on kommentoitu ulos";

Esimerkissä alin rivi esittelee erityisen kätevän käyttökohteen kommenteille: kirjoitettua koodia ei tarvitse poistaa jos haluaa tilapäisesti kokeilla jotain.

Lisää tulostamisesta

Kuten aiemmin huomattiin, print-komento tulostaa aina rivinvaihdon tulostettavan tekstin jälkeen.

Tulostettavan tekstin osana voi olla muutamia erikoismerkkejä. Tärkein näistä on \n, joka vaihtaa riviä. Erikoismerkkejä on muitakin.

Esimerkki

print "Eka rivi\nToka rivi\nKolmas rivi";

Ylläoleva tulostaa ajettaessa seuraavaa:

Eka rivi
Toka rivi
Kolmas rivi

Tehtäviä

Hei Maailma! (Ja Mualima!)

Tee ohjelma, jonka tulostus on seuraava:

Hei Maailma!
(Ja Mualima!)

Python

Tee ohjelma, jonka tulostus on seuraava:

####  #   #  #####  #  #  ####  #   #
#  #   # #     #    #  #  #  #  ##  #
####    #      #    ####  #  #  # # #
#       #      #    #  #  #  #  #  ##
#       #      #    #  #  ####  #   #

Ääkköset ohjelmakoodissa

Python ei tiedä ääkkösistä ellei sille erikseen kerrota niiden olemassaolosta. Jos yrität ajaa seuraavan ohjelman

merkkijono = "Tässäpä tämä taas!"
print merkkijono;

Näet seuraavanlaisen virhetulosteen

File "/hei.py", line 1
SyntaxError: Non-ASCII character '\xc3' in file /hei.py on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Tämä tarkoittaa sitä, että ohjelma näkee aakkosen, joka ei kuulu ASCII-merkistöön. Suomalaiset erikoismerkit, kuten ä ja ö, eivät kuulu ASCII-merkistöön jota python käyttää oletuksena. Jotta voisimme käyttää ääkkösiä ja muita tuttuja merkkejä, meidän tulee kertoa python-tulkille käytetystä merkistöstä. Tämä tapahtuu lisäämällä jokaisen lähdekooditiedoston alkuun rivi # -*- coding: utf-8 -*-. Tämä kertoo pythonille että merkistönä käytetään utf-8 -merkistöä.

Kun ylläolevaan lähdekooditiedostoon lisää merkistövinkin, ohjelma toimii oikein.

# -*- coding: utf-8 -*-
merkkijono = "Tässäpä tämä taas!"
print merkkijono;
Tässäpä tämä taas!

Huomaa että rivin # -*- coding: utf-8 -*- tulee olla aina lähdekooditiedoston alussa. Vaikkei esimerkeissä ole aina merkistöstä kertovaa riviä, käytä sitä aina jatkossa!

Tehtäviä

Nimi

Tee ohjelma, joka tulostaa nimesi.

Ohjelman tulostus voi olla seuraava:

Oskari Opiskelija

Aakkoset

Tee ohjelma, joka tulostaa kaikki aakkoset.

Ohjelman tulostuksen tulee olla seuraava:

abcdefghijklmnopqrstuvwxyzåäö

Muuttuja ja sijoitus

Muuttujat ja tietotyypit

Ohjelmoinnissa eräs keskeinen käsite on muuttuja. Muuttuja kannattaa ajatella lokerona, johon voi tallettaa tietoa. Talletettavalla tiedolla on aina tyyppi. Tyyppejä ovat esimerkiksi teksti (String), kokonaisluku (int), liukuluku (float) ja totuusarvo (boolean). Muuttujaan asetetaan arvo yhtäsuuruusmerkillä (=).

kuukausia = 12;

Yllä olevassa asetuslauseessa asetetaan muuttujaan kuukausia arvo 12. Asetuslause luetaan "muuttuja kuukausia saa arvon 12". Huomaa että muuttujalle ei ole erikseen kerrottu tyyppiä, vaan ohjelmoijan tulee tietää muuttujan tyyppi.

Muuttujan arvo voidaan yhdistää merkkijonoon +-merkillä seuraavan esimerkin mukaisesti.

Esimerkkejä muuttujista ja tietotyypeistä

teksti = "merkkijono";
kokonaisluku = 123;
liukuluku = 3.141592653;
onkoTotta = True;

print "Tekstimuuttujan arvo on " + str(teksti);
print "Kokonaislukumuuttujan arvo on " + str(kokonaisluku);
print "Liukulukumuuttujan arvo on " + str(liukuluku);
print "Totuusarvomuuttujan arvo on " + str(onkoTotta);
Tekstimuuttujan arvo on merkkijono
Kokonaislukumuuttujan arvo on 123
Liukulukumuuttujan arvo on 3.141592653
Totuusarvomuuttujan arvo on true

Komento str

Kun muuttujien arvoja yhdistetään tulostusta varten, tulee ne ensiksi muuttaa tekstiksi. Komennolla str voi luoda tekstityyppisen muuttujan muista muuttujatyypeistä.

Komennolle str annetaan sulkujen sisään muuttuja, josta se tekee uuden tekstityyppisen muuttujan tulostusta varten. Esimerkiksi komento teksti = str(3) loisi tekstimuuttujan, joka saa arvokseen "3". Kun yhdistämme tekstiä ja muita muuttujatyyppejä komennon str-avulla, muiden muuttujien tyypit eivät muutu, vaan luomme aina uuden muuttujan.

Muuttujan arvon muuttaminen

Muuttuja säilyttää arvonsa kunnes siihen asetetaan toinen arvo.

kokonaisluku = 123;
print "Kokonaislukumuuttujan arvo on " + str(kokonaisluku);

kokonaisluku = 42;
print "Kokonaislukumuuttujan arvo on " + str(kokonaisluku);
Kokonaislukumuuttujan arvo on 123
Kokonaislukumuuttujan arvo on 42

Vaihteleva tietotyyppi

Kun muuttujaan asetetaan arvo, se päättelee oman tyyppinsä asetetun arvon perusteella. Muuttujan tyyppi on ei python-kielessä ole pysyvä, eli kun tyyppi on asetettu, voidaan sitä muuttaa asettamalla sen arvo uudestaan. Esimerkiksi tekstimuuttuja voi muuttua kokonaislukumuuttujaksi.

merkkijono = "tsuppadui!";
print "Aika hauskaa: " + merkkijono;
merkkijono = 42;
print "Aika hauskaa: " + merkkijono; # ei onnistu sillä merkkijono on nyt kokonaislukutyyppinen. 
                                     # komento str auttaa tässä, kokeile itse!

Sallittu ja kuvaava muuttujan nimi

Muuttujan nimeämistä rajoittavat tietyt ehdot.

Muuttujan nimessä ei saa olla tiettyjä erikoismerkkejä, kuten huutomerkkejä (!). Ääkköset eivät ole sallittuja. Välilyönti ei ole sallittu, sillä se erottaa komentojen osat toisistaan. Välilyönti kannattaa korvata alaviivalla _ tai camelCase-tyylillä, jolloin nimi muistuttaneeHiemanKamelia. Huom! Muuttujien nimien ensimmäinen kirjain kirjoitetaan aina pienellä!

muuttuja_alaviivalla = 3;
camelCaseMuuttuja = 7;

Numeroita voidaan käyttää muuttujan nimessä, kunhan nimi ei ala numerolla. Nimi ei myöskään voi koostua pelkistä numeroista.

7muuttuja = 4; # Ei sallittu!
muuttuja7 = 4; # Sallittu, mutta ei kuvaava muuttujan nimi

Muuttuja kannattaa nimetä siten, että sen käyttötarkoitus on selvää ilman kommentteja tai miettimistä. Tällä kurssilla muuttujat pitää nimetä kuvaavasti.

Sallittuja muuttujien nimiä

Virheellisiä muuttujien nimiä

Kurssin nimeämiskäytäntö on camelCase. Esimerkiksi kuukauden ensimmäistä päivää kuvaavan muuttujan nimi on kuukaudenEnsimmainenPaiva

Huomioi, että nimeät muuttujat nyt ja jatkossakin yllä esiteltyjen hyvien käytäntöjen mukaan.

Tehtäviä

Tarina

Tee ohjelma, joka tulostaa tarinan. Henkilön nimi tallennetaan muuttujaan, joten jos nimeä halutaan muuttaa, riittää antaa muuttujalle toinen arvo ja uusi nimi ilmestyy kaikkiin kohtiin tarinassa.

Voit halutessasi keksiä paremman tarinan.

Jos muuttujassa lukee "Matti", ohjelma voisi tulostaa seuraavaa:

Syrjäisessä laaksossa asui nuorukainen Matti,
joka oli ammatiltaan lammaspaimen.
Matti oli vasta herännyt,
kun pihaan ratsasti tumma-asuinen ritari.
Pian Matti saisi kuulla, että hänet oli
valittu tärkeään tehtävään...

Jos muuttujassa lukee "Arto", ohjelma voisi tulostaa seuraavaa:

Syrjäisessä laaksossa asui nuorukainen Arto,
joka oli ammatiltaan lammaspaimen.
Arto oli vasta herännyt,
kun pihaan ratsasti tumma-asuinen ritari.
Pian Arto saisi kuulla, että hänet oli
valittu tärkeään tehtävään...

Numeroiden tulostus

Korjaa allaoleva ohjelma siten että se tulostaa muuttujan ika-arvon oikein:

# -*- coding: utf-8 -*-
ika = 5

print "Muuttujassa ika on arvo " + ika;
Muuttujassa ika on arvo 5

Laskentaa

Laskentaoperaatiot ovat varsin suoraviivaisia: +, -, * ja /. Erikoisempana operaationa on %, joka on jakojäännös, eli modulo. Laskentajärjestys on myös varsin suoraviivainen: operaatiot lasketaan vasemmalta oikealle sulut huomioon ottaen.

eka = 2;  # kokonaislukutyyppinen muuttuja eka saa arvon 2
toka = 4; # kokonaislukutyyppinen muuttuja toka saa arvon 4
summa = eka + toka;  # kokonaislukutyyppinen muuttuja summa saa arvon eka + toka, eli 2 + 4
                     # huomaa että summa-muuttujankin tyyppi on kokonaisluku

print "2 + 4 on " + str(summa); # tulostetaan muuttujan summa arvo
2 + 4 on 6

Laskujärjestyksen määrääminen

Sulkuja voi käyttää laskemisen avuksi aivan kuten oikeassakin matematiikassa. Suluilla siis voidaan määrätä laskujärjestystä.

laskuSuluilla = (1 + 1) + 3 * (2 + 5);
laskuSuluitta = 1 + 1 + 3 * 2 + 5;

print "Lasku suluilla: " + str(laskuSuluilla);
print "Lasku suluitta: " + str(laskuSuluitta);
Lasku suluilla: 23
Lasku suluitta: 13

Yllä olevan sulkuesimerkin voi suorittaa myös askeleittain.

laskuSuluilla = (1 + 1);
laskuSuluilla = laskuSuluilla + 3 * (2 + 5);

laskuSuluitta = 1 + 1;
laskuSuluitta = laskuSuluitta + 3 * 2;
laskuSuluitta = laskuSuluitta + 5;

print "Lasku suluilla: " + str(laskuSuluilla);
print "Lasku suluitta: " + str(laskuSuluitta);
Lasku suluilla: 23
Lasku suluitta: 13

Voimme siis käyttää muuttujissa olevia arvoja osana laskutoimituksia!

Laskentaoperaatioita voidaan suorittaa lähes missä tahansa kohdassa ohjelmakoodia.

eka = 2;
toka = 4;

print (eka+toka);
print (2 + toka - eka - toka);

Liukuluvut eli desimaaliluvut

Jako ja jakojäännös ovat hieman hankalampia kuin kokonaisluvut. Liukuluku ja kokonaisluku menevät helposti sekaisin. Jos kaikki laskuoperaatiossa olevat muuttujat ovat kokonaislukuja, on tulos myös kokonaisluku.

tulos = 3 / 2;     # tulos on 1 (kokonaisluku), sillä 3 ja 2 ovat myös kokonaislukuja

Jakojäännös kertoo jakojäännöksen. Esimerkiksi laskun 7 % 2 jakojäännös on 1.

int jakojaannos = 7 % 2; # jakojaannos on 1 (kokonaisluku)

Jos jakolaskun jakaja tai jaettava (tai molemmat!) ovat liukulukuja, tulee tulokseksi myös liukuluku

kunJaettavaOnLiukuluku = 3.0 / 2;  # tulokseksi: 1.5
kunJakajaOnLiukuluku = 3 / 2.0;    # tulokseksi: 1.5

Seuraava esimerkki tulostaa "1.5", sillä jaettavasta tehdään liukuluku kertomalla se liukuluvulla (1.0 * 3 = 3.0) ennen jakolaskua.

jaettava = 3;
jakaja = 2;

tulos = 1.0 * jaettava / jakaja;
print tulos; 

Mitä seuraava tulostaa?

int jaettava = 3;
int jakaja = 2;

tulos = jaettava / jakaja * 1.0;
print tulos;

Tehtäviä

Yhteenlasku

Tee ohjelma, jonka avulla voidaan laskea kahden luvun summa. Ohjelman alussa määritellään kaksi muuttujaa, jotka sisältävät summattavat luvut. Voit tarvittaessa käyttää myös muita muuttujia.

Esimerkiksi jos muuttujissa on luvut 5 ja 4, ohjelman tulostus on seuraava:

5 + 4 = 9

Jos taas muuttujissa on luvut 73457 ja 12888, ohjelman tulostus on seuraava:

73457 + 12888 = 86345

Kertolasku

Tee edellistä ohjelmaa vastaava ohjelma, joka laskee kertolaskun.

Esimerkiksi jos muuttujissa on luvut 2 ja 8, ohjelman tulostus on seuraava:

2 * 8 = 16

Jos taas muuttujissa on luvut 277 ja 111, ohjelman tulostus on seuraava:

277 * 111 = 30747

Kuinka suuren kertolaskun ohjelmasi pystyy laskemaan?

Sekunnit vuodessa

Tee ohjelma, joka laskee, kuinka monta sekuntia on vuodessa. Voit olettaa, että vuodessa on 365 päivää (eli ei ole karkausvuosi).

Ohjelman tulostus on seuraava:

Vuodessa on X sekuntia.

X:n kohdalle tulee ohjelmasi laskema tulos.

Katenointi eli merkkijonojen yhdistäminen

Tarkastellaan vielä lähemmin merkkijonojen yhdistämistä +-merkinnän avulla.

Jos operaatiota + sovelletaan kahden merkkijonon välille, syntyy uusi merkkijono, jossa kaksi merkkijonoa on yhdistetty. Huomaa nokkela välilyönnin käyttö lauseen "muuttujien" osana!

tervehdys = "Hei ";
nimi = "kaikki";
hyvastely = ", ja näkemiin!";

lause = tervehdys + nimi + hyvastely;

print lause;
Hei kaikki, ja näkemiin!

Jos vain toinen + operaation kohteista on merkkijono, tulee toinenkin kohde muuttaa ensin merkkijonoksi. Tämähän tehtiin komennolla str.

print "tuossa on kokonaisluku --> " + str(2);
print str(2) + " <-- tuossa on kokonaisluku";

Edellä esitellyt laskusäännöt pätevät täälläkin:

print "Neljä: " + str(2+2);
print "Mutta! kaksikymmentäkaksi: " + str(2) + str(2);

Edellisiä tietoja yhdistelemällä pystymme tulostamaan muuttujan arvoja ja tekstiä sekaisin:

x = 10;

print "muuttujan x arvo on: " + str(x);

y = 5;
z = 6;

print "y on " + str(y) + " ja z on 6";

Käyttäjän syötteen lukeminen

Tähän asti ohjelmamme ovat olleet kovin yksipuolisia. Seuraavaksi luemme syötettä käyttäjältä. Käytämme syötteen lukemiseen erityistä raw_input-komentoa.

Kokeillaan komentoa raw_input. Sille annetaan sulkuihin parametrina merkkijono, jota käyttäjältä kysytään ennen syötteen lukemista. Älä hätäile vaikka saattaa näyttää vaikeaselkoiselta, pythonin valmiiksi tarjoamien komentojen käyttö tulee vielä tutuksi.

luettuTeksti = raw_input("Ostaisitko porkkanaa vai kaalia? ");
print "Ostaisin " + luettuTeksti;
Ostaisitko porkkanaa vai kaalia? Kaalia
Ostaisin Kaalia

Ylläolevassa esimerkissä käyttäjän syöte on kirjoitettu kursiivilla. NetBeansia käyttäessä käyttäjän tulee kirjoittaa viesti output-laatikkoon.

Merkkijonon lukeminen

Seuraava koodi lukee käyttäjän nimen ja tulostaa tervehdyksen:

# -*- coding: utf-8 -*-

nimi = raw_input("Mikä on nimesi? ");
print "Hei, " + nimi;
Mikä on nimesi? Arto
Hei, Arto

Kaikki komentoa raw_input-kutsuttaessa saatavat muuttujat ovat tekstityyppisiä. Tutustutaan seuraavaksi miten ne saa muutettua esimerkiksi kokonaisluvuksi.

Kokonaisluvun lukeminen

Komento raw_input ei osaa suoraan lukea kokonaislukuja kokonaislukutyyppisiksi muuttujiksi, joten käytämme toista apuvälinettä merkkijonon kokonaisluvuksi muuttamisessa. Komento int muuttaa sille annetussa tekstimuuttujassa olevan kokonaisluvun kokonaislukumuuttujaksi. Komennolle annetaan tekstimuuttuja sulkuihin, ja se palauttaa kokonaisluvun joka asetetaan kokonaislukumuuttujaan.

kolmonenMerkkijonona = "3";
kolmonenKokonaisLukuna = int(kolmonenMerkkijonona);

Sama esimerkki, mutta luetaan kokonaisluku käyttäjältä merkkijonona.

kokonaislukuMerkkijonona = raw_input("Anna kokonaisluku: ");
kokonaisluku = int(kokonaislukuMerkkijonona);

print "Annoit " + str(kokonaisluku);

Komentoja voi usein myös ketjuttaa. Käytetään komennon raw_input antamaa merkkijonoa suoraan komennossa int. Tällöin luetaan käyttäjältä ensiksi syöte, jonka jälkeen komento int muuttaa syötteen kokonaisluvuksi. Käytämme jatkossa alla esitettyä tapaa kokonaisluvun lukemiseen.

kokonaisluku = int(raw_input("Anna kokonaisluku: "));

print "Annoit " + str(kokonaisluku);

Kysytään seuraavaksi käyttäjältä nimi, ja sen jälkeen ikä. Tällä kertaa esimerkissä on myös ohjelmarunko mukana.

# -*- coding: utf-8 -*-

nimi = raw_input("Nimesi: ");                # luetaan ensin tekstityyppinen nimi
ika = int(raw_input("Kuinka vanha olet: ")); # ja sitten ikä lukemalla ensin tekstimuuttuja ja muuttamalla se kokonaisluvuksi

print "Nimesi on siis: " + nimi + ", ja ikäsi " + str(ika) + ", hauska tutustua.");

Tehtäviä

Summaaja

Tee ohjelma, joka kysyy käyttäjältä kaksi lukua ja tulostaa niiden summan.

Anna ensimmäinen luku: 6
Anna toinen luku: 2

Lukujen summa: 8

Esimerkissä punainen väri tarkoittaa käyttäjän kirjoittamaa tekstiä.

Jakaja

Tee ohjelma, joka kysyy käyttäjältä kaksi lukua ja tulostaa niiden osamäärän. Varmista, että 3 / 2 = 1.5. Jos desimaaliosa katoaa, lue monisteen kohdasta Liukuluvut eli desimaaliluvut missä vika on.

Anna ensimmäinen luku: 3
Anna toinen luku: 2

Jakolasku: 3 / 2 = 1.5

Ympyrän kehän pituus

Ympyrän kehän pituus lasketaan kaavalla kehän pituus on 2 * pii * säde. Tee ohjelma, joka kysyy käyttäjältä ympyrän säteen ja laskee sen perusteella ympyrän kehän pituuden. Pythonista löytyy valmis piin arvo, saat sen kirjoittamalla import math; lähdekoodin alkuun ja math.pi laskutoimitukseen. Ratkaisuhahmotelma:

# -*- coding: utf-8 -*-
import math;

# testataan että voidaan tulostaa piin (liki)arvo
print math.pi

# ja tänne tehtävän ratkaisu
Anna ympyrän säde: 20

Ympyrän kehä: 125.66370614359172

Ikien summa

Tee ohjelma, joka kysyy kahden käyttäjän nimet ja iät. Tämän jälkeen ohjelma tulostaa henkilöiden ikien summan.

Kerro nimi: Matti
Kerro ikä: 14

Kerro nimi: Arto
Kerro ikä: 12

Matti ja Arto ovat yhteensä 26 vuotta vanhoja.

Lukujen keskiarvo

Tee ohjelma, joka kysyy käyttäjältä kolme lukua ja tulostaa niiden keskiarvon.

Anna kolme lukua:
5
2
3

Lukujen 5, 2 ja 3 keskiarvo on 3.3333333333333335

Liukulukujen kanssa tapahtuu usein pyöristysvirheitä, niin myös tässä esimerkissä. Lukujen keskiarvo on 3,333333..., mutta ohjelmassa loppuun ilmestyy numero 5.

Valinta ja totuusarvot

Jotta ohjelman suoritus voisi haarautua, tarvitsemme käyttöömme valintakäskyn.

luku = 11;

if luku > 10:
  print "Luku oli suurempi kuin 10";

Ehto luku > 10 muuntautuu totuusarvoksi True tai False. Valintakäsky if käsittelee siis lopulta vain ja ainoastaan totuusarvoja. Yllä oleva ehtolause luetaan "jos luku on suurempi kuin 10".

Huomaa, että if -lauseen perään ei tule puolipistettä vaan kaksoispiste, sillä lause ei lopu ehto-osan jälkeen.

Ehdon toteutuessa suoritettava lähdekoodi sisennetään. Sisennys aloittaa lohkon (block), jonka sisältö suoritetaan jos ehto on tosi. Lohko loppuu kun sisennys loppuu, ja se voi olla kuinka pitkä tahansa.

Vertailuoperaattoreita ovat seuraavat:

luku = 55;

if luku != 0:
  print "Luku oli erisuuri kuin 0";

if luku >= 1000:
   print "Luku oli vähintään 1000";  

Lohkon sisällä voi olla mitä tahansa koodia, myös toinen valintakäsky.

x = 45;
luku = 55;

if luku > 0:
   print "Luku on positiivinen";
   if luku > x:
     print " ja suurempi kuin muuttujan x arvo"; 
     print "muuttujan x arvohan on " + x;

Huomaa että toisessa valintakäskyssä on oma sisennyksensä!

Vertailuoperaattoreita voi käyttää myös ehtojen ulkopuolella. Tällöin ehdon totuusarvo (True tai False) asettuu totuusarvomuuttujaan.

eka = 1
toka = 3

onkoSuurempi = eka > toka;

Yllä olevassa esimerkissä totuusarvomuuttuja onkoSuurempi sisältää nyt totuusarvon False.

Totuusarvomuuttujaa voidaan käyttää ehtolauseessa ehtona.

eka = 1
toka = 3

onkoPienempi = eka < toka;

if onkoPienempi:
  print "1 on pienempi kuin 3!";
1 on pienempi kuin 3!

Koodin sisennys

Huomaa, että if-komennon jälkeisen lohkon, eli :-merkkiä seuraavien rivien komentoja ei kirjoiteta samalle tasolle (eli yhtä "vasemmalle") kuin komentoa if, vaan ne sisennetään hieman oikealle. Sisentäminen tapahtuu tabulaattorimerkillä (q:n vasemmalla puolella oleva merkki). Kun lohko sulkeutuu, sisennys loppuu.

Sisennys on oleellinen seikka ohjelmien ymmärrettävyyden kannalta. Jos ohjelmakoodia ei ole sisennetty oikein, ei python osaa tulkata komentoja. NetBeans auttaa sisennyksessä. Ohjelman saa sisennettyä helposti painamalla yhtä aikaa shift, alt ja f.

else

Jos valinnan ehto on epätotta, eli totuusarvo on false, voidaan suorittaa toinen vaihtoehtoinen lohko koodia, tämä käy sanan else avulla.

luku = 4;

if luku > 5:
  print "Lukusi on suurempi kuin viisi!";
else:
  print "Lukusi on viisi tai alle!";
Lukusi on viisi tai alle!

Tehtäviä

Positiivinen luku

Tee ohjelma, joka kysyy käyttäjältä luvun ja kertoo, onko se positiivinen (eli suurempi kuin nolla).

Anna luku: 5

Luku on positiivinen.
Anna luku: -2

Luku ei ole positiivinen.

Täysi-ikäisyys

Tee ohjelma, joka kysyy käyttäjän ikää ja kertoo, onko tämä täysi-ikäinen (eli 18-vuotias tai vanhempi).

Kuinka vanha olet? 12

Et ole vielä täysi-ikäinen!
Kuinka vanha olet? 32

Olet jo täysi-ikäinen!

Pariton vai parillinen?

Tee ohjelma, joka kysyy käyttäjältä luvun ja ilmoittaa, onko se parillinen vai pariton.

Anna luku: 2
Luku 2 on parillinen
Anna luku: 7
Luku 7 on pariton

Vihje: Luvun jakojäännös 2:lla kertoo, onko luku parillinen vai pariton. Jakojäännos taas saadaan %-operaattorilla

elif

Sana elif on kuten else, mutta lisäehdolla. elif tulee sanoista "else if", ja se asetetaan if-ehdon jälkeen. elif ehtoja voi olla useita.

luku = 3;

if luku == 1:
  print "Luku on yksi";
elif luku == 2:
  print "Lukuna on kaksi";
elif luku == 3:
  print "Kolme lienee lukuna!";
else:
  print "Aika paljon!";
Kolme lienee lukuna!

Luetaan ylläoleva esimerkki: 'Jos luku on yksi, tulosta "Luku on yksi", muuten jos luku on kaksi, tulosta "Lukuna on kaksi", muuten jos lukuna on kolme, tulosta "Kolme lienee lukuna". Muulloin, tulosta "Aika paljon!"'.

Tehtäviä

Suurempi luku

Tee ohjelma, joka kysyy käyttäjältä kaksi lukua ja tulostaa niistä suuremman. Jos luvut ovat yhtä suuret, ohjelma huomaa myös tämän.

Esimerkkitulostuksia:

Anna ensimmäinen luku: 5
Anna toinen luku: 3

Suurempi luku: 5
Anna ensimmäinen luku: 5
Anna toinen luku: 8

Suurempi luku: 8
Anna ensimmäinen luku: 5
Anna toinen luku: 5

Luvut ovat yhtä suuret!

Arvosanat ja pisteet

Tee ohjelma, joka ilmoittaa kurssiarvosanan seuraavan taulukon mukaisesti.

pistemääräarvosana
0–29hylätty
30–341
35–392
40–443
45–494
50–605

Esimerkkitulostuksia:

Anna pisteet [0-60]: 37

Arvosana: 2
Anna pisteet [0-60]: 51

Arvosana: 5

Merkkijonojen vertailu

Merkkijonoja, eli tekstejä, voidaan myös vertailla käyttäen ylläesitettyjä operaatioita.

teksti = "kurssi";

if teksti == "marsipaani":
  print "Teksti-muuttujassa on teksti marsipaani.";
else:
  print "Teksti-muuttujassa ei ole tekstiä marsipaani.";

Kahta tekstimuuttujaa voidaan myös verrata keskenään, aivan kuten kokonaislukumuuttujiakin.

teksti = "kurssi";
toinenTeksti = "pursi";

if teksti == toinenTeksti:
  print "Samat tekstit!";
else: 
  print "Ei samat tekstit!";

Merkkijonoille on vielä kaksi erityisoperaatiota in ja not in, joiden avulla voidaan tutkia löytyykö haluttu merkkijono toisesta merkkijonosta.

teksti = "saippua";
toinenTeksti = "saippuakauppias";

if teksti in toinenTeksti:
  print "Teksti " + teksti + " löytyy tekstistä " + toinenTeksti;

if teksti not in toinenTeksti: 
  print "Ei löytynyt :(";

Ylläoleva ohjelma voidaan kirjoittaa myös else-haaraa käyttäen.

teksti = "saippua";
toinenTeksti = "saippuakauppias";

if teksti in toinenTeksti:
  print "Teksti " + teksti + " löytyy tekstistä " + toinenTeksti;
else: 
  print "Ei löytynyt :(";

Tehtäviä

Salasana

Tee ohjelma, joka kysyy käyttäjältä salasanan. Jos salasana on oikea, ohjelma tulostaa salaisen tiedon.

Valitse itse sopiva salasana ja salainen tieto.

Anna salasana: kissa
Salasana on väärin!
Anna salasana: tappara
Salasana on väärin! 
Anna salasana: ohpe
Salasana on oikein!
Salainen tieto: ...

Luvut suuruusjärjestyksessä

Tee ohjelma, joka kysyy käyttäjältä kolme lukua ja tulostaa ne suuruusjärjestyksessä.

Anna ensimmäinen luku: 8
Anna toinen luku: 1
Anna kolmas luku: 3

Luvut suuruusjärjestyksessä: 1, 3 ja 8

Toimiihan ohjelmasi myös, jos käyttäjä antaa monta kertaa saman luvun?

Loogiset operaatiot

vuosi = 2012;
kuukausi = 1;

print "Tällä hetkellä on:";

if vuosi == 2012 and kuukausi == 1:
  print "Vuoden 2012 tammikuu";

if vuosi == 2011 and kuukausi == 3:
  print "Vuoden 2012 maaliskuu";
Tällä hetkellä on:
Vuoden 2012 tammikuu
print "Onkohan luku väliltä 5-10: ";
luku = 7;

if luku > 4 and luku < 11:
  print "On! :)";
else:
  print "Ei ollut :(";
Onkohan luku väliltä 5-10: 
On! :)
print "Onkohan luku 1, 50 tai 100: ");
int luku = 50;

if valinta == 1 or valinta == 50 or valinta == 100:
  print "On! :)";
else;
  print "Ei ollut :(";
Onkohan luku 1, 50 tai 100: 
On! :)

Tehtäviä

Iän tarkistus

Tee ohjelma, joka kysyy käyttäjän iän ja tarkistaa, että se on mahdollinen (ainakin 0 ja korkeintaan 120).

Kuinka vanha olet? 10
OK
Kuinka vanha olet? 55
OK
Kuinka vanha olet? -3
Mahdotonta!
Kuinka vanha olet? 150
Mahdotonta!

Käyttäjätunnukset

Tee ohjelma, joka tunnistaa seuraavat käyttäjät:

tunnussalasana
aleksitappara
elinakissa

Ohjelma näyttää käyttäjälle henkilökohtaisen viestin tai ilmoittaa, jos tunnus tai salasana on väärin.

Anna tunnus: aleksi
Anna salasana: tappara
Tappara on terästä!
Anna tunnus: elina
Anna salasana: kissa
Miau miau!
Anna tunnus: aleksi
Anna salasana: jokerit
Virheellinen tunnus tai salasana!

Karkausvuosi

Vuosi on karkausvuosi, jos se on jaollinen 4:llä. Kuitenkin jos vuosi on jaollinen 100:lla, se on karkausvuosi vain silloin, kun se on jaollinen myös 400:lla.

Tee ohjelma, joka tarkistaa, onko vuosi karkausvuosi.

Anna vuosi: 2011
Vuosi ei ole karkausvuosi.
Anna vuosi: 2012
Vuosi on karkausvuosi.

Erikoistilanteita

Klassinen kirjoitusvirhe muuttujia vertailtaessa on seuraavanlainen.

luku = 42144;

if luku = 82133:
  print "Ehdossa on vain yksi yhtäsuuruusmerkki kahden sijasta.";
  print "Python-tulkki onneksi herjaa tästä, sillä ehdossa täytyy olla totuusarvotyyppinen (boolean) arvo.";

Lukeminen ja ehdot

Yhdistellään hieman tähän mennessä opittua. Pyydetään käyttäjää valitsemaan luku joka ei ole 10 eikä 100.

valinta = int(raw_input("Valitse luku, ei lukua 10, eikä lukua 100: ")); # luetaan merkkijono ja muutetaan se kokonaisluvuksi

valitsiOikein = False;
if valinta != 10 and valinta != 100:
  valitsiOikein = True;

if valitsiOikein:
  print "Hyvin valittu.";
else:
  print "Soo soo!";

Yllä oleva ohjelma kysyy käyttäjältä lukua. Jos käyttäjä valitsee ohjeen vastaisesti 100 tai 10, toruu ohjelma käyttäjää. Jos käyttäjä valitsi ohjeen mukaisesti, ohjelma sanoo "Hyvin valittu."

Toiston alkeet

Huomamme usein ohjelmakoodissamme tilanteita missä sama asia toistuu usein. Esimerkiksi, jos haluamme tulostaa luvut yhdestä kymmeneen, on yksi -- kohta onneksi turha -- ratkaisu kirjoittaa jokainen käsky erikseen.

print 1;
print 2;
print 3;
print 4;
print 5;
print 6;
print 7;
print 8;
print 9;
print 10;

Entä jos haluaisimme tulostaa kaikki luvut yhdestä sataan, tai vaikka luvut yhdestä käyttäjän antamaan syötteeseen asti? Tällaisissa tapauksissa käytetään toistorakenteita. Toistorakenteiden avulla voidaan toteuttaa toistuvia käskyjä.

while

Komento while toistaa lohkonsa koodia niin kauan kun määritelty ehto on voimassa. Komennon while ehto-osa toimii kuten if:ssä.

Kuten kaikki lohkot, while:n lohko alkaa kaksoispisteellä. Lohkossa oleva koodi on sisennettyä, ja lohko päättyy sisennyksen loppumiseen.

Seuraavassa esimerkissä tulostetaan luvut 1,2,..,9,10. Kun luku-muuttuja saa arvokseen yli 10, while-ehto ei ole enää voimassa ja toistaminen lopetetaan.

luku = 1;

while luku < 11:
  print luku;
  luku = luku + 1;
1
2
3
4
5
6
7
8
9
10

Lue ylläoleva "niin pitkään kuin luku on pienempi kuin 11, tulosta luku ja kasvata lukua yhdellä".

Ylläolevassa koodissa ehto-lausessa olevaa muuttujaa luku kasvatettiin jokaisella kierroksella yhdellä. Päivitysehto voi olla mikä tahansa.

luku = 1024;

while luku >= 1:
  print luku;
  luku /= 2; # luku = luku / 2;

Tehtäviä

Yhdestä sataan

Tee ohjelma, joka tulostaa kokonaisluvut väliltä 1–100.

Ohjelman tulostus on seuraava:

1
2
3
(välissä paljon rivejä)
98
99
100

Sadasta yhteen

Tee ohjelma, joka tulostaa kokonaisluvut väliltä 100–1.

Ohjelman tulostus on seuraava:

100
99
98
(välissä paljon rivejä)
3
2
1

Parilliset luvut

Tee ohjelma, joka tulostaa parilliset kokonaisluvut väliltä 2–100.

2
4
6
(välissä paljon rivejä)
96
98
100

Lukuun asti

Kirjoita ohjelma, joka tulostaa kokonaisluvut 1:stä käyttäjän antamaan lukuun asti.

Mihin asti? 3
1
2
3
Mihin asti? 5
1
2
3
4
5

Alaraja ja yläraja

Kirjoita ohjelma, joka kysyy käyttäjältä ensimmäisen ja viimeisen luvun ja tulostaa niiden välissä olevat luvut. Käyttäjä voi antaa ensin pienemmän luvun ja sitten suuremman luvun tai päinvastoin.

Ensimmäinen: 5
Viimeinen: 8
5
6
7
8
Ensimmäinen: 16
Viimeinen: 12
16
15
14
13
12

Arvauspeli

Muunna seuraavasta lähdekoodista peli, jossa joudut arvaamaan numeroa.

# -*- coding: utf-8 -*-
import random;

arvattava = random.randint(0, 1000); # arvattava on luku 0 ja 1000 välillä

# ohjelman toteutus
Arvaa luku [0-1000]: 200
Liian pieni!
Arvaa luku [0-1000]: 700
Liian pieni!
Arvaa luku [0-1000]: 750
Liian suuri!
Arvaa luku [0-1000]: 720
Liian suuri!
Arvaa luku [0-1000]: 710
Oikein!

Arvasit luvun 710 oikein 5 arvauksella.

Vanha arvo

Usein tahdotaan päivittää vanhojen muuttujien arvoja. Tämä onnistuu kirjoittamalla tavallinen sijoituslauseke.

pituus = 100;

pituus = pituus - 50;
pituus = pituus * 2;
pituus = pituus / 2;

# pituus on nyt 50

Sijoitusoperaatiot

Koska vanhan muuttujan arvon päivittäminen on niin yleinen operaatio, on sitä varten erityiset sijoitusoperaatiot, jotka eivät ehkä ole ainakaan aluksi niin helppolukuisia.

pituus = 100;

pituus += 10;  # sama kuin pituus = pituus + 10;
pituus -= 50;  # sama kuin pituus = pituus - 50;

Huomaa, että muuttujalla tulee olla arvo ennen kuin sitä voidaan käyttää. Seuraava esimerkki ei toimi, sillä muuttujalle pituus ei ole alussa asetettu mitään arvoa.

pituus = pituus + 100;   # ei toimi!
pituus += 100;           # ei toimi!

Kun arvo on kerrottu, toimii laskutkin oikein.

pituus = 0;
pituus = pituus + 100;
pituus += 100;

# muuttujan pituus arvo on 200

Myös muille kuin yhteen- ja vähennyslaskuille on vastaavat sijoitusoperaatiot.

pituus = 100;

pituus *= 10;  # sama kuin pituus = 10 * pituus; 
pituus /= 100; # sama kuin pituus = pituus / 100;
pituus %= 3;   # sama kuin pituus = pituus % 3;

# muuttujan pituus arvo 1

Usein ohjelmissa esiintyy toisto jonka aikana muuttujaan lasketaan jokin toistosta riippuvainen arvo. Seuraava ohjelma laskee tulon 4*3 hieman kömpelöllä tavalla eli summana 3+3+3+3:

tulos = 0;

i = 0; # kierrosmuuttuja
while i < 4:
   tulos = tulos + 3;
   i += 1;   # tarkoittaa samaa kuin i = i+1;

Alussa tulos = 0. Toistossa muuttujan tulos arvo nousee joka kierroksella 3:lla. Ja koska toistoja on 4, on lopulta muuttujan arvona siis 3*4.

Käyttämällä yllä esiteltyä sijoitusoperaattoria, sama saadaan aikaan seuraavasti:

int tulos = 0;

int i = 0;
while i < 4:
   tulos += 3; # tämä on siis sama kuin tulos = tulos + 3;
   i += 1;     # tarkoittaa samaa kuin i = i + 1;

Tehtäviä

Summa 1+2+3+...+n

Tee ohjelma, joka laskee summan 1+2+3+...+n, missä n on käyttäjän syöttämä luku.

Ohjelman voi tehdä käyttäen while-silmukkaa. Voit myös tehdä tehtävän laajentamalla 1.4:n toiminnallisuutta summan laskemisella.

Esimerkkitulostuksia:

Mihin asti? 3
Summa 1+2+...+3 on 6
Mihin asti? 7
Summa 1+2+...+7 on 28

Huom: tulostuksen tarkka muoto ei ole tässä eikä seuraavassa tehtävässä kovin oleellinen, tärkeintä on summan laskeminen ja sen tulostaminen.

Summa alku+...+loppu

Muuta edellistä tehtävää siten, että käyttäjä määrää myös summan laskemisen aloituskohdan. Voit olettaa, että käyttäjä antaa ensin pienemmän luvun ja sitten suuremman luvun.

Esimerkkitulostuksia:

Ensimmäinen: 3
Viimeinen: 5
Summa 3+...+5 on 12
Ensimmäinen: 2
Viimeinen: 8
Summa 2+...+8 on 35

Kertoma

Tee ohjelma, joka laskee luvun n kertoman. Kertoma n! lasketaan kaavalla 1*2*3*...*n. Esimerkiksi 4! = 1*2*3*4 = 24. Lisäksi on määritelty, että 0! = 1.

Esimerkkitulostuksia:

Anna luku: 3
Luvun 3 kertoma on 6
Anna luku: 10
Luvun 10 kertoma on 3628800

while-True -- break

Avainsana break mahdollistaa toistosta poistumisen kesken toiston suoritusta. Yleinen while-toistolauseen käyttötapa onkin ns. while-True -käyttötapa, jossa toistoa jatketaan näennäisesti ikuisesti while ( true ), mutta toiston sisällä hoidetaan toistosta poistuminen. Esimerkiksi seuraava salasanaa kysyvä ohjelma:

while True:
  salasana = raw_input("Kirjoita salasana: ");

  if salasana == "salasana":
    break;
  
  print "Väärin kirjoitettu!";


print ""; # tulostetaan tyhjä rivi
print "Kiitos!";
Kirjoita salasana: Mikki
Väärin kirjoitettu!
Kirjoita salasana: ASd
Väärin kirjoitettu!
Kirjoita salasana: salasana

Kiitos!

Kun käyttäjä syöttää merkkijonon "salasana", lauseke salasana == "salasana" saa arvon True. Tällöin mennään lohkoon jossa on avainsana break. Break lopettaa toiston suorittamisen välittömästi, jolloin ohjelman suoritusta jatketaan toistolohkon jälkeiseltä riviltä.

while ja lopetus muuten kuin breakilla

Seuraavaksi, kysymme käyttäjän ikää. Jos ikä ei ole välillä 5-85, annetaan huomautus ja kysytään ikä uudelleen. while-lauseen ehto voi siis olla mitä tahansa totuusarvon tuottavaa.

ika = int(raw_input("Ikäsi: "));

while ika < 5 or ika  > 85:
  print "Valehtelet";
  if ika < 5:
      print "Olet niin nuori ettet osaa kirjoittaa";
  elif ika > 85:
      print "Olet niin vanha ettet osaa käyttää tietokonetta";

  ika = int(raw_input("syötä ikäsi uudelleen: "));

print "Ikäsi on siis "+ str(ika);

Ikuinen silmukka

Yksi suosituimmista ohjelmointivirheistä toistolauseissa on tehdä vahingossa ikuinen silmukka. Seuraavassa yritetään tulostaa ruudulle 10 kertaa "En enää ikinä ohjelmoi ikuista silmukkaa":

i = 0;

while i<10:
  print "En enää ikinä ohjelmoi ikuista silmukkaa";

Toistolauseen kertojen määrää kontrolloiva muuttuja i on aluksi 0 ja toistoja tehdään niin kauan kuin i < 10, eli i-muuttujassa oleva arvo on pienempi kuin 10. Käy kuitenkin hieman hassusti sillä i:n arvoa ei muuteta missään. Toistoehto pysyy ikuisesti totena.

Tällaisissa tilanteissa joudut lopettamaan ohjelman suorituksen itse. NetBeans-ohjelmointiympäristössä on Output-ikkunan vieressä punainen neliö "Stop", jolla ohjelman suorituksen saa keskeytettyä. Komentorivillä ajettaessa painamalla ctrl ja c samaan aikaan lopettaa ohjelman suorituksen.

Tehtäviä

Lukujen lukeminen

Tee ohjelma, joka kysyy käyttäjältä lukuja, kunnes käyttäjä antaa luvun -1. Kun käyttäjä syöttää luvun -1, ohjelma tulostaa "Kiitos ja näkemiin!" ja päättyy.

Anna lukuja:
5
2
4
-1
Kiitos ja näkemiin!

Lukujen summa

Laajenna edellistä ohjelmaa siten, että ohjelma ilmoittaa käyttäjän syöttämien lukujen summan. (Lukua -1 ei lasketa mukaan.)

Anna lukuja:
5
2
4
-1
Summa: 11

Lukujen summa ja lukumäärä

Laajenna edellistä ohjelmaa siten, että ohjelma ilmoittaa myös käyttäjien antamien lukujen lukumäärän. (Lukua -1 ei lasketa mukaan.)

Anna lukuja:
5
2
4
-1
Summa: 11
Lukuja: 3

Lukujen keskiarvo

Muuta edellistä ohjelmaa siten, ohjelma ilmoittaa lukujen keskiarvon. (Lukua -1 ei lasketa mukaan.)

Anna lukuja:
5
2
4
-1
Summa: 11
Lukuja: 3
Keskiarvo: 3.666666666666

Parilliset ja parittomat

Laajenna edellistä ohjelmaa siten, että ohjelma ilmoittaa parillisten ja parittomien lukujen määrän. (Lukua -1 ei lasketa mukaan.)

Anna lukuja:
5
2
4
-1
Summa: 11
Lukuja: 3
Keskiarvo: 3.666666666666
Parillisia: 2
Parittomia: 1

Huomio: Ohjelmien tekeminen pienissä paloissa

Viidessä edeltävissä tehtävässä tehtiin oikeastaan ainoastaan yksi ohjelma, mutta ohjelman rakentaminen tapahtui hyvin pienissä paloissa. Tämä on ehdottoman suositeltava tapa AINA kun ohjelmoit.

Eli kun teet ohjelmaa, oli se sitten tehtävä tai oma projektisi, kannattaa edetä hyvin pienissä paloissa. Älä koskaan yritä ratkaista koko ongelmaa kerralla. Aloita jollain helpolla asialla jonka tiedät varmasti osaavasi, esim. aluksi keskityttiin aluksi pelkästään siihen, että osataan pysäyttää ohjelma käyttäjän syöttäessä luvun -1. Kun yksi ohjelman osa on saatu toimimaan, voidaan siirtyä ratkaisemaan jotain seuraavaa varsinaisen ongelman osaongelmaa.

Osa tulevistakin tehtävistä on edellisten tehtävien tapaan valmiiksi osiin pilkottuja. Usein jokainen osa kuitenkin pitää vielä pilkkoa ohjelmoinnin kannalta vieläkin pienempiin paloihin. Kannattaa jopa tehdä siten, että lähes jokaisen koodirivin jälkeen suoritetaan ohjelma ja varmistetaan, että ratkaisu on etenemässä haluttuun suuntaan.


Pienin ja suurin

Tee ohjelma (tai laajenna edellistä), joka kysyy käyttäjältä lukuja, kunnes käyttäjä antaa luvun -1. Sitten ohjelma ilmoittaa pienimmän ja suurimman luvun. (Lukua -1 ei lasketa mukaan.)

Anna lukuja:
9
5
2
-1
Pienin: 2
Suurin: 9
Anna lukuja:
5
3
7
8
-1
Pienin: 3
Suurin: 8
Anna lukuja:
4
-1
Pienin: 4
Suurin: 4

for

Seuraavassa esimerkissä aloitamme laskemisen 0:sta ja toistamme while-toistolausekkeen lohkoa, kunnes olemme lukeneet käyttäjältä kolme lukua, jolloin ehto ei ole enää voimassa.

print "Anna kolme lukua ja kerron niiden summan";

luettu = 0; 
summa = 0;

while luettu < 3:
  luku = int(raw_input("Anna luku: "));

  summa = summa + luku;   # tai summa += luku;
  luettu += 1;            # tai luettu = luettu + 1;


print "Lukujen summa on: " + str(summa);

Tiedämme usein jo etukäteen kuinka monta kertaa tahdomme suorittaa toistolausekkeen lohkon. Edellisessä esimerkissä suoritimme lohkon 3 kertaa. Tämän voi ilmaista myös käyttämällä for-toistolausetta seuraavalla tavalla.

print "Anna kolme lukua ja kerron niiden summan";

summa = 0;

for i in range(3): # i in range(3) tarkoittaa käydään luvut 0, 1, 2 yksitellen läpi
  luku = int(raw_input("Anna luku: "));

  summa = summa + luku;   # tai summa += luku;


print "Lukujen summa on: " + str(summa);

Komento range antaa kaikki luvut nollan ja komennolle range parametrina annetun luvun välillä. Esimerkiksi komento range(10) antaisi luvut 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Tässä ei kannata säikähtää yksikirjaimista muuttujaa i. Sen käytöllä on tietojenkäsittelyssä ja ohjelmoinnissa pitkät perinteet.

for-lauseke käy yksitellen range-komennolla saadut luvut läpi. Jokainen luku on vuorollaan muuttujassa i. Muuttujan i arvon voi tulostaa aivan kuten kaikkien muidenkin muuttujien arvon. Esimerkiksi lukujen 1, 2, ... , 10 tulostaminen käy seuraavasti for-lausekkeen avulla.

for i in range(10): # i in range(10) käy läpi luvut 0, 1, ..., 9
  print str(i + 1);
1
2
3
4
5
6
7
8
9
10

Komennolle range voi myös kertoa luvun josta aloitetaan. Ylläolevan tulostuksen saa aikaan myös seuraavasti:

for i in range(1, 11): # i in range(1, 11) käy läpi luvut 1, 2, ..., 10
  print i; # huomaa että pelkkää lukua ei tarvitse muuttaa tekstityyppiseksi tulostettaessa

Käytännössä for on while-lauseen pelkistetympi muoto. Komennolle range voi asettaa myös kolmannen arvon, joka kertoo askeleen koosta. Esimerkiksi komento range(1, 10, 3) käy läpi luvut 1, 4, 7 (luku 10 ei tule mukaan koska toiseksi arvoksi -- eli ylärajaksi -- on asetettu 10).

print "Tulostetaan joka kolmas luku.";

for i in range(1, 10, 3):
  print i;
Tulostetaan joka kolmas luku.
1
4
7

Tehtäviä

Tee seuraavat tehtävät for-silmukan avulla:

Yhdestä sataan

Tee ohjelma, joka tulostaa kokonaisluvut väliltä 1–100.

Ohjelman tulostus on seuraava:

1
2
3
(välissä paljon rivejä)
98
99
100

Sadasta yhteen

Tee ohjelma, joka tulostaa kokonaisluvut väliltä 100–1.

Ohjelman tulostus on seuraava:

100
99
98
(välissä paljon rivejä)
3
2
1

Parilliset luvut

Tee ohjelma, joka tulostaa parilliset kokonaisluvut väliltä 2–100.

2
4
6
(välissä paljon rivejä)
96
98
100

Lohkoista ja sisäkkäisistä toistolauseista

Lohko alkaa kaksoispisteellä : ja sisennyksellä ja loppuu sisennyksen loppuessa. Kuten on jo nähty, sisennyksiä käytetään muunmuassa ehto- ja toistolauseiden alueen rajaamisessa. Mielenkiintoinen sisennyksen -- materiaali käyttää hyvin paljon termiä lohko -- ominaisuus on myös se, että lohkossa määritellyt muuttujat ovat näkyvissä myös lohkon ulkopuolella. Seuraavassa esimerkissä määritellään ehtolausekkeeseen liittyvän lohkon sisällä tekstimuuttuja lohkonSisallaMaariteltyTeksti, joka on olemassa myös lohkon ulkopuolella. Lohkossa esitellyn muuttujan tulostus lohkon ulkopuolella siis toimii -- mutta vain jos lohkoon päästään!

luku = 5;

if luku == 5:
  lohkonSisallaMaariteltyTeksti = "Jea!";

print lohkonSisallaMaariteltyTeksti; # tulostaa Jea!

Lohkossa voidaan käyttää ja muuttaa sen ulkopuolella määriteltyjä muuttujia.

luku = 5;

if luku == 5:
  luku = 6;

print luku; # tulostaa luvun 6

Lohkon sisällä voi olla mitä tahansa koodia, eli esim for-lauseen sisällä voi olla myös toinen for tai vaikkapa while. Sisäkkäiset toistolauseet ovatkin ohjelmoinnissa aika yleisiä. Tarkastellaan seuraavaa ohjelmaa:

for i in range(3):
   print str(i) + ": ";

   arvot = ""
   for j in range(3):
     arvot += str(j) + " ";

   print arvot;

Ohjelman tulostus on seuraava:

0: 
0 1 2 
1: 
0 1 2 
2: 
0 1 2 

Eli mitä ohjelmassa tapahtuukaan? Jos ajatellaan pelkkää ulommaista for:ia, on toiminnallisuus helppo ymmärtää:

for i in range(3):
  print str(i) + ": ";
  # sisemmäinen for

Eli ensin muuttuja i saa arvokseen 0, tulostetaan 0: ja rivinvaihto. Tämän jälkeen i kasvaa ja tulostuu ykkönen, jne., eli ulompi for saa aikaan seuraavan:

0:
1:
2:

Myös sisempi for on helppo ymmärtää erillään. Se asettaa muuttujaan arvot arvoja yksitellen, ja lopulta arvo on 0 1 2 . Kun yhdistämme nämä kaksi, huomaamme, että sisin for suorittaa tulosteensa aina juuri ennen uloimman for:in tulostamaa rivinvaihtoa.

Entä jos muutamme ohjelmaa hiukan:

for i in range(3):
   print str(i) + ": ";

   arvot = ""
   for j in range(i):
     arvot += str(j) + " ";

   print arvot;

Sisemmän for:in toistojen määrä riippuukin nyt ulomman for:in muuttujan i arvosta. Eli kun i=0, ei sisempi toistolauseke tee mitään. Kun i=1, tulostaa sisin looppi 0, jne. Koko ohjelman tulostus on siis seuraava:

0:

1:
0
2: 
0 1

Seuraava ohjelma tulostaa lukujen 1..10 kertotaulun.

for i in range(1, 11):
  rivi = ""
  for j in range(1, 11):
    rivi = rivi + str(i * j) + " ";
  
  print rivi;

Tulostus näyttää seuraavalta:

1 2 3 4 5 6 7 8 9 10 
2 4 6 8 10 12 14 16 18 20 
3 6 9 12 15 18 21 24 27 30 
4 8 12 16 20 24 28 32 36 40 
5 10 15 20 25 30 35 40 45 50 
6 12 18 24 30 36 42 48 54 60 
7 14 21 28 35 42 49 56 63 70 
8 16 24 32 40 48 56 64 72 80 
9 18 27 36 45 54 63 72 81 90 
10 20 30 40 50 60 70 80 90 100 

Ylimmällä rivillä luvun 1 kertotaulu. Alussa i=1 ja sisimmän loopin muuttuja j saa arvot 1...10. Jokaisella i, j arvoparilla tallennetaan niiden tulo tekstimuuttujaan rivi. Tekstimuuttuja alustetaan tyhjäksi aina kun i saa uuden arvon. Eli alussa i=1, j=1, sitten i=1, j=2, ..., i=1, j=10 seuraavaksi i=2, j=1, jne.

Muuta ohjelmaa siten, että tulostatkin sisemmässä for:issa tulon sijaan i:n ja j:n arvot niin näät miten ne etenevät.

Lisää toistotehtäviä

Vinkki! Voit liittää merkkijonoja yhteen +-operaation avulla. Esimerkiksi seuraava ohjelma tulostaisi merkkijonon aaaaa

aat = "";
for i in range(5):
  aat += "a";

print aat;

Tähtien tulostus

Tee ohjelma, joka tulostaa käyttäjän antaman määrän tähtiä.

Kuinka monta tähteä? 5
*****
Kuinka monta tähteä? 8
********

Suorakulmion tulostus

Tee ohjelma, joka tulostaa suorakulmion, jonka leveyden ja korkeuden käyttäjä antaa.

Vihje: Tee kaksi sisäkkäistä silmukkaa.

Anna leveys: 4
Anna korkeus: 3
****
****
****
Anna leveys: 10
Anna korkeus: 5
**********
**********
**********
**********
**********

Portaikon tulostus

Tee ohjelma, joka tulostaa portaikon, jonka kerrosten määrän käyttäjä antaa.

Kuinka monta kerrosta? 3
*
**
***
Kuinka monta kerrosta? 5
*
**
***
****
*****

Kuusen tulostus

Tee ohjelma, joka tulostaa kuusen, jonka korkeuden käyttäjä antaa.

Anna korkeus: 3
  *
 ***
*****
  *
Anna korkeus: 5
    *
   ***
  *****
 *******
*********
    *

PIN-koodi

PIN-koodissa on neljä numeroa väliltä 1–9. Tee ohjelma, joka tulostaa järjestyksessä kaikki PIN-koodit.

Vihje: Tee neljä sisäkkäistä silmukkaa!

1111
1112
1113
(välissä paljon rivejä)
5629
5631
5632
(välissä paljon rivejä)
9997
9998
9999

Lista

Lista on muuttuja, joka voidaan käsittää eräänlaisena lokerikkona arvoille. Listan pituus tai koko on lokerikon paikkojen lukumäärä, eli kuinka monta arvoa listaan on laitettu. Listan arvoja kutsutaan listan alkioiksi.

Muistellaan hetki muuttujan luontia ja sen arvon asetusta. Luodaan kokonaislukutyyppinen muuttuja luku ja asetetaan sen arvoksi 3.

luku = 3;

Listan luonti tapahtuu lähes samalla tavalla, mutta listan alkioita asetettaessa alkiot lisätään merkinnän [] sisällä. Kolmen alkion kokonaislukutyyppinen lista määritellään seuraavasti.

luvut = [100, 1, 42];

Huomaa että perusasetus on täysin sama, kerrotaan muuttujan jonka jälkeen asetetaan muuttujalle sen arvo. Yllä muuttujan nimi on luvut ja se saa arvokseen kolme lukua [100, 1, 42]. Lista alustetaan hakasuluilla, jossa listaan asetettavat muuttujat on esitelty pilkulla eriteltyinä.

Listaan voidaan asettaa mitä tahansa aikaisemmin esiteltyjä tietotyyppejä (tekstityyppejä, kokonaislukuja, liukulukuja).

tekstiLista = ["Matti P.", "Matti V."];
liukulukuLista = [1.20, 3.14, 100.0, 0.6666666667];

Listan alkioihin voidaan viitata indeksillä, joka on kokonaisluku. Indeksi kertoo paikan listan sisällä. Listan ensimmäinen alkio on paikassa nolla, seuraava yksi ja niin edelleen. Indeksi (=kokonaisluku) annetaan listamuuttujan perään hakasulkeiden sisällä.

# indeksi     0  1   2   3  4  5     6     7
luvut =    [100, 1, 42, 23, 1, 1, 3200, 3201];

print luvut[0];	# tulostaa luvun listan indeksistä 0, eli luvun 100
print luvut[2];	# tulostaa luvun listan indeksistä 2, eli luvun 42

Yllä olevan listan koko (eli pituus) on 8.

Arvon asettaminen listan alkioon tapahtuu kuten tavalliseen muuttujaan, mutta listaan asettaessa täytyy myös kertoa indeksi johon arvo asetetaan. Asetus tapahtuu kuten aikaisemmin esitellyillä muuttujilla.

luvut = [100, 1, 42];

luvut[1] = 101;
luvut[0] = 1;

# luvut-lista on nyt [1,101,42]
print luvut;

Jos indeksillä osoitetaan listan ohi, eli alkioon jota ei ole olemassa, niin saadaan virhe IndexError: list assignment index out of range, joka kertoo että indeksi johon osoitimme ei ole olemassa. Listan ohi (indeksiin joka on pienempi kuin 0, tai indeksiin joka on suurempi tai yhtäsuuri kuin listan koko) ei siis saa viitata.

Listan koko

Listan koko tai pituus saadaan selville komennolla len, joka saa parametrinaan listamuuttujan. Kun puhumme parametrista, tarkoitamme komennon jälkeen tulevien sulkujen sisään asetettavia muuttujia tai arvoja.

Seuraavassa esimerkissä tulostetaan listan koko.

luvut = [100, 1, 42];

print "Listassa luvut on yhteensä " + str(len(luvut)) + " alkiota.";
Listassa luvut on yhteensä 3 alkiota.

Huomaa että komento len palauttaa kokonaislukutyyppisen muuttujan, eli joudumme käyttämään str-komentoa jotta voimme katenoida sen merkkijonoon.

Listan läpikäynti

Listan läpikäyntiin soveltuu luontevasti for-toistolauseke. Tällöin toistolausekkeessa käytettävä muuttuja saa arvokseen vuorollaan kunkin listan alkion indeksin. Seuraavassa esimerkissä listan koko (len(listamuuttuja)) on 5, joten muuttuja i saa arvot 0, 1, 2, 3 ja 4. Ohjelma tulostaa listassa olevat luvut.

luvut = [1, 8, 10, 3, 5];

for i in range(len(luvut)):
  print luvut[i];

for-each

Usein listan läpikäynnissä ei ole todellista tarvetta luetella listan indeksejä, vaan ainoa kiinnostava asia ovat listassa olevat arvot. Tällöin voidaan käyttää for-each-rakennetta seuraavan esimerkin mukaisesti. Nyt toistolausekkeen ehdossa annetaan vain käytettävälle muuttujalle nimi ja listan nimi in-operaatiolla erotettuna. Toistolausekkeen muuttuja saa arvokseen vuorollaan kunkin listassa olevan arvon.

luvut = [1, 8, 10, 3, 5];

for luku in luvut:
  print luku;

Lue "Jokaiselle luvulle listassa luvut: tulosta luku". Ylläoleva ohjelma tulostaa jokaisen listassa luvut olevan luvun. Samaa toistorakennetta voi käyttää myös tekstityyppisille muuttujille:

nimet = ["Arto V.", "Matti P.", "Matti L.", "Antti L.", "Pekka M.", "Juhana L.", "Martin P."];

for nimi in nimet:
  print nimi;
Arto V.
Matti P.
Matti L.
Antti L.
Pekka M.
Juhana L.
Martin P.

Huom! For-each-tyylisellä läpikäynnillä listan alkoihin ei pysty asettamaan arvoja! Esimerkiksi seuraava esimerkki ei muuta listassa olevia nimiä!

nimet = ["Arto V.", "Matti P.", "Matti L.", "Antti L.", "Pekka M.", "Juhana L.", "Martin P."];
ika = 1;

for nimi in nimet:
  nimi = nimi + " on " + str(ika) + " vuotta vanha.";
  ika = ika + 1;

for nimi in nimet:
  print nimi;
Arto V.
Matti P.
Matti L.
Antti L.
Pekka M.
Juhana L.
Martin P.

Nimien muuttaminen onnistuu kun listaa käydään läpi indeksimuuttujan avulla.

nimet = ["Arto V.", "Matti P.", "Matti L.", "Antti L.", "Pekka M.", "Juhana L.", "Martin P."];
ika = 1;

for i in range(len(nimet)):
  nimet[i] = nimet[i] + " on " + str(ika) + " vuotta vanha.";
  ika = ika + 1;

for nimi in nimet:
  print nimi;
Arto V. on 1 vuotta vanha.
Matti P. on 2 vuotta vanha.
Matti L. on 3 vuotta vanha.
Antti L. on 4 vuotta vanha.
Pekka M. on 5 vuotta vanha.
Juhana L. on 6 vuotta vanha.
Martin P. on 7 vuotta vanha.

Seuraavassa esimerkissä muutetaan listassa oleva merkkijono "Arto V." merkkijonoon "Matti V.".

nimet = ["Arto V.", "Matti P.", "Matti L.", "Antti L.", "Pekka M.", "Juhana L.", "Martin P."];
ika = 1;

for i in range(len(nimet)):
  if nimet[i] == "Arto V.":
     nimet[i] = "Matti V.";

for nimi in nimet:
  print nimi;
Matti V.
Matti P.
Matti L.
Antti L.
Pekka M.
Juhana L.
Martin P.

Tehtäviä

Alusta loppuun

Tee ohjelma, joka tulostaa listassa olevat luvut alusta loppuun.

Lista määritellään koodin alussa seuraavasti:

luvut = [5, 1, 3, 4, 2];

Ohjelman tulostus on seuraava:

5
1
3
4
2

Jos listan määrittely muuttuu, myös ohjelman toiminnan tulee muuttua. Eli kirjoita silmukkasi siten, että ne toimivat kaikenlaisille listoille. Listan alkioiden määrän kertoo len(lista). Toinen ratkaisu on käyttää for-each-silmukkaa.

luvut = [2, 7, 3];
2
7
3

Lista lopusta alkuun

Tee ohjelma, joka tulostaa listassa olevat luvut lopusta alkuun.

Lista määritellään koodin alussa seuraavasti:

luvut = [5, 1, 3, 4, 2];

Ohjelman tulostus on seuraava:

2
4
3
1
5

Lista tähtinä

Tee ohjelma, joka tulostaa listan lukuja vastaavat määrät tähtiä.

Lista määritellään koodin alussa seuraavasti:

luvut = [5, 1, 3, 4, 2];

Ohjelman tulostus on seuraava:

*****
*
***
****
**

Listan lukujen summa

Tee ohjelma, joka tulostaa listan lukujen summan.

Lista määritellään koodin alussa seuraavasti:

luvut = [5, 1, 3, 4, 2];

Ohjelman tulostus on seuraava:

15

Tyylikäs tulostus

Tee ohjelma, joka tulostaa listassa olevat luvut tyylikkäästi. Lukujen väliin tulee pilkku ja välilyönti. Viimeisen luvun jälkeen ei kuitenkaan tule pilkkua.

Lista määritellään koodin alussa seuraavasti:

luvut = [5, 1, 3, 4, 2];

Ohjelman tulostus on seuraava:

5, 1, 3, 4, 2

Lukujen paikan vaihtaminen

Tee ohjelma, joka antaa käyttäjälle mahdollisuuden vaihtaa kahden luvun paikkaa listassa.

Lista määritellään koodin alussa seuraavasti:

luvut = [5, 1, 3, 4, 2];

Esimerkkisyötteitä:

Listan sisältö: 5, 1, 3, 4, 2
1. kohta: 0
2. kohta: 1
Listan sisältö: 1, 5, 3, 4, 2
Listan sisältö: 5, 1, 3, 4, 2
1. kohta: 1
2. kohta: 3
Listan sisältö: 5, 4, 3, 1, 2

Mitä ohjelmasi tekee, jos käyttäjä antaa virheellisen syötteen?

Onko luku listassa?

Tee ohjelma, joka kertoo, onko käyttäjän antama luku listassa.

Lista määritellään koodin alussa seuraavasti:

luvut = [7, 2, 4, 8];

Esimerkkitulostuksia:

Anna luku: 4
Luku on listassa.
Anna luku: 3
Lukua ei ole listassa.

Onko sana listassa?

Tee ohjelma, joka kertoo, onko käyttäjän antama sana listassa.

Lista määritellään koodin alussa seuraavasti:

sanat = ["apina", "banaani", "cembalo"];

Esimerkkitulostuksia:

Anna sana: apina
Sana on listassa.
Anna sana: siili
Sanaa ei ole listassa.

Uuden listan luonti

Usein emme tiedä etukäteen kuinka monta alkiota listassa tulee olemaan. Pystymme lisäämään uusia alkioita listan loppuun listamuuttujaan (ja oikeastaan listatyyppiin) liittyvän komennon append avulla.

luvut = []; # tyhjä lista lukuja

for i in range(100):
  luvut.append(i);

if len(luvut) == 100:
  print "Listan pituus on " + str(len(luvut));
else:
  print "Jotain kummallista tapahtui. Listan pituus oli eri kuin 100.";

Seuraavassa esimerkissä on ohjelma, joka kysyy käyttäjältä lukujen määrän ja joukon lukuja. Sitten ohjelma tulostaa luvut uudestaan samassa järjestyksessä. Käyttäjän antamat luvut tallennetaan listaan.

lukuja = int(raw_input("Kuinka monta lukua? "));
print ""; # tulostetaan rivinvaihto

luvut = [];

for i in range(lukuja):
  luku = int(raw_input("Anna " + str(i + 1) + ". luku: "))
  luvut.append(luku);

print ""; # tulostetaan rivinvaihto
print "Luvut uudestaan:";
for luku in luvut:
  print luku;

Eräs ohjelman suorituskerta voisi olla seuraavanlainen:

Kuinka monta lukua? 4

Anna 1. luku: 4
Anna 2. luku: 8
Anna 3. luku: 2
Anna 4. luku: 1

Luvut uudestaan:
4
2
8
1

Tehtäviä

Lukujen kääntäminen

Tee ohjelma, joka kysyy käyttäjältä lukujen määrän ja joukon lukuja. Sitten ohjelma tulostaa luvut käänteisessä järjestyksessä.

Kuinka monta lukua? 4
Anna luvut:
4
2
8
1
Käänteinen järjestys:
1
8
2
4

Lukujen järjestäminen

Pythonissa on valmiina paljon kaikenlaista hyödyllistä. Viime viikolla käytettiin muunmuassa vakiota math.pi, joka sisältää piin arvon. Pythonissa on myös valmis metodi listan järjestämiseen.

Seuraava komento järjestää listan, jonka nimi on luvut:

luvut.sort()

Tee ohjelma, joka kysyy käyttäjältä lukujen määrän ja joukon lukuja. Tämän jälkeen ohjelma tulostaa luvut järjestyksessä.

Kuinka monta lukua? 4
Anna luvut:
5
2
8
1
Luvut järjestyksessä:
1
2
5
8

Sanojen kysely

Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa saman sanan uudestaan. Voit olettaa, että käyttäjä antaa korkeintaan 1000 sanaa.

Anna sana: porkkana
Anna sana: selleri
Anna sana: nauris
Anna sana: lanttu
Anna sana: selleri
Annoit saman sanan uudestaan!

Sanalista

Tee ohjelma, joka kysyy käyttäjältä tietyn määrän sanoja listaan. Tämän jälkeen käyttäjä voi tarkistaa, onko tietty sana listassa. Ohjelma päättyy, kun käyttäjä kirjoittaa sanan "pois".

Kuinka monta sanaa? 3
Anna sana: apina
Anna sana: banaani
Anna sana: cembalo
Nyt voit kysyä sanoja.
Anna sana: banaani
Sana on listassa.
Anna sana: kurpitsa
Sana ei ole listassa.
Anna sana: cembalo
Sana on listassa.
Anna sana: pois
Hei hei!

Lista apuvälineenä

Lista on myös kätevä apuväline esimerkiksi hyväksyttävien arvojen säilyttämiseen. Seuraavassa esimerkissä pidämme hyväksyttäviä lukuja listassa, kysymme käyttäjältä luvun ja käymme kaikki listan luvut läpi tarkistaen samalla, onko käyttäjän antama luku hyväksytty vai ei.

	
hyvaksyttavatLuvut = [4,8,10];

valinta = int(raw_input("Valitse 4,8 tai 10: "));
totteliko = False;

for luku in hyvaksyttavatLuvut:
  if luku == valinta:
    totteliko = True;

if totteliko:
  print "Hyvin valittu";
else:
  print "Valitsit luvun " + str(valinta) + ", joka ei ollut sallittu.";

Muunnelma edellisestä esimerkistä:

luvut = [3,8,9,14];

print "Minulla on neljä lukua väliltä 1-15.";

arvaus = int(raw_input("Arvaa luku 1-15 välillä: "));

for luku in luvut:
  if luku == arvaus:
    print "Arvasit oikein luvun " + str(luku) + "!";

Lista järjestetyn tiedon säilyttäjänä

Seuraavassa "yhdistämme" kaksi listaa siten, että kahdessa listassa on yhtä monta alkiota ja jokainen alkio liittyy toisen listan vastaavan indeksin alkioon. Tällöin kummassakin listassa on tietoa samassa järjestyksessä.

arvosanat = [3, 2, 1, 1];
nimet = ["Juhana L.", "Antti L.", "Matti L.", "Pekka M."];

indeksi = 0;
while indeksi < len(arvosanat):
  print nimet[indeksi] + ": " + str(arvosanat[indeksi]);
  indeksi = indeksi + 1;

Funktiot

Olemme jo käyttäneet monia erilaisia komentoja: sijoitusta, laskutoimituksia, vertailua, if:iä, for:ia ja whileä. Ruudulle tulostaminen on tehty "komentoa" print käyttäen. Listan pituus tunnistetaan "komennolla" len. Tuttuja ovat myös raw_input ja int.

Huomaamme, että jälkimmäinen joukko edellä lueteltuja komentoja poikkeaa if:istä, for:ista, while:stä ym. siinä, että komennon perässä on sulut ja joskus sulkujen sisällä komennolle annettava syöte. "Sulkuihin päättyvät" eivät oikeastaan olekaan komentoja vaan funktioita.

Teknisesti ottaen funktio tarkoittaa koodinpätkää, jota voi kutsua muualta ohjelmakoodista. Koodirivi print "olen metodi!" siis tarkoittaa, että kutsutaan funktiota, joka suorittaa ruudulle tulostamisen. Funktion suorituksen jälkeen palataan siihen kohtaa missä ennen funktiokutsua oltiin menossa. Funktiolle suluissa annettua syötettä kutsutaan funktion parametriksi. Funktio print on poikkeus sinänsä, että se ei tarvitse sulkuja toimiakseen. Oikeasti jokaisessa print-kutsussa olisi myös voinut olla sulut mukana.

Parametrin lisäksi funktiolla voi olla paluuarvo. Esim. tuttu koodinpätkä:

syote = raw_input("Kerro jotain: ");

sisältää kutsun funktioon raw_input jolle annetaan parametrina merkkijono joka käyttäjälle näytetään. Funktiolla on paluuarvona käyttäjän syöttämä merkkijono. Vastaavasti paljon käyttämämme kokonaisluvun lukeminen:

luku = int(raw_input("Anna kokonaisluku: "));

sisältää kaksi funktiokutsua. Ensin kutsutaan funktiota raw_input, jonka paluuarvona on merkkijono. Funktion raw_input palauttama merkkijono annetaan heti parametrina funktiolle int, joka palauttaa parametrina annetun merkkijonon kokonaislukuna.

Funktiokutsuja voi siis ketjuttaa siten, että "sisemmästä" funktiokutsusta palautunut arvo annetaan "ulommalle" funktiokutsulle parametrina.

Funktiokutsuja on oikeastaan kahdenlaisia. Osa liittyy muuttujiin siten, että muuttujan nimen jälkeen tulee piste ja sen jälkeen funktion nimi. Osa taas on erillisiä. Esimerkiksi listamuuttujilla on funktio append, jota käytetään yhdessä listamuuttujan kanssa listamuuttuja.append(3). Tämä yhdistäminen johtuu yksinkertaistaen siitä, että tiedettäisiin mihin listaan lisätään.

Opimme hiukan myöhemmin tarkemmin mistä tässä pisteen vasemmanpuoleisessa osassa on kyse.

Tähän mennessä käyttämämme metodit ovat kaikki olleet pythonin valmiita funktioita. Hetken kuluttua opimme tekemään myös omia funktioita. Ensin kuitenkin puhutaan merkkijonoista.

Merkkijonot

Tässä osiossa tutustaan tarkemmin merkkijonoihin. Olemme jo käyttäneet tekstimuuttujia tulostuksen yhteydessä sekä oppineet vertailemaan merkkijonoja toisiinsa. Merkkijonoja vertailtiin kuten kokonaislukuja ja liukulukuja vertailuoperaatioiden avulla (==, !=, >, ...).

elain = "Koira";

if elain == "Koira":
  print elain + " sanoo vuh vuh";
elif elain == "Kissa":
  print elain + " sanoo miau miau";

Merkkijonoihin liittyy myös muita funktioita, esimerkiksi funktio lower palauttaa uuden merkkijonon, jossa kaikki merkkijonon kirjaimet on muutettu pieniksi.

syote = raw_input("Kirjoita jotain: ");
syote = syote.lower();

print "Kirjoittamasi teksti pienillä kirjaimilla: " + syote;
Kirjoita jotain: PORKKANA
Kirjoittamasi teksti pienillä kirjaimilla: porkkana

Edellisen luvun muuttujiin liittyvät funktiot voi saada edellisestä lisävalaistusta. Eli kun kutsutaan syote.lower(), on kyse nimenomaan muuttujassa syote olevan merkkijonon funktion syote() kutsumisesta.

Merkkijonoilta voi kysyä niiden pituutta lähes samalla tavalla kuin listoilta käyttäen funktiota len.

sana1 = "banaani";
sana2 = "kurkku";

print "Banaanin pituus on " + str(len(sana1));
print "Kurkku pituus on " + str(len(sana2));

Merkkijono on oikeastaan lista yksittäisiä kirjaimia. Voimmekin viitata tiettyyn merkkiin käyttämällä listarakenteista tuttua lista[indeksi] indeksiviittaustyyliä. Huomaa että indeksit alkavat aina nollasta, ja esimerkiksi merkkijonon kolmas merkki on indeksissä 2.

kirja = "Kalavale";

print "Kirjan kolmas kirjain on " + kirja[2];
Kirjan kolmas kirjain on l

Tehtäviä

Miksi seuraava esimerkki kaataa ohjelman?

kirja = "Kalavale"
print "Kirjan viimeinen merkki on " + kirja[len(kirja)]

Ensimmäinen kirjain

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen ensimmäisen kirjaimen.

Anna nimi: Pekka
Ensimmäinen kirjain: P
Anna nimi: Katariina
Ensimmäinen kirjain: K

Viimeinen kirjain

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen viimeisen kirjaimen.

Anna nimi: Pekka
Viimeinen kirjain: a
Anna nimi: Katariina
Viimeinen kirjain: a

Kirjaimet erikseen

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa sen kirjaimet erikseen.

Anna nimi: Pekka
1. kirjain: P
2. kirjain: e
3. kirjain: k
4. kirjain: k
5. kirjain: a
Anna nimi: Katariina
1. kirjain: K
2. kirjain: a
3. kirjain: t
4. kirjain: a
5. kirjain: r
6. kirjain: i
7. kirjain: i
8. kirjain: n
9. kirjain: a

Nimen kääntäminen

Tee ohjelma, joka kysyy käyttäjän nimen ja tulostaa sen väärinpäin.

Anna nimi: Pekka
Väärinpäin: akkeP
Anna nimi: Katariina
Väärinpäin: aniirataK

Muita merkkijonoihin ja listoihin liittyviä toiminnallisuuksia

Usein tahdotaan lukea merkkijonosta jokin tietty osa. Listarakenteista (eli myös merkkijonosta) pystyy ottamaan osalistoja, joilla merkkijonon osan erottaminen onnistuu. Hakasulkuoperaatiolle pystyy antamaan myös alku- ja loppukohdan seuraavasti

kirja = "Kalavale";

print kirja[:4];  # tulostaa "Kala"
print kirja[4:];  # tulostaa "vale"
print kirja[2:6]; # tulostaa "lava"

Hakasuluissa annetaan siis alkuindeksi ja loppuindeksi merkkijono[alkuindeksi:loppuindeksi]. Jos alkuindeksi jätetään pois, aloitetaan alusta. Vastaavasti jos loppuindeksi jätetään pois, otetaan alkuindeksistä merkkijonon loppuun asti.

Koska listan pilkkova hakasulkuoperaatio palauttaa myös listan, voidaan arvo ottaa talteen muuttujaan.

kirja = "8 veljestä";

loppuosa = kirja[2:];
print "7 " + loppuosa;

Jo aiemmin mainittiin merkkijonoihin liittyvät toiminnot in ja not in. Nämä toiminnot toimivat myös muissa listarakenteissa.

Merkkijonosta voi myös etsiä tietyn merkkijonon alkuindeksiä funktiolla find. Funktio find saa parametrina etsittävän merkkijonon sekä vapaaehtoisesti annettavan aloitus- ja lopetusindeksin. Funktio palauttaa arvon -1 jos etsittyä merkkijonoa ei löydy.

kirja = "Kalavale";

print "Lava löytyy kalavaleen kohdasta: " + str(kirja.find("lava"));
Lava löytyy kalavaleen kohdasta 2

Tehtäviä

Alkuosa

Tee ohjelma, joka tulostaa sanan alkuosan. Ohjelma kysyy käyttäjältä sanan ja alkuosan pituuden.

Anna sana: esimerkki
Alkuosan pituus: 4
Tulos: esim
Anna sana: esimerkki
Alkuosan pituus: 7
Tulos: esimerk

Loppuosa

Tee ohjelma, joka tulostaa sanan loppuosan. Ohjelma kysyy käyttäjältä sanan ja loppuosan pituuden. Käytä ohjelmassa metodia substring.

Anna sana: esimerkki
Loppuosan pituus: 4
Tulos: rkki
Anna sana: esimerkki
Loppuosan pituus: 7
Tulos: imerkki

Sana sanassa

Tee ohjelma, joka kysyy käyttäjältä kaksi sanaa. Sitten ohjelma kertoo, onko toinen sana ensimmäisen sanan osana. Käytä ohjelmassa funktiota find.

Anna 1. sana: suppilovahvero
Anna 2. sana: ilo
Sana 'ilo' on sanan 'suppilovahvero' osana.
Anna 1. sana: suppilovahvero
Anna 2. sana: suru
Sana 'suru' ei ole sanan 'suppilovahvero' osana.

Omat funktiot

Olemme tähän mennessä ohjelmoineet ohjelmamme siten, että kaikki tapahtuu yhdessä jatkumossa ja koodia luetaan ylhäältä alas.

Edellä puhuttiin jo funktioista, ja mainittiin että "funktio tarkoittaa koodinpätkää, jota voi kutsua muualta ohjelmakoodista". Pythonin valmiita funktioita on käytetty jo oikeastaan ensimmäisestä ohjelmasta lähtien.

Valmiiden funktioiden käytön lisäksi ohjelmoija voi kirjoittaa itse funktioita joita sovellus kutsuu. Oikeastaan on hyvin poikkeuksellista jos ohjelmassa ei ole yhtään itse kirjoitettua funktiota. Tästälähtien lähes jokainen ohjelma sisältääkin itsekirjoitettuja funktioita.

Kirjoitetaan tästä eteenpäin (kunnes toisin opitaan!) funktiot lähdekoodin yläosaan, ja funktioita itse käyttävä ohjelma funktioiden alapuolelle:

# -*- coding: utf-8 -*-

# ensin funktiomäärittelyt
def hei():
    print "No heipä hei!";
    return;

# ja sitten ohjelma joka käyttää funktioita!
print "No hei.";
hei();

Ylläolevassa esimerkissä on luotu oma funktio nimeltä hei. Funktio määritellään komennolla def jonka jälkeen tulee funktion nimi, eli nimi jolla funktiota kutsutaan. Funktion nimen jälkeen tulee sulut ja kaksoispiste. Kaksoispisteen jälkeen alkaa lohko, jonka sisällä määritellään funktion toiminnallisuus.

Ylläolevassa esimerkissä funktio päätetään komennolla return, joka voidaan vapaasti suomentaa joko palaa tai palauta. Tutustumme arvon palauttaviin funktioihin hieman myöhemmin.

Kun ylläoleva esimerkki ajetaan, tulostuu seuraavanlainen teksti:

No hei.
No heipä hei!

Lähdekoodin suoritus siis alkaa funktiomäärittelyiden jälkeen. Ensin tulee vastaan komento print "No hei.", joka oikeasti kutsuu pythonin valmista tulostusfunktiota print. Kun rivi suoritetaan, tulostuu näytölle teksti "No hei.". Tämän jälkeen mennään seuraavalle riville, jossa on komento hei(). Python-tulkki näkee suluista että kyseessä on funktiokutsu ja etsii hei-nimisen funktion. Funktio hei suoritetaan, eli ajetaan komento print "No heipä hei!", jota seuraa komento return. Komento return tarkoittaa tässä tapauksessa sitä, että palataan takaisin kohtaan josta funktiota kutsuttiin. Funktiokutsun hei() jälkeen ei ole muita komentoja, ja ohjelman suoritus päättyy.

Itsekirjoitetun funktion kutsuminen on helppoa, kirjoitetaan funktion nimi ja perään sulut ja puolipiste. Seuraavassa esimerkissä kutsutaan funktiota tervehdi ensin kerran ja sen jälkeen useita kertoja.

# -*- coding: utf-8 -*-

# ensin funktiomäärittelyt
def tervehdi():
    print "Terveisiä funktiosta tervehdi!";
    return;

# ja sitten ohjelma joka käyttää funktioita!
print "Kokeillaan onnistuuko funktion kutsuminen:";
tervehdi();

print "Näyttää siltä, kokeillaan vielä:";
tervehdi();
tervehdi();
tervehdi();

Ohjelman suoritus saa aikaan seuraavan tulosteen:

Kokeillaan onnistuuko funktion kutsuminen:
Terveisiä funktiosta tervehdi!
Näyttää siltä, kokeillaan vielä:
Terveisiä funktiosta tervehdi!
Terveisiä funktiosta tervehdi!
Terveisiä funktiosta tervehdi!

Tässä siis huomionarvoista on, että koodin suoritus etenee siten, että pääohjelman rivit suoritetaan ylhäältä alas yksi kerrallaan. Koodirivin ollessa funktiokutsu, mennään suorittamaan funktion koodirivit jonka jälkeen palataan siihen kohtaan josta kutsu tapahtui, tai tarkemminottaen funktiokutsun jälkeiseen kohtaan.

Jatkossa kun esittelemme funktioita, emme erikseen mainitse että niiden täytyy sijaita omalla paikallaan. Funktioita ei esimerkiksi voi määritellä toisen funktion sisällä.

Tehtäviä

Tekstin tulostus

Tee funktio tulostaTeksti, joka tulostaa tekstin "Alussa olivat suo, kuokka ja python." sekä rivinvaihdon.

Ohjelman tulostus:

Alussa olivat suo, kuokka ja python.

Monta tulostusta

Laajenna edellistä ohjelmaasi siten, että pääohjelma kysyy käyttäjältä, montako kertaa teksti tulostetaan eli montako kertaa metodia kutsutaan.

Ohjelman tulostus:

Kuinka monta kertaa tulostetaan? 7
Alussa olivat suo, kuokka ja python.
Alussa olivat suo, kuokka ja python.
Alussa olivat suo, kuokka ja python.
Alussa olivat suo, kuokka ja python.
Alussa olivat suo, kuokka ja python.
Alussa olivat suo, kuokka ja python.
Alussa olivat suo, kuokka ja python.

Funktion parametrit

Funktiosta saa huomattavasti monikäyttöisemmän antamalla sille parametreja. Parametrit ovat muuttujia, jotka määritellään funktion ylimmällä rivillä funktion nimen jälkeen olevien sulkujen sisällä. Kun funktiota kutsutaan, sen parametreille annetaan arvot kutsuvaiheessa.

Seuraavassa esimerkissä määritellään parametrillinen funktio tervehdi, jolla on tekstityyppinen parametri nimi

def tervehdi(nimi):
  print "Hei " + nimi + ", terveiset funktiosta!";

Kutsutaan funktiota tervehdi siten, että parametrin nimi arvoksi asetetaan ensimmäisellä kutsulla Matti ja toisella kutsulla Arto.

# ensin funktiomäärittelyt
def tervehdi(nimi):
  print "Hei " + nimi + ", terveiset funktiosta!";

# sitten ohjelman toiminnallisuus
tervehdi("Matti");
tervehdi("Arto");
Hei Matti, terveiset funktiosta!
Hei Arto, terveiset funktiosta!

Aivan kuten kutsuttaessa pythonin valmista print-metodia, voi oman metodin kutsussa parametrina käyttää monimutkaisempaa ilmausta:

nimi1 = "Antti";
nimi2 = "Mikkola";
tervehdi( nimi1 + " " + nimi2 );
ika = 24;
tervehdi("Juhana " + str(ika) + " vuotta");
Hei Antti Mikkola, terveiset funktiosta!
Hei Juhana 24 vuotta, terveiset funktiosta!

Molemmissa tapauksissa funktiolla on edelleen vain 1 parametri. Parametrin arvo lasketaan ennen funktion kutsumista. Ensimmäisessä tapauksessa parametrin arvo saadaan merkkijonokatenaationa nimi1 + " " + nimi2 joka siis on arvoltaan Antti Mikkola ja jälkimmäisessä tapauksessa merkkijonokatenaatiosta "Juhana " + str(ika) + " vuotta".

Lista parametrina

Listan käyttö parametrina onnistuu ilman ongelmia:

def tulostaAlkiot(lista):
  for alkio in lista:
    print alkio;

luvut = [1, 2, 3, 4, 5];
tulostaAlkiot(luvut);
1
2
3
4
5

Huomaa että parametrin nimi funktion sisällä voi olla aivan vapaasti valittu, nimen ei tarvitse missään tapauksessa olla sama kuin kutsuvassa ohjelmassa. Edellä listaa kutsutaan metodin sisällä nimellä lista, metodin kutsuja taas näkee saman listan luvut-nimisenä.

Monta parametria

Funktiolla voidaan määritellä useampia parametreja. Tällöin metodin kutsussa parametrit annetaan samassa järjestyksessä.

def tervehdi(nimi, mistaTerveiset):
  print "Hei " + nimi + ", terveiset " + mistaTerveiset;
kuka = "Matti";
terveiset = "Kyröjoelta";

tervehdi(kuka, terveiset);
tervehdi(kuka, terveiset + " ja Kumpulasta");

Jälkimmäisessä tervehdi-funktion kutsussa toinen parametri muodostetaan katenoimalla muuttujaan terveiset teksti " ja Kumpulasta". Tämä suoritetaan ennen varsinaista funktion suoritusta.

Hei Matti, terveiset Kyröjoelta
Hei Matti, terveiset Kyröjoelta ja Kumpulasta	

Funktio kutsuu toista funktiota

Pääohjelmamme, eli tässä vaiheessa alhaalla oleva koodi, ei suinkaan ole ainoa joka voi kutsua funktioita. Funktiot voivat kutsua myös toisiaan. Tehdään funktio tervehdiMontaKertaa, joka tervehtii käyttäjää useasti funktion tervehdi avulla:

def tervehdi(nimi):
  print "Hei " + nimi + ", terveiset funktiosta!");

def tervehdiMontaKertaa(nimi, kerrat):
  for i in range(kerrat):
    tervehdi(nimi);

tervehdiMontaKertaa("Antti", 3);
print "ja";
tervehdiMontaKertaa("Martin", 2);

Tulostuu:

Hei Antti, terveiset funktiosta!
Hei Antti, terveiset funktiosta!
Hei Antti, terveiset funktiosta!
ja
Hei Martin, terveiset funktiosta!
Hei Martin, terveiset funktiosta!

Tehtäviä

Tähtien tulostus

Tee funktio tulostaTahtia, joka tulostaa annetun määrän tähtiä ja rivinvaihdon.

tulostaTahtia(5)
tulostaTahtia(3)
tulostaTahtia(9)

Ohjelman tulostus:

*****
***
*********

Viestin tulostus

Tee funktio tulostaViesti, joka tulostaa annetun viestin tähdillä reunustettuna. Käytä funktiossa apuna funktiota tulostaTahtia.

Tee ohjelma seuraavaan runkoon:

tulostaViesti("Tervetuloa!");
tulostaViesti("ABC");
tulostaViesti("Hei hei!");

Ohjelman tulostus:

***************
* Tervetuloa! *
***************
*******
* ABC *
*******
************
* Hei hei! *
************

Viestien tulostus

Tee funktio tulostaViestit, joka tulostaa listassa annetut viestit tähdillä reunustettuina. Käytä funktiossa apuna funktiota tulostaViesti.

lista = ["Tervetuloa", "ABC", "Hei hei!"];
tulostaViestit(taulukko);

Ohjelman tulostus:

***************
* Tervetuloa! *
***************
*******
* ABC *
*******
************
* Hei hei! *
************

Merkkien tulostus

Tee funktio tulostaMerkkeja, joka tulostaa annetun määrän annettuja merkkejä.

tulostaMerkkeja(5, 'A');
tulostaMerkkeja(3, 'B');
tulostaMerkkeja(9, 'C');

Ohjelman tulostus:

AAAAA
BBB
CCCCCCCCC

Funktiot ja muuttujien näkyvyys

Yritetään muuttaa funktion sisältä pääohjelman muuttujan arvoa.

def kasvataKolmella():
  x = x + 3;

x = 1;
kasvataKolmella();

print x;

Ohjelma ei kuitenkaan toimi, sillä funktio ei näe muuttujaa x.

Yleisemminkin voi todeta, että pääohjelman muuttujat eivät suoraan näy muuttujat eivät näy funktioiden sisään, ja funktioiden muuttujat eivät näy muille funktioille tai pääohjelmalle. Yleisesti käytetty keino tiedon viemiseen funktioon on funktiolle annettavan parametrin käyttö.

Python mahdollistaa funktioiden määrittelyn siten, että ne näkevät funktioiden ulkopuolella määritellyt muuttujat avainsanan global avulla. Seuraavassa yllä esitetty esimerkki uudestaan, tällä kertaa globaalia muuttujaa käyttäen.

def kasvataKolmella():
  global x;
  x = x + 3;

x = 1;
kasvataKolmella();

print x;
4

Globaalien muuttujien käyttöä ohjelmissa kannattaa kuitenkin välttää, ja tyytyä tässä vaiheessa palauttamaan yksi arvo funktiota kohti.

Funktioiden paluuarvot

Funktio voi myös palauttaa arvon. Edellä olevissa esimerkeissä funktiot eivät palauttaneet mitään arvoa. Tämä on merkitty joko jättämällä return-lause pois funktiomäärittelystä, tai jättämällä return omalle rivilleen.

def tervehdi():
  print "Heippa vaan!";
  return;

Arvon palauttavaa funktiota määriteltäessä annetaan funktiossa olevalle return-komennolle palautettava muuttuja. Seuraavassa funktio joka palauttaa aina luvun 10.p>

def palautetaanAinaKymppi():
  return 10;

Jotta funktion kutsuja voisi käyttää paluuarvoa, tulee paluuarvo ottaa talteen muuttujaan:

luku = palautetaanAinaKymppi();

print "metodi palautti luvun " + str(luku);

Funktion paluuarvo siis sijoitetaan muuttujaan aivan kuin mikä tahansa muukin arvo. Paluuarvo voi toimia myös osana mitä tahansa lauseketta:

luku = 4 * palautetaanAinaKymppi() + palautetaanAinaKymppi() / 2 - 8;

print "laskutoimituksen tulos " + str(luku);

Seuraavassa esimerkissä määritellään funktio summan laskemiseen. Tämän jälkeen funktiota käytetään laskemaan luvut 2 ja 7 yhteen, funktiokutsusta saatava paluuarvo asetetaan muuttujaan lukujenSumma.

def summa(eka, toka):
  return eka + toka;

lukujenSumma = summa(2, 7);
print "Lukujen 2 ja 7 summa on " + str(lukujenSumma);

Laajennetaan edellistä esimerkkiä siten, että käyttäjä syöttää luvut.

def summa(eka, toka):
  return eka + toka;


eka = int(raw_input("Anna ensimmäinen luku: "));
toka = int(raw_input("Anna toinen luku: "));

print "Luvut ovat yhteensä: " + str(summa(eka,toka));

Kuten huomataan, funktion paluuarvoa ei tarvitse välttämättä sijoittaa muuttujaan, se voi olla osana tulostuslausetta aivan kuten mikä tahansa muukin arvo. Kannattaa kuitenkin huomata, että merkkijonoon liitettäessä kokonaislukumuuttujat tulee muuttaa myös teksti- tai merkkijonotyyppisiksi funktiokutsulla str.

Seuraavassa esimerkissä funktiota summa kutsutaan kokonaisluvuilla, jotka saadaan summa-metodin paluuarvoina.

def summa(eka, toka):
  return eka + toka;

a = 3;
b = 2;

summienSumma = summa(summa(1,2), summa(a,b));
# 1) suoritetaan sisemmät metodit:
#    summa(1,2) = 3   ja summa(a,b) = 5
# 2) suoritetaan ulompi metodi:
#    summa(3,5) = 8 

print summienSumma;
8

Funktion omat muuttujat

Seuraava funktio laskee syötteinään saamiensa luvun keskiarvon. Metodi käyttää apumuuttujia summa ja ka. Metodin sisäisen muuttujan määrittely tapahtuu tutulla tavalla.

def keskiarvo(luku1, luku2, luku3):
  summa = luku1 + luku2 + luku3;
  ka = summa/3.0;

  return ka;

Tehtäviä

Lukujen summa

Tee funktio summa, joka laskee listan lukujen summan.

Tee metodi seuraavaan runkoon:

lista = [3, 2, 7, 2]
print "Summa: " + str(summa(lista))

Ohjelman tulostus:

Summa: 14

Lukujen keskiarvo

Tee funktio keskiarvo, joka laskee listan lukujen keskiarvon. Funktiossa kannattaa käyttää apuna funktiota summa.

lista = [3, 2, 7, 2]
print "Keskiarvo: " + str(keskiarvo(lista))

Ohjelman tulostus:

Keskiarvo: 3.5

Pienin

Tee funktio pienin, joka palauttaa listan pienimmän luvun.

lista = [3, 2, 7, 2];
print "Pienin: " + str(pienin(lista))

Ohjelman tulostus:

Pienin: 2

Suurin

Tee funktio suurin, joka palauttaa listan suurimman luvun.

lista = [3, 2, 7, 2];
print "Suurin: " + str(suurin(lista))

Ohjelman tulostus:

Suurin: 7

Lista paluuarvona

Funktiot voivat yksittäisten arvojen lisäksi palauttaa myös muunmuassa listoja. Esimerkiksi merkkijonoja sisältävän listan palauttava metodi voisi olla seuraavannäköinen.

def annaSeitsemanKaapiota():
  kaapiot = ["Viisas", "Jörö", "Lystikäs", "Unelias", "Ujo", "Nuhanenä", "Vilkas"];
  return kaapiot;

for kaapio in annaSeitsemanKaapiota():
  print kaapio;
Viisas
Jörö
Lystikäs
Unelias
Ujo
Nuhanenä
Vilkas

Uudelleenkäytettävyys

Funktiosta saadaan uudelleenkäytettävä siten, että se ei ota kantaa ohjelmaan jossa sitä käytetään. Tällöin toimivaa ja testattua funktiota voidaan uudelleenkäyttää missä ohjelmassa tahansa.

Seuraavassa teemme funktion, joka käy parametrinään saadun listan läpi ja palauttaa True jos kaikki arvot olivat positiivisia.

def onkoKokonaanPositiivinen(lukuLista):
  kokonaanPositiivinen = True;

  for arvo in lukuLista:
    if arvo < 0:
      kokonaanPositiivinen = False;

  return kokonaanPositiivinen;

lokerikko = [1, 3, -1, 3];

if onkoKokonaanPositiivinen(lokerikko):
  print "Lokerikko on kokonaan positiivinen";
else:
  print "Lokerikko ei ole kokonaan positiivinen";

return

Kun metodissa suoritetaan return-käsky, niin metodin suoritus loppuu välittömästi. Käyttämällä tätä tietoa hyväksi voimme kirjoittaa edellisen onkoKokonaanPositiivinen-metodin hiukan suoraviivaisemmin (ja ehkä selkeämmin).

def onkoKokonaanPositiivinen(lukuLista):
  for arvo in lukuLista:
    if arvo < 0:
      return False;

  return True;

Tehtäviä

Onko luku listassa

Tee funktio onkoLukuListassa, joka saa parametrinaan listan ja luvun. Jos luku on taulukossa, funktio palauttaa True ja muulloin False.

Ohjelman rakenne on seuraava:

def onkoLukuListassa(lista, luku):
  # kirjoita koodia tähän


# itse ohjelma
luvut = [1, 3, 5, 7];

luku = int(raw_input("Anna luku: "))
if onkoLukuListassa(luvut, luku):
  print str(luku) + " on listassa."
else:
  print str(luku) + " ei ole listassa." 
Anna luku: 3
Luku on listassa.
Anna luku: 4
Luku ei ole listassa.

Lukujen kysyminen

Tee funktio kysyLuvut, joka kysyy käyttäjältä lukuja ja palauttaa ne listana. Liitä ohjelmaan mukaan aiemmin tehdyt funktiot summa ja keskiarvo.

def kysyLuvut():
  # kysy täällä käyttäjältä lukuja

# pääohjelman hahmotelma
luvut = kysyLuvut()
print "Summa: " + str(summa(luvut))
print "Keskiarvo: " + str(keskiarvo(luvut))
Anna luvut:
5
2
11
Summa: 18
Keskiarvo: 6.0

Arvauspeli

Tee yksinkertainen arvauspeli. Pelin toiminta alkaa sillä, että käyttäjältä kysytään arvattavat luvut:

Anna luvut:
5
2
11
Kiitos. Siirrytään arvausvaiheeseen.

Kun luvut on syötetty, tulostetaan 40 rivinvaihtoa kutsumalla komentoa print "" 40 kertaa.

Tämän jälkeen käyttäjälle (joka on kenties eri ihminen kun lukujen syöttäjä) annetaan 3 arvausta. Arvausten jälkeen ilmoitetaan, kuinka monta arvauksista oli oikein.

Arvaa mitä lukuja taulukossa on.
Saat kolme arvausta.
4
99
2
Kiitos arvauksista. 1 oikein.

Tee peli käyttäen kahden edellisen tehtävän funktioita. Pääohjelma muotoutuu seuraavaan tapaan:

luvut = kysyLuvut()

# tulosta tyhjää

oikeatArvaukset = 0
for i in range(3):
  # lue arvaus käyttäjältä
  # tarkasta onko luku listassa käyttäen aiemmin toteutettua funktiota
  # jos on, päivitä oikeat arvaukset

# tulosta oikeuden arvausten määrä

Arvauspeli versio 2.0

Laajennetaan edellistä tehtävää niin, että pelaajalla on rajaton määrä arvauksia. Kun käyttäjä antaa syötteenä -99, loppuu peli ja käyttäjälle ilmoitetaan arvausten lukumäärä ja oikeiden arvausten lukumäärä. Peli etenee seuraavasti:

...

Arvaa mitä lukuja taulukossa on.
Kun kyllästyt, syötä -99.
4
7
2
100
11
-99
Arvauksia oli 5, joista oikein 2.

Arvauspeli versio 3.0

Laajennetaan edellistä tehtävää siten, että ohjelma kysyy pelin jälkeen, haluaako käyttäjä pelata uudestaan. Lopuksi ohjelma ilmoittaa, kuinka monta peliä pelattiin yhteensä.

...

Arvaa mitä lukuja taulukossa on. Kun kyllästyt, syötä -99.
4
7
2
100
11
-99
Arvauksia oli 5, joista oikein 2.

Uusi peli (k/e)? k

Arvaa mitä lukuja taulukossa on. Kun kyllästyt, syötä -99.
-1
5
2
-99
Arvauksia oli 3, joista oikein 0.

Uusi peli (k/e)? e

Pelaajia tänään 2.

Funktiot ja parametrien kopioituminen

Tarkastellaan vielä muutamaa funktioihin liittyvää tärkeää yksityiskohtaa.

Hieman aiemmin materiaalissa oli esimerkki, jossa yritetään muuttaa funktion sisältä pääohjelman muuttujan arvoa:

def kasvataKolmella():
  x = x + 3;

x = 1;
kasvataKolmella();
print "Muuttujassa x on arvo " + str(x);

Ohjelma ei kuitenkaan toimi, sillä funktio ei näe pääohjelman muuttujaa x.

Tämä johtuu siitä, että pääohjelman muuttujat eivät näy funktioiden sisään. Ja yleisemmin: minkään funktion sisäiset muuttujat eivät näy muille funktioille. Ainoa hyvä keino viedä funktiolle tietoa ulkopuolelta on parametrin avulla.

Yritetään korjata edellinen esimerkki ja välitetään pääohjelman muuttuja x funktiolle parametrina.

def kasvataKolmella(x):
  x = x + 3;

x = 1;
kasvataKolmella(x);
print "Muuttujassa x on arvo " + str(x);

Ohjelma ei toimi ihan odotetulla tavalla. Funktiossa olevat parametrit ovat eri muuttujia kuin "pääohjelmassa" esitellyt muuttujat. Edellä funktio kasvataKolmella siis kasvattaa samannimistä, mutta ei samaa! muuttujaa x.

Kun funktiolle annetaan parametri, sen arvo kopioituu funktiossa käytettäväksi. Yllä olevassa esimerkissä funktiolle kasvataKolmella annetusta muuttujasta x luodaan funktiota varten kopio, jonka arvoa muokataan funktion sisällä. Alkuperäiselle muuttujalle x ei siis tehdä mitään.

Funktiosta saa toki välitettyä tietoa kutsujalle käyttäen paluuarvoa, eli "returnaamalla" halutun arvon. Edellinen saadaan toimimaan muuttamalla koodia hiukan:

def kasvataKolmella(x):
  x = x + 3;
  return x;

x = 1;
x = kasvataKolmella(x);
print "Muuttujassa x on arvo " + str(x);

Edelleen on niin, että funktio käsittelee pääohjelman x:n arvon kopiota. Pääohjelma sijoittaa funktion palauttaman arvon muuttujaan x, joten muutos tulee tämän takia voimaan myös pääohjelmassa. huomaa, että edellisessä ei ole mitään merkitystä sillä mikä on parametrin nimi metodissa. Koodi toimii täysin samoin oli nimi mikä tahansa, esim.

def kasvataKolmella(kasvatettavaLuku):
  kasvatettavaLuku = kasvatettavaLuku + 3;
  return kasvatettavaLuku;

x = 1;
x = kasvataKolmella(x);
print "Muuttujassa x on arvo " + str(x);

Eli todettiin, että funktion sisällä olevat parametrit ovat eri muuttujia kuin funktiolle kutsuttaessa annettavat parametrit. Ainoastaan parametrin arvo kopioituu kutsujasta funktioon.

Asia ei kuitenkaan ole ihan näin yksinkertainen. Jos metodille annetaan parametrina lista, käy niin että sama lista näkyy metodille ja kaikki metodin listaan tekemät muutokset tulevat kaikkialla voimaan.

def asetaNolliksi(lukulista):
  for i in range(len(lukulista)):
    lukulista[i] = 0;
# -*- coding: utf-8 -*-
def asetaNolliksi(lukulista):
  for i in range(len(lukulista)):
    lukulista[i] = 0;


luvut = [1, 2, 3, 4, 5];

print "Tulostetaan luvut ennen funktiokutsua:";
for luku in luvut:
  print luku;

asetaNolliksi(luvut);

print "Tulostetaan luvut funktiokutsun jälkeen:";
for luku in luvut:
  print luku;

Tulostetaan luvut ennen funktiokutsua: 1 2 3 4 5 Tulostetaan luvut funktiokutsun jälkeen: 0 0 0 0 0

Eli toisin kuin kokonaislukutyyppinen parametri, lista ei kopioidu vaan funktio käsittelee suoraan parametrina annettua listaa. Toistaiseksi riittää tietää, että arvon kopioituminen tapahtuu muuntyyppisille parametreille kuin listoille.

Toisin kuin kokonaislukutyyppinen muuttuja, lista ei sijaitsekaan samalla tapaa "lokerossa", vaan listan nimi, eli ylläolevassa esimerkissä luvut onkin ainoastaan viite paikkaan missä lista oikeasti sijaitsee. Yksi tapa ajatella asiaa, on että lista on "langan päässä", eli listamuuttujan nimi luvut on lanka jonka päästä lista löytyy. Kun funktiokutsun parametrina on lista, käykin niin että funktiolle annetaan lanka jonka päässä on sama lista jonka funktion kutsuja näkee.

Pääohjelmassa ja funktiossa metodilla on oma lanka, eli oma muuttuja joka kuvaa listaa, mutta langan päässä on sama lista ja kaikki muutokset mitä funktio tekee listaan tapahtuvat täsmälleen samaan listaan jota pääohjelma käyttää. Huomaamme myöhemmin että hyvin moni asia on oikeasti "langan päässä".

Huomaa jälleen että parametrin nimi funktion sisällä voi olla aivan vapaasti valittu, nimen ei tarvitse missään tapauksessa olla sama kuin kutsuvassa. Edellä listaa kutsutaan funktion sisällä nimellä lukulista, funktion kutsuja taas näkee saman listan luvut-nimisenä.

Ohjeita koodin kirjoittamiseen ja ongelmanratkaisuun

Yksi maailman johtavista ohjelmistonkehittäjistä Kent Beck on lausunut mm. seuraavasti:

Yritämme jatkossa toimia näiden ohjeiden mukaan. Aletaan ottamaan nyt ensimmäisiä askelia Kent Beckin viitoittamalla tiellä.

Hengittävä koodi

Koodia joka on kirjoitettu ilman välejä on ikävä lukea:

t = [1, 2, 3, 4, 5];
for luku in t:
  print luku;
print "";
for i in range(len(t)):
  t[i] = t[i] + 1;
for luku in t:
  print luku;
print "";

Erotellaan loogiset kokonaisuudet rivinvaihdoin:

t = [1, 2, 3, 4, 5];

for luku in t:
  print luku;
print "";

for i in range(len(t)):
  t[i] = t[i] + 1;

for luku in t:
  print luku;
print "";

Nyt koodissa alkaa olla jo järkeä. Esim. listan tulostus ja listan alkioiden arvojen kasvatus ovat omia loogisia kokonaisuuksia, joten ne on erotettu rivinvaihdolla. Koodissa on mukavaa ilmavuutta ja koneen lisäksi ihmisen alkaa jo olla miellyttävämpi lukea koodia.

Copy pasten eliminointi metodeilla

Ohjelmoijan lähes pahin mahdollinen perisynti on copy paste -koodi, eli samanlaisen koodinpätkän toistuminen koodissa useaan kertaan. Esimerkissämme listan tulostus tapahtuu kahteen kertaan. Tulostuksen hoitava koodi on syytä erottaa omaksi funktiokseen ja laittaa pääohjelma kutsumaan luotua funktiota.

def tulostaLista(lista):
  for luku in lista:
    print luku;
  print "";

t = [1, 2, 3, 4, 5];

tulostaLista(t);

for i in range(len(t)):
  t[i] = t[i] + 1;

tulostaLista(t);

Erillisten tehtävien erottaminen omiksi selkeästi nimetyiksi funktioiksi

Nyt koodi alkaa olla jo selkeämpää. Selvästi erillinen kokonaisuus, eli listan tulostus on oma helposti ymmärrettävä funktionsa. myös itse ohjelman luettavuus on kasvanut. Huomaa myös, että funktio on nimetty mahdollisimman kuvaavasti, eli siten että funktion nimi sanoo sen mitä funktio tekee.

Ohjelmassa on kuitenkin vielä hiukan siistimisen varaa. Pääohjelma on vielä sikäli ikävä, että siistien funktiokutsujen seassa on vielä suoraan listaa käsittelevä "epäesteettinen" koodinpätkä. Erotetaan tämäkin omaksi funktiokseen:

def tulostaLista(lista):
  for luku in lista:
    print luku;
  print "";

def kasvataListanAlkioitaYhdella(lista):
  for i in range(len(lista)):
    lista[i] = lista[i] + 1;

t = [1, 2, 3, 4, 5];

tulostaLista(t);
kasvataListanAlkioitaYhdella(t);
tulostaLista(t);

Eli vaikka copy pastea ei ollutkaan, loimme loogiselle kokonaisuudelle (listan arvojen kasvattaminen yhdellä) oman kuvaavasti nimetyn funktion. Lopputuloksena oleva pääohjelma on nyt erittäin ymmärrettävä, lähes suomen kieltä. Molemmat metodit ovat myös erittäin yksinkertaisia ja selkeitä ymmärtää.

Kuvaava muuttujien nimentä

Esimerkkikoodissamme on vielä hyvin epämääräinen muuttuja t. Korvataan se kuvaavammalla muuttujannimellä lista.

def tulostaLista(lista):
  for luku in lista:
    print luku;
  print "";

def kasvataListanAlkioitaYhdella(lista):
  for i in range(len(lista)):
    lista[i] = lista[i] + 1;

luvut = [1, 2, 3, 4, 5];

tulostaLista(luvut);
kasvataListanAlkioitaYhdella(luvut);
tulostaLista(luvut);

Koodin yleistäminen

Abstrahoimalla eli yleistämällä koodia hiukan lisää voisimme muokata funktion kasvataListanAlkioitaYhdella ottamaan toisena parametrinaan kasvatusmäärän. Funktiot ja ohjelma näyttäisivät tällöin seuraavalta:

def tulostaLista(lista):
  for luku in lista:
    print luku;
  print "";

def kasvataListanAlkioita(lista, maara):
  for i in range(len(lista)):
    lista[i] = lista[i] + maara;

luvut = [1, 2, 3, 4, 5];

tulostaLista(luvut);
kasvataListanAlkioita(luvut, 1);
tulostaLista(luvut);

Kent Beck olisi varmaan tyytyväinen aikaansaannokseemme, koodi on helposti ymmärrettävää, helposti muokattavaa eikä sisällä copy pastea.

Ongelman ratkaiseminen paloittain

Tarkastellaan seuraavaa tehtäväkuvausta:

Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa saman sanan uudestaan. Voit olettaa, että käyttäjä antaa korkeintaan 1000 sanaa.

Anna sana: porkkana
Anna sana: selleri
Anna sana: nauris
Anna sana: lanttu
Anna sana: selleri

Annoit saman sanan uudestaan!

Useasti tehtävää ratkottaessa ohjelmoija ei oikein tiedä miten lähestyä tehtävää, eli miten jäsentää ongelma ja mistä aloittaa.

Ongelma koostuu oikeastaan kahdesta "aliongelmasta". Ensimmäinen on sanojen toistuva lukeminen käyttäjältä kunnes tietty ehto toteutuu. Tämä voitaisiin hahmotella seuraavaan tapaan ohjelmarungoksi:

while True:
  sana = raw_input("Anna sana: ");

  if "pitää lopettaa":
    break;

print "Annoit saman sanan uudestaan";

Sanojen kysely pitää lopettaa siinä vaiheessa kun on syötetty jokin jo aiemmin syötetty sana. Päätetään tehdä funktio, joka huomaa että sana on jo syötetty. Vielä ei tiedetä miten funktio kannattaisi tehdä, joten tehdään siitä vasta runko:

def onJoSyotetty(sana):
  return True;

while True:
  sana = raw_input("Anna sana: ");

  if onJoSyotetty(sana):
    break;

print "Annoit saman sanan uudestaan";

Ohjelmaa on hyvä testata koko ajan, joten tehdään funktiosta kokeiluversio:

def onJoSyotetty(sana):
  if sana == "loppu":
    return True;

  return False;


while True:
  sana = raw_input("Anna sana: ");

  if onJoSyotetty(sana):
    break;

print "Annoit saman sanan uudestaan";

Nyt jatkuu nyt niin kauan kunnes syötteenä on sana loppu:

Anna sana: porkkana
Anna sana: selleri
Anna sana: nauris
Anna sana: lanttu
Anna sana: loppu
Annoit saman sanan uudestaan!

Ohjelma ei siis toimi vielä kokonaisuudessaan, mutta ensimmäinen osaongelma eli ohjelman pysäyttäminen kun tietty ehto toteutuu on saatu toimimaan.

Toinen osaongelma on aiemmin syötettyjen sanojen muistaminen. Lista sopii tietysti mainiosti tähän tarkoitukseen.

Kun uusi sana syötetään, on se lisättävä syötettyjen sanojen joukkoon. Tämä tapahtuu kutsumalla lista-muuttujan append-funktiota.

def onJoSyotetty(sana):
  if sana == "loppu":
    return True;

  return False;


sanat = []; # luodaan tyhjä lista

while True:
  sana = raw_input("Anna sana: ");

  if onJoSyotetty(sana):
    break;

  sanat.append(sana);

print "Annoit saman sanan uudestaan";

Jälleen kannattaa testata, että ohjelma toimii edelleen. Voi olla hyödyksi esim. lisätä ohjelman loppuun testitulostus, joka varmistaa että syötetyt sanat todella menivät listaan:

def onJoSyotetty(sana):
  if sana == "loppu":
    return True;

  return False;


sanat = []; # luodaan tyhjä lista

while True:
  sana = raw_input("Anna sana: ");

  if onJoSyotetty(sana):
    break;

  sanat.append(sana);

print "Annoit saman sanan uudestaan";
print sanat; # voimme tulostaa listan sisällön yksinkertaisella print-kutsulla

Muokataan aiemmin tekemämme funktio onJoSyotetty tutkimaan onko kysytty sana jo syötettyjen joukossa, eli listassa:

def onJoSyotetty(sana, joSyotetyt):
  for syotetty in joSyotetyt:
    if sana == syotetty:
      return True;

  return False;

Yllä oleva funktio toimii toivotusti. Huomaamme kuitenkin että teemme ylimääräistä työtä. Listarakenteella on olemassa myös operaattori in, jonka avulla voimme tutkia onko alkio listassa.

def onJoSyotetty(sana, joSyotetyt):
  if sana in joSyotetyt:
    return True;

  return False;

Ohjelma vielä kokonaisuudessaan, muutetaan myös kohtaa jossa funktiota onJoSyotetty kutsutaan.

def onJoSyotetty(sana, joSyotetyt):
  if sana in joSyotetyt:
    return True;

  return False;


sanat = []; # luodaan tyhjä lista

while True:
  sana = raw_input("Anna sana: ");

  if onJoSyotetty(sana, sanat):
    break;

  sanat.append(sana);

print "Annoit saman sanan uudestaan";

Nyt koodi on valmis ja alkaa olla kohtuullisen luettava.

Eli ohjelmoidessasi, seuraa aina näitä neuvoja:

Hyvät ja kokeneet ohjelmoijat noudattavat näitä käytänteitä sen takia että ohjelmointi olisi helpompaa, ja että ohjelmien lukeminen, ylläpitäminen ja muokkaaminen olisi helpompaa.

Tehtäviä

Tässä tehtäväsarjassa tehtävän ohjelman käytössä on aineisto erään kurssin opiskelijoiden pistemääristä:

pisteet = [41, 44, 25, 31, 52, 48, 40, 46, 38, 39, 34, 32, 36, 55, 59, 42, 57, 38, 24, 50];

Tämä tehtäväsarja on kokonaisuus, joten tee tehtävien funktiot saman ohjelman sisään.

Pisteet arvosanaksi

Ohjelmoi funktio laskeArvosana, joka laskee arvosanan sille annetun pistemäärän perusteella.

pistemääräarvosana
0–290
30–341
35–392
40–443
45–494
50–605

Ohjelmamme runko on tällä hetkellä seuraava:

def laskeArvosana(pisteet):
  # kirjoita koodia tähän


# pääohjelma
pisteet = [41, 44, 25, 31, 52, 48, 40, 46, 38, 39, 34, 32, 36, 55, 59, 42, 57, 38, 24, 50];
for pistemaara in pisteet:
  print "Pistemäärä" + str(pistemaara) + " antaa arvosanan " + str(laskeArvosana(pistemaara))

Ohjelman tulostuksen tulisi olla seuraava:

pistemäärä 41 antaa arvosanan 3
pistemäärä 44 antaa arvosanan 3
pistemäärä 25 antaa arvosanan 0
pistemäärä 31 antaa arvosanan 1
(paljon rivejä välissä)
pistemäärä 50 antaa arvosanan 5

Arvosanajakauma listaan

Muuta edellistä ohjelmaa niin, että eri arvosanojen lukumäärät tallennetaan listaan arvosanat. Ideana on, että listassa arvosanat on 6 kohtaa, yksi jokaiselle arvosanalle. Esimerkiksi arvosanat[3] kertoo, kuinka moni opiskelija on saanut arvosanan 3.

Ohjelmaan tulee kaksi uutta funktiota: Funktiolle selvitaArvosanat annetaan opiskelijoiden pistemäärät listassa ja funktio palauttaa toisen listan, joka sisältää arvosanojen lukumäärät. Funktion tulostaArvosanat toteutus on annettu.

Ota tästä mallia ohjelman toteutukseen:

def selvitaArvosanat(pisteet):
  arvosanat = [0, 0, 0, 0, 0, 0]
  # kirjoita koodia tähän

  return arvosanat;

def tulostaArvosanat(arvosanat):
  for numero in range(5):
    print str(numero) + ": " + str(arvosanat[numero]) + " kpl";

# pääohjelma
pisteet = [41, 44, 25, 31, 52, 48, 40, 46, 38, 39, 34, 32, 36, 55, 59, 42, 57, 38, 24, 50];
arvosanat = selvitaArvosanat(pisteet)
tulostaArvosanat(arvosanat)

Ohjelman tulostuksen tulisi olla seuraava:

0: 2 kpl
1: 3 kpl
2: 4 kpl
3: 4 kpl
4: 2 kpl
5: 5 kpl

Arvosanajakauma tähtinä

Liitä ohjelmaan mukaan aiemmin toteuttamasi funktio tulostaTahtia, joka tulostaa annetun määrän tähtiä. Nyt ohjelman tulostuksen tulisi olla seuraava:

5: *****
4: **
3: ****
2: ****
1: ***
0: **

Hyväksymisprosentti

Liitä ohjelmaan mukaan funktio hyvaksymisprosentti, joka laskee parametrina annetun arvosanajakauman perusteella hyväksymisprosentin (kuinka moni sai arvosanan 1–5).

Pääohjelman lopullinen muoto on seuraava:

pisteet = [41, 44, 25, 31, 52, 48, 40, 46, 38, 39, 34, 32, 36, 55, 59, 42, 57, 38, 24, 50];
arvosanat = selvitaArvosanat(pisteet)
tulostaArvosanat(arvosanat)
print "Hyväksymisprosentti: " + str(hyvaksymisprosentti(arvosanat)) + " %"

Nyt ohjelman tulostuksen tulisi olla seuraava:

5: *****
4: **
3: ****
2: ****
1: ***
0: **
Hyväksymisprosentti: 90.0 %

Tässä tapauksessa opiskelijoita oli kaikkiaan 20 ja arvosanan 1–5 sai 18 opiskelijaa, joten hyväksymisprosentti oli (18/20)*100 = 90.

Huomaa miten tässä monimutkainen ongelma ratkottiin pienempinä osaongelmina, jotka saatettiin yksi kerrallaan toimintakuntoon. Ohjelmointiongelmia kannattaa useimmiten lähestyä juuri tähän tyyliin!

Listan järjestäminen

Hyvin usein törmätään tilanteeseen, jossa lista lukuja tai merkkijonoja on järjestettävä suuruusjärjestykseen. Aiemmin esiintyneessä tehtävässä neuvottiin, että Pythonin valmiin kaluston avulla lista saadaan järjestettyä seuraavasti:

# -*- coding: utf-8 -*-
lista = [];
# laitetaan listaan lukuja
lista.append(2);
lista.append(7);
lista.append(1);
lista.append(6);

# tulostetaan lista
print lista; # tulostus: [2, 7, 1, 6]

# järjestetään lista
lista.sort();

# tulostetaan lista
print lista; # tulostus: [1, 2, 6, 7]

Listan järjestäminen on siis helppoa, ja valmiin kaluston käyttö riittää. Ohjelmoijan yleissivistykseen kuuluu yleensä ainakin yhden järjestämisalgoritmin (eli tavan järjestää lista) tuntemus. Tällä kurssilla tutustutaan yhteen "klassiseen" järjestämisalgoritmiin, valintajärjestämiseen. Tutustuminen tapahtuu tekemällä sarja harjoitustehtäviä.

Tehtäviä

Tämä tehtäväsarja on kokonaisuus, joten tee tehtävien funktiot saman ohjelman sisään.

Pienimmän indeksi

Tee funktio pienimmanIndeksi, joka palauttaa listan pienimmän luvun indeksin (eli luvun kohdan listassa).

Seuraava koodi esittelee funktion toimintaa:

#  indeksit:   0  1  2  3  4
luvut =       [3, 2, 5, 4, 8];
print "Pienimmän indeksi: " + str(pienimmanIndeksi(luvut))
Pienimmän indeksi: 1

Listan pienin luku on 2, ja sen indeksi on 1. Muistathan, että listan numerointi alkaa 0:sta.

Pienimmän indeksi loppuosassa

Tee funktio pienimmanIndeksiAlkaen, joka toimii samalla tavalla kuin edellisen tehtävän funktio mutta ottaa huomioon vain listan loppuosan. Funktiolle annetaan taulukon lisäksi indeksi, josta lähtien pienintä lukua etsitään.

Metodin runko on seuraava:

def pienimmanIndeksiAlkaen(lista, alkuIndeksi):
  # kirjoita koodia tähän

Seuraava koodi esittelee funktion toimintaa:

#  indeksit:   0  1  2  3  4
luvut =       [3, 2, 5, 4, 8];

print pienimmanIndeksiAlkaen(luvut, 1)
print pienimmanIndeksiAlkaen(luvut, 2)
print pienimmanIndeksiAlkaen(luvut, 4)
1
3
4	

Esimerkissä ensimmäinen funktiokutsu etsii pienimmän luvun indeksistä 1 aloittaen. Tällöin pienin luku on 2, jonka indeksi on 1. Vastaavasti toinen funktiokutsu etsii pienimmän luvun indeksistä 2 aloittaen. Tällöin pienin luku on 4, jonka indeksi on 3.

Taulukon tulostaminen

Usein on tarvetta tarkistaa, mitä lukuja listassa on. Tehdään sitä varten funktio tulostaLista, joka tulostaa listan luvut.

Seuraava koodi esittelee metodin toimintaa:

luvut = [3, 2, 5, 4, 8];
tulostaLista(luvut);
3, 2, 5, 4, 8

Lukujen vaihtaminen

Tee funktio vaihda, jolle annetaan lista ja kaksi sen indeksiä. Funktio vaihtaa indekseissä olevat luvut keskenään.

Funktion runko on seuraava:

def vaihda(lista, ekaIndeksi, tokaIndeksi):
  # kirjoita vaihtamiskoodi tähän

Seuraava koodi esittelee funktion toimintaa:

luvut = [3, 2, 5, 4, 8]

tulostaLista(luvut);

vaihda(luvut, 1, 0);
tulostaLista(luvut);

vaihda(luvut, 0, 3);
tulostaLista(luvut);
3, 2, 5, 4, 8
2, 3, 5, 4, 8
4, 3, 5, 2, 8

Järjestäminen

Nyt koossa on joukko hyödyllisiä funktioita, joiden avulla voimme toteuttaa järjestämisalgoritmin nimeltä vaihtojärjestäminen.

Vaihtojärjestämisen idea on seuraava:

Toisin sanoen:

Toteuta funktio jarjesta, joka perustuu yllä olevaan ideaan. Metodissa on syytä olla silmukka, joka käy läpi listan indeksejä. Funktioista pieninIndeksiAlkaen ja vaihda on varmasti hyötyä. Tulosta myös listan sisältö ennen järjestämistä ja jokaisen kierroksen jälkeen, jotta voit varmistaa algoritmin toimivan oikein.

Testaa funktion toimintaa ainakin seuraavalla esimerkillä:

luvut = [8, 3, 7, 9, 1, 2, 4]
jarjesta(luvut)

Ohjelman tulosteen tulisi olla seuraavanlainen:

8, 3, 7, 9, 1, 2, 4
1, 3, 7, 9, 8, 2, 4
1, 2, 7, 9, 8, 3, 4
1, 2, 3, 9, 8, 7, 4
1, 2, 3, 4, 8, 7, 9
1, 2, 3, 4, 7, 8, 9
1, 2, 3, 4, 7, 8, 9

Huomaa, miten taulukko tulee pikkuhiljaa järjestykseen alkaen alusta ja edeten loppua kohti.

Binäärihaku

Tämä kappale toimii johdatuksena binäärihaun ideaan. Tavoitteena on tehdä tekoäly, joka arvaa käyttäjän valitseman luvun. Tekoäly olettaa, että luku on välillä 1...n, missä ohjelma kysyy aluksi käyttäjältä luvun n. Tekoäly kysyy käyttäjältä kysymyksiä muotoa "Onko lukusi suurempi kuin X?" ja päättelee oikean luvun käyttäjän vastausten perusteella.

Tekoäly saa selville nopeasti käyttäjän luvun, jos se pitää muistissa pienintä ja suurinta mahdollista lukua. Tekoäly kysyy aina, onko käyttäjän luku suurempi kuin näiden lukujen keskiarvo, jolloin joka kysymyksen jälkeen mahdollisten lukujen määrä puolittuu. Lopulta pienin ja suurin luku ovat samat, jolloin käyttäjän luku on paljastunut.

Seuraavassa esimerkissä käyttäjä valitsee luvun 44:

Anna suurin mahdollinen luku: 100
Valitse jokin luku väliltä 1...100.
Esitän sinulle seuraavaksi sarjan kysymyksiä. Vastaa niihin rehellisesti.
Onko lukusi suurempi kuin 50? (k/e)
e
Onko lukusi suurempi kuin 25? (k/e)
k
Onko lukusi suurempi kuin 38? (k/e)
k
Onko lukusi suurempi kuin 44? (k/e)
e
Onko lukusi suurempi kuin 41? (k/e)
k
Onko lukusi suurempi kuin 43? (k/e)
k
Valitsemasi luku on 44.

Yllä olevassa esimerkissä mahdollinen lukualue on aluksi 1...100. Kun käyttäjä kertoo, että luku ei ole yli 50, mahdollinen lukualue on 1...50. Kun käyttäjä kertoo, että luku on yli 25, mahdollinen lukualue on 26...50. Samanlainen päättely jatkuu, kunnes saavutaan lukuun 44.

Puolitus- eli binäärihaun mukaisesti tässä puolitetaan mahdollinen hakualue jokaisella kysymyksellä, jolloin kysymyksiä tarvitaan vähän. Jopa lukuvälillä 1..1000000 pitäisi kulua korkeintaan 20 kysymystä. Kannattanee kirjoittaa varsinainen arvuuttelupeli omaan def arvausPeli(pieninluku, suurinluku)-funktioonsa.

Vihje: Tee totuusarvon (True tai False) palauttava funktio, joka esittää käyttäjälle sopivan kysymyksen ja lukee käyttäjän vastauksen. Jos käyttäjän antama vastaus oli sopiva, niin metodi palauttaa true ja muulloin false.

Binäärihaun simulointi

Teimme aiemmin funktion, joka etsii, onko jokin luku listassa. Jos lista on iso, on etsiminen kohtuullisen aikaavievää. Lista on pahimmillaan käytävä läpi alusta loppuun.

Jos listan luvut on järjestetty suuruusjärjestykseen, voi etsimisen tehdä huomattavasti nopeammin.

Tarkastellaan seuraavaa listaa:

# indeksit   0   1   2   3    4   5    6   7   8   9  10
# luvut     -7  -3   3   7   11  15   17  21  24  28  30

Oletetaan, että olemme etsimässä lukua 17. Sen sijaan, että aletaan käydä lukuja läpi alusta asti, ollaan ovelia ja katsotaan, mikä luku on listan puolessa välissä. Listan puolen välin indeksi on isoin indeksi 10 jaettuna kahdella eli 5. Puoliväli on merkattu seuraavaan tähdellä:

                                  * 
# indeksit   0   1   2   3    4   5    6   7   8   9  10
# luvut     -7  -3   3   7   11  15   17  21  24  28  30

Puolessa välissä on luku 15. Olemme etsimässä lukua 17, joten koska listan alkiot ovat suuruusjärjestyksessä, ei etsitty luku voi missään tapauksessa olla puolivälin vasemmalla puolella. Etsintäalue voidaan siis rajata listan suurempaan puoliskoon. Seuraavassa on merkitty harmaalla se osa taulukkoa jossa etsitty ei voi olla:

# indeksit    0   1   2   3   4    5    6   7   8   9  10
# luvut      -7  -3   3   7  11   15   17  21  24  28  30

Jäljelle jääneelle etsintäalueelle voidaan tehdä samalla tavalla. Mennään sen puoleen väliin ja katsotaan mikä alkio on puolessa välissä. Puoliväli löytyy ottamalla etsintäalueen pienimmän ja suurimman indeksin summa ja jakamalla se kahdella, eli (6+10)/2 = 16/2 = 8. Kohta on merkitty alle tähdellä.

                                                *
# indeksit    0   1   2   3   4    5    6   7   8   9  10
# luvut      -7  -3   3   7  11   15   17  21  24  28  30

Etsintäalueen puolessa välissä on 24. Koska luvut ovat suuruusjärjestyksessä, ei etsittävä voi missään tapauksessa olla etsintäalueen puolenvälin oikealla puolella. Etsintäalue rajautuu taas, eli harmaat alueet on käsitelty:

                                                                     
# indeksit    0   1   2   3   4    5    6   7   8   9  10
# luvut      -7  -3   3   7  11   15   17  21  24  28  30

Etsintä jatkuu samalla tavalla. Etsintäalueen puoliväli löytyy ottamalla etsintäalueen pienimmän ja suurimman indeksin summa ja jakamalla se kahdella, eli (6+7)/2 = 6,5. Pyöristettynä alaspäin indeksi on 6. Kohta on merkitty alle tähdellä.

                                        *
# indeksit    0   1   2   3   4    5    6   7   8   9  10
# luvut      -7  -3   3   7  11   15   17  21  24  28  30

Indeksissä 6 on luku 17, joka on sama kuin etsitty luku. Nyt voidaan lopettaa binäärihaku ja ilmoittaa, että etsitty luku löytyi. Jos luku ei olisi ollut taulukossa, etsintäalue olisi jäänyt lopulta tyhjäksi. Tällöin voi ilmoittaa ettei lukua löytynyt.


Simuloi kynällä ja paperilla, miten binäärihaku toimii seuraavissa tilanteissa:

Listassa on luvut

# indeksit   0   1   2   3   4   5   6   7   8   9  10  11  12  13
# luvut     -5  -2   3   5   8  11  14  20  22  26  29  33  38  41

Näytä, miten binäärihaku etenee, kun etsitään seuraavia lukuja:

Tehtävä: Binäärihaun toteutus

Tee funktio binaariHaku, joka selvittää binäärihakua käyttäen, onko parametrina annettu luku parametrina annetussa jo järjestyksessä olevassa listassa.

Vihje: pidä muistissa etsintäalueen alku- ja loppukohdat. Alussa näiden arvot ovat:

alku = 0
loppu = len(lista)

Metodin toiminnan ydin voi olla esim seuraavanlainen:

while alku <= loppu:
  puolivali = (alku + loppu) / 2
  
  if lista[puolivali] == etsittava:
    return True

  # rajoita entsintäaluetta sopivasti muuttamalla 
  # joko muuttujan alku tai loppu arvoa

return False

Ohjelman suoritus näyttää seuraavalta:

Luvut: -3, 2, 3, 4, 7, 8, 12

Anna haettava luku: 8

Luku 8 löytyi
Luvut: -3, 2, 3, 4, 7, 8, 12

Anna haettava luku: 99

Lukua 99 ei löytynyt

Satunnaisuus

Ohjelmissa tahdotaan satunnaisuutta syystä tai toisesta. Useimmiten kaikenlaista satunnaisuutta voidaan simuloida pyytämällä satunnaisia lukuja. Tämä onnistuu käyttäen Pythonista valmiiksi löytyvää random-apumoduulia (palaamme moduuleihin myöhemmin).

Ennen kuin menet eteenpäin, yritä ensiksi etsiä googlesta tietoa satunnaisuudesta käyttämällä avainsanoja "python" ja "random". Eli hae sanoilla "python random". Tutustu muutamaan ensimmäiseen hakutulokseen, ja yritä luoda ohjelma joka lisää kymmenen satunnaista lukua väliltä 0...10 listaan ja tulostaa listan sisällön. Jos onnistut, mainiota! -- Jos et, älä huoli -- ehdit vielä oppia :).

Pythonin random-apumoduulista saa satunnaisia lukuja annetulla välillä seuraavasti..

# -*- coding: utf-8 -*-
import random; # tuodaan random-moduuli käyttöön

print random.randint(0, 10); # pyydetään ja tulostetaan luku väliltä 0...10

Yllä olevassa koodissa käytetään random-moduulin tarjoamaa funktiota randint, jolle annetaan parametrina kokonaisluvut alaraja ja yläraja. Funktiopalauttaa satunnaisen kokonaisluvun väliltä alaraja...yläraja.

Kymmenen satunnaista lukua väliltä 0...10 saa haettua vaikkapa seuraavasti:

# -*- coding: utf-8 -*-
import random; # tuodaan random-moduuli käyttöön

luvut = [];

luvut.append(random.randint(0, 10)); # pyydetään luku väliltä 0...10 ja lisätään se listaan

print luvut; # tulostetaan luvut lopuksi

Usein esiintyy tarve saada liukuluku väliltä [0..1] (esimerkiksi todennäköisyyksien yhteydessä). Random-moduuli tarjoaa myös satunnaisia liukulukuja, joita saa funktiolla random. Liukulukujen avulla voidaan simuloida epävarmoja tapahtumia. Tarkastellaan seuraavia säämahdollisuuksia:

Luodaan edellä olevista arvioista sääennuste ensi viikolle.

# -*- coding: utf-8 -*-
import random; # tuodaan random-moduuli käyttöön

# funktio sään ennustamiselle
def ennustaSaa(todennakoisyys):
    if todennakoisyys <= 0.6:
        return "Aurinko paistaa";
    elif todennakoisyys <= 0.9:
        return "Sataa lunta";
    else:
        return "Sataa räntää";

# ennustetaan sää kaikille viikon päiville:
paivat = ["Ma", "Ti", "Ke", "To", "Pe", "La", "Su"];

print "Seuraavan viikon sääennuste:";

for paiva in paivat:
    saa = ennustaSaa(random.random());
    
    print paiva + ": " + saa;

Ohjelman tulostus voisi olla esimerkiksi seuraavanlainen:

Seuraavan viikon sääennuste:
Ma: Aurinko paistaa
Ti: Aurinko paistaa
Ke: Sataa räntää
To: Aurinko paistaa
Pe: Aurinko paistaa
La: Sataa lunta
Su: Aurinko paistaa

On mahdollista saada myös satunnaislukuja jotka ovat normaalijakautuneita. Tähän voimme käyttää random-moduulin funktiota normalvariate, joka saa parametrinaan keskiarvon ja keskihajonnan:

# -*- coding: utf-8 -*-
import random; # tuodaan random-moduuli käyttöön

print random.normalvariate(30, 2); # valitaan satunnainen luku normaalijakaumasta, 
                                   # jonka keskiarvo on 30 ja keskihajonta 2

Tätä käyttäen saamme esimerkiksi tulostettua testidataa erilaisia hauskoja simulaatioita varten. Jos tiedämme että jonkun moottorityypin keski-ikä on 10 vuotta kahden vuoden keskihajonnalla, voimme tulostaa liudan esimerkkejä eri ikäisistä moottoreista:

# -*- coding: utf-8 -*-
import random; # tuodaan random-moduuli käyttöön

keski_ika = 10;
keskihajonta = 2;

for i in range(100):
    ika = random.normalvariate(keski_ika, keskihajonta);

    print "Moottori " + str(i + 1) + " hajoaa " + str(ika) + " vuoden kuluttua.";

(jos et koe mielenkiintoa satunnaisuuden eri lajeihin sillä ei ole merkitystä!)

Tehtäviä

Nopan heittäminen

Tee ohjelma, joka heittää noppaa ja ilmoittaa silmäluvun.

Ohjelman mahdollisia tulostuksia ovat esimerkiksi seuraavat:

Silmäluku: 3
Silmäluku: 5

Kolikon heittäminen

Tee ohjelma, joka heittää kolikkoa ja ilmoittaa tuloksen (kruuna tai klaava).

Ohjelman mahdolliset tulostukset ovat seuraavat:

Tulos: kruuna
Tulos: klaava

Koiran nimi

Tee ohjelma, joka arpoo koiralle nimen valmiiksi annetusta listasta.

Lista voisi olla esimerkiksi seuraava:

koirat = ["Turre", "Pluto", "Ärjy", "Milou", "Peni"]

Tällöin ohjelman mahdollisia tulostuksia ovat mm. seuraavat:

Koiran nimi: Pluto
Koiran nimi: Milou

Salasanan arpoja

Tee ohjelma, joka arpoo käyttäjälle salasanan. Salasana muodostuu merkeistä a-z ja siinä on 8 merkkiä.

Ohjelman tulostuksia ovat esimerkiksi seuraavat:

Salasana: zitbfsnt
Salasana: wlprcusq

Sekoittaja

Tee ohjelma, joka sekoittaa listan sisällön. Sovella ohjelmassa satunnaisuutta haluamallasi tavalla.

Ohjelman runko on seuraava:

luvut = [1, 2, 3, 4, 5, 6, 7, 8]
sekoitaLista(luvut)
tulostaLista(luvut)

Ohjelman tulostuksia ovat esimerkiksi seuraavat:

7, 2, 3, 5, 1, 8, 4, 6
1, 5, 6, 2, 8, 7, 3, 4

Lottoarvonta

Tee ohjelma, joka arpoo viikon lottonumerot. Lottonumerot ovat väliltä 1–39 ja niitä arvotaan 7.

Ohjelman mahdollisia tulostuksia ovat seuraavat:

Lottonumerot:
3 5 10 14 15 27 37
Lottonumerot:
2 9 11 18 23 32 34

Rekursio

Funktio voi kutsua itse itseään. Tätä ilmiötä sanotaan rekursioksi. Tällöin ainoa asia, millä suorituksen kestoa ja sen loppumista voidaan kontrolloida on käyttää hyödykseen parametreja. Ohjelmoitaessa rekursiivinen funktio, pitää käsiteltävät tapaukset usein jakaa kahteen eri luokkaan, joita ovat:

Lasketaan lukujen summa 1+2+...+n rekursiivisesti.

# -*- coding: utf-8 -*-

def summa(luku):
    if luku == 1:
        return 1;

    return luku + summa(luku - 1);

print summa(50);

Edellä kirjoitettu metodi laskee parametrilla luku summan 1+...+luku ja palauttaa sen arvon. Pelkkä ykkösen summa on ykkönen, joten se otetaan rekursion pohjaksi. Isommat summat palautetaan ykkösen summan laskemiseen kutsumalla yhdellä luvulla pienempää summaa rekursiivisesti.

Ohessa on tutulta vaikuttava listan lukujen summan laskeva metodi. Tällä kertaa se on toteutettu rekursiivisesti.

# -*- coding: utf-8 -*-

def summa(lista, kasiteltava_indeksi):
    if len(lista) >= kasiteltava_indeksi:
        return 0;

    return lista[kasiteltava_indeksi] + summa(lista, kasiteltava_indeksi + 1);


luvut = [1, 3, 5, 7, 9]

print summa(luvut, 0);

Esitelty summa-metodi kutsuu itseään rekursiivisesti kun listaa ei ole käsitelty vielä loppuun. Kun lista on käsitelty, niin pätee len(lista) == kasiteltava_indeksi ja palautetaan 0, joka lopettaa rekursiivisen kutsun. Parametrina annettu kasiteltava_indeksi kontrolloi rekursiivisia kutsuja. Siinä pidetään tallessa jo käsiteltyjen lukujen lukumäärää.

Kutsumalla rekursiivista metodia eri kohdissa metodin suoritusta voidaan saada aikaan mielenkiintoinen suoritusjärjestys. Seuraava metodi tulostaa mielenkiintoisen kuvion.

# -*- coding: utf-8 -*-

def tahtiPyramidi(tahtia):
    if tahtia == 0:
        return;

    tahtiPyramidi(tahtia-1);

    tulostus = "";
    for i in range(tahtia):
        tulostus = tulostus + "*";

    print tulostus;
    tahtiPyramidi(tahtia-1);


# pääohjelma alkaa
tahtiPyramidi(1);
print "----------";
tahtiPyramidi(2);
print "----------";
tahtiPyramidi(3);

Ohjelman tulostus on seuraavanlainen.

*
----------
*
**
*
----------
*
**
*
***
*
**
*

Tässä kannattaa kiinnittää huomiota, että kutsuttaessa metodia tahtiPyramidi antamalle sille kokonaisluku n, niin ensin tulostetaan lukua n-1 vastaava kuvio. Tämän jälkeen tulostetaan n tähteä ja lopuksi tulostetaan uusiksi lukua n-1 vastaava kuvio. Isompien lukujen kuvio koostuu siis kahdesta kappaleesta pienempiä kuvioita, sekä ne erottavasta jonosta tähtiä. Kuten edellä jo mainittiin, rekursiivinen metodi ratkaisee ongelman "aikaisempien" tapausten avulla.

Tehtäviä

Kertoma

Luonnollisen luvun n kertoma n! lasketaan kaavalla 1*2*3*...*n. Esimerkiksi 5! = 1*2*3*4*5 = 120. Lisäksi on sovittu, että 0! = 1. Seuraavassa on kertoman rekursiivinen määritelmä:

Toteuta rekursivinen funktio kertoma, joka laskee kertoman edelliseen määritelmään perustuen.

luku = int(raw_input("Anna luku: "))
print "Kertoma: " + str(kertoma(luku))

Ohjelman suoritus voi näyttää seuraavalta:

Anna luku: 5
Kertoma: 120

Merkkijonon kääntäminen

Toteuta rekursiivinen funktio kaanna, joka kääntää merkkijonon.

Vihje: Erota merkkijonosta ensimmäinen merkki ja loppuosa. Käännä loppuosa rekursiivisesti ja lisää sen perään ensimmäinen merkki.

merkkijono = raw_input("Anna merkkijono: ")
print "Väärinpäin: " + kaanna(merkkijono);
Anna merkkijono: esimerkki
Väärinpäin: ikkremise

PIN-koodi

Aiemmin tehtiin ohjelma, joka tulostaa kaikki PIN-koodit, jotka muodostuvat numeroista 1–9 ja joiden pituus on 4 numeroa.

Ohjelman rajoituksena oli, että PIN-koodin pituus oli koodattu ohjelman sisään sisäkkäisten silmukoiden määränä. Rekursion avulla tämä rajoitus poistuu helposti.

Toteuta ohjelma, jolle annetaan PIN-koodin pituus ja suurin numero. Ohjelma tulostaa kaikki tällaiset PIN-koodit suuruusjärjestyksessä.

Koodin pituus: 3
Suurin numero: 2
111
112
121
122
211
212
221
222
Koodin pituus: 5
Suurin numero: 4
11111
11112
11113
11114
11121
...
44444

Olio-ohjelmointi

Pieni johdanto olio-ohjelmointiin ennen aloitusta.

Proseduraalisessa ohjelmoinnissa, eli tähän asti opiskelemassamme ohjelmointityylissä ohjelma jäsennellään jakamalla se pienempiin osiin, eli funktioihin. Funktio toimii ohjelman erillisenä osana, ja sitä voi kutsua mistä tahansa ohjelmasta. Funktiota kutsuttaessa ohjelman suoritus siirtyy funktion alkuun, ja suorituksen päätyttyä palataan takaisin siihen kohtaan mistä funktiota kutsuttiin.

Olio-ohjelmoinnissa, kuten proseduraalisessa ohjelmoinnissa, pyritään jakamaan ohjelma pieniin osiin. Olio-ohjelmoinnissa pienet osat ovat olioita. Jokaisella oliolla on oma yksittäinen vastuunsa -- eli se sisältää joukon yhteenkuuluvaa tietoa ja toiminnallisuutta. Olio-ohjelmat koostuvat useista olioista, joiden yhteinen toiminta määrittelee järjestelmän toiminnan.

Olio

Olemme jo käyttäneet monia valmiita olioita. Esimerkiksi listat ovat olioita. Jokainen yksittäinen lista koostuu yhteenkuuluvasta tiedosta, eli olion tilasta ja listaolioihin liittyy toiminnallisuutta, eli metodt joilla voidaan muutella olion tilaa. Metodit ovat kuin funktioita, mutta ne liittyvät aina tiettyyn olioon. Esim. seuraavassa ohjelmanpätkässä on kaksi lista-olioa kaupungit ja maat:

kaupungit = [];
maat = [];

maat.append("Suomi");
maat.append("Saksa");
maat.append("Hollanti");

kaupungit.append("Berliini");
kaupungit.append("Nijmegen");
kaupungit.append("Turku");
kaupungit.append("Helsinki");
        
print "maita " + str(len(kaupungit));
print "kaupunkeja " + str(len(kaupungit));

Molemmat olioista, maat ja kaupungit elävät omaa elämäänsä, molempien "tila" on toisten olioiden tilasta riippumaton. Esim. olion maat tila koostuu listalla olevista merkkijonoista "Suomi", "Saksa" ja "Hollanti" ja todennäköisesti myös tiedosta kuinka monta maata listalla on.

Olioon liittyvää metodia kutsuttaessa tulee pisteen vasemmalle puolelle sen olion nimi, jolle metodia kutsutaan. Eli kun lisätään listalle maat uusi merkkijono, kutsu on muotoa maat.append("merkkijono") eli kutsutaan maat oliolle sen metodia append. Metodin mahdollisesti palauttama tulos riippuu olion maat tilasta, eli muut listat kuten kaupungit eivät vaikuta metodin suoritukseen millään tavalla.

Myös merkkijonot ovat olioita joihin liittyy metodeja. Pythonin dokumentaatio tarjoaa kattavan listan merkkijonojen muokkaamiseen sopivia metodeja http://docs.python.org/library/stdtypes.html#string-methods.

Luokka

On selvää että kaikki oliot eivät ole keskenään samankaltaisia. Esimmerkiksi listaoliot poikkeavat ratkaisevasti merkkijono-olioista. Kaikilla listaolioilla on samat metodit (append, pop, reverse, ...) ja vastaavasi kaikilla merkkijono-olioilla on samat metodit (find, split, upper, ...). ArrayList- ja String-olioiden metodit eivät ole samat, nämä ovatkin erityyppisiä olioita.

Tietyn olioryhmän tyyppiä kutsutaan luokaksi, eli lista (list) on luokka, merkkijono (str) on luokka, jne...

Tiettyn luokan kaikilla olioilla on samat metodit, sekä samankaltainen tila. Esim. kaikkien listojen tila koostuu listalle talletetuista alkioista. Merkkijono-olioiden tila taas koostuu merkkijonon muodostavista kirjaimista.

Luokka ja sen oliot

Luokka siis määrittelee minkälaisia luokan oliot ovat:

voidaan ajatella, että luokka kuvaa sen olioiden "rakennuspiirustukset".

Otetaan analogia tietokoneiden ulkopuoleisesta maailmasta. Rintamamiestalot lienevät kaikille suomalaisille tuttuja. Voidaan ajatella, että jossain on olemassa piirustukset jotka määrittelevät minkälainen rintamamiestalo on. Piirrustukset siis ovat luokka, eli ne määrittelevät luokan olioiden luonteen:

Yksittäiset oliot eli rintamamiestalot on tehty kaikki samojen piirustusten perusteella, eli ne kuuluvat samaan luokkaan. Yksittäisten olioiden tila eli ominaisuudet (esim. seinien väri, katon rakennusmateriaali ja väri, kivijalan väri, ovien rakennusmateriaali ja väri, ...) vaihtelevat. Seuraavassa yksi "rintamamiestalo-luokan olio":

Oman luokan määritteleminen - oliomuuttujat

Yleensä luokka määritellään jotain mielekästä kokonaisuutta varten. Joskus "mielekäs kokonaisuus" voi esim. kuvata jotain reaalimaailman asiaa. Jos tietokoneohjelman pitää käsitellä henkilötietoja, voisi olla mielekästä määritellä erillinen luokka Henkilo joka kokoaa yhteen henkilöön liittyvät metodit ja ominaisuudet.

Aloitetaan.

# -*- coding: utf-8 -*-

print "Hei maailma.";

Luomme nyt ohjelmaamme uuden luokan nimeltä Henkilo. Luokka on tässä esimerkissä samassa lähdekooditiedostossa, mutta siirrämme sen myöhemmin erilliseen lähdekooditiedostoon.

class Henkilo:

    def kerro(self):
        return "Hei!";


print "Hei maailma.";
arto = Henkilo()
print "Mitä kertoo?: " + arto.kerro();
Hei maailma.
Kertomus: Hei!

Luokan määrittely aloitetaan sanomalla class jota seuraa luokan nimi (esimerkissämme Henkilo). Luokan määrittely aloittaa uuden koodilohkon, joten asetamme kaksoispisteen määrittelyn perään. Jokaisella luokalla täytyy olla määrittely, eli jotain sisältöä. Määrittelimme luokalle Henkilo metodin kerro, joka palauttaa merkkijonon "Hei!". Toisin kuin funktiot, metodeille annetaan aina parametreina vähintään arvo self, joka on viittaus juuri luotavaan olioon.

Luokka siis määrittelee mitä metodeja ja ominaisuuksia luokan olioilla on. Päätetään, että jokaisella henkilöllä on nimi ja ikä. Nimi on luonnollista esittää merkkijonona, ja ikä taas kokonaislukuna. Lisätään nämä rakennuspiirustuksiimme siten, että niillä on aluksi jo joku arvo:

# -*- coding: utf-8 -*-

class Henkilo:
    nimi = "arto";
    ika = 5;

    def kerro(self):
        return "Hei!";

Eli määrittelimme tässä että kaikilla Henkilo-luokan olioilla on nimi ja ika. Märitettely tapahtuu hiukan kuten normaalin muuttujan määrittely.

Luokan sisälle määriteltyjä muuttujia kutsutaan oliomuuttujiksi tai olion kentiksi tai olion attribuuteiksi. Rakkaalla lapsella on monta nimeä.

Eli olemme määritelleet että kaikilla henkilöolioilla on muuttujat nimi ja ika, eli henkilöiden "tila" koostuu niiden nimestä ja iästä.

Muutetaan vielä Henkilöä siten, että se kertoo nimensä ja ikänsä.

# -*- coding: utf-8 -*-

class Henkilo:
    nimi = "arto";
    ika = 5;

    def kerro(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);


print "Hei maailma.";
arto = Henkilo()

print "Mitä kertoo?: " + arto.kerro();
Hei maailma.
Mitä kertoo?: Hei! Olen arto ja ikäni on 5

Huomaa miten self-viitettä, eli viitettä itseen käytetään metodissa kerro. Luokasta Henkilo voi olla luotuna useita olioita samaan aikaan, jolloin viite self kertoo että kyseessä on juuri tämän olion sisäiset muuttujat eivätkä mitkään muut.

Oman luokan määritteleminen - konstruktori eli tilan alustus

Yleensä luotavalle oliolle halutaan asettaa luontivaiheessa jokin alkutila. Oliot siis luodaan komennolla LuokanNimi(). Olion synnyttämisen yhteydessä olisikin kätevä pystyä antamaan arvo joillekkin olioiden muuttujille. Esim. henkilön synnytyksessä olisi kätevää pystyä antamaan nimi jo syntymähetkellä:

arto = Henkilo("Arto");

Tämä onnistuu määrittelemällä oliolle ns. konstruktori, eli olion luontihetkellä suoritettava "metodi". Seuraavassa on määritelty Henkilo:lle konstruktori joka asettaa iäksi 0 ja nimeksi parametrina saatavan merkkijonon:

# -*- coding: utf-8 -*-

class Henkilo:
    nimi = "arto";
    ika = 5;

    def __init__(self, nimi):
        self.nimi = nimi;
        self.ika = 0;

    def kerro(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

arto = Henkilo("Arto")
print "Mitä kertoo?: " + arto.kerro();
Mitä kertoo?: Hei! Olen Arto ja ikäni on 0

Konstruktori on siis metodi jonka nimi on __init__. Konstruktorille parametrina tuleva arvo tulee sulkuihin konstruktorin nimen perään. Konstruktorin voi ajatella olevan metodi, jonka python suorittaa kun olio luodaan sanomalla Henkilo("Pekka"); Aina kun jostain luokasta luodaan olio, kutsutaan kyseisen luokan konstruktoria.

Muutama huomio: konstruktorin sisällä on komento self.ika = 0. Tässä asetetaan arvo 0 olion sisäiselle muuttujalle ika. Toinen komento self.nimi = nimi; taas asettaa olion sisäiselle muuttujalle nimi arvoksi parametrina annetun merkkijonon nimi. Huomaa että ilman määrittelyä self emme tietäisi mihin nimi-muuttujaan parametrina saatu arvo asetetaan.

Vielä yksi huomio: jos ohjelmoija ei tee luokalle ollenkaan konstruktoria, on luokalla ns. oletuskonstruktori, eli konstruktorin joka ei tee mitään. Eli jos konstruktoria ei jostain syystä tarvita, ei sellaista tarvitse ohjelmoida.

Koska konstruktori asettaa oliomuuttujille arvot, voimme ohjelmoida luokan Henkilo jatkossa seuraavasti:

class Henkilo:

    def __init__(self, nimi):
        self.nimi = nimi;
        self.ika = 0;

    def kerro(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

    def vanhene(self):
        self.ika = self.ika + 1;

Lisätään luokalle vielä kaksi muuttujaa "pituus" ja "paino", jotka kummatkin saavat arvoksi 0.

class Henkilo:

    def __init__(self, nimi):
        self.nimi = nimi;
        self.ika = 0;
        self.pituus = 0;
        self.paino = 0;

    def kerro(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

    def vanhene(self):
        self.ika = self.ika + 1;

Oman luokan määritteleminen - metodit

Olemme jo esimerkissämme nähneet Henkilo-olioon liittyviä metodeja. Lisätään Henkilo:lle metodi jonka avulla olio vanhenee vuodella.

# -*- coding: utf-8 -*-

class Henkilo:

    def __init__(self, nimi):
        self.nimi = nimi;
        self.ika = 0;
        self.pituus = 0;
        self.paino = 0;

    def kerro(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

    def vanhene(self):
        self.ika = self.ika + 1;


arto = Henkilo("Arto")
print "Mitä kertoo?: " + arto.kerro();
arto.vanhene();
print "Mitä kertoo?: " + arto.kerro();
Mitä kertoo?: Hei! Olen Arto ja ikäni on 0
Mitä kertoo?: Hei! Olen Arto ja ikäni on 1

Metodin sisällä on yksi koodirivi joka kasvattaa oliomuuttujan ika arvoa yhdellä.

Luodaan kaksi henkilöä ja pyydetään niitä kertomaan jotain itsestään:

# henkilön määrittely...

pekka = Henkilo("Pekka")
antti = Henkilo("Antti")

print pekka.kerro();
print antti.kerro();

pekka.vanhene();

print pekka.kerro();
print antti.kerro();

Tulostuu:

Hei! Olen Pekka ja ikäni on 0
Hei! Olen Antti ja ikäni on 0
Hei! Olen Pekka ja ikäni on 1
Hei! Olen Antti ja ikäni on 0

Eli "syntyessään" molemmat oliot ovat nollavuotiaita. Olion pekka metodia vanhene kutsutaan kerran. Kuten tulostus näyttää, tämä saa aikaan sen että Pekan ikä onkin 1 vuosi. Kutsumalla metodia Pekkaa vastaavalle oliolle, siis toisen henkilöolion ikä ei muutu.

Jatketaan ohjelman laajentamista. Tehdään henkilölle metodi jonka avulla voidaan selvittää onko henkilö täysi-ikäinen. Metodi palauttaa joko True tai False:

class Henkilo:

    def __init__(self, nimi):
        self.nimi = nimi;
        self.ika = 0;
        self.pituus = 0;
        self.paino = 0;

    def taysiIkainen(self):
        if self.ika < 18:
            return False;
        
        return True;

    def kerro(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

    def vanhene(self):
        self.ika = self.ika + 1;

Ja testataan:

pekka = Henkilo("Pekka");
antti = Henkilo("Antti");

for i in range(30):
    pekka.vanhene();

antti.vanhene();

if pekka.taysiIkainen():
    print "Täysi-ikäinen: " + pekka.kerro();
else:
    print "Alaikäinen: " + pekka.kerro();


if antti.taysiIkainen():
    print "Täysi-ikäinen: " + antti.kerro();
else:
    print "Alaikäinen: " + antti.kerro();
Täysi-ikäinen: Hei! Olen Pekka ja ikäni on 30
Alaikäinen: Hei! Olen Antti ja ikäni on 1

Nyt henkilön pystyy "tulostamaan" ainoastaan siten, että nimen lisäksi tulostuu ikä. On tilanteita, joissa haluamme tietoon pelkän olion nimen. Oliomuuttujiin pääsee pythonissa käsiksi myös suoraan olion kautta .-operaattoria käyttämällä.

Muotoillaan testimme tulostukset toisenlaiseksi:

pekka = Henkilo("Pekka");
antti = Henkilo("Antti");

for i in range(30):
    pekka.vanhene();

antti.vanhene();

if pekka.taysiIkainen():
    print "Täysi-ikäinen: " + pekka.nimi;
else:
    print "Alaikäinen: " + pekka.nimi;


if antti.taysiIkainen():
    print "Täysi-ikäinen: " + antti.nimi;
else:
    print "Alaikäinen: " + antti.nimi;

Tulostus:

Täysi-ikäinen: Pekka
Alaikäinen: Antti

__str__

Olemme syyllistyneet edellä osittain huonoon ohjelmointityyliin tekemällä metodin jonka avulla olio tulostetaan, eli metodin kerro. Suositeltavampi tapa on määritellä oliolle metodi jonka palauttaa olion "merkkijonoesityksen". Merkkijonoesityksen palauttavan metodin nimen on pythonissa tapana aina olla __str__. Määritellään seuraavassa henkilölle tämä metodi:

class Henkilo:
    # ...

    def __str__(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

Eli __str__ toimii samaan tyyliin kuin kerro, mutta se ei itse tulosta mitään vaan palauttaa merkkijonoesityksen itsestään, jotta metodin kutsuja voi halutessaan suorittaa tulostamisen.

Metodia käytetään hieman yllättävällä tavalla:

pekka = Henkilo("Pekka");
antti = Henkilo("Antti");

for i in range(30):
    pekka.vanhene();

antti.vanhene();

print pekka;
print antti;
Hei! Olen Pekka ja ikäni on 30
Hei! Olen Antti ja ikäni on 1

Periaatteena on, että pääohjelma pyytää olion merkkijonoesityksen kutsumalla funktiota __str__. Jos olion haluaa katenoida merkkijonomuuttujaan, tulee metodin __str__ kutsu tulee kirjoittaa str-funktion avulla seuraavasti:

print "Alaikäinen: " + str(antti);

Tehtäviä: Kello laskurin avulla

Tässä tehtäväsarjassa tehdään luokka YlhaaltaRajoitettuLaskuri ja sovelletaan sitä kellon tekemiseen.

Rajoitettu laskuri

Tehdään luokka YlhaaltaRajoitettuLaskuri. Luokan olioilla on seuraava toiminnallisuus:

Luokan runko on siis seuraava:

class YlhaaltaRajoitettuLaskuri:
  def __init__(self, ...):
    self.laskuri = ...;
    self.ylaraja = ...;

  def seuraava(self):
    # kirjoita koodia tähän

  def __str__(self):
    # kirjoita koodia tähän

Vihje: metodissa __str__ tulee palauttaa merkkijonotyyppinen arvo. Kokonaislukumuuttujasta x saa merkkijonomuodon funktion str avulla..

Seuraavassa on pääohjelma, joka käyttää laskuria:

laskuri = YlhaaltaRajoitettuLaskuri(4)
print "Arvo alussa: " + str(laskuri)

for i in range(10):
  laskuri.seuraava()
  print "arvo: " + str(laskuri)

Laskurille asetetaan ylärajaksi konstruktorissa 4, joten laskurin arvo on luku 0:n ja 4:n väliltä. Huomaa, miten metodi seuraava vie laskurin arvoa eteenpäin, kunnes se pyörähtää 4:n jälkeen 0:aan:

Ohjelman tulostuksen tulisi olla seuraava:

arvo alussa: 0
arvo: 1
arvo: 2
arvo: 3
arvo: 4
arvo: 0
arvo: 1
arvo: 2
arvo: 3
arvo: 4
arvo: 0

Etunolla tulostukseen

Tee __str__-metodista sellainen, ttä se lisää arvon merkkijonoesitykseen etunollan, os laskurin arvo on vähemmän kuin 10. Eli jos laskurin arvo on esim. 3, alautetaan merkkijono "03", os arvo taas on esim. 12, alautetaan normaaliin tapaan merkkijono "12".

Muuta pääohjelma seuraavaan muotoon ja varmista, että tulos on haluttu.

laskuri = YlhaaltaRajoitettuLaskuri(14)
print "Arvo alussa: " + str(laskuri)

for i in range(10):
  laskuri.seuraava()
  print "arvo: " + str(laskuri)
arvo alussa: 00
arvo: 01
arvo: 02
arvo: 03
arvo: 04
arvo: 05
arvo: 06
arvo: 07
arvo: 08
arvo: 09
arvo: 10
arvo: 11
arvo: 12
arvo: 13
arvo: 14
arvo: 00
arvo: 01

Kello, ensimmäinen versio

Käyttämällä kahta laskuria voimme muodostaa kellon. Tuntimäärä on laskuri, jonka yläraja on 23, ja minuuttimäärä on laskuri jonka yläraja on 59. Kuten kaikki tietävät, kello toimii siten, että aina kun minuuttimäärä pyörähtää nollaan, tuntimäärä kasvaa yhdellä.

Tee ensin laskurille metodi arvo, joka palauttaa laskurin arvon:

  def arvo():
    # kirjoita koodia tähän

Tee sitten kello täydentämällä seuraava pääohjelmarunko:

minuutit = YlhaaltaRajoitettuLaskuri(59)
tunnit = YlhaaltaRajoitettuLaskuri(23)    

for i in range(121):
  print str(tunnit) + ":" + str(minuutit) # tulostetaan nykyinen aika
  # minuuttimäärä kasvaa
  # jos minuuttimäärä menee nollaan, tuntimäärä kasvaa

Jos kellosi toimii oikein, sen tulostus näyttää suunnilleen seuraavalta:

00:00
00:01
...
00:59
01:00
01:01
01:02
...
01:59
02:00

Varmista, että kellosi siirtyy näyttämään keskiyöllä aikaa 00:00.

Kello, toinen versio

Laajenna kelloasi myös sekuntiviisarilla.Tee myös laskurille metodi asetaArvo, jolla laskurille pystyy asettamaan halutun arvon. Tämän metodin avulla voit muuttaa kellon ajan heti ohjelman alussa haluamaksesi.

Laita kellosi alkamaan ajasta 23:59:50 ja varmista, että vuorokauden vaihteessa kello toimii odotetusti.

Voit testata kellon toimintaa seuraavalla ohjelmalla

import time # otetaan pythonin time-kirjasto käyttöön

sekunnit = YlhaaltaRajoitettuLaskuri(59)
minuutit = YlhaaltaRajoitettuLaskuri(59)
tunnit = YlhaaltaRajoitettuLaskuri(23)

sekunnit.asetaArvo(50);
minuutit.asetaArvo(59);
tunnit.asetaArvo(23);

while True:
  print str(tunnit) + ":" + str(minuutit) + ":" + str(sekunnit)
  time.sleep(1) # nukutaan yksi sekunti

  # toteuta: lisää kellon aikaa sekunnilla eteenpäin

Nyt kello käy ikuisesti ja kasvattaa arvoaan sekunnin välein. Sekunnin odotus tapahtuu komennolla time.sleep(1). Jotta komento toimisi, pitää pääohjelmaan lisätä komento import time, joka tuo aika-toimintoihin liittyvän moduulin käyttöön.

Tärkeitä kommentteja liittyen olioiden käyttöön. Lue nämä ehdottomasti.

Olio-ohjelmoinnissa on kyse pitkälti käsitteiden eristämisestä omaksi kokonaisuudekseen tai toisin ajatellen abstraktioiden muodostamisesta. Voisi ajatella, että on turha luoda oliota jonka sisällä on ainoastaan luku, eli että saman voisi tehdä suoraan int-muuttujilla. Asia ei kuitenkaan ole näin. Jos kello koostuu pelkästään kolmesta int-muuttujasta joita kasvatellaan, muuttuu ohjelma lukijan kannalta epäselvemmäksi, koodista on vaikea "nähdä" mistä on kysymys. Aiemmin materiaalissa mainitsimme jo kokeneen ja kuuluisan ohjelmoijan Kent Beckin neuvon "Any fool can write code that a computer can understand. Good programmers write code that humans can understand", eli koska viisari on oikeastaan oma selkeä käsitteensä, on siitä ohjelman ymmärrettävyyden parantamiseksi hyvä tehdä oma luokka, eli YlhaaltaRajoitettuLaskuri.

Käsitteen erottaminen omaksi luokaksi on monellakin tapaa hyvä idea. Ensinnäkin tiettyjä yksityiskohtia (esim. laskurin pyörähtäminen) saadaan piilotettua luokan sisään (eli abstrahoitua). Sen sijaan että kirjoitetaan if-lause ja sijoitusoperaatio, riittää, että laskurin käyttäjä kutsuu selkeästi nimettyä metodia seuraava(). Aikaansaatu laskuri sopii kellon lisäksi ehkä muidenkin ohjelmien rakennuspalikaksi, eli selkeästä käsitteestä tehty luokka voi olla monikäyttöinen. Suuri etu saavutetaan myös sillä, että koska laskurin toteutuksen yksityiskohdat eivät näy laskurin käyttäjille, voidaan yksityiskohtia tarvittaessa muuttaa.

Totesimme että kello sisältää kolme viisaria, eli koostuu kolmesta käsitteestä. Oikeastaan kello on itsekin käsite. Teemme siitä kohta luokan Kello, jotta voimme luoda selkeitä Kello-olioita. Kello tulee siis olemaan olio jonka toiminta perustuu "yksinkertaisimpiin" olioihin eli viisareihin. Tämä on juuri olio-ohjelmoinnin suuri idea: ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista.

Otamme nyt varovaisia ensiaskelia oliomaailmassa. Kannattaa pitää mielessä ehkä käsittämättömältäkin kuulostava lausahdus, ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista, joka alkaa myöhemmin tuntua ehkä jopa järkeenkäyvältä ja itsestäänselvältä.

Tehtäväsarja: Lukutilasto

Lukujen määrä

Tee luokka Lukutilasto, joka tuntee seuraavat toiminnot:

Luokan ei tarvitse tallentaa mihinkään lisättyjä lukuja, vaan riittää muistaa niiden määrä. Metodin lisaaLuku ei tässä vaiheessa tarvitse edes ottaa huomioon, mikä luku lisätään tilastoon, koska ainoa tallennettava asia on lukujen määrä.

Luokan runko on seuraava:

class Lukutilasto:
  def __init__(self):
    self.maara = ...

  def lisaaLuku(self, luku):
    # kirjoita koodia tähän

  def haeMaara(self):
    # kirjoita koodia tähän

Seuraava ohjelma esittelee luokan käyttöä:

tilasto = Lukutilasto()
tilasto.lisaaLuku(3)
tilasto.lisaaLuku(5)
tilasto.lisaaLuku(1)
tilasto.lisaaLuku(2)
print "Määrä: " + str(tilasto.haeMaara())

Ohjelman tulostus on seuraava:

Määrä: 4

Summa ja keskiarvo

Laajenna luokkaa seuraavilla toiminnoilla:

Luokan runko on seuraava:

class Lukutilasto:
  def __init__(self):
    self.maara = ...
    self.summa = ...

  def lisaaLuku(self, luku):
    # kirjoita koodia tähän

  def haeMaara(self):
    # kirjoita koodia tähän

  def haeSumma(self):
    # kirjoita koodia tähän

  def haeKeskiarvo(self):
    # kirjoita koodia tähän

Seuraava ohjelma esittelee luokan käyttöä:

tilasto = Lukutilasto();
tilasto.lisaaLuku(3);
tilasto.lisaaLuku(5);
tilasto.lisaaLuku(1);
tilasto.lisaaLuku(2);
print "Määrä: " + str(tilasto.haeMaara())
print "Summa: " + str(tilasto.haeSumma())
print "Keskiarvo: " + str(tilasto.haeKeskiarvo())

Ohjelman tulostus on seuraava:

Määrä: 4
Summa: 11
Keskiarvo: 2.75

Summa käyttäjältä

Tee ohjelma, joka kysyy lukuja käyttäjältä, kunnes käyttäjä antaa luvun -1. Sitten ohjelma ilmoittaa lukujen summan.

Käytä ohjelmassa luokkaa Lukutilasto summan laskemiseen.

Anna lukuja:
4
2
5
4
-1
Summa: 15

Huom! Kai teit ohjelman siten, että lukujen kysely ei tapahdu luokan Lukutilasto sisältämässä ohjelmakoodissa.

Kaksi summaa

Muuta edellistä ohjelmaa niin, että ohjelma laskee erikseen parillisten ja parittomien lukujen summaa.

Määrittele ohjelmassa kaksi oliota luokasta Lukutilasto: toinen laskee parillisten lukujen summan ja toinen laskee parittomien lukujen summan.

Anna lukuja:
4
2
5
2
-1
Parillisten summa: 8
Parittomien summa: 5

Tehtäväsarja: Kivi, paperi, sakset

Tässä tehtäväsarjassa tehdään tekoäly kivi, paperi, sakset -peliin.

Tekoälyn runko

Tee luokka KPSTekoaly, josta tulee tekoäly kivi, paperi, sakset -peliin. Luokkaan tulee metodi teeSiirto, joka palauttaa tekoälyn tekemän siirron. Merkkijonot "k", "p" ja "s" vastaavat siirtoja kivi, paperi ja sakset.

Tee tekoälystä ensin sellainen, että sen siirto on aina kivi "k".

Luokan runko on seuraava:

class KPSTekoaly:
  def teeSiirto(self):
    # kirjoita koodia tähän

Pääohjelma näyttää seuraavalta:

...
tekoaly = KPSTekoaly()

kierroksia = int(raw_input("Kuinka monta kierrosta? "))
for kierros in range(kierroksia):
  pelaajanSiirto = raw_input("Anna siirto (k, p tai s): ")
  tekoalynSiirto = tekoaly.teeSiirto()

  print "Pelaajan siirto: " + pelaajanSiirto
  print "Tekoälyn siirto: " + tekoalynSiirto

Peli voi edetä esimerkiksi seuraavasti:

Kuinka monta kierrosta? 3
Anna siirto (k, p tai s): s
Pelaajan siirto: s
Tekoälyn siirto: k
Anna siirto (k, p tai s): p
Pelaajan siirto: p
Tekoälyn siirto: k
Anna siirto (k, p tai s): k
Pelaajan siirto: k
Tekoälyn siirto: k

Voittajan tarkistus

Lisää luokka KPSTuomari, jonka runko on seuraava:

class KPSTuomari:
  def maaritaVoittaja(self, pelaajanSiirto, tekoalynSiirto):
    # kirjoita koodia tahan

Metodin maaritaVoittaja tarkoituksena on selvittää pelaajan ja tekoälyn siirtojen perusteella, kumpi on voittaja vai tuliko tasapeli. Toteuta metodi niin, että se tulostaa tiedon voittajasta.

Kivi, paperi, sakset -pelissä kivi voittaa sakset, sakset voittavat paperin ja paperi voittaa kiven.

Pääohjelma on nyt seuraava:

...
tekoaly = KPSTekoaly()
tuomari = KPSTuomari()

kierroksia = int(raw_input("Kuinka monta kierrosta? "))
for kierros in range(kierroksia):
  pelaajanSiirto = raw_input("Anna siirto (k, p tai s): ")
  tekoalynSiirto = tekoaly.teeSiirto()

  print "Tekoälyn siirto: " + tekoalynSiirto
  tuomari.maaritaVoittaja(pelaajanSiirto, tekoalynSiirto)

Peli voi edetä esimerkiksi seuraavasti:

Kuinka monta kierrosta? 3
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): p
Tekoälyn siirto: k
Pelaaja voitti!
Anna siirto (k, p tai s): k
Tekoälyn siirto: k
Tuli tasapeli!

Satunnainen tekoäly

Nyt on aika tehdä tekoälystä parempi, koska ei ole järkevää, että tekoäly valitsee aina kiven.

Tee tekoälystä satunnainen: se valitsee yhtä todennäköisesti kiven, saksen ja paperin.

Pisteenlasku

Laajenna tuomariluokkaa niin, että se pitää kirjaa pelaajan ja tekoälyn voitoista. Lisää luokkaan myös metodi tulostaTilasto, joka tulostaa tilaston voitoista. Lisää tämän metodin kutsu pääohjelman loppuun.

Kuinka monta kierrosta? 5
Anna siirto (k, p tai s): s
Tekoälyn siirto: s
Tuli tasapeli!
Anna siirto (k, p tai s): p
Tekoälyn siirto: s
Tekoäly voitti!
Anna siirto (k, p tai s): k
Tekoälyn siirto: k
Tuli tasapeli!
Anna siirto (k, p tai s): s
Tekoälyn siirto: p
Pelaaja voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!

Tilasto:
Pelaaja: 1 voittoa
Tekoäly: 2 voittoa

Muistava tekoäly

Lisää tekoälyyn muisti: tekoäly ottaa siirtonsa valinnassa huomioon, mitä käyttäjä on valinnut aiemmin eri tilanteissa. Päätä itse, millä tavoin tarkalleen tekoäly hyödyntää muistia.

Lisää tekoälyyn metodi kirjaaSiirto, jota kutsutaan käyttäjän siirron jälkeen. Sen avulla tekoäly saa tietoonsa, mitä siirtoja käyttäjä on tehnyt.

Pääohjelmaan tehdään seuraava muutos:

...
        pelaajanSiirto = raw_input("Anna siirto (k, p tai s): ")
        tekoalynSiirto = tekoaly.teeSiirto()
        tekoaly.kirjaaSiirto(pelaajanSiirto)
        print "Tekoälyn siirto: " + tekoalynSiirto
...

Jos tekoäly käyttää muistiaan hyvin, se voi sopeutua esimerkiksi tilanteeseen, jossa käyttäjä valitsee aina saman siirron:

Kuinka monta kierrosta? 5
Anna siirto (k, p tai s): s
Tekoälyn siirto: p
Pelaaja voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!

Tilasto
Pelaaja: 1 voittoa
Tekoäly: 4 voittoa

Oman metodin kutsu

Olio voi kutsua myös omia metodeitaan. Jos esim. halutaan, että __str__-metodin palauttama merkkijonoesitys kertoisi myös henkilön täysi-ikäisyyden, kannattaa __str__-metodista kutsua metodia taysiIkainen:

    def __str__(self):
        if self.taysiIkainen():
            return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika) + ". Olen siis täysi-ikäinen!";
        else:
            return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

Eli kun olio kutsuu omaa metodiaan, riittää pelkkä self-määre metodin kutsun alkuun.

Oletusarvojen lisääminen konstruktoriin

Kaikki henkilöoliot ovat luontihetkellä 0-vuotiaina, sillä konstruktori asettaa uuden henkiön ika-oliomuuttujan arvoksi 0:

    def __init__(self, nimi):
        self.nimi = nimi;
        self.ika = 0;
        self.pituus = 0;
        self.paino = 0;

Haluaisimme, myös vaihtoehtoisen tavan luoda henkilön siten, että nimen lisäksi konstruktorin parametrina annettaisiin ikä. Tämä onnistuu helposti, sillä konstruktoriin voi määritellä oletusarvoja. Muutetaan henkilön konstruktoria siten, että ikä voidaan antaa oletusarvoisena parametrina.

    def __init__(self, nimi, ika=0):
        self.nimi = nimi;
        self.ika = ika;
        self.pituus = 0;
        self.paino = 0;

Nyt voimme kutsua Henkilo-luokan konstruktoria kahdella eri tavalla:

pekka = Henkilo("Pekka", 30);
antti = Henkilo("Antti");

antti.vanhene();

print pekka;
print antti;
Hei! Olen Pekka ja ikäni on 30. Olen siis täysi-ikäinen!
Hei! Olen Antti ja ikäni on 1

Tehtävä: Kellosta olio

Kellosta olio

Teimme aiemmin ylhäältä rajoitetun laskurin, jonka avulla rakennettiin pääohjelmassa toimiva kello. Tehdään nyt myös itse kellosta olio. Luokan kello runko näyttää seuraavalta:

class Kello:
  def __init__(self, ...):
    self.tunnit = ...;
    self.minuutit = ...;
    self.sekunnit = ...;

  def etene(self):
    # kello etenee sekunnilla

  def __str__(self):
    # palauttaa kellon merkkijonoesityksen

Toteuta konstruktori ja puuttuvat metodit. Voit testata kelloasi seuraavalla pääohjelmalla:

import time;

kello = Kello(23, 59, 50) # tunnit, minuutit, sekunnit

while True:
  print kello
  kello.etene()
  time.sleep(1)

Tulostuksen tulisi edetä seuraavasti:

23:59:50
23:59:51
23:59:52
23:59:53
23:59:54
23:59:55
23:59:56
23:59:57
23:59:58
23:59:59
00:00:00
00:00:01

Oletusarvojen antaminen metodille

Konstruktorien tapaan myös metodeille voi antaa oletusarvoja, eli saman nimistä metodia voi kutsua eri parametreillä. Tehdään vanhene-metodista uudempi versio, joka mahdollistaa henkilön vanhentamisen parametrina olevalla vuosimäärällä:

    def vanhene(self, vuodet = 1):
        self.ika = self.ika + vuodet;

Seuraavassa "Pekka" syntyy 24-vuotiaana, vanhenee ensin vuoden ja sitten 10 vuotta:

pekka = Henkilo("Pekka", 24);
print pekka;

pekka.vanhene();
print pekka;

pekka.vanhene(10);
print pekka;

Tulostuu:

Hei! Olen Pekka ja ikäni on 24. Olen siis täysi-ikäinen!
Hei! Olen Pekka ja ikäni on 25. Olen siis täysi-ikäinen!
Hei! Olen Pekka ja ikäni on 35. Olen siis täysi-ikäinen!

Henkilöllä on siis useampia tapoja kutsua vanhene-nimistä metodia. Se kumpi metodi valitaan suoritettavaksi riippuu metodikutsussa käytettyjen parametrien määrästä.

Tee nyt viikon 5 tehtävät 3

Olio on langan päässä

Luvussa listoista mainittiin, että lista on "langan päässä". Myös oliot ovat langan päässä. Mitä tämä oikein tarkoittaa? Tarkastellaan seuraavaa esimerkkiä:

pekka = Henkilo("Pekka", 24);

Syntyy siis olio. Olioon päästään käsiksi muuttujan pekka avulla. Teknisesti ottaen olio ei ole muuttujan pekka "sisällä" (eli lokerossa pekka) vaan pekka viittaa syntyneeseen olioon. Toisin sanonen olio on pekka-nimisetä muuttujasta lähtevän "langan päässä". Kuvana asiaa voisi havainnollistaa seuraavasti (kuvassa Henkilo pekka on merkitty arvolla p:

Lisätään ohjelmaan muuttuja h ja annetaan sille alkuarvoksi p. Mitä nyt tapahtuu?

pekka = Henkilo("Pekka", 24);
print pekka;

h = pekka;
h.vanhene(26);

print pekka;
Tulostuu:
Hei! Olen Pekka ja ikäni on 24. Olen siis täysi-ikäinen!
Hei! Olen Pekka ja ikäni on 50. Olen siis täysi-ikäinen!

Eli Pekka on alussa 24 vuotias, h:ta vanhennetaan 26:llä vuodella ja sen seurauksena Pekka vanhenee! Mistä on kysymys?

Komento h = pekka; saa aikaan sen, että h rupeaa viittaamaan samaan olioon mihin pekka viittaa. Eli ei synnykään kopioa oliosta, vaan molemmissa muuttujissa on langan päässä sama olio. Kuvana:

Esimerkissä siis "vieras henkilö h ryöstää Pekan identiteetin". Pekka kyllästyy identiteetin ryöstäjään. Seuraavassa on esimerkkiä jatkettu siten, että luodaan uusi olio ja pekka alkaa viittaamaan uuteen olioon:

pekka = Henkilo("Pekka", 24);
print pekka;
h = pekka;

h.vanhene(26);

print pekka;

pekka = Henkilo("Pekka Mikkola", 24);
print pekka;

Tulostuu:

Hei! Olen Pekka ja ikäni on 24. Olen siis täysi-ikäinen!
Hei! Olen Pekka ja ikäni on 50. Olen siis täysi-ikäinen!
Hei! Olen Pekka Mikkola ja ikäni on 24. Olen siis täysi-ikäinen!

Muuttuja pekka viittaa siis ensin yhteen olioon, mutta rupeaa sitten viittaamaan toiseen olion. Seuraavassa kuva tilanteesta viimeisen koodirivin jälkeen:

Jatketaan vielä esimerkkiä laittamalla h viittaamaan "ei mihinkään", eli None:en:

pekka = Henkilo("Pekka", 24);
print pekka;
h = pekka;

h.vanhene(26);

print pekka;

pekka = Henkilo("Pekka Mikkola", 24);
print pekka;

h = None;
print h;

Viimeisen koodirivin jälkeen näyttää seuraavalta:

Alempaan olioon ei nyt viittaa kukaan. Oliosta on tullut "roska". Pythonin roskienkerääjä käy siivoamassa aika ajoin roskaksi joutuneet oliot. Jos näin ei tehtäisi, ne jäisivät kuluttamaan turhaan koneen muistia ohjelman suorituksen loppuun asti.

Huomaamme, että viimeisellä rivillä yritetään vielä tulostaa "ei mitään" eli None. Käy seuraavasti:

Hei! Olen Pekka ja ikäni on 24. Olen siis täysi-ikäinen!
Hei! Olen Pekka ja ikäni on 50. Olen siis täysi-ikäinen!
Hei! Olen Pekka Mikkola ja ikäni on 24. Olen siis täysi-ikäinen!
None

Mitä tapahtuu jos yritämme kutsua "ei minkään" metodia, esim. metodia vanhene:


pekka = Henkilo("Pekka", 24);
print pekka;
h = pekka;

h.vanhene(26);

print pekka;

pekka = Henkilo("Pekka Mikkola", 24);
print pekka;

h = None;
h.vanhene();
print h;

Tulos:

Hei! Olen Pekka ja ikäni on 24. Olen siis täysi-ikäinen!
Hei! Olen Pekka ja ikäni on 50. Olen siis täysi-ikäinen!
Hei! Olen Pekka Mikkola ja ikäni on 24. Olen siis täysi-ikäinen!
Traceback (most recent call last):
  File "<osoite-lähdekooditiedostoon>", line 38, in 
    h.vanhene();
AttributeError: 'NoneType' object has no attribute 'vanhene'

Eli käy huonosti. Tämän on ehkä ensimmäinen kerta elämässäsi kun näet tekstin AttributeError: 'NoneType' object has no attribute 'vanhene'. Voimme luvata, että tulet näkemään sen vielä uudelleen.

Koodissa on käsite! Tehdään siitä olio!

Rakensimme aiemmin askel askeleelta ohjelmaa sanojen kyselyyn. Tehtäväkuvaus oli seuraava:

Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa saman sanan uudestaan. Voit olettaa, että käyttäjä antaa korkeintaan 1000 sanaa.

Anna sana: porkkana
Anna sana: selleri
Anna sana: nauris
Anna sana: lanttu
Anna sana: selleri
Annoit saman sanan uudestaan!

Päädyimme luvussa 17 seuraavaan ratkaisuun:

def onJoSyotetty(sana, joSyotetyt):
  if sana in joSyotetyt:
    return True;

  return False;


sanat = []; # luodaan tyhjä lista

while True:
  sana = raw_input("Anna sana: ");

  if onJoSyotetty(sana, sanat):
    break;

  sanat.append(sana);

print "Annoit saman sanan uudestaan";

Totesimme tällöin, että vaikka ratkaisu oli valmis, oli se luettavuudeltaan kohtuullinen.

Pääohjelman käyttämät apumuuttujat, ohjelmassamme lista sanat ovat todella ikäviä "matalan tason" detaljeja pääohjelman kannalta. Pääohjelman kannaltahan on oleellista, että muistetaan niiden sanojen joukko jotka on nähty jo aiemmin. Sanojen joukko, tämähän on selkeä erillinen "käsite", tai abstraktio. Tälläiset selkeät käsitteet ovat potentiaalisia olioita ja huomattuaan koodissaan "käsitteen" viisas ohjelmoija eristää sen luokaksi.

Eli päätämme tehdä luokan Sanajoukko. Haluaisimme, että pääohjelma näyttää seuraavalta:

aiemmatSanat = Sanajoukko();

while True:
  sana = raw_input("Anna sana: ");
  
  if aiemmatSanat.sisaltaa(sana):
    break;

  aiemmatSanat.lisaa(sana);

print "Annoit saman sanan uudestaan";

Pääohjelman, eli sanajoukon käyttäjän kannalta Sanajoukko olisi hyvä jos sillä olisi metodit sisaltaa jolla tarkastetaan sisältyykö annettu sana jo sanajoukkoon ja lisaa jolla annettu sana lisätään joukkoon.

Huomaamme, että näin kirjoitettuna pääohjelman luettavuus taas hieman parempi kuin listaa käyttäen. Pääohjelma on lähes suomen kieltä!

Luokan Sanajoukko runko näyttää seuraavanlaiselta:

class Sanajoukko:
  # sopivia oliomuuttujia 	

  # konstruktori

  # metodi sisaltaa

  # metodi lisaa

Voimme toteuttaa sanajoukon siirtämällä aiemman ratkaisumme listan sanajoukon oliomuuttujaksi:

class Sanajoukko:
    sanat = []
    
    def sisaltaa(self, sana):
        return sana in self.sanat;

    def lisaa(self, sana):
        self.sanat.append(sana);

Ratkaisu on nyt varsin elegantti. Erillinen käsite on saatu erotettua ja pääohjelma näyttää siistiltä. Kaikki "likaiset yksityiskohdat" (lista ja lukumäärä sanoista) on saatu siivottua eli kapseloitua olion sisälle.

Voi olla, että jatkossa ohjelmaa halutaan laajentaa siten, että Sanajoukko-luokan olisi osattava uusia asiota. Jos esim. pääohjelmassa haluttaisiin tietää kuinka moni syötetyistä sanoista oli palindromi, voidaan sanajoukkoa laajentaa metodilla palindromeja:


aiemmatSanat = Sanajoukko();

while True:
  sana = raw_input("Anna sana: ");

  if aiemmatSanat.sisaltaa(sana):
    break;

  aiemmatSanat.lisaa(sana);

print "Annoit saman sanan uudestaan";
print "Sanoistasi " + str(aiemmatSanat.palindromeja()) + " oli palindromeja";

Pääohjelma säilyy siistinä, viimeisellä rivillä kutsutaan uutta hyvin nimettyä metodia, palindromien laskeminen jää Sanajoukko-olion huoleksi. Metodin toteutus voisi olla seuraavanlainen:

class Sanajoukko:
    # ...

    def palindromeja(self):
        palindromit = 0;
        for sana in self.sanat:
            if self.onPalindromi(sana):
                palindromit = palindromit + 1;

        return palindromit;

    def onPalindromi(self, sana):
        loppu = len(sana) - 1;

        for indeksi in range(len(sana) / 2):
            if sana[indeksi] != sana[loppu-indeksi]:
                return False;

        return True;

Eli metodi palindromeja käy läpi kaikki joukon sanat ja apumetodin onPalindromi avulla laskee paindromien määrän.

Lisää luokista ja olioista

Metodin parametrina olio

Olemme nähneet että metodien parametrina voi olla esim. kokonaislukumuuttuja, merkkijono, liukuluku tai lista. Listat ja merkkijonot ovat olioita, joten kuten arvata saattaa, metodi voi saada parametriksi minkä tahansa tyyppisen olion. Demonstroidaan tätä esimerkillä.

Linnanmäen laitteisiin hyväksytään henkilöitä joiden pituus ylittää tietyn annetun rajan. Kaikissa laitteissa raja ei ole sama. Tehdäänlaitteesta vastaava luokka. Olioa luotaessa annetaan konstruktorille parametriksi pienin pituus millä laitteeseen jäseneksi pääsee ja laitteen nimi.

class Laite:
    def __init__(self, nimi, pituusraja):
        self.nimi = nimi;
        self.pituusraja = pituusraja;

Tehdään sitten metodi jonka avulla voidaan tarkastaa hyväksytäänkö tietty henkilö laitteen kyytiin, eli onko henkilön pituus tarpeeksi suuri. Metodi palauttaa True jos hyväksytään, False jos ei.

class Laite:
    def __init__(self, nimi, pituusraja):
        self.nimi = nimi;
        self.pituusraja = pituusraja;

    def paaseeKyytiin(self, Henkilo):
        if Henkilo.pituus >= self.pituusraja:
            return True;

        return False;

Huom! tässä käytetään aiemmin luotua Henkilo-oliota.

Testipääohjelma:

vuoristorata = Laite("Vuoristorata", 120);
pekka = Henkilo("Pekka", 24);

if vuoristorata.paaseeKyytiin(pekka):
    print pekka.nimi + " pääsi kyytiin :)";
else:
    print pekka.nimi + " ei päässyt kyytiin :("

Ohjelma tulostaa:

Pekka ei päässyt kyytiin :(

Huomaa että voit viitata oliomuuttujiin .-operaattorin avulla. Esimerkiksi pekka-olion muuttujaan nimi pääsee käsiksi kirjoittamalla pekka.nimi.

Metodille voi siis antaa myös olioita parametrina. Pythonilla ohjelmoitaessa on tapana määritellä metodissa annettavat oliotyyppiset parametrit Isolla alkukirjaimella:

    def paaseeKyytiin(self, Henkilo):
        if Henkilo.pituus >= self.pituusraja:
            return True;

        return False;
Tee nyt viikon 6 tehtävät 1

Metodin parametrina toinen samantyyppinen olio

Jatkamme edelleen luokan Henkilo parissa. Haluamme vertailla kahden henkilön ikää. Vertailu voidaan hoitaa usealla tavalla. Henkilön ikä-muuttujaan voitaisiin viitata suoraan pääohjelmassa, jolloin kahden henkilön iän vertailu tapauhtuisi tällöin seuraavasti:

pekka = Henkilo("Pekka");
juhana = Henkilo("Juhana") 
 
if pekka.ika > juhana.ika:
  print pekka.nimi + " on vanhempi kuin " + juhana.nimi;

Opettelemme kuitenkin nyt hieman "oliohenkisemmän" tavan kahden henkilön ikävertailun tekemiseen.

Teemme Henkilo-luokalle metodin vanhempiKuin jonka avulla tiettyä henkilö-olioa voi verrata parametrina annettuun henkilöön iän perusteella.

Metodia on tarkoitus käyttää seuraavaan tyyliin:

pekka = Henkilo("Pekka", 20);
juhana = Henkilo("Juhana") 
 
if pekka.vanhempiKuin(juhana):
  print pekka.nimi + " on vanhempi kuin " + juhana.nimi;
else:
  print pekka.nimi + " ei ole vanhempi kuin " + juhana.nimi;

Tässä siis kysytään Pekalta onko hän Juhanaa vanhempi, Pekka vastaa True jos on ja False muuten. Eli teknisesti ottaen kutsutaan "Pekkaa" vastaavan olion johon pekka viittaa metodia, ja parametriksi annetaan "Juhanaa" vastaavan olion viite juhana.

Ohjelman kuuluu tulostaa

Pekka on vanhempi kuin Juhana

Metodi saa siis parametrikseen henkilöolion (tarkemmin sanottuna viitteen henkilöolioon, eli "langan päässä" olevan henkilön) ja vertaa omaa ikäänsä self.ika verrattavaksi annetun henkilön ikään VerrattavaHenkilo.ika. Toteutus näyttää seuraavalta:

class Henkilo:
    # ...

    def vanhempiKuin(self, Henkilo):
        return self.ika > Henkilo.ika;

Pystymme siis lukemaan toiseen olioon liittyvän ika-muuttujan arvon kirjoittamalla Henkilo.ika.

Tee nyt viikon 6 tehtävät 2

Olioita listalla

Aiemmin tutustuttiin helppokäyttöiseen listarakenteeseen. Listoille pystyi helposti laittamaan esim. merkkijonoja ja listalla olevien merkkijonojen läpikäynti, etsiminen, poistaminen, järjestäminen ym. olivat vaivattomia toimenpiteitä.

Listoihin voidaan laittaa minkä tahansa tyyppisiä oliota. Luodaan seuraavassa henkilölista ja lisätään sinne muutama henkilöolio:

lista = [];

# voidaan ensin luoda henkiloolio muuttujaan
juhana = Henkilo("Juhana");
# ja lisätä se sitten listalle
lista.append(juhana);

# tai voidaan myös luoda olio lisättäessä
lista.append(Henkilo("Matti"));
lista.append(Henkilo("Martin"));

print "Henkilöt vastasyntyneenä: ";
for henkilo in lista:
    print henkilo;

for henkilo in lista:
    henkilo.vanhene(30);


print "Henkilöt 30 vuoden kuluttua: ";
for henkilo in lista:
    print henkilo;

Ohjelman tulostus:

Henkilöt vastasyntyneenä: 
Hei! Olen Juhana ja ikäni on 0
Hei! Olen Matti ja ikäni on 0
Hei! Olen Martin ja ikäni on 0
Henkilöt 30 vuoden kuluttua: 
Hei! Olen Juhana ja ikäni on 30
Hei! Olen Matti ja ikäni on 30
Hei! Olen Martin ja ikäni on 30
Tee nyt viikon 6 tehtävät 3

Luokat erillisissä tiedostoissa

Pythonissa voidaan ajatella erillisiä lähdekooditiedostoja Moduuleina. Moduulit voivat sisältää funktioita ja / tai luokkamäärittelyjä. Luo erillinen tiedosto Luokat.py ja kopioi seuraava lähdekoodi tiedoston sisään:

class Henkilo:
    def __init__(self, nimi, ika=0):
        self.nimi = nimi;
        self.ika = ika;
        self.pituus = 0;
        self.paino = 0;

    def taysiIkainen(self):
        if self.ika < 18:
            return False;

        return True;

    def vanhempiKuin(self, Henkilo):
        return self.ika > Henkilo.ika;

    def __str__(self):
        return "Hei! Olen " + self.nimi + " ja ikäni on " + str(self.ika);

    def vanhene(self, maara=1):
        self.ika = self.ika + maara;


class Laite:
    def __init__(self, nimi, pituusraja):
        self.nimi = nimi;
        self.pituusraja = pituusraja;

    def paaseeKyytiin(self, Henkilo):
        if Henkilo.pituus >= self.pituusraja:
            return True;

        return False;

Luo tämän jälkeen samaan kansioon toinen lähdekooditiedosto nimelta ohjelma.py. Kopioi seuraava lähdekoodi tiedoston ohjelma.py sisään:

# -*- coding: utf-8 -*-
from Luokat import *;

vuoristorata = Laite("Vuoristorata", 120);
pekka = Henkilo("Pekka", 24);

if vuoristorata.paaseeKyytiin(pekka):
    print pekka.nimi + " pääsi kyytiin :)";
else:
    print pekka.nimi + " ei päässyt kyytiin :("

Kun suoritat lähdekooditiedoston ohjelma.py näet seuraavan tulostuksen:

Pekka ei päässyt kyytiin :(

Tiedoston ohjelma.py toisella rivillä oleva komento from Luokat import *; lataa kaikki määrittelyt tiedostosta Luokat.py, jonka jälkeen voimme käyttää niitä pääohjelmassamme.

Ohjelmien pilkkominen useampaan osaan -- oleellinen ohjelmoijan taito -- ei koske pelkästään ohjelman pilkkomista metodeiksi ja luokiksi, vaan myös erillisiksi lähdekooditiedostoiksi pilkkomista.

Toisen tekemän ohjelmakoodin lukeminen

Yksi ohjelmoijan tärkeimmistä taidoista on muiden tekemän koodin lukeminen. Tutustu alla olevaan ohjelmakoodiin ja ota selvää mitä se tekee. Koodi käyttää pygame-kirjastoa, joka on pythonille tarkoitettu pelien tekemistä helpottava apukirjasto.

Tehtävänäsi on pilkkoa alla oleva ohjelmakoodi osiin. Paras lähestymistapa on piirtää paperille erilliset luokat, joista ohjelma koostuu. Tämän jälkeen luokista voi hahmotella niihin liittyvää toiminnallisuutta -- eli tutustua niiden metodeihin. Kun olet jo hieman hahmottanut yksittäisten luokkien rakennetta, ala ottamaan selville miten luokat kommunikoivat keskenään.

Lisää pelien tekemisestä ja pygame-kirjastosta löytyy täältä!

# -*- coding: utf-8 -*-
import pygame, sys

class Suunta:
    VASEMMALLE = 1
    OIKEALLE = 2
    YLOS = 3
    ALAS = 4


class Mato:
    def __init__(self, alueenleveys=32, alueenkorkeus=20):
        self.osat = []
        self.suunta = Suunta.VASEMMALLE
        self.x = alueenleveys / 2
        self.y = alueenkorkeus / 2
        self.pituus = 5
    
    def muutaSuuntaa(self, uusiSuunta):
        # ei saa lähteä vastakkaiseen suuntaan
        if self.suunta == Suunta.VASEMMALLE and uusiSuunta == Suunta.OIKEALLE:
            return
        if self.suunta == Suunta.OIKEALLE and uusiSuunta == Suunta.VASEMMALLE:
            return
        if self.suunta == Suunta.YLOS and uusiSuunta == Suunta.ALAS:
            return
        if self.suunta == Suunta.ALAS and uusiSuunta == Suunta.YLOS:
            return

        self.suunta = uusiSuunta

    def kaannyVasemmalle(self):
        self.muutaSuuntaa(Suunta.VASEMMALLE)

    def kaannyOikealle(self):
        self.muutaSuuntaa(Suunta.OIKEALLE)
    
    def kaannyYlos(self):
        self.muutaSuuntaa(Suunta.YLOS)

    def kaannyAlas(self):
        self.muutaSuuntaa(Suunta.ALAS)

    def liiku(self):
        # ensin liikkuminen
        if self.suunta == Suunta.VASEMMALLE:
            self.x = self.x - 1
        if self.suunta == Suunta.OIKEALLE:
            self.x = self.x + 1
        if self.suunta == Suunta.YLOS:
            self.y = self.y - 1
        if self.suunta == Suunta.ALAS:
            self.y = self.y + 1
        
        # sitten kasvaminen ja lyhentaminen
        self.osat.append((self.x, self.y))    
        if len(self.osat) > self.pituus:
            self.osat.pop(0)

    def kasva(self):
        self.pituus = self.pituus + 1
        
    def onAlueella(self, alueenleveys=32, alueenkorkeus=20):
        for osa in self.osat:
            if osa[0] < 0 or osa[0] > alueenleveys:
                return False
            if osa[1] < 0 or osa[1] > alueenleveys:
            	return False

        return True

    def osuu(self, x, y):
        for osa in self.osat:
            if osa[0] == x and osa[1] == y:
            	return True

        return False


# tapahtumankäsittelijälle annetaan pygamesta tulevat tapahtumat
# ja se hoitaa pelimaailman hahmojen liikkeet
class Tapahtumankasittelija:
    def __init__(self, mato):
        self.mato = mato

    def hoidaTapahtuma(self, tapahtuma):
        if tapahtuma.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

        if tapahtuma.type == pygame.KEYDOWN:
            if tapahtuma.key == pygame.K_LEFT:
                self.mato.kaannyVasemmalle()
            if tapahtuma.key == pygame.K_RIGHT:
                self.mato.kaannyOikealle()
            if tapahtuma.key == pygame.K_UP:
                self.mato.kaannyYlos()
            if tapahtuma.key == pygame.K_DOWN:
                self.mato.kaannyAlas()

class Naytto:
    def __init__(self, mato, pelinnimi="Matopeli", leveys=32, korkeus=20, ruudunleveys=20):
        self.mato = mato
        self.naytto = pygame.display.set_mode((leveys*ruudunleveys, korkeus*ruudunleveys))
        self.ruudunleveys = ruudunleveys

        pygame.display.set_caption(pelinnimi)

    def piirra(self):
        # tyhjennetään tausta täyttämällä se mustalla värillä
        self.naytto.fill((0, 0, 0))

        # piirretaan mato
        rl = self.ruudunleveys 
        for pala in self.mato.osat:
            pygame.draw.rect(self.naytto, (255, 0, 0), (pala[0] * rl, pala[1] * rl, rl, rl))
        
        # naytetaan piirretty alue
        pygame.display.flip()


class Peli:
    def __init__(self, mato, tapahtumankasittelija, naytto):
        self.mato = mato
        self.tapahtumankasittelija = tapahtumankasittelija
        self.naytto = naytto

        self.kello = pygame.time.Clock()


    # tapahtuman käsittely
    def hoidatapahtumat(self):
        tapahtuma = pygame.event.poll()
        self.tapahtumankasittelija.hoidaTapahtuma(tapahtuma)

    def hoidalogiikka(self):
        self.mato.liiku()

    def piirra(self):
        self.naytto.piirra()


    def pelaa(self):
        while True:
            self.hoidatapahtumat()
            self.hoidalogiikka()
            self.piirra()
            self.kello.tick(10)
  


## itse ohjelma:

# mato
mato = Mato()

# tapahtumankäsittelijä
tapahtumankasittelija = Tapahtumankasittelija(mato)

naytto = Naytto(mato)

peli = Peli(mato, tapahtumankasittelija, naytto)
peli.pelaa()

Onnea ohjelmoijan uralle! Jos haluaisit nähdä materiaalissa selvennystä joihinkin osioihin -- tai lisää johonkin liittyen -- muista lähettää palautetta ja toiveita materiaalin alussa esitettyyn sähköpostiosoitteeseen!