Harjoitustehtävien otsikoilla on värikoodaus: Vihreät tehtävät on syytä tehdä joka tapauksessa. Värittämättömiä ei ole ihan pakko tehdä, mutta nekin ovat hyvin hyödyllisiä ja myös vaikuttavat pisteisiin. Keltaiset tehtävät ovat vähän haastavampia. Nekin lasketaan mukaan harjoituspisteitä määrättäessä, mutta ilmankin niitä harjoituksista voi saada maksimipisteet.
Huom: Jokaisen ohjelmatiedoston alkuun on kirjoitettava kommenttina harjoituskerta, tehtävän numero ja tekijän nimi tyyliin:
// 5. harjoitukset, tehtävä 2.1, Oili Opiskelija
Huom: Ohjaajien eli pajamestarien sivulta löytyy hienoja testauksen apuvälineitä tehtävien tekemisen avuksi!
Huom: Ohjaajien eli pajamestarien sivulta löytyy ohjeita komentoriviin NetBeansissa!
HUOM: Tehtävät voivat vielä muuttua!
Syötteiden lukemisen välineitä voidaan toteuttaa monenlaisella logiikalla. Pojimmiltaan kaikki ohjelmalle tarjottu syöte on pelkkää bittivirtaa. Yleensä ohjelmoijalle on onneksi tarjolla välineitä, joilla lukuoperaatioita voidaan kirjoittaa korkeammalla abstraktiotasolla: luetaan tavuja, merkkejä, rivejä, lukuja, ...
Lukemisen välineitä voidaan kutsua tyypitetyiksi, jos ohjelmoija voi/saa ilmaista, minkä tyyppisen tiedon hän haluaa lukea. Esimerkki tällaisesta on vaikkapa Javan Scanner-luokan tarjoama välineistö: nextInt, nextDouble, ...
Tyypitetyssä lukemisessa virheisiin voidaan suhtautua kahdella tavalla: Yksi tapa on antaa tyypiltään virheellisen syötteen keskeyttää ohjelman toiminta. Esimerkiksi kokonaisluvuksi kelpaamaton syöte aiheuttaa virhetilanteen, poikkeuksen, joka päättää ohjelman suorituksen ellei ohjelmoija itse sieppaa poikkeusta ja käsittele tapausta.
Toinen mahdollisuus on tehdä lukuoperaatioista sellaisia, että "virheitä ei voi tulla" – ainakaan tavanomaisissa virhetilanteissa. Esimerkiksi lukujen lukeminen voidaan tehdä sitkeäksi: lukuoperaatio itse väsymättä pyytelee kunnollista syötettä kunnes/jos viimein saa sellaisen. Tässäkin strategiassa jotkin vakavat virheet saattavat johtaa ohjelman suorituksen keskeyttämiseen, mutta sekin voidaan ehkä tehdä ymmärrettävämmin kuin Java-ohjelmissa oletusarvoisesti tapahtuu.
Rakennellaan vaiheittain kirjastoluokka Lukija standardisyöttövirran turvalliseen tyypitettyyn lukemiseen.
Toteuta luokka Lukija, jolla voi lukea stardardisyöttövirran rivejä yksi kerrallaan.
APIn lähtökohta:
Lukija tietenkin on syytä toteuttaa Scanner-olioa käyttäen. Riviksi kelpaa millainen merkkijono tahansa; Scannerin tapa lukea rivejä soveltuu siis sellaisenaan. Scannerin InputStream-parametrinen konstruktorikaan ei aiheuta poikkeuksia. Mutta metodi nextLine voi epäonnistua kahdella tavalla:
NoSuchElementException - if no line was found IllegalStateException - if this scanner is closed
Vuorovaikutteisessa ohjelmassa ensinmainittu poikkeus aiheutuu, kun käyttäjä kirjoittaa ohjelmalle tiedostonlopun (Linux ctrl-d, Windows ctrl-z). Ensinmainittu poikkeus ei tällä kertaa ole virhetilanne, ks. metodin selitys. Varaudu kuitenkin toiseen poikkeukseen antamalla selkeä virheilmoitus ja lopettamalla ohjelman suoritus.
Käyttöesimerkki:
System.out.println("Anna rivi!"); String rivi = Lukija.lueRivi(); ... while (true) { // "1.5-kertainen" toisto rivi = Lukija.lueRivi(); if (rivi == null) // <<<<< toiston lopetus! break; System.out.println(rivi); } ...
Havainnollista luokan käyttöä pienellä ohjelmalla.
Täydennetään Lukijaa kokonaislukujen lukemisen taidolla.
API-täydennys:
Ei ole kokonaisluku! Yritä uudelleen.
Ohjelmaesimerkki:
System.out.println("Anna kokonaisluku!"); int luku = Lukija.lueKokonaisluku(); luku = Lukija.lueKokonaisluku("Paljonko on tusina? "); if (luku != 12) System.out.println("Väärin!");
Käyttöesimerkki:
Anna kokonaisluku! Mörkö Ei ole kokonaisluku! Yritä uudelleen. 4008 Paljonko on tusina? Mörkö Paljonko on tusina? 13 Väärin!
Huomaa että kokonaisluvun lukeminen on toteutettava siten, että koko syöttörivi käsitellään. Muuten saattaa tulla tuttuja(?) synkronointiongelmia. Luvut siis on annettava omalla rivillään. Ylimääräiset alku- ja loppuvälilyönnit eivät haittaa.
Esimerkkitilanne:
Anna kokonaisluku! 1828 36 Ei ole kokonaisluku! Yritä uudelleen. 1828 Mörkö Ei ole kokonaisluku! Yritä uudelleen. 1828
Käytä siis luentomateriaalin luvussa X esiteltyjä välineitä Integer.parseInt(...) ja trim().
Täydennetään Lukijaa rajoitetulla välillä olevien kokonaislukujen lukemisen taidolla. Kuormitetaan yhä lisää metodia lueKokonaisluku.
API-täydennys:
throw new IllegalArgumentException("Tyhjä lukualue!");
Ohjelmaesimerkki:
int luku = Lukija.lueKokonaisluku(1, "Anna positiivinen kokonaisluku!"); luku = Lukija.lueKokonaisluku("Anna ei-positiivinen kokonaisluku!", 0); luku = Lukija.lueKokonaisluku(-100, "Anna luku väliltä -100-100!", 100); luku = Lukija.lueKokonaisluku(12, "böö!", 6);
Esimerkki oljelmanpätkän suorituksesta:
Anna positiivinen kokonaisluku! Mörkö Anna positiivinen kokonaisluku! -123 Anna positiivinen kokonaisluku! 123 Anna ei-positiivinen kokonaisluku! 1 Anna ei-positiivinen kokonaisluku! 0 Anna luku väliltä -100-100! 123 Anna luku väliltä -100-100! -99 Exception in thread "main" java.lang.IllegalArgumentException: Tyhjä lukualue! at TiesMika.main(TiesMika.java:15)
Täydennetään Lukija-luokkaa vielä desimaalilukujen lukemisen taidolla.
API-täydennys:
Ei ole desimaaliluku! Yritä uudelleen.
Ohjelmaesimerkki:
System.out.println("Anna luku!"); double luku = Lukija.lueDesimaaliluku(); luku = Lukija.lueDesimaaliluku("Mikä on lieriön korkeus? "); luku = Lukija.lueDesimaaliluku("Miten kaukana on pitkällä? ");
Käyttöesimerkki:
Anna luku! Mörkö Ei ole desimaaliluku! Yritä uudelleen. -123.4 Mikä on lieriön korkeus? Mörkö Mikä on lieriön korkeus? 3.14 Miten kaukana on pitkällä? 3,14
Laaditaan joukko ohjelmia, jotka suodattavat (filtteröivät) tai muokkaavat standardisyöttövirtaa tavalla tai toisella erilaiseksi standarditulosvirraksi. Näitä ohjelmia voi toki suorittaa näppäimistöltä ja näytöltä, mutta niiden pääasiallinen käyttötarkoitus liittyy tiedostojen käsittelyyn uudelleenohjauksen avulla.
Ohjelma laskee tiedoston merkkien määrän. Mukaan ei lasketa rivinvaihtomerkkejä. Ohjelman ainoa tuloste on yksi kokonaisluku.
Käyttöesimerkki:
$ java LaskeMerkit eka toka (ctrl-d tai ctrl-z) 7 $ java LaskeMerkit < tiedosto.txt 49876 $
Ohjelma muokkaa tiedostosta sellaisen, jossa kaikki pienet kirjaimet on muutettu suuriksi. Menettelyllä voi olla käyttöä, kun etsitään tekstimassasta sanoja piittaamatta siitä, onko ne kirjoitettu isoin vai pienin kirjaimin.
Luokassa String on kelpo väline homman vaivattomaan hoiteluun.
Käyttöesimerkki:
$ java Suurenna Ei tätä nyt ihan tähän ole tarkoitettu. EI TÄTÄ NYT IHAN TÄHÄN OLE TARKOITETTU. (ctrl-d tai ctrl-z) $ java Suurenna < koe.txt TÄMÄ KAIKKI TULEE TUOSTA TIEDOSTOSTA! $ java Suurenna < koe.txt > isokoe.txt $
Ohjelma suodattaa pois kaikki rivit, joilla ei esiinny komentoriviparametrina annettua merkkijonoa.
Käyttöesimerkki:
$ java SuodataRiveja Virhe: komennolle pitää antaa suodatinsana! $ java SuodataRiveja kissa kissa kävelee. kissa kävelee. Kissa kävelee. Koira kävelee. Kaikissa tapauksissa. Kaikissa tapauksissa. moi vaan (ctrl-d tai ctrl-z) $ java SuodataRiveja kissa < teos.txt > kissateos.txt $
Ohjelma muokkaa syötteestä sellaisen version, jossa joka rivin alkuun lisätään rivinumero, kaksoispiste ja välilyönti.
Käyttöesimerkki:
$ java RiveilleNumerot Päivää! 1: Päivää! abc 2: abc (ctrl-d tai ctrl-z) $ java RiveilleNumerot < koe.txt 1: eka rivi 2: .... 3: kolkku 4: eikä muuta $ java RiveilleNumerot < tiedosto.txt > n_tiedosto.txt $
Ohjelma laskee tiedoston eräiden merkkiluokkien prosentuaalista osuutta tiedoston kokonaismerkkimäärästä komentoriviparametrin ohjaamalla tavalla:
Välilyöntejä, sarkaimia, rivin loppuja, ym. "white spacea" ei tässä laskennassa oteta mukaan kokonaismerkkimäärään.
Ohjelman ainoa tuloste on kokonaislukuna ilmaistu prosenttiluku.
Käyttöesimerkki:
$ java VokKonsMuu Komentoriviparametri (v, k tai e) puuttuu! $ java VokKonsMuu ö Komentoriviparametri (v, k tai e) puuttuu! $ java VokKonsMuu v abc defg hij (ctrl-d tai ctrl-z) 30 $ java VokKonsMuu v < MinaOlliJaOrvokki.txt 67 $
Tehdään konkreettista kirjallisuudentutkimusta: Tutkitaan Juhani Ahon romaania Rautatie (Rautatie.txt) ja Mark Twainin teosta Huckleberry Finn (HuckleberryFinn.txt).
Käytä edellä tehtyjä välineitä ja luennolla esiteltyä ohjelmaa LaskeRivit.java. Laadi niistä komentoputkia seuraavia tutkimuksia varten.
Huomaa että teostiedostot ovat suoraan Gutenberg-projektin tarjoamassa muodossa. Jos haluat luotettavia tutkimustuloksia, tiedostoista kannattaa editoida pois turhat osat.
Toteuta seuraavat ohjelmat "komentorivikomentoina" – virheistä annetaan selkeät ilmoitukset, mutta ohjelmat eivät kysele mitään. Tiedostojen nimet annetaan komentoriviparametreina.
Ohjelma JaaKahdeksi jakaa yhden tekstitiedoston kahdeksi siten, että parittomat rivit menevät yhteen tiedostoon, parilliset toiseen. Ohjelma käynnistetän komennolla:
java JaaKahdeksi jaettavaTiedosto.txt parittomatRivit.txt parillisetRivit.txt
Havainnollista ohjelman käyttöä pienillä esimerkeillä.
Ohjelma YhdistaYhdeksi yhdistää kaksi tekstitiedostoa yhdeksi siten, että joka toinen rivi otetaan toisesta, joka toinen toisesta. Jos jompikumpi syöttötiedosto loppuu ennen toista, pidemmän rivit kopioidaan sellaisenaan yhdistettyyn tiedostoon. Ohjelma käynnistetän komennolla:
java YhdistaYhdeksi eka.txt toka.txt yhdistettyTiedosto.txt
Havainnollista ohjelman käyttöä pienillä esimerkeillä.
Edellä tehtiin yksittäisiä pieniä ohjelmia kirjallisuudentutkimuksen tarpeisiin. Nyt toteutetaan työkalu, jolla tutkija voi ehkäpä vaivattomammin tutkia tekstejä.
Voit pyrkiä uudelleenkäyttämään toteuttamiesi työkalujen algoritmeja tässä tehtävässä sillä kauhistellulla cut-and-paste-tekniikalla. Mutta odotahan vaan, mitä seuraa...
Toteuta komentotulkkilogiikalla tekstilaboratorio, joka tarjoaa seuraavat palvelut:
Nykytiedosto on siis laboratorion keskeinen käsite. Tämä tiedosto on tutkimuksen ja käsittelyn kohteena.
Käyttöesimerkki:
$ java Tekstilaboratorio Ei tiedostoa. 1 lue 2 tallenna 3 lopeta 1 Mikä? KesayonAjatelma.txt Ei löydy tiedostoa KesayonAjatelma.txt Ei tiedostoa. 1 lue 2 tallenna 3 lopeta 1 Mikä? KesayonUnelma.txt Tiedosto: KesayonUnelma.txt 1 lue 2 tallenna 3 lopeta 4 Virheellinen komento! Tiedosto: KesayonUnelma.txt 1 lue 2 tallenna 3 lopeta 3 Tiedostoa ei ole tallennettu. Lopetetaanko silti? (k=kyllä) EI! Tiedosto: KesayonUnelma.txt 1 lue 2 tallenna 3 lopeta 2 Millä nimellä talteen? SyysyonUnelma.txt SyysyonUnelma.txt on jo olemassa. Korvataanko se? (k=kyllä) k Tiedosto: SyysyonUnelma.txt 1 lue 2 tallenna 3 lopeta 3 $
Lisää laboratorioon komento merkkejä, joka laskee ja tulostaa nykytiedoston merkkien määrän. Mukaan ei lasketa rivinvaihtomerkkejä.
Käyttöesimerkki:
$ java Tekstilaboratorio Ei tiedostoa. 1 lue 4 merkkejä 2 tallenna 3 lopeta 1 Mikä? Rautatie.txt Tiedosto: Rautatie.txt 1 lue 4 merkkejä 2 tallenna 3 lopeta 4 189233 Tiedosto: Rautatie.txt 1 lue 4 merkkejä 2 tallenna 3 lopeta 3 $
Lisää laboratorioon komento Operaatio suurenna, joka muokkaa nykytiedostosta sellaisen, jossa kaikki pienet kirjaimet on muutettu suuriksi.
Huomaa että nykytiedostoa muokattaessa ohjelman on syytä luoda tilapäinen aputiedosto, joka tietenkin ohjelman suorituksen päättyessä on poistettava.
Käyttöesimerkki:
$ java Tekstilaboratorio Ei tiedostoa. 1 lue 4 merkkejä 2 tallenna 5 suurenna 3 lopeta 1 Mikä? Rautatie.txt Tiedosto: Rautatie.txt 1 lue 4 merkkejä 2 tallenna 5 suurenna 3 lopeta 5 Tiedosto: Rautatie.txt 1 lue 4 merkkejä 2 tallenna 5 suurenna 3 lopeta 3 $
Lisää laboratorioon komento suodata, joka poistaa nykytiedostosta kaikki rivit, jotka eivät sisällä annettua merkkijonoa.
Huomaa että nykytiedostoa muokattaessa ohjelman on syytä luoda tilapäinen aputiedosto, joka tietenkin ohjelman suorituksen päättyessä on poistettava.
Käyttöesimerkki:
$ java Tekstilaboratorio Ei tiedostoa. 1 lue 4 merkkejä 2 tallenna 5 suurenna 3 lopeta 6 suodata 1 Mikä? Rautatie.txt Tiedosto: Rautatie.txt 1 lue 4 merkkejä 2 tallenna 5 suurenna 3 lopeta 6 suodata 6 Anna suodatinsana! kissa Tiedosto: Rautatie.txt 1 lue 4 merkkejä 2 tallenna 5 suurenna 3 lopeta 6 suodata 3 $
Lisää laboratorioon komento numeroi, joka muokkaa nykytiedostoa siten, että joka rivin alkuun lisätään rivinumero, kaksoispiste ja välilyönti.
Huomaa että nykytiedostoa muokattaessa ohjelman on syytä luoda tilapäinen aputiedosto, joka tietenkin ohjelman suorituksen päättyessä on poistettava.
Käyttöesimerkki:
$ java Tekstilaboratorio Ei tiedostoa. 1 lue 4 merkkejä 7 numeroi 2 tallenna 5 suurenna 3 lopeta 6 suodata 1 Mikä? Rautatie.txt Tiedosto: Rautatie.txt 1 lue 4 merkkejä 7 numeroi 2 tallenna 5 suurenna 3 lopeta 6 suodata 7 Tiedosto: Rautatie.txt 1 lue 4 merkkejä 7 numeroi 2 tallenna 5 suurenna 3 lopeta 6 suodata 3 $
Lisää laboratorioon komento vokaali, joka tutkii ja ilmoittaa nykytiedoston vokaalien, konsonanttien ja erikoismerkkien prosentuaaliset osuudet. Välilyöntejä, sarkaimia, rivin loppuja, ym. "white spacea" ei tässä laskennassa oteta mukaan kokonaismerkkimäärään.
Käyttöesimerkki:
$ java Tekstilaboratorio Ei tiedostoa. 1 lue 4 merkkejä 7 numeroi 2 tallenna 5 suurenna 8 vokaali 3 lopeta 6 suodata 1 Mikä? Teos.txt Tiedosto: Teos.txt 1 lue 4 merkkejä 7 numeroi 2 tallenna 5 suurenna 8 vokaali 3 lopeta 6 suodata 8 54 % vokaaleja 29 % konsonantteja 7 % erikoismerkkejä Tiedosto: Teos.txt 1 lue 4 merkkejä 7 numeroi 2 tallenna 5 suurenna 8 vokaali 3 lopeta 6 suodata 3 $
Kahdessa erilaisesessa kirjallisuudentutkimuskaluston toteutustavassa on paljon yhteistä. Ja copy-paste-ohjelmointi ei ole kovin tyylikästä.
Kehittele jokin tapa, ohjelman rakenne, ohjelman arkkitehtuuri, jolla voit toteuttaa molemmat palvelut jouttumatta uudelleenkirjoittamaan tai kopioimaan samaa ohjelmakoodia sinne tänne.
Hahmottele arkkitehtuurisi myös Javan luokkakaluston avulla ihan konkreettisina luokkina ja muina rakenteina. Varsinaiset algoritmien toteutukset voit tähän luurankoon jättää tekemättä
Tai jos teet ensin tämän tehtävän, 2. ja 4. tehtäväsarjan tekeminen voi olla helppoa... ;-)