2 Algoritmit ja ohjelmointi



Samankaltaiset tiedostot
Tutoriaaliläsnäoloista

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)

811120P Diskreetit rakenteet

811120P Diskreetit rakenteet

Muistutus aikatauluista

Java-kielen perusteet

Ohjelmoinnin perusteet Y Python

Osoitin ja viittaus C++:ssa

Java-kielen perusteita

Java-kielen perusteet

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Ongelma(t): Miten mikro-ohjelmoitavaa tietokonetta voisi ohjelmoida kirjoittamatta binääristä (mikro)koodia? Voisiko samalla algoritmin esitystavalla

3. Muuttujat ja operaatiot 3.1

Ohjelmoinnin perusteet Y Python

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssi Y1

ITKP102 Ohjelmointi 1 (6 op)

Algoritmit 2. Luento 8 To Timo Männikkö

Zeon PDF Driver Trial

Ohjelmoinnin perusteet Y Python

Palautetta viime luennosta

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 4: Ohjelmointi, skriptaus ja Python

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Kerta 2. Kerta 2 Kerta 3 Kerta 4 Kerta Toteuta Pythonilla seuraava ohjelma:

Ohjelmoinnin peruskurssi Y1

Imperatiivisen ohjelmoinnin peruskäsitteet. Meidän käyttämän pseudokielen lauseiden syntaksi

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin peruskurssi Y1

PERL. TIE Principles of Programming Languages. Ryhmä 4: Joonas Lång & Jasmin Laitamäki

Mathematica Sekalaista asiaa

Harjoitus 3 (viikko 39)

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin perusteet Y Python

Matriisit ovat matlabin perustietotyyppejä. Yksinkertaisimmillaan voimme esitellä ja tallentaa 1x1 vektorin seuraavasti: >> a = 9.81 a = 9.

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

System.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Taulukot. Jukka Harju, Jukka Juslin

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Tietorakenteet ja algoritmit

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin peruskurssi Y1

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

System.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);

ITKP102 Ohjelmointi 1 (6 op)

12. Javan toistorakenteet 12.1

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

1. Omat operaatiot 1.1

Ohjelmoinnin perusteet Y Python

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Johdatus Ohjelmointiin

Pythonin Kertaus. Cse-a1130. Tietotekniikka Sovelluksissa. Versio 0.01b

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

Python-ohjelmointi Harjoitus 2

Ohjelmoinnin perusteet Y Python

13. Loogiset operaatiot 13.1

Python-ohjelmointi Harjoitus 5

Ohjelmoinnin perusteet Y Python

Algoritmit 2. Luento 13 Ti Timo Männikkö

Kysymyksiä koko kurssista?

Aliohjelmatyypit (2) Jakso 4 Aliohjelmien toteutus

Ohjelmoinnin perusteet Y Python

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

Jakso 4 Aliohjelmien toteutus

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

Luento 5. Timo Savola. 28. huhtikuuta 2006

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin perusteet Y Python

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta.

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Tieto- ja tallennusrakenteet

12. Javan toistorakenteet 12.1

Jakso 4 Aliohjelmien toteutus

4. Lausekielinen ohjelmointi 4.1

815338A Ohjelmointikielten periaatteet

Harjoitustyö: virtuaalikone

Ohjelmoinnin perusteet Y Python

Luento 4 Aliohjelmien toteutus

811312A Tietorakenteet ja algoritmit I Johdanto

Tietotekniikan valintakoe

Vasen johto S AB ab ab esittää jäsennyspuun kasvattamista vasemmalta alkaen:

Ohjelmoinnin perusteet Y Python

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Transkriptio:

2 Algoritmit ja ohjelmointi 2.1 Algoritmin käsite ja historiallista taustaa Algoritmi on suoritusjärjestyksessä oleva joukko yksikäsitteisiä, toteutettavissa olevia käskyjä, jotka määrittävät äärellisen prosessin. Ensinnäkin, algoritmin askelten tulee olla järjestyksessä. Tämä tarkoittaa sitä, että algoritmilla tulee olla selkeä rakenne. Sen ei tarvitse olla peräkkäinen (ensin toteutetaan käsky 1, sitten käsky 2, jne.). Jotkin algoritmit, ns. rinnakkaisalgoritmit, sisältävät useita käskyjonoja, jotka eri prosessorit toteuttavat moniajoympäristössä. Muista esimerkkejä löytyy virtapiireillä toteutetuista algoritmeista, joissa jokainen portti voidaan ajatella yhdeksi askeleeksi algoritmissa. Toiseksi, algoritmin askeleitten täytyy olla toteutettavissa. Ajatellaanpa seuraavaa algoritmia : 1. Luettele kaikki positiiviset kokonaisluvut 2. Järjestä ne laskevaan järjestykseen 3. Valitse listan ensimmäinen Nämä ohjeet eivät muodosta algoritmia, koska askeleet 1 ja 2 ovat mahdottomia toteuttaa. Kaikkia positiivisia kokonaislukuja ei pysty luettelemaan. Ja ainakaan niitä ei pysty järjestämään suurimmasta alkaen. Mikä on suurin kokonaisluku? Sellaista ei ole! 85 86 Määritelmä edellyttää myös, että askeleet ovat yksikäsitteisiä. Puhuttaessa yksikäsitteisyydestä erottelu algoritmin ja sen esityksen välillä on tärkeä. Meteorologille ohje muunna Celsius-astelukema Fahrenheiteiksi on riittävä, mutta maallikko voi haluta yksityiskohtaisempaa ohjetta esim. muunnoskaavan F = 9 5C + 32 muodossa. Tulemme näkemään, kuinka primitiivejä käytetään epäselvyyden poistamiseen. Vaatimus, että algoritmi määrittää äärellisen prosessin, tarkoittaa sitä, että algoritmin suoritus päättyy joskus. Tämä vaatimus on peräisin teoreettisesta tietojenkäsittelytieteestä, joka pyrkii vastaamaan sellaisiin kysymyksiin kuin mitkä ovat koneiden ja algoritmien rajat? Tällöin pyritään erottamaan algoritmisesti ratkeavat ongelmat. Päättymättömillä prosesseilla on kuitenkin useita hyödyllisiä sovelluksia, kuten sairaalapotilaan elintoimintojen tarkkailu tai lennonvalvonta. 87 88

Algoritmin esittäminen vaatii jonkinlaista kieltä. Ihmisen tapauksessa kyse voisi olla luonnollisesta kielestä. Tietojenkäsittelyoppi lähentyy ongelmaa määrittämällä algoritmien rakenneosat, primitiivit, joita tarkastellaan myöhemmin. Antamalla näille tarkat määritelmät saadaan poistettua moniselitteisyys, ja vaatimalla niiden käyttöä aina algoritmeja rakennettaessa, saavutetaan yhdenmukainen yksityiskohtaisuuden aste. Primitiivit, sekä säännöt siitä, miten niitä yhdistellään, muodostavat ohjelmointikielen. Jokainen primitiivi sisältää kaksi asiaa: kieliopin (syntaksin) ja merkityksen (semantiikan). Syntaksi tarkoittaa symbolin muodollista esittämistä ja semantiikka sen sisältöä. Syntaksi vastaa kysymykseen: miten?. Ohjelma on syntaktisesti oikein, jos se noudattaa kielen syntaksia. Syntaksivirheellä tarkoitetaan kielen syntaksin rikkomista. Syntaksivirhe estää lauseen suorittamisen ja usein yksikin virheellinen lause estää koko ohjelman suorittamisen. Semantiikka määrää, mitä kielen lauseet tarkoittavat. Se vastaa siis kysymykseen: mitä?. Ohjelmointikielet on suunniteltu siten, että ne ovat syntaksiltaan ja semantiikaltaan huomattavasti yksinkertaisempia kuin luonnolliset kielet. 89 90 Valitsemalla muistipaikoille, operandeille ja komennoille kuvaavat nimet ohjelmoija saattoi suuresti parantaa ohjelman luettavuutta. Tarkastellaan esimerkiksi tilannetta että meidän pitää laskea yhteen muistipaikoissa 1010 ja 1011 olevat ja tallentaa tulos paikkaan 1100, voisimme toimia seuraavasti. Käytämme muistipaikasta 1010 nimeä HINTA, muistipaikasta 1011 nimeä VERO ja paikasta 1100 nimeä KOKONAIS, sama rutiini näyttää seuraavalta: LOAD R5 HINTA LOAD R6 VERO ADD R0 R5 R6 STORE R0 KOKONAIS HALT Kun tällaiset tekniikat tulivat alkujaan käyttöön, ohjelmoijat käyttivät tätä merkintätapaa suunnitellessaan ohjelmaa paperille, ja myöhemmin käänsivät sen konekieliseen asuun. Ei kestänyt kuitenkaan kauan, kun tajuttiin, että käännösprosessin voisi antaa koneelle itselleen. Niinpä kehitettiin ohjelmia, jotka käänsivät kuvailevassa muodossa olevan tekstin koneen ymmärtämään muotoon. Näitä ohjelmia kutsuttiin assemblereiksi. Nimi tulee englannin kielen to assemble -verbistä, jonka katsottiin kuvaavan ohjelman toimintaa. Tällaisia kieliä kutsuttiin taas assembly-kieliksi. Komento HALT tarkoittaa lopetusta. 91 92

Kun assembly-kielet kehitettiin, niiden katsottiin edustavan suurta harppausta kohti parempaa ohjelmointitekniikkaa. Itse asiassa monet katsoivat niiden edustavan kokonaan uutta ohjelmointikielten sukupolvea. Aikanaan assembly-kielet tultiin tuntemaan toisen sukupolven kielinä; ensimmäinen sukupolvi oli itse konekieli. On kuitenkin huomattava, että assembly-kielten primitiivit ovat samoja, kuin vastaavassa konekielessä. Niinpä assembly-kielellä kirjoitettu ohjelma on olennaisesti koneriippuvainen. Ohjelman antamat ohjeet oli ilmaistu tietyn koneen ehdoilla. Assembly-kielellä kirjoitettua ohjelmaa ei voinut helposti siirtää toiselle koneelle. Se täytyi kirjoittaa uudelleen vastaamaan toisen koneen rekistereitä ja siinä noudatetun käskyrakenteen mukaisesti. Toinen assembly-kielten heikkous on, että ohjelmoijan täytyy edetä koneen sanelemin pienin askelin. Tietojenkäsittelyopin tutkijat alkoivat kehittää ohjelmointikieliä, joiden varaan olisi helpompi rakentaa ohjelmia kuin assembly-kielillä. Tuloksena oli ohjelmointikielien kolmas sukupolvi, jonka kielet olivat sekä korkeammalla tasolla, että laiteriippumattomia. Tunnettuja esimerkkejä ovat FORTRAN (FORmula TRANslator), joka kehitettiin tieteellisiin tarkoituksiin, sekä COBOL (COmmon Business Oriented Language). Yleisesti kolmannen sukupolven kielten tarkoituksena oli tunnistaa korkean tason primitiivit, joiden varaan ohjelmat voisi rakentaa. Esimerkiksi lause assign kokonais value hinta + vero ilmaisee edellä tarkastelemamme kahden keskusmuistissa olevan luvun hinta ja vero summan arvon ja tallettaa sen muistipaikkaan kokonais viittaamatta siihen, miten koneen pitäisi se suorittaa. 93 94 Yleisesti ottaen rakenne assign tunnistin value lauseke on selvästikin eräs korkean tason primitiivi. Edellä olevat esimerkit on esitetty niin sanotulla pseudokoodilla, joka on merkintätapa, jonka avulla voidaan esittää algoritmisia ideoita sitomatta kuitenkaan esitystä mihinkään olemassa olevaan ohjelmointikieleen. Kun nämä korkean tason primitiivit oli saatu määritetyksi, kirjoitettiin käännösohjelma, joka käänsi näillä korkean tason primitiiveillä kirjoitetun ohjelman konekielisiksi. Niiden täytyy koota (to compile) useita konekielisiä käskyjä yhteen halutun primitiivin toteuttamiseksi. Tällaisia ohjelmia kutsutaan kääntäjiksi (compiler). Käännösohjelman suosittu vaihtoehto, tulkki (interpreter), ilmaantui kolmannen sukupolven kielten toisenlaisen käytön yhteydessä. Tulkit toteuttavat ohjeet sitä mukaa kun niitä käännettiin, ne eivät siis varastoineet koko ohjelman käännettyä versiota. 95 Kolmannen sukupolven kielten myötä laiteriippumattomuus pääosin toteutui. Ne eivät sisältäneet viittauksia laitteisiin, joten niitä saattoi periaatteessa käyttää millä tahansa koneella sopivan kääntäjän avulla. Todellisuus osoittautui kuitenkin monimutkaisemmaksi. Kun kääntäjä rakennetaan, sen käyttämä kone asettaa joka tapauksia rajoituksia: esim. rekisterien koko ja muistitilan määrä vaikuttavat käsiteltävien kokonaislukujen määrään. Moisista ehdoista seuraa, että samalla ohjelmointikielellä on eri murteita koneesta riippuen. Keskeistä tässä ongelmassa on se, ettei välttämättä ole yksimielisyyttä kielen tarkasta määritelmästä. Tämän vuoksi esim. American National Standard Institute (ANSI) ja International Organization for Standardization (ISO) ovat ottaneet käyttöön ja julkaisseet monen suositun kielen standardit. Toisaalta jotkin epäviralliset standardit ovat tulleet suosituiksi, koska ohjelmoijat ovat halunneet kirjoittaa ohjelmia noilla murteilla. 96

2.2 Perinteiset ohjelmointikäsitteet Imperatiivinen paradigma edustaa perinteistä lähestymistapaa ohjelmointiin. CPU:n hae-dekoodaa-suorita -sykli perustuu juuri tähän malliin. Toinen malli jota jonkin verran tarkastelemme on olio-orientoitunut malli, jossa data nähdään aktiivisina olioina pikemmin kuin imperatiivisen paradigman passiivisina kohteina. Seuraavaksi tarkastellaan imperatiivisten ohjelmointikielten yleisiä käsitteitä. Tätä varten otetaan yksinkertaisia esimerkkejä Python-kielestä, joka on tulkattava kieli. Monien tulkattavien kielten tapaan Pythonia voi kirjoittaa interaktiivisesti. Tällöin komennot syötetään suoraan Python-tulkin kehotteeseen. Käynnistyttyään käskyllä python komentotulkki siirtyy komentotilaan, jota osoittaa kunkin rivin alussa olevat merkit >>>. Jos ohjelma on vähänkin pidempi, se kannattaa tietenkin tallettaa tiedostoon. Python-ohjelman talletusmuoto on tavallinen tekstitiedosto, jonka pääte on.py. Osoitteessa http://www.python.org on Pythonin virallinen kotisivu, josta löytyy paljon hyödyllistä materiaalia sekä tietysti Python-tulkki ladattavaksi omalle koneelle. Kurssin loppupuolella tarkastellaan näitä asioita Javalla. 97 98 Lause ja lauseke Imperatiivisen algoritmin yksittäisiä käskyjä tai komentoja sanotaan lauseiksi (sentence, statement). Lausekkeella (expression) puolestaan tarkoitetaan luku- tai muita arvoja, muuttujaviittauksia ja erilaisia laskutoimituksia tai muita operaatioita sisältävää ilmausta, jolla on sen osista määräytyvä yksikäsitteinen arvo. Lausekkeen ja lauseen välinen ero on se, että lausekkeella on arvo, lauseella ei. Lauseketta ei (yleensä) voi käyttää yksinään, vaan sitä on käytettävä aina lauseen osana, mitä kuvastaa jo sanan lauseke vähättelevä loppuliitekin. Tietotyypit, muuttujat ja sijoituslauseet Olemme nähneet, että kuvaavien nimien liittäminen tiettyihin muistipaikkoihin on hyödyllisempää kuin numeeristen osoitteiden käyttäminen. Tällaisia tunnisteita kutsutaan muuttujiksi, mikä viittaa siihen, että muistipaikan sisältä voi muuttua ohjelman suorituksen edetessä. Muistamme, että tietokone käsittelee vain binääristä tietoa, eli periaatteessa nollia ja ykkösiä. Samalla merkkijonolla voi olla siis useita erilaisia tulkintoja. Tietotyypin käsite sisältää sekä datan tulkinnan, että sille sallitut operaatiot. Tavallisimpia tietotyyppejä ovat kokonaisluku, reaaliluku, merkkijono ja totuusarvo. 99 100

Kokonaisluvut talletetaan usein kahden komplementti -esityksenä ja niille sallitut toiminnot sisältävät tavalliset aritmeettiset operaatiot ja vertailut >>> 2 + 3 # yhteenlasku 5 >>> 2-3 # vähennyslasku -1 >>> 2 * 3 # kertolasku 6 >>> 2**3 # potenssiin korotus 8 >>> 2 * (2 + 5) 14 >>> 4 <= 5 # vertailu True >>> 4 == 6 # vertailu False Reaaliluku voi saada muitakin arvoja kuin kokonaisluvut. Se tallennetaan yleensä liukulukuesityksenä. Merkkijonomuuttuja talletetaan yleensä ASCII- tai Unicode-muodossa. Operaatio voi olla esimerkiksi kahden merkkijonon liittäminen peräkkäin: >>> "palo" + "auto" # yhdistetään kaksi sanaa paloauto Totuusarvoilla on käytössä mm. seuraavat operaatiot, jotka vastaavat ensimmäisessä luvussa tarkastelemiamme operaatioita. A and B: tosi jos sekä A ja B ovat totta, epätosi muulloin A or B: tosi jos A tai B ovat totta, epätosi muulloin not B: tosi, jos B on epätosi. 101 102 Lisäksi on olemassa vertailulausekkeita, jotka palauutavat totuusarvon: x == y: tosi jos x on yhtäsuuri kuin y x!= y tai x <> y: tosi jos x on erisuuri kuin y Esimerkiksi >>> "hauki" == "kala" False >>> not "hauki" == "kala" True Kaikkein perustavinta laatua oleva käskylause on sijoituslause, joka asettaa tietyn arvon muuttujaan. Sijoituslause esim. Pythonissa ja Javassa näyttää tältä muuttuja = arvo Pythonissa on käytössä ns. dynaaminen tyypitys. Tämä merkitsee sitä, että Python määrittelee muuttujan tyypin sen mukaan, mikä arvo siihen sijoitetaan. Muuttujien tyypistä ei siis tarvitse huolehtia itse, vaan Python-tulkki päättelee sen muuttujaan asetetusta arvosta. Muuttujia ei myöskään tarvitse määritellä ennen niiden käyttöä. >>> luku = 2 >>> nimi = "Kalle" >>> type(luku) <type int > >>> type(nimi) <type str > Monessa muussa kielessä, esim. Javassa, käytetään staattista tyypitystä, jossa ohjelmoijan on aina itse määriteltävä jokaisen muuttujan tyyppi: esim. int i = 12 String nimi = "Kalle" 103 104

Tarkastellaan seuraavaa esimerkkiä: Asetuslause muuttuja = arvo jäsennetään seuraavasti: 1. Lasketaan asetuslauseen oikean puolen lausekkeen arvo käyttäen sen sisältämien muuttujaviittausten arvoina muuttujien tämänhetkisiä arvoja. 2. Asetetaan tämä arvo vasemman puolen muuttujan (uudeksi) arvoksi. Jos asetettavalla muuttujalla oli ennestään jokin arvo, se häviää. Asetusoperaattorina käytettävä symbolin = ei pidä antaa hämätä: asetusoperaatio ei ole symmetrinen yhtäsuuruusoperaatio. Huomaa, että jos asetettavalla muuttujalla oli ennestään jokin arvo, se häviää. i = i + 1 Jos lausetta tarkastellaan matemaattisesti, se on mieletön, sillä siitä seuraa 0 = 1. Lause jäsennetään seuraavasti: 1. Haetaan muistipaikassa i oleva luku. 2. Lisätään tähän lukuun 1. 3. Talletetaan näin saatu uusi arvo muistipaikkaan i. >>> i = 100 # asetetaan i:n arvo >>> i = i + 1 # kasvatetaan arvoa yhdellä >>> print i # tulostetaan i:n arvo 101 105 106 Muunnoksia erityyppisten muuttujien välillä voi tehdä tyyppimuunnosten avulla. Niiden syntaksi on tyyppi(arvo). Kaikkia tyyppejä ei voi luonnollisesti muuttaa toisikseen, ja lisäksi jotkin tyyppimuunnokset voivat hävittää informaatiota. >>> x = 5 >>> type(x) <type int > >>>float(x) 5.0 >>> mjono = "0.5" >>> int(mjono) Traceback (most recent call last): File "<stdin>", line 1, in? ValueError: invalid literal for int(): 0.5 >>> float(mjono) 0.5 >>> y=float(mjono) >>> y 0.5 >>> int(y) 0 107 108

Syöttö ja tulostus Kommentit Kokemus on osoittanut, että riippumatta siitä, miten hyvin ohjelma on suunniteltu, lisäinformaatio on hyödyllistä, jopa välttämätöntä, kun ihminen yrittää ymmärtää sitä, mitä ohjelma tekee. Kommentoinnilla tarkoitetaan siis ohjelman tai jonkin ohjelman osan toimintaa kuvaavan tekstin kirjoittamista ohjelmakoodin sisään. Kommentit eivät vaikuta ohjelman toimintaan. Pythonissa tulkki ei lue enää rivejä #-merkin jälkeen. Tarkastellaan tässä lyhyesti syöttöä ja tulostamista Pythonissa. Näytölle tulostetaan komennolla print. >>> print "Hello world!" Hello world! >>> x = 2 >>> y = 6 >>> print "Luku", x, "potenssiin", y, "on", x**y Luku 2 potenssiin 6 on 64 Pythonissa syötteen lukeminen käyttäjältä tapahtuu input- tai raw_input-käskyllä. input-käskyä käytetään kun halutaan käyttäjää syöttävän numeron ja raw_input-käskyä, kun syötteeksi halutaan merkkijono. 109 110 >>> x = input("anna luku: ") Anna luku: 3 >>> print x 3 >>> type(x) <type int > >>> x = input("anna luku: ") Anna luku: Kolme Traceback (most recent call last): File "<stdin>", line 1, in? File "<string>", line 0, in? NameError: name Kolme is not defined >>> x = raw_input("anna merkkijono: ") Anna merkkijono: Kolme >>> print x Kolme >>> type(x) <type str > 111 112

Valinta Yksinkertaisin valinnan muoto on valinta jonkin toiminnon tekemisen ja sen tekemättä jättämisen välillä. 2.3 Ohjauslauseet Tähän asti on tarkasteltu vain käskyjä, jotka on suoritettu peräkkäin. Ohjauslauseet ovat käskylauseita, jotka kontrolloivat ohjelman käskyjen suoritusjärjestystä. Usein ohjauslauseita sanotaankin kontrollirakenteiksi. Tosi Ehto? Epätosi Toiminto 1 Toiminto 2 113 114 Python-kielessä valinnan syntaksi on seuraava: if ehto: toiminto 1 toiminto 2 Seuraavassa on ohjelma (tiedostossa 3Min.py), joka pyytää käyttäjältä kolme lukua ja palauttaa niistä pienimmän. Se perustuu suoraan if-else -valintaan. print "Ohjelma palauttaa kolmen luvun minimin" x = input("anna ensimmainen luku: ") y = input("anna toinen luku: ") z = input("anna kolmas luku: ") if x < y: if x < z: min = x min = z if y < z: min = y min = z print "Lukujen minimi on", min 115 116

Toisto Huomaa rivien sisentäminen. Kaikissa ohjelmointikielissä sisentäminen kuuluu hyvään ohjelmointitapaan, mutta Pythonissa on pakko sisentää, sillä se on ainoa tapa määrittää lauserakenne. Tämä tarkoittaa siis sitä, ettei Pythonissa käytettä koodilohkojen määrittämiseen mitään erityisiä merkkejä, kuten erimerkiksi Javassa kaarisulkeita { }. Todetaan vielä, että else-haara ei ole välttämätön valinnan yhteydessä. if lampotila > 25: print "On helle" Käskyjen kirjoittaminen peräkkäin sekä valinta eivät riitä algoritmeissa riittävien ilmaisuvoiman saavuttamiseksi. Riittävä yleisyys saavutetaan toiston eli iteraation avulla. Toistoa nimitetään usein myös silmukaksi. Toistorakennetta while käytetään, kun jotakin halutaan toistaa niin kauan kun jokin ehto on voimassa; ei siis välttämättä kertaakaan, jos ehto on heti epätosi. Toiminnon suorituksen jälkeen testataan ehdon voimassaolo ja toiminto suoritetaan uudestaan, mikäli ehto on voimassa. Näin ollen toistorakenteen rungossa tulee tehdä sellaisia toimenpiteitä, jotka vaikuttavat ehdon voimassaoloon. Toisin sanoen, päättymättömän silmukan välttämiseksi täytyy rakenteen sisällä olla jotakin, mikä asettaa jossain vaiheessa ehdon epätodeksi. Pythonissa while-silmukka on muotoa: while ehto: toiminto 117 118 7Kerto.py laskee luvun 7 kertotaulun. j = 1 while j <= 10: print j,"x 7 = ",j*7 j = j + 1 Tulos näyttää seuraavalta: 1 x 7 = 7 2 x 7 = 14 3 x 7 = 21 4 x 7 = 28 5 x 7 = 35 6 x 7 = 42 7 x 7 = 49 8 x 7 = 56 9 x 7 = 63 10 x 7 = 70 Toistorakennetta while voidaan havainnollistaa seuraavalla kaaviolla: Ehdon testaus Ehto tosi Toiminto Ehto epätosi While-toistorakennetta voidaan kutsua myös ns. alkuehtoiseksi toistoksi. 119 120

Monissa kielissä, ei kuitenkaan Pythonissa, on käytössä myös loppuehtoinen toisto. For-silmukka Ehto epätosi Toiminto Ehdon testaus Ehto tosi Hieman erilainen tapa kontrolloida toistojen määrää on käyttää for-silmukkaa, jolloin ns. silmukkamuuttuja käy läpi esimerkiksi kokonaislukuja joltain väliltä tai vaikkapa listan alkioita. Tarkastellaan seuraavaa Python-esimerkkiä. Kirjoitetaan seuraavat rivit tiedostoon Autot.py. # Tulostetaan listan automerkit autot = ["Audi", "BMW", "Cadillac", "Fiat", "Lada", "Opel"] for auto in autot: print auto Sen suorittaminen näyttää tietystikin seuraavalta: $ python Autot.py Audi BMW Cadillac Fiat Lada Opel 121 122 Pythonin for-silmukkaa voi kuvata seuraavasti: for muuttuja in lista: toiminto >>> x = [4,2,6,9,3,6,3] >>> x [4, 2, 6, 9, 3, 6, 3] >>> len(x) 7 >>> x[4] 3 >>> x[4] = 7 >>> x [4, 2, 6, 9, 7, 6, 3] >>> x.sort() >>> x [2, 3, 4, 6, 6, 7, 9] Esim. Javassa for-silmukkaa toimii niin, että jotain toistetaan haluttu määrä kertoja. Se toteutetaan niin, että kokonaislukutyyppinen silmukkamuuttuja alustetaan joksikin alkuarvoksi, ja niin kauan kuin silmukkamuuttujan arvo on enintään lopetusarvo, haluttu toiminto suoritetaan. for (i = 1; i <= n; i = i + 1) toiminto Tällöin toiminto suoritetaan n kertaa siten, että ensimmäisellä kerralla i = 1, toisella suorituskerralla i = 2 jne; viimeisellä kierroksella i = n. 123 124

Funktio range(n) tuottaa listan, jossa ovat luvut 0, 1, 2,..., n-1, eli lukuja on listassa n kappaletta. Parametrina voidaan antaa myös toinen luku m, jolloin komento range(m, n) tuottaa listan luvuista m, m+1, m+2,..., n-1. Käskylle voidaan antaa vielä kolmaskin parametri, joka määrittelee monta askelta tulee olla lukujen välillä. >>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> range(5,15) [5, 6, 7, 8, 9, 10, 11, 12, 13, 14] >>> range(0,20,2) [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] range-käskyn avulla on mahdollista toteuttaa helposti monenlaisia listoja, jonka vuoksi sitä voidaan käyttää for-silmukassa. Esimerkiksi seuraava algoritmi (tiedostossa TulostaLuvut.py tulostaa luvut 1, 2,..., n, missä n on ohjelman kysymä luku. # Tulostetaan luvut n = input("anna luku: ") for i in range(1,n+1): print i 125 126 Seuraava yksinkertainen ohjelma (tiedostossa PotenssiinKaksi.py tulostaa lukujen kakkosen potenssit kysyttyyn arvoon saakka: # Tulostetaan kakkosen potenssit n = input("anna luku: ") for x in range(1,n+1): xx = x*x print "Luku", x, "potenssiin kaksi on", xx Ohjelman suoritus näyttää seuraavalta: $ python PotenssiinKaksi.py Anna luku: 5 Luku 1 potenssiin kaksi on 1 Luku 2 potenssiin kaksi on 4 Luku 3 potenssiin kaksi on 9 Luku 4 potenssiin kaksi on 16 Luku 5 potenssiin kaksi on 25 127 2.4 Modulaarisuus Tässä kappaleessa tarkastellaan modulaarisuusperiaatetta. Algoritmeja kannattaa suunnitella asteittain tarkentavalla menetelmällä. Itsenäistä algoritmikomponenttia, joka voidaan suunnitella käyttöyhteydestä riippumattomasti ja liittää mihin tahansa algoritmiin, jossa kyseinen osatehtävä esiintyy, kutsutaan algoritmimoduuliksi. Muita yleisesti käytettyjä nimiä algoritmimoduulille ovat aliohjelma, rutiini ja metodi. 128

Parametrisointi Metodien rajauksessa ja suunnittelussa on hyvä ottaa huomioon seuraavat seikat: Matemaattisesti algoritmimoduulia voidaan pitää funktiona, jolla on m syöttöparametria ja n tulosta. Tehtäväkohtaisuus: yksi metodi ratkaisee yhden (osa)tehtävän, ei useampia. Parametrit m kpl. Aliohjelma Tulos n kpl Luonnollisuus: mahdollisimman luontevien osatehtävien erottaminen algoritmista tuo merkittäviä etuja paitsi lisääntyvän selkeyden muodossa myös metodien yleisen käyttökelpoisuuden paranemisena. Sopiva abstraktiotaso: metodi on hyvä rakentaa alimoduuleista, jotka ovat keskenään suunnilleen samalla abstraktiotasolla. Aliohjelmaa, jolla on tulos (n 1), sanotaan funktioksi. Jos n = 0, sanotaan metodia proseduuriksi. Funktion merkitys perustuu sen syöttöparametriensa perusteella tuottamaan arvoon; se palauttaa arvon. Proseduurin merkitys puolestaan perustuu arvon sijasta ns. sivuvaikutukseen. Proseduuri voi myös tulostaa, piirtää, kirjoittaa levylle, jne. Funktion ja proseduurin kutsun syntaktinen ja semanttinen ero kiteytyy siinä, että edellinen on lauseke ja jälkimmäinen on lause. 129 130 Pythonissa kaikki aliohjelmat ovat funktioita, vaikka ne eivät sisältäisikään return-lausetta. Pythonissa funktioiden määrittely tapahtuu def-rakenteen avulla. Käskyn syntaksi on def funktion nimi(parametrilista): Funktion runko Kun funktiota kutsutaan, kutsulauseessa ilmaistut parametrit vastaavat yksitelleen funktion otsikossa (ensimmäinen rivi) mainittuja parametrejä kutsulauseen ensimmäinen parametri otsikon ensimmäistä, toinen toista, jne. Sitten kutsun parametrit sijoitetaan otsikon parametrien paikalle ja proseduuri suoritetaan. Otsikon parametreja kutsutaan usein muodollisiksi parametreikse, kun taas kutsun parametrit ovat todellisia parametreja, sillä ne ovat todellisuudessa käytettäviä arvoja. Joissakin ohjelmointikielissä todelliset parametrit kopioidaan muodollisiksi parametreiksi, joten alirutiini saa käsitellä vain kopioita. Tällöin puhutaan arvoparametreista. Arvoparametrin käyttö suojelee kutsuvan ohjelman muuttujia mahdollisilta suunnitteluvirheiltä aliohjelmissa. Jos esimerkiksi kutsuva yksikkö välittää metodille työntekijän sosiaaliturvatunnuksen, kutsuva yksikkö ei missään tapauksessa halua proseduurin muuttavan sitä. Valitettavasti arvoparametrien käyttö on tehotonta, kun parametreja on paljon. Tehokkaampaa on antaa proseduurille todellisten parametrien osoitteet. Tässä tapauksessa puhutaan viittausparametreista. Viittausparametreja käytettäessä funktio tai proseduuri voi muuttaa dataa, joka on kutsuvan ohjelman sisällä. Tällainen on toivottavaa, kun kutsuttava proseduuri esimerkiksi lajittelee taulukon. Proseduuriahan kutsutaan juuri sen vuoksi, että taulukkoon tehdään muutoksia. 131 132

Jos nyt käytetään arvoparametreja, proseduurin parametrin Muodollinen muuttuminen ei näy muuttujassa Todellinen. Esimerkkinä tarkastelemme seuraavaa Python-funktiota, joka ei palauta mitään arvoa. Kutsuva ohjelma todellinen 5 Aliohjelma muodollinen 5 def function Testi (Muodollinen): Muodollinen = Muodollinen + 1 Lisäksi oletetaan, että muuttujalla Todellinen on arvo 5, ja me kutsumme funktiota testi käskyllä Testi(Todellinen) a) Kun aliohjelmaa kutsutaan, sille välitetään kopio tiedoista Kutsuva ohjelma Aliohjelma todellinen muodollinen 5 6 b) ja aliohjelma käsittelee omaa kopiotaan Kutsuva ohjelma todellinen 5 c) kun aliohjelma on tehnyt tehtävänsä, kutsuneen ohjelman ympäristö ei ole muuttunut 133 134 Sen sijaan käytettäessä viiteparametreja, muuttujan Todellinen arvo kasvaa yhdellä. Kutsuva ohjelma todellinen 5 todellinen 6 Kutsuva ohjelma todellinen 6 Aliohjelma muodollinen a) Kun aliohjelmaa kutsutaan, muodollisesta parametrista tulee viittaus todelliseen Kutsuva ohjelma Aliohjelma muodollinen b) Näin aliohjelma tekee muutokset todelliseen parametriin +1 Aliohjelmissa määritellyt muuttujat ovat yleensä paikallisia, mikä tarkoittaa sitä, että niitä voi käyttää vain aliohjelman sisällä. Tämä eliminoi sekaannukset, joita voisi sattua, jos kaksi toisistaan riippumatta kirjoitettua alirutiinia käyttäisi samannimistä muuttujaa. On kuitenkin tilanteita, jolloin muuttujan tulee olla koko ohjelman käytettävissä. Tällöin muuttujaa sanotaan globaaliksi. Useimmat ohjelmointikielet tuntevat molemmat tyypit. c) ja muutos säilyy aliohjelman loputtuakin 135 136

Funktion rungossa on oltava yksi tai useampia return-lauseita, joilla spesifioidaan moduulin palauttama arvo. Return-lauseen yleinen muoto on: return lauseke, missä lauseke on mielivaltainen lauseke. Return-lauseen suoritus aiheuttaa seuraavat toimenpiteet: 1. lasketaan lausekkeen lauseke arvo 2. päätetään moduulin lasku 3. palautetaan ohjelman kontrolli kutsuvaan moduuliin, sen kutsukohtaan ja asetetaan moduulin kutsulle lausekkeen lauseke arvo Näin ollena funktion suorituksen tulee aina päättyä return-lauseeseen, jotta funktion kutsu saa arvon. Algoritmimoduulit soveltuvat luonnollisella tavalla asteittain tarkentavaan menetelmään ja osittavaan suunnitteluun (top-down design). Koska moduulit ovat itsenäisiä osakokonaisuuksia, ne voidaan suunnitella toisistaan riippumatta. Moduulit selkeyttävät algoritmeja ja helpottavat niiden ylläpitoa. Erityisesti muiden kuin algoritmin alkuperäisen kirjoittajan on helpompi muuttaa algoritmia myöhemmin. Modulaarisuus helpottaa myös virheiden etsintää ja oikeellisuuden toteamista, koska algoritmia voidaan tarkastella paloittain. Huolellisesti suunniteltua moduulia voidaan käyttää hyväksi missä tahansa yhteydessä, jossa kyseinen (osa)tehtävä esiintyy. Yleiskäyttöiset moduulit voidaan koota erityiseksi moduulikirjastoksi. Hyvin organisoidut moduulikirjastot tehostavat suuresti samantyyppisten algoritmien laatimista vastaisuudessa. 137 138 Kirjoitetaan tiedostoon KertoT.py seuraava funktion määrittely, joka tulostaa parametrina annetun n:n kertotaulun. def kertotaulu(n): for i in range(1,11): print n, "*", i, "=", n*i Tämän jälkeen kirjoitetaan tiedostoon TulostaKerto.py seuraavat rivit: from KertoT import kertotaulu m = input("anna kokonaisluku 1-10: ") kertotaulu(m) $ python TulostaKerto.py Anna kokonaisluku 1-10: 8 8 * 1 = 8 8 * 2 = 16 8 * 3 = 24 8 * 4 = 32 8 * 5 = 40 8 * 6 = 48 8 * 7 = 56 8 * 8 = 64 8 * 9 = 72 8 * 10 = 80 Ensin pyydetään tiedostosta KertoT.py funktiota kertotaulu ohjelman käyttöön. Tämän jälkeen pyydetään käyttäjää antamaan jokin kokonaisluku väliltä 1-10 ja talletetaan se muuttujaan m. Lopuksi funktio kertotaulu tulostaa todellisena parametrina annetun m:n kertotaulun. 139 140

IsPositive.py testaa onko annettu luku negatiivinen vai positiivinen. def positive(y): if y < 0: return False return True x = input("anna luku: ") if positive(x): print x, "on positiivinen" print x, "on negatiivinen" Huomaa, että positive-funktiossa on kaksi return-lausetta, mutta vain toinen niistä aina suoritetaan. Toisaalta Pythonissa on mahdollista, että funktio palauttaa useampia arvoja kerralla. Kertoma määritellään matemaattisesti n! = 1 2 3 n. Kirjoitetaan tiedostoon Kertoma.py seuraavat rivit. def kertoma(n): tulos = 1 laskuri = 1 while laskuri <= n: tulos = tulos * laskuri laskuri = laskuri + 1 return tulos N = input("anna positiivinen kokonaisluku: ") for luku in range(1,n+1): print luku, "! = ",kertoma(luku) 141 142 Ohjelman suoritus näyttää seuraavalta: >>> python Kertoma.py Anna positiivinen kokonaisluku: 7 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 Kaikkein tärkeimmät funktiot, jotka liittyvät esimerkiksi tekstin tulostamiseen tai muuttujien käsittelyyn, ovat Pythonissa sisäänrakennettuja. Mutta monesti on tarvetta liittää ohjelmaan mukaan erillisiä hyödyllisiä funktioita. Pythonissa moduuli tarkoittaa enemmänkin hyödyllisten funktioiden kokoelmaa, kuin yhtä aliohjelmaa. Pythonin mukana tulee useita valmiita moduuleja. Lisäksi Internet on pullollaan ihmisten vapaasti käytettäväksi antamiaan moduuleja. Moduuli otetaan käyttöön komennolla import. Sen kaikki funktiot otetaan käyttöön from random import *. Esimerkiksi nyt komento randint(1,6) generoi satunnaisia kokonaislukuja väliltä 1 6, joten sitä voi käyttää nopanheittoon. 143 144

2.5 Iteraatio ja rekursio Algoritmisen ongelmanratkaisun päästrategiat ovat iteraatio ja rekursio. Rekursio ja iteraatio ovat kuin saman kolikon kaksi puolta. Tarkastellaan kahta seuraavaa algoritmia, jotka kuvaavat kahta eri näkökulmaa aidan maalaamiseen. def maalaa(aita): while aitaa maalaamatta: suorita laudan maalaus siirry seuraavaan lautaan def maalaa(aita): if aitaa maalaamatta: suorita laudan maalaus maalaa(loput aidasta) Rekursiivisen ratkaisun konstruoimiseksi vaaditaan kaksi seikkaa: ongelmalla pitää olla ainakin yksi triviaali (itsestään selvä) tapaus, joka ratkeaa suoraan, ei-rekursiivisesti ei-triviaalit tapaukset ratkeavat rekursiivisilla kutsuilla, joiden tulee lähestyä jotakin triviaalia tapausta Vaatimuksista seuraa, että rekursiivisten kutsujen tulee aina olla ehdollisia. Rekursiivisessa moduulissa siis ongelman ei-triviaalit tapaukset redusoidaan alkuperäisen ongelman kaltaisiksi, mutta yksinkertaisemmiksi ongelman tapauksiksi, ja triviaalit tapaukset ratkaistaan suoraan. Ongelman ei-rekursiivisesti ratkeavaa triviaalia tapausta/tapauksia sanotaan rekursion pohjaksi ja rekursiivista kutsuja, joissa lähestytään rekursion pohjaa, rekursioaskeleeksi. Rekursion pohja voi koostua yhdestä tai useammasta triviaalista tapauksesta. 145 146 Matemaattisesti kertoma voidaan määritellä myös seuraavasti: 1 jos n = 0 n! = n (n 1)! muulloin Tämän määritelmän perusteella voidaan suoraan kirjoittaa kertoman laskeva rekursiivinen algoritmi (tiedostoon Fact.py). def factorial(n): if n == 0: return 1 return (n * factorial(n-1)) >>> from Fact import factorial >>> factorial(5) 120 >>> factorial(30) 265252859812191058636308480000000L Edellisessä tulosteessa kirjain L merkitsee, että kyseessä on tyyppi long (pitkä kokonaisluku. >>> type(factorial(30)) <type long > 147 148

factorial(4) if 4==0:... return 4 * factorial(3) if 3==0:... return 3 * factorial(2) if 2==0:... returns 4*6 = 24 returns 3*2 = 6 returns 2*1 = 2 returns 1 return 2 * factorial(1) if 1==0:... returns 1 return 1 * factorial(0) if n==0: return 1 Rekursion pohja Rekursio vai iteraatio? Rekursio ja iteraatio ovatkin useasti saman kolikon kaksi puolta, vaihtoehtoisia strategioita. Koska tietokone toimii iteratiivisesti, on selvää, että jokainen rekursiivinen algoritmi voidaan muuntaa iteratiiviseksi viimeistään konekielelle käännettäessähän näin aina tapahtuukin. Fibonaccin lukujono F(n) = 0,1,1,2,3,5,8,13,21,34,55... määritellään seuraavan palautuskaavan avulla: 0 kun n = 0 F(n) = 1 kun n = 1 F(n 1) + F(n 2) muulloin 149 150 import time #ottaa käyttöön time-moduulin def RekFibonacci(n): if n == 0 or n==1: return n return RekFibonacci(n-1) + RekFibonacci(n-2) def IterFibonacci(n): if n == 0: return 0 FiboApu1 = 0 FiboApu2 = 1 for i in range(1,n+1): Fibo = FiboApu1 + FiboApu2 FiboApu2 = FiboApu1 FiboApu1 = Fibo return Fibo N = input("anna positiivinen kokonaisluku: ") alku=time.time() # ajastin print "F(",N,") = ",RekFibonacci(N) loppu=time.time() print "Rekursiivinen lasku vei aikaa", loppu-alku, "sekuntia" alku=time.time() # ajastin print "F(",N,") = ",IterFibonacci(N) loppu=time.time() print "Iteratiivinen lasku vei aikaa", loppu-alku, "sekuntia" 151 152

Suoritusaikavertailu näyttää seuraavalta: $ python Fibonacci.py Anna positiivinen kokonaisluku: 30 F( 30 ) = 832040 Rekursiivinen lasku vei aikaa 2.4886701107 sekuntia F( 30 ) = 832040 Iteratiivinen lasku vei aikaa 0.00037693977356 sekuntia Anna positiivinen kokonaisluku: 33 F( 33 ) = 3524578 Rekursiivinen lasku vei aikaa 10.3415739536 sekuntia F( 33 ) = 3524578 Iteratiivinen lasku vei aikaa 0.000364780426025 sekuntia Kuvassa on esitetty puu, joka kuvaa luvun F(5) laskemista. F(3) + F(2) + F(1) F(0) F(1) F(4) + F(2) + F(1) F(0) F(5) + F(1) F(2) + F(0) F(3) + F(1) 153 154 Hanoin tornit R.E.Allardice & A.Y.Fraser: La Tour d Hanoï, Proceedings of the Edinburgh Mathematical Society 2 (1884) 50 53. 1. Siirrä (tavalla tai toisella) 7 päällimmäistä kiekkoa lähtötolpasta aputolppaan. Lähtötolppa Aputolppa Torni lähtötolpasta maalitolppaan Kiekko kerrallaan Isompi ei saa olla pienemmän päällä Maalitolppa Lähtötolppa Aputolppa Maalitolppa 155 156

2. Siirrä kiekko lähtötolpasta maalitolppaan. 3. Siirrä (tavalla tai toisella) aputolppaan siirretyt 7 kiekkoa maalitolppaan. Lähtötolppa Aputolppa Maalitolppa Lähtötolppa Aputolppa Maalitolppa 157 158 Edellä kuvattu kahdeksan kiekon tapauksen ratkaisevaa menetelmää voidaan menestyksellisesti soveltaa myös mihin tahansa määrään kiekkoja n: 1. Siirretään n 1 pienintä kiekkoa lähtötolpasta keskimmäiseen aputappiin. On siis ratkaistava n 1-kiekkoinen Hanoin Tornien ongelma. 2. Siirretään kiekko lähtötolpasta maalitolppaan. 3. Siirretään n 1 kiekkoa aputapista maalitolppaan. On siis jälleen ratkaistava n 1-kiekkoinen Hanoin tornien ongelma. Kirjoitetaan moduuli Hanoi, joka ratkaisee n-kiekkoisen Hanoin tornien ongelman. Ohjelmaa ei tietenkään voi suorittaa tietokoneella, sillä meillä on määrittelemättä rutiini, joka siirtää kiekon tornista toiseen (esimerkiksi tietokoneen ruudulla). def Hanoi(n, Lähtö, Apu, Maali): if n > 0: Hanoi(n-1, Lähtö, Maali, Apu) Siirrä kiekko tolpasta Lähtö tolppaan Maali Hanoi(n-1, Apu, Lähtö, Maali) 159 160

Hanoin tornien tapauksessa voitaisiin kysyä, mikä on pienin määrä siirtoja, joilla n-kiekkoinen Hanoin Tornien ongelma voidaan ratkaista. Merkitään tätä pienintä siirtojen lukumäärää T(n):llä. 1. Hanoi(n-1, Lähtö, Apu, Maali) vaatii T(n 1) siirtoa. 2. Tämä vaihe siis vaatii yhden siirron. 3. Hanoi(n-1, Apu, Maali, Lähtö) vaatii T(n 1) siirtoa. Edellisen perusteella voidaan kirjoittaa rekursioyhtälö: T(n) = T(n 1) + 1 + T(n 1) = 2T(n 1) + 1, kun n 1. Soveltamalla edellä olevaa yhtälöä itseensä saadaan: T(n) = 2T(n 1) + 1 = 2(2T(n 2) + 1) + 1 = 2 2 T(n 2) + 2 + 1 = 2 2 (2T(n 3) + 1) + 2 + 1 = 2 3 T(n 3) + 2 2 + 2 + 1 = 2 4 T(n 4) + 2 3 + 2 2 + 2 1 + 2 0 = 2 k T(n k) + 2 k 1 +... + 2 2 + 2 1 + 2 0 k 1 = 2 k T(n k) + i=0 2 i 161 162 Saadaan n 1 T(n) = 2 n T(0) + 2 i = i=0 n 1 2 i i=0 2.6 Olio-orientoitunut ohjelmointi class Nelio: def init (self, sivu): self.sivu = sivu 2T(n) = 2 1 + + 2 n 1 + 2 n T(n) = 2 0 + 2 1 + + 2 n 1 2T(n) T(n) = 2 0 + 0 + + 0 + 2 n Hanoin tornin siirtojen määräksi saadaan siis def LaskeAla(self): return self.sivu**2 def Muutu(self,muutos): self.sivu = self.sivu + muutos n 1 T(n) = 2 i = 2 n 1 i=0 163 164

class Ympyra: def init (self, sade): self.sade = sade def LaskeAla(self): import math return math.pi*(self.sade**2) def Muutu(self,muutos): self.sade = self.sade + muutos 1. Metodin yksi metodi on nimetty: init. Se on konstruktoriksi (constructor) nimetty erityismetodi. Konstruktoria kutsutaan Pythonissa automaattisesti kun uusi objekti luodaan, esim. uusi Nelio-olio. Lisäksi Nelio-luokan olio määrätään sille oma parametrina välitetty sivun pituus sivu. 2. Metodeissa esiintyy sana self. Ideana on, että metodeita kutsutaan aina tietylle objektille, ja self viittaa juuri tähän objektiin. 165 166 >>> from muodot import * >>> a = Nelio(5) >>> a.laskeala() 25 >>> a.muutu(-2) >>> a.sivu 3 >>> a.laskeala() 9 >>>b = Ympyra(10) >>> b.laskeala() 314.15926535897933 >>> b.muutu(3) >>> b.sade 13 167 Toinen olio-ohjelmointiin tyypillinen piirre on kapselointi, mikä tarkoittaa rajoitettua pääsyä olion sisäisiä ominaisuuksia koskevaan dataan. Esimerkiksi neliön a sivun pituuden lukeminen suoraan komennolla a.sivu on huonoa ohjelmointia sen sijaan meidän tulisi laatia metodi PalautaSivu, jolloin neliön a sivun pituuden saisi palautettua komennolla a.palautasivu(). 168