OHJ-3100 Ohjelmien ylläpito ja evoluutio 1 Harjoitustyö Ohjaaja: Outi Sievi-Korte outi.sievi-korte@tut.fi TE213 Päivystys ti klo 14-16 2 Yleiskatsaus Yleisesittely Geneettiset algoritmit Ohjelmistoarkkitehtuurit Frankenstein Tehtävänanto Johdanto Käyttöliittymä Metriikoiden määrittely Fitness-arvon laskeminen Luonnonvalinnan suorittaminen Output Dokumentti Arvostelu Palautus 1
Yleisesittely 3 Toteutetaan Frankenstein-työkalun laajennus Frankenstein tuottaa automaattisesti ohjelmistoarkkitehtuurisuunnitelmia Frankenstein lähtee nolla-arkkitehtuurista ja kehittää arkkitehtuuria käyttämällä suunnittelutyylejä ja malleja Arkkitehtuurin laatua arvioidaan muunneltavuuden, tehokkuuden ja ymmärrettävyyden suhteen Laadukasta arkkitehtuuria etsitään geneettisellä algoritmilla Laajennuksessa toteutetaan t t t QMOOD-metriikoita it arkkitehtuurien i laadun mittaamiseen Työssä oletetaan tuntemusta Java-ohjelmointikielestä Javan erot C++:aan? http://www.cprogramming.com/tutorial/java/syntax-differences-javac++.html Geneettiset algoritmit 4 Geneettiset algoritmit kuuluvat meta-heuristisiin etsintäalgoritmeihin i ihi Etsintä on epädeterminististä Käytetään, kun ratkaisujoukko on suuri, ja determinististä algoritmia ei voida toteuttaa Geneettiset algoritmit pohjautuvat darwinistiseen teoriaan evoluutiosta Yksilöt katsotaan sitä laadukkaammiksi, mitä paremmin ne ovat sopeutuneet ympäristöönsä, ä ja mitä paremmat mahdollisuudet d niillä on tuottaa laadukkaita jälkeläisiä Luonnonvalinta korjaa huonot yksilöt pois, jotta populaation koko pysyy hallinnassa 2
5 Geneettiset algoritmit peruskäsitteitä Geneettiset algoritmit nojautuvat biologisiin käsitteisiin. Populaatio = ratkaisujoukko tietyllä ajanhetkellä Sukupolvi = ajanhetki, n. sukupolvi ilmaisee, että on tehty n iteraatiota, puhutaan n. sukupolven populaatiosta == n. populaatio Yksilö = ratkaisu Kromosomi = ratkaisun mallinnos Geeni = kromosomin (ratkaisun) muunneltava osa Alleeli li = geenin saama arvo, osan eri variaatiot t Mutaatio = geeniin (ratkaisun osaan) kohdistuva muutos Risteytys = kahden yksilön (ratkaisun) eri ominaisuuksien yhdistäminen Fitness-funktio = hyvyysfunktio = laatufunktio, laskee yksilön hyvyyden. Mitä parempi hyvyysarvo, sitä todennäköisemmin yksilö selviää luonnonvalinnasta ja osallistuu risteytyksiin 6 Geneettiset algoritmit prosessi Input: ratkaisun formalisointi kromosomimuodossa Luo populaatio Mutatoi kromosomeja Risteytä kromosomeja Laske hyvyysarvo yksilöille Suorita luonnonvalinta 3
7 Geneettiset algoritmit esimerkki Esimerkki: repuntäyttöongelma. Reppuun mahtuu 9 tilavuusyksikön verran tavaraa. Tarkoitus on saada reppu mahdollisimman täyteen niin, että reppu painaa mahdollisimman paljon, mutta tilavuus ei ylitä repun tilavuutta. 8 Geneettiset algoritmit esimerkki Reppuun yritetään saada mahtumaan seuraavat esineet: Tiili Cokis Kirja Läppäri Kengät Paino 5 1 3 4 2 Tilavuus 1 2 3 4 5 4
Geneettiset algoritmit esimerkki 9 Mallinnus: Validi ratkaisu on joukko esineitä, jotka eivät yhteensä ylitä repun tilavuutta. Ratkaisu voidaan mallintaa bittivektorilla, jossa 0 tarkoittaa, että kyseisen indeksin määrittämää esinettä ei pakata reppuun ja 1 tarkoittaa, että kyseinen esine pakataan. Nyt vektori on kromosomi, vektorin kentät ovat geenejä ja bitit ovat alleeleja. Esimerkki: Oletetaan, että esineet mallinnetaan samassa järjestyksessä kuin edelle, eli: Tiili cokis kirja läppäri kengät 0 1 0 0 0 Tässä ratkaisussa vain cokis on otettu reppuun mukaan, ja näin ollen reppu painaa 1 yksikön ja sen tilavuudesta on käytetty 2 yksikköä. Ratkaisu on validi, sillä tilavuusrajoite ei ylity, mutta ratkaisu ei varmasti ole optimaalinen. Geneettiset algoritmit esimerkki 10 Mutaatio muuttaa ratkaisua. Mutaatio kohdistuu yleensä yhteen geeniin, mutta voi kohdistua myös koko k kromosomiin. Mutaatio riippuu aina valitusta mallinnuksesta, sillä se vaihtaa geenin alleelin toiseen. Bittivektorin tapauksessa mutaatio on selkeä: 0 vaihtuu 1:ksi tai toisinpäin. Esimerkki: mutaatio 0 1 0 0 0 0 1 1 0 0 Nyt reppuun on pakattu cokiksen lisäksi kirja, ja reppu painaa 4 yksikköä. Tilavuudesta viedään 5 yksikköä, joten ratkaisu on validi. 5
Geneettiset algoritmit esimerkki 11 isä Risteytys yhdistää eri ratkaisuja. Yksinkertaisimmillaan risteytyskohta arvotaan, ja ratkaisun puoliskot yhdistetään, jolloin tuloksena on kaksi uutta ratkaisua. Risteytykseen osallistuvia ratkaisuja sanotaan vanhemmiksi, ja tuotettuja ratkaisuja jälkeläisiksi. Esimerkki: 0 1 1 0 0 1 0 0 0 1 äiti lapsi 1 lapsi 2 0 1 0 0 1 1 0 1 0 0 Geneettiset algoritmit esimerkki 12 Fitness-arvolla mitataan ratkaisun hyvyyttä. Reppuesimerkissä fitness-arvo on suoraviivaisesti i i laskettavissa: mikäli tilavuutta ei ole ylitetty, ratkaisu on sitä parempi mitä painavampi se on. Yleensä fitness-arvon laskeminen ei ole yhtä suoraviivaista, eikä arvojen perusteella voida tehdä vertailua, ts. jos ratkaisu A saa fitness-arvon 1 ja ratkaisu B arvon 10, todennäköisesti B ei kuitenkaan ole 10 kertaa parempi kuin A. Fitness-arvoilla voidaan kuitenkin järjestää ratkaisut. Edellisen kalvon ratkaisuille voidaan laskea hyvyysarvot: Isä-ratkaisu sisältää cokiksen ja kirjan 1 + 3 = 4 Äiti-ratkaisu sisältää tiilen ja kengät 5 + 2 = 7 Lapsi 1 sisältää cokiksen ja kengät 1 + 2 = 3 Lapsi 2 sisältää tiilen ja kirjan 5 + 3 = 8 yksikköä 6
Geneettiset algoritmit esimerkki 13 Luonnonvalinta toteutetaan karsimalla huonot yksilöt pois. Helpoimmillaan pidetään vain populaation parhaat yksilöt, eliitti, ja karsitaan suoraan huonoimmat pois. Tällöin kuitenkin ki on riski, että algoritmi konvergoituu liian nopeasti. Jotta populaatiossa olisi riittävästi hajontaa, toteutetaan valinta todennäköisyyksiin perustuvalla valinnalla. Yleensä tähän yhdistetään elitismi. Esimerkki: Tarkistetaan, ovatko ratkaisut valideja: Isä-ratkaisu sisältää cokiksen ja kirjan 2 + 3 = 5 Äiti-ratkaisu sisältää tiilen ja kengät 1 + 5 = 6 Lapsi 1 sisältää cokiksen ja kengät 2 + 5 = 7 Lapsi 2 sisältää tiilen ja kirjan 1 + 3 = 4 Kaikki tilavuudet ovat alle sallitun (9). Lapsi 2 on paras yksilö (fitness-arvo 8); valitaan se eliittinä suoraan seuraavalle kierrokselle. Lopuille (isä, äiti, lapsi1) määrätään todennäköisyydet niiden fitness-arvojen perusteella. Ohjelmistoarkkitehtuurit 14 Ohjelmistoarkkitehtuuri on ikään kuin ohjelmiston luuranko ISO standardi määrittelee arkkitehtuurin käsittävän järjestelmän peruskäsitteet tai ominaisuudet omassa ympäristössään, kuvattuna elementteinä, niiden välisinä suhteina ja suunnittelua ohjaavina periaatteina Arkkitehtuurin voidaan katsoa olevan ikään kuin laki, joka ohjaa järjestelmän toteutusta Arkkitehtuuria suunnitellessa sovitaan esim. tiettyjen teknologioiden käytöstä, tietorakenteiden käytöstä ja suunnittelumallien käytöstä 7
Ohjelmistoarkkitehtuurit 15 Ohjelmistoarkkitehtuurin pääasiallinen tarkoitus on vastata laatuvaatimuksiin Järjestelmä voidaan yleensä toteuttaa toiminnallisuuden puolesta hyvin monella eri tavalla, mutta nämä eri toteutustavat voivat olla laadultaan hyvin erilaisia Laatua arvioidaan eri ominaisuuksien perusteella, esim. muunneltavuus, tehokkuus, luotettavuus ja käytettävyys Arkkitehtuuri pyritään suunnittelemaan niin, että mahdollisimman moni laatuvaatimus täyttyy Ohjelmistoarkkitehtuurit 16 Arkkitehtuurityylit ja suunnittelumallit ovat hyväksi havaittuja ratkaisuja tiettyihin ongelmiin Arkkitehtuurityylejä käytetään yleensä ratkaistaessa korkean tason ongelmia, jotka käsittävät koko arkkitehtuurin, kun taas suunnittelumalleja käytetään paikallisempiin, muutaman komponentin käsittäviin ongelmiin Frankensteinissa on käytössä kaksi arkkitehtuurityyliä (viestinvälittäjä ja asiakas-palvelin) sekä viisi suunnittelumallia (Mediator, Façade, Strategy, Adapter, Template Method) 8
Ohjelmistoarkkitehtuurit tyylit ja mallit 17 Viestinvälittäjä Ohjelmistoarkkitehtuurit tyylit ja mallit 18 Asiakas-palvelin (Client-server) 9
Ohjelmistoarkkitehtuurit tyylit ja mallit 19 Façade Ohjelmistoarkkitehtuurit tyylit ja mallit 20 Mediaattori 10
Ohjelmistoarkkitehtuurit tyylit ja mallit 21 Strategia Ohjelmistoarkkitehtuurit tyylit ja mallit 22 Adapteri 11
Ohjelmistoarkkitehtuurit tyylit ja mallit 23 Template Method Frankenstein 24 Toteuttaa geneettisen algoritmin ja kehittää arkkitehtuurin. Komentoriviperustainen (GUI toteutettu Eclipse plug-ininä, jätetty harjoitustyöstä pois). Input: nolla-arkkitehtuuri sekä GA:n parametrit tekstitiedostoina Output: arkkitehtuuri luokkakaaviona 12
Frankenstein 25 Frankenstein 26 Syöte: nolla-arkkitehtuuri == toiminnalliset vaatimukset perustuen järjestelmän j ä ns. vastuisiin ii ja domain-tuntemukseen Vastuut muutetaan luokkakaaviossa yksiselitteisesti metodeiksi ja attribuuteiksi. Jokainen arkkitehtuuri (eli ratkaisu) on yksi kromosomi. Jokainen operaatio(== metodi/attribuutti == vastuu) arkkitehtuurissa mallinnetaan yhdellä supergeenillä (SuperGene). Supergeenissä on oma kenttä jokaiselle operaation ominaisuudelle. Kromosomi/geenikoodaus on operaatio- eikä luokkalähtöinen! Kromosomissa EI ole mallinnettu luokkakaaviota! 13
Frankenstein työkalun käyttö 27 Työkalua käytetään komentoriviltä input file / output data file / scenarios / select_parents / select_co_point / output_graph_file / weights / probabilities / generations / population/ initialvariation / pareto / alterprobs / algorithm Syötetiedostot: input file = nolla-arkkitehtuuritiedosto, weights = painot (fitness funktiolle), probabilities = todennäköisyydet (mutaatioille) GA-parametrit: generations = sukupolvien lukumäärä, population = populaation koko, algorithm = valittu algoritmi, työssä ga Output-tiedostot: tiedostot: output data file = logitiedosto, output graph file = luokkakaaviotiedosto Muut: boolean-arvoja, harjoitustyössä käytä kaikissa arvoa false alykoti_dom.txt ehome_test.txt false false false ehome_test weights_ehome.txt probabilities.txt 250 100 false false false ga Frankenstein työkalun käyttö 28 Output: Tuloksena saadaan.dot-tyyppinen tiedosto Tuottaakseen.dot-tiedoston, Frankenstein tarvitsee UMLGraph.jar-paketin, joka täytyy olla ladattuna samaan paikkaan, mistä Frankensteinia suoritetaan. UMLGraph.jar käyttää tools.jar:ia, jonka täytyy niinikään sijaita samassa paikassa..dot-tiedosto täytyy muuntaa kuvaksi GraphViz-ohjelmalla, käytetään komentoriviltä: dot Tgif ofilename.gif filename.dot 14
Tehtävänanto johdanto 29 Tehtävänä on laajentaa Frankenstein-työkalua siten, että voidaan vertailla omilla metriikoilla saatuja laatuarvoja ar QMOOD-metriikoihin perustuviin laatuarvioihin Harjoitustyössä toteutetaan QMOOD-metriikkakokoelmasta uudelleenkäytettävyys (reusability) ja funktionaalisuus (functionality) laatukriteereihin tarvittavat metriikat soveltuvin osin. Vertailua varten työkalua muokataan siten, että käyttäjä voi kertoa, millä metriikoilla haluaa arkkitehtuureja arvioitavan ts. käytetäänkö luonnonvalintaa suoritettaessa omia vai QMOOD-metriikoita Toteutetaan metodit, jotka laskevat QMOOD-metriikat Myös QMOOD-metriikka-arvot tallennetaan logitiedostoon Tehtävänanto käyttöliittymä 30 Käyttäjän pitää voida määritellä, mitä metriikoita käytetään vanhoja Frankenstein-metriikoita, jotka laskevat muunneltavuutta, tehokkuutta ja kompleksisuutta, vai uusia QMOOD-metriikoita Vastaavasti kuin nyt annetaan painot olemassaoleville laatukriteereille, myös QMOOD-laaduille pitää voida antaa painot. Toteutus on vapaa nykyistä yy painotiedostoa voi laajentaa tai QMOOD-painot voi antaa omassa tiedostossaan tai jollain muulla tavoin. 15
Tehtävänanto metriikoiden määrittely 31 Suunnitelman koko (DSC) Suunnitelman koko on yhtä kuin arkkitehtuurista löytyvien luokkien määrä. Huomaa, että normaalien luokkien lisäksi myös patterneilla on ns. teknisiä luokkia, jotka tulee laskea mukaan. DSC = design size in classes Tehtävänanto metriikoiden määrittely 32 Luokkien väliset riippuvuudet (DCC) Lasketaan, kuinka monta riippuvuutta on eri luokkien välillä. Jos operaatio a riippuu operaatio b:stä, ts. a kutsuu b:tä (a b), ja a ja b ovat eri luokissa, silloin näiden luokkien välillä on riippuvuus. Riippuvuudet yhteen suuntaan lasketaan vain kertaalleen! Esim: a b, a d, d c, e d, b f. DCC = 4. a c b d e f DCC = Direct class coupling 16
Tehtävänanto metriikoiden määrittely 33 2.3.3 Koheesio (cohesion, CAM) QMOOD-määritelmän mukaan metodien välistä koheesiota saadaan laskettua sen mukaan, miten samanalaisia parametreja niillä on. Frankensteinissa ei oteta kantaa parametreihin, vaan metodi = operaatio = vastuu (eli suurempi kokonaisuus) metriikkaa sovelletaan hieman. Lasketaan, kuinka monta operaatiota kutsuu samaa operaatiota luokan sisällä. Koko arkkitehtuurin koheesioarvo on luonnollisesti kaikkien luokkien koheesioarvojen summa. Huom! Ei laskettavissa suoraan kuvasta, luokan sisäiset yhteydet piirretään vain kertaalleen. CAM = Cohesion among methods of class Tehtävänanto metriikoiden määrittely 34 CAM - esimerkki a b c d e f g h i j k l m n a f b f k f j f g j m j b d CAM = 4 + 2 = 6 17
Tehtävänanto metriikoiden määrittely 35 2.3.4 Julkisuus(CIS) Julkisuus lasketaan sen mukaan, miten moni operaatio on tarjolla rajapinnan kautta. Huomaa, että rajapinta voi olla normaali tai patternin kautta tuleva rajapinta (esim. Strategiassa). CIS = kaikkien rajapinnoista i löytyvien operaatioiden id summa CIS = Class interface size Tehtävänanto metriikoiden määrittely 36 2.3.5 Hierarkiat (NOH) Hierarkioita laskettaessa summataan arkkitehtuurista löytyvien hierarkioiden määrä. Käytännössä siis summataan periytymisketjuja. Frankenstein ei (tässä vaiheessa) mahdollista sattumanvaraista periyttämistä, ja aliluokkia käytetään vain Template Method patternin tapauksessa. Täten siis hierarkioiden määrä (NOH) on yhtä kuin Template Method-patternien määrä. NOH = Number of hierarchies 18
Tehtävänanto fitness-arvon laskeminen 37 Fitness-funktioon lisätään uusi metodi jokaista uutta metriikkaa varten Fitness-funktiossa tulee laskea metriikoiden avulla arvot laatuarvoille Uudelleenkäytettävyys = 0.25*CAM 0.25*DCC + 0.5*CIS + 0.5*DSC Funktionaalisuus = 0.19*CAM + 0.27*CIS + 0.27*DSC + 0.27*NOH Tehtävänanto fitness-arvon laskeminen 38 QMOOD-laatuarvojen merkitystä säädellään painolla Syötteenä annettua painoa käytetään laatuarvon kertoimena, samaan tapaan kuin Frankenstein-laatuarvoilla Mikäli paino on 0, laatuarvoja ei lasketa lainkaan Jokaisen QMOOD-metriikan arvo talletetaan omaa muuttujaansa Samoin laatuarvot talletetaan omiin muuttujiinsa 19
Tehtävänanto luonnonvalinta 39 Geneettinen algoritmi tekee luonnonvalinnan fitness-arvojen perusteella. Kun arkkitehtuurit ovat käyneet läpi mutaation ja fitness-arvioinnin, ne laitetaan järjestykseen fitness-arvojen perusteella. Järjestyksen pohjalta suoritetaan ns. rulettipyörävalinta (kts. tehtävänanto). Nykyisessä toteutuksessa fitness-järjestys ja sitä seuraava rulettipyörävalinta perustuu Frankenstein-metriikoihin. Harjoitustyössä tulee toteuttaa vaihtoehtoinen QMOODlaatuarvoihin perustuva fitness-järjestely (ja sitä seuraava valinta). Geneettinen algoritmi suorittaa järjestelyn ja valinnan sitten sen mukaan, mitä käyttäjä on valinnut. Tehtävänanto output 40 Output on tässä tapauksessa yhtä kuin logitiedosto, vrt. nykyinen output-tiedosto. Tiedostoon tulee jokaisen yksilön kohdalle tallentaa sille lasketut raa at metriikka-arvot. Lisäksi tiedoston loppuun tulee kerätä yhteenveto, eli jokaisen sukupolven 10 parhaan yksilön keskiarvot laatuarvoista (vrt. nykyinen output). Toteutus on vapaa, mutta tulee tehdä siten, että QMOOD-laatuarvojen ja Frankenstein-metriikoiden vertailu on mahdollisimman helppoa! 20
Tehtävänanto dokumentti 41 Dokumenttipohja on vapaamuotoinen. Dokumentista tulee ilmetä, mitä lisäyksiä/muutoksia koodiin on tehty kussakin eri vaiheessa, minne ko. muutokset/lisäykset on tehty (luokan nimi), ja miten mahdolliset uudet komponentit on toteutettu Käytännössä tulee käydä läpi edellä olevat kohdat käyttöliittymästä tulosteeseen. Lisäksi koodiin tulee kommentoida tehdyt muutokset SELKEÄSTI. Muutetut/lisätyt kohdat tulee jokainen kommentoida alla olevaan tyyliin /** * * <Ryhmän jäsenten nimet> *<lyhyesti, mitä on lisätty/muutettu> */ Tehtävänanto dokumentti 42 Dokumentissa tulee antaa ohje QMOOD-laajennoksen käyttöön Tarvittavat syötteet, tuloksien tulkinta Esimerkkiajo, sis. annetut syötteet (komentoriviparameterit sekä tiedostot), viimeisen sukupolven parhaan yksilön fitness-arvot sekä Frankenstein- että QMOOD-metriikoilla, keskiarvo-fitnessit molemmilla metriikoilla koko ajosta tuotettu arkkitehtuuri kuvana (luonnollisesti kuvatun arkkitehtuurin tulee olla samasta ajosta kuin annetut fitness-arvot, eli viimeisen sukupolven parhaan yksilön fitness-arvot ovat kyseisestä arkkitehtuurista). PALAUTE 21
Arvostelu minimivaatimukset 43 Minimivaatimuksin (pakolliset vaatimukset) tehdystä harjoitustyöstä saa 1-4 pistettä. Työn tulee muodostaa testattava kokonaisuus! 1-3 pistettä saa, mikäli työstä löytyy kaikki vaaditut osiot, mutta esim. metriikoiden lasku ei tapahdu oikein, syötteiden annossa tai output-tiedostossa on jotain vikaa tai output on toteutettu vaikeaselkoisesti. 4 pistettä saa, mikäli kaikki vaatimukset toteutuvat, metriikat lasketaan oikein, ja syötteet ja output on toteutettu laadukkaasti. Arvostelu minimivaatimukset 44 Käyttäjä voi valita, haluaako käyttää Frankenstein- vai QMOODmetriikoita arkkitehtuureita järjestettäessä Käyttäjä voi antaa painot uusille QMOOD-laatukriteereille GA suorittaa arkkitehtuurien valinnan käyttäjän valinnan mukaan (QMOOD tai Frankenstein-laatuarvot) Fitness-funktioon on toteutettu metriikat DSC, DCC, CAM Fitness-arvoina tallennetaan metriikoihin perustuvat QMOODlaatuarvot Uudelleenkäytettävyys ja Funktionaalisuus (siltä osin kuin toteutetut metriikat ne määrittelevät). 22
Arvostelu - minimivaatimukset 45 Output-tiedostoon saadaan talteen jokaisen yksilön QMOOD- laatuarvot eriteltyinä i metriikoittain i i Output-tiedostoon kerätään jokaisen sukupolven 10 parhaan yksilön keskiarvot myös QMOOD-metriikoista laatuarvoittain (vrt. nykyinen output) Dokumentointi (sekä itse dokumentti että kommentit) on tehty ohjeiden mukaisesti Arvostelu täydellisesti toteutettu tehtävänanto 46 Harjoitustyöstä voi saada maksimissaan 6 pistettä. Pakollisen osuuden lisäksi i voi toteuttaa valinnaisia i i osuuksia, joilla voi nostaa pistemäärää. Toteutettu metriikka CIS, ja päivitetty laatuarvot siten, että kyseinen metriikka on myös otettu huomioon (1p) Toteutettu metriikka NOH, ja päivitetty laatuarvot siten, että kyseinen metriikka on myös otettu huomioon (1p) Ohjelma tuottaa UML-luokkakaavion tarvitsemat tiedot.javatiedostoihin, joiden perusteella UMLGraph tuottaa javadocia hyväksikäyttäen.dot-tyyppisen graafitiedoston. Muokkaa ohjelmaa siten, että.java-tiedostot poistetaan automaattisesti, kun graafitiedosto on tuotettu. (1p). 23
Palautus 47 Harjoitustyö tulee palauttaa vastaavanlaisena pakettina kuin se on tällä hetkellä ladattavissa. Ts. ryhmien tulee toteuttaa muutoksensa koodiin, tallentaa paketti, ja palauttaa se harjoitustyön ohjaajalle. Zip-paketti tulee nimetä ryhmän mukaan esim: Virtanen_Nieminen_Koskinen.zip Dokumentti tulee olla mukana zip-paketissa. Palautus ohjaajan sähköpostiin (outi.sievi-korte@tut.fi) tai verkkoon (lähetä ohjaajalle linkki, josta paketin voi ladata). Harjoitustyö on palautettava viimeistään 8.12. 2012 24