Ohjelmoinnin perusteet Python-kielellä

Arto Vihavainen, Matti Paksula, Matti Luukkainen, Antti Laaksonen, Pekka Mikkola, Juhana Laurinharju, Martin Pärtel

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.

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ö

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: latin-1 -*-. Tämä kertoo pythonille että merkistönä käytetään latin-1 -merkistöä.

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

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

Huomaa että rivin # -*- coding: latin-1 -*- 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!
Tee nyt viikon 1 tehtävät 2

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: latin-1 -*-
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: latin-1 -*-

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: latin-1 -*-

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: latin-1 -*-
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: latin-1 -*-
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

Miksi seuraava esimerkki kaataa ohjelman?

kirja = "Kalavale";
print "Kirjan viimeinen merkki on " + kirja[len(kirja)];
Tee nyt viikon 3 tehtävät 1

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
Tee nyt viikon 3 tehtävät 2

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: latin-1 -*-

# 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: latin-1 -*-

# 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ä.

Tee nyt viikon 3 tehtävät 3

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!
Tee nyt viikon 3 tehtävät 4

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;
Tee nyt viikon 3 tehtävät 5

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;
Tee nyt viikon 3 tehtävät 6

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: latin-1 -*-
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.

Tee nyt viikon 4 tehtävät 1

Listan järjestäminen

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

# -*- coding: latin-1 -*-
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ä.

Tee nyt viikon 4 tehtävät 2

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: latin-1 -*-
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: latin-1 -*-
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: latin-1 -*-
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: latin-1 -*-
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: latin-1 -*-
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ä!)

Tee nyt viikon 4 tehtävät 3

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: latin-1 -*-

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: latin-1 -*-

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: latin-1 -*-

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.

Tee nyt viikon 4 tehtävät 6

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: latin-1 -*-

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: latin-1 -*-

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: latin-1 -*-

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: latin-1 -*-

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: latin-1 -*-

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);
Tee nyt viikon 5 tehtävät 1
Tee nyt viikon 5 tehtävät 2

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

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.

Tee nyt viikon 5 tehtävät 4, 5 ja 6

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: latin-1 -*-
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. Onnea ohjelmointiuralle!