Scala Ohjelmointi Harjoitukset 1
Harjoitus 1 Tutustutaan välineisiin ja kielen perusrakenteisiin sekä scriptitulkkiin Avaa Scala komentotulkki (scala shell), kirjoita ja suorita seuraavat scriptikoodit: print("hello, World!") val pi = 3.14 print(pi * pi) val x = "Hello" var y = x + ", World!" print (y) val z = <html><body><h1>hello</h1></body></html> print (z) Se alkuverryttelystä. Seuraavaksi avaa Notepad++ tai vastaava tekstieditori, ja kirjoita scala koodi joka kysyy käyttäjän nimen, ja tulostaa sen takaisin tervehdyksen kera, esim. Hello, John! Tallenna koodi nimellä exercise1.scala Konsolista voi lukea syötteitä näin: val line = Console.readLine Aja scripti komentoriviltä komennolla scala exercise1.scala 2
Harjoitus 2 tietotyypit ja merkkijonofunktiot Tee uusi Scala ohjelma, tällä kertaa määrittele paketti, objekti ja main metodi kuten materiaalissa kuvataan. Objektin nimeksi ja tiedostonimeksi sopii Calculator, ja paketiksi exercise2. Kirjoita koodia joka - kysyy käyttäjältä kaksi syötettä - muuntaa ne numeroiksi ja laskee ne yhteen ja tulostaa lopputuloksen ruudulle (Tyypinmuunnos onnistuu toint funktiolla, esim. val result = line1.toint + line2.toint ) Jatka tehtävää siten että otatkin vain yhden syötteen, jossa on kaksi lukua ja operaattori, välilyönnein erotettuna, esim. 1 / 2 Tehtävänäsi on tunnistaa operaattorit +, -, / ja *, ja tulostaa niiden mukainen tulos, esim. ylläolevasta tulostuisi tulos: 0.5 Tässä tarvitset merkkijonofunktioita apunasi, esim. - length kertoo merkkijonon koko pituuden -indexof(" ") kertoo mistä indeksikohdasta löytyy ensimmäinen välilyönti, merkit ennen sitä muodostavat ensimmäisen luvun jonka voit muuntaa numeroksi - lastindexof(" ") kertoo mistä löyty toinen välilyönti, merkit sen jälkeen muodostavat toisen luvun - indexof ja lastindexof sijaintien välistä löytyy operaattorimerkki, sitä voit tutkia esim. equals metodilla - substring(a, b) antaa merkit välillä a ja b, sisältäen indeksin a mutta ei b:tä Voit halutessasi ratkaista ongelman myös jollain toisella tapaa, esim. tokens funktiolla. Tarvitset tässä harjoituksessa myös ehtolausetta. Niitä ei vielä ole esitelty mutta ne ovat rakenteeltaan helppoja, esim. näin: if (merkki.equals("+")) { // do something } else if (merkki.equals("-")){ // do something else } 3
Voi helpottaa jos teet ensimmäisen ja toisen välilyönnin sijainneista apumuuttujat, esim. begin ja end. Ratkaise yksi ongelma kerrallaan, voit testata sovellusta välissä esim. tulostamalla muuttujien arvoja ruudulle. Huomaa että substring hakee merkistä a merkkiin b-1 asti, ole tarkkana Tässä esimerkki sovelluksesta: Enter calculation, for example 1 + 1 23 + 54 23 + 54 = 77 4
Harjoitus 3 Silmukointi, ehtolauseet Tehdään seuraavaksi kokonainen scala-tiedosto jonka voi myös kääntää. Tee uusi tiedosto, paketiksi exercise3, tiedostonimeksi NumberGuess.scala, ja editoi haluamallasi editorilla. Kirjoita tiedostoon helloworld esimerkin mukaiset määritykset eli object NumberGuess, def main, jne Kirjoita main funktion sisälle koodi joka arpoo satunnaisluvun välillä 0-9, ja kysyy käyttäjältä arvausta silmukassa. Jos arvaus meni yli tai ali, kerro se käyttäjälle ja kysy uutta arvausta. Jos arvaus osuu nappiin, onnittele käyttäjää ja lopeta sovellus. Satunnaisluvun väliltä 0-9 saa arvottua seuraavasti: val machinenumber = scala.util.random.nextint(9)+1 Komento jolla voit muuttaa merkkijonosyötteen numeroksi on: val guessnumber = guessstring.toint Komento jolla voit lopettaa suorituksen on tällä kertaa return (scalassa ei ole komentoja break ja continue) Tällä kertaa emme aja koodia vain scriptinä, vaan tarkoitus on kääntää se bytecodeksi ja suorittaa käännetyssä muodossa. Näin suorituskyky on kymmeniä kertoja parempi ja nautimme virtuaalikoneen automaattioptimoinneista. Voit kääntää koodin komentoriviltä näin: scalac NumberGuess.scala Ja ajaa sen: scala exercise3.numberguess Tai voit ajaa ja kääntää esim. Netbeans tai Eclipse tai Idea editorissa. Komentoriviltä huomioi että kääntäminen tulee tehdä lähdekoodikansiossa, mutta ajaminen tulee tehdä juurikansiossa, eli pakettirakenteen yllä. Lisätehtävä: Tee ohjelma joka tulostaa kertotaulua 100*100 asti, eli ensimmäiselle riville tulokset 1x1, 1x2, 1x3,... toiselle 2x1, 2x2, 2x3,... jne. Jos tämä tuntuu helpolta, lisää kertotaulutulostukseen mitkä luvut kerroit keskenään, esim. 2x1=2, 2x2=4, 2x3=6, jne 5
Harjoitus 4 Luokat ja funktiot Scalassa Tehdään uusi luokka nimeltä Person, paketiksi sopii vaikkapa exercise4. Luodaan luokalle muutama ominaisuus, esim. - firstname - lastname - birthday - email Mieti ominaisuuksien tyypit ja päätä ne itse. Määritä oliolle myös muutama konstruktori joilla olion voi nopeasti luoda. Esim. antamalla nimet, nimet ja syntymäpäivän, tai ei mitään. Sitten funktioiden kimppuun: Tee kaksi funktiota, ensimmäinen on funktio getage, joka laskee henkilön syntymäpäivän ja nykyisen päivän erotuksen avulla henkilön iän ja palauttaa sen. Huomioi laskussa vain kokonaisten vuosien osuus. Vinkki: käytä birthday tyyppinä java.util.date oliota. Suuri osa sen metodeista on deprekoituneita mutta sen avulla on helppo asettaa vuodet johonkin arvoon, ja hakea vuosikomponentti. Nykyisen aikaleiman saat luomalla upouuden instanssin Date oliosta. Alla on huomioita miten tämä oikeasti kannattaisi tehdä mutta tämän harjoituksen pääteema on miten tehdään olioon logiikkaa, joten tehdään asia tällä kertaa Java-kielen olemassaolevaa peruskirjastoa käyttäen. Joka tapauksessa, käytä tässä apunasi deprekoitunutta metodia getyear, ja laske niiden erotus. Tarkasti ottaen pitäisi huomioida vielä kuukaudet ja päivätkin, mutta jätetään se lisätehtäväksi. Toinen funktio on tostring, ja sen on tarkoitus ylikirjoittaa kantaluokan versio, joten tutki scala.object dokumentaatiota jotta se onnistuu. Tehtävänäsi on palauttaa merkkijono jossa on henkilön nimet sekä ikä näkyvissä. Huomaa että ylikirjoittaessa käytetään avainsanaa override. Tee toinen luokka, PersonMain, jossa luot pari oliota ja testaat niiden funktioita. Huomaa että koska Date todella on hieman outo luokka, voit luoda instanssin haluamillasi arvoilla tähän tapaan: new java.util.date(1960-1900,2-1,1) Eli, ylläoleva luo päivän 1.2.1960, koska vuodet lasketaan alkaen 1900, kuukaudet 0-pohjaisesti ja päivät 1- pohjaisesti. 1.1.2010 olisi 2010-1900, 1-1, 1 eli 110, 0,1 Huom! Funktionaalisessa ohjelmoinnissa tyypillisesti vältetään tilatietoa olioissa, mutta sillekin on omat käyttönsä. Huom! Java Date/Calendar tyypit ovat myös kovasti sotkuisia ja yleinen suositus olisi käyttää scalan kanssa ScalajTime/Joda Time kirjastoja, mutta tässä harjoituksessa yksinkertaistetaan asiaa jotta vältytään perusharjoituksissa lisäkirjastojen asentelulta. 6
Harjoitus 5 Funktiot, rekursio Toteuta funktio joka laskee montako kertaa annettu merkki toistuu annetussa merkkijonossa. Funktiota kutsutaan tähän tapaan: val result = count(mystring, chartolookfor) Ratkaise ongelma käyttämättä valmiita String luokan funktioita tai silmukkarakenteita. Helppoa? Tee toinen funktio joka kääntää listan elementtien järjestyksen, käyttämättä silmikkarakenteita tai valmiita reverse-funktioita. Käytä rekursiota. Funktiota tulisi voida kutsua tähän tapaan: reverse(list(1, 1, 2, 3, 5, 8)) Voitko tehdä funktioista tail recursive tyyppisiä? Lopuksi, tee aiemmasta GuessNumber harjoituksesta versio joka toimii rekursiolla, ei silmukalla. 7
Harjoitus 6 Kokoelmaluokat: Array, List, Tuple, Set, Map Tee sovellus joka kerää komentorivisyötteitä List rakenteeseen, merkkijonomuodossa. Sovelluksen logiikka tähän tapaan: - lue komentorivisyöte -jos syöte oli 'EXIT', lopeta -jos syöte oli 'LIST', tulosta listan sisältö alkio kerrallaan ruudulle -muutoin lisää syöte listaan Testaa sovelluksen toimintaa. Sopisiko tähän List tai Tuple paremmin? Lisätehtävä: Lue komentoriviltä nimi ja ikä. Talleta nämä tiedot Map rakenteeseen samaan tapaan kuin edellä, mutta nyt laitetaan nimi avaintietokentäksi, siten että nimen avulla löytyy oikea ikä salamannopeasti. Kannattanee toteuttaa listaustoiminto kuten edellä, jotta voit testat menevätkö tiedot Map:iin ja osaatko käydä myös sen tietorakenteena läpi. Saattaa helpottaa jos teet pääsilmukan jossa komento on NEW, LIST tai EXIT, ja NEW toiminnolla kysyt nimen ja iän. Lisätehtävä: Muuta edellämainittu sovellus rekursiiviseksi funktioksi. Muuta rakenne siten että funktio kysyy komennon, ja riippuen sen arvosta suorittaa toiminnon, joko rekursoi kysymään uuden komennon tai lopettaa jos komento oli EXIT. Silmukkarakenne pitäisi poistua koodista täysin. 8
Harjoitus 7 Perintä, Trait ja Mixin Tehdään nopea harjoitus yllämainituista. Teit aiemmin luokan Person. Tee seuraavaksi: - Muuta Person abstraktiksi luokaksi. Määritä Person luokalle uusi abstrakti funktio makenoise(), ilman toteutusta - Luokka Employee joka perii luokan Person, määrittele työntekijälle muutama uusi ominaisuus, esim. palkka, osasto ja työsopimuksen alkamispäivä. Toteuta makenoise siten että se tulostaa: HELLO - Trait nimeltä Wolf, joka määrittelee makenoise() funktion. Funktion toteutuksessa tulosta ruudulle: HOWL - Määrittele luokka nimeltä WereWolf, joka perii luokan Person ja käyttää piirrettä Wolf Kirjoita pääohjelmakoodia joka luo instanssin yllämainituista luokista ja testaa niiden toimintaa. Lisätehtävä: Tutustu API referencen kautta olemassaoleviin Scala piirreluokkiin ja siihen miten niitä käytetään 9
Harjoitus 8 Tiedostot Kirjoita scalalla ohjelma joka lukee HTML tiedoston (joko paikallinen tiedosto, tai jos internet on käytössä, url osoitteen kautta, esim. ) läpi. Ohjelman tulee tutkia luettua sisältöä ja tulostaa näkyviin <h1> tagejä sisältävät rivit. Eli jos sivulla on yksi h1-tagi, se tulostuu, jos on useampia, ne tulostuvat kaikki. Jotta tehtävä pysyy inhimillisessä mittakaavassa, voit olettaa että <h1> pari on samalla luetulla rivillä, näinhän ei tosiasiassa aina ole sotkuisissa HTML sivuissa. Huomaa myös että tehtävänä ei ole kaivella tagien sisältöä, vaan voit tulostaa koko rivin. (Tagien sisältöön löytyisi jakarta commons-kirjastosta oiva StringUtils luokka ja substringbetween metodi) Lisätehtävä: Ota tag komentoriviparametrina Tutki mahdollisuuksia kirjoittaa tiedosto, esim. luoda HTML tiedosto käyttäen FileWriter luokkaa. 10
Harjoitus 9 Actorit ja rinnakkainen suoritus Toteuta actoreilla kaksi rinnakkain ajavaa tehtävää, jotka pyörivät loputtomiin. Laita yksi tulostamaan Hello ja toinen tulostamaan World. Toteuta pääohjelma joka käynnistää actorit. Tulostuksen pitäisi näyttää jotakuinkin tältä: Hello Hello World Hello World World Eli säikeet eivät tulosta tietojaan tasaisesti vuorotellen koska mitään synkronointia ei ole käytössä. Joten seuraavaksi, ota oppia pingpong-mallista ja pistä säikeet vuorottelemaan sanomien ja case classien avulla tähän tapaan: Hello World Hello World Oliko helppoa? Hyvä, lisätehtävänä voit tehdä jotain rakentavampaa Actor-luokilla. Tässä muutama idea: - Kirjoita ohjelma joka luo satunnaislukuja taulukkoon, esim. miljoona kappaletta. Seuraavaksi toteuta Actoreita käyttäen ohjelmakoodi, joka laskee keskiarvon kaikista luvuista. - Kirjoita ohjelma joka laskee montako tiedostoa on hakemistossa - alihakemistoineen. Käytä tässä actoreita ja rekursiota laskemaan alihakemistoja 11
Harjoitus 10 Yksikkötestausta JUnitilla ja ScalaTestillä Lataa ScalaTest http://www.scalatest.org/download, pura paketti sopivaan paikkaan. Lataa myös Junit 4.jar esim. http://www.junit.org/ Tee koodit haluamaasi paikkaan, parasta varmaan tehdä ne tekstieditorilla sopivasta pohjasta. Tarvitset tähän funktion jossa on testattavaa koodia, paras lopputulos on funktiolla jossa ei ole sivuvaikutuksia, eli se ottaa vain arvoja sisään ja ulos, ei esim. käytä println lauseita tai lue konsolia. Ellei sinulle ole vielä sellaista, tee sellainen nyt. Komentoriviltä: Kääntäminen tapahtuu tähän tapaan (muuta polut oman ympäristösi ja luokkasi nimen ja paketin mukaan oikein): scalac -cp.;c:\java\scalatest-1.2\scalatest-1.2.jar;c:\java\junit-4.8.2.jar test\testmathing.scala Ajaminen sitten näin: scala -cp.;c:\java\scalatest-1.2\scalatest-1.2.jar;c:\java\junit-4.8.2.jar org.junit.runner.junitcore test.testmathing 12