Materiaalin copyright © Arto Wikla. Materiaalia saa vapaasti käyttää itseopiskeluun. Muu käyttö vaatii luvan.

Ohjelmoinnin jatkokurssi: harjoitukset s2010: 5/6 (29.11.-3.12.)

(Muutettu viimeksi 1.12.2010 klo 8:30, sivu perustettu 24.11.2010.)

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!

Tyypitettyä turvallista lukemista

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.

Lukija.java, vaihe 1: rivien lukeminen

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.

Lukija.java, vaihe 2: kokonaislukujen lukeminen

Täydennetään Lukijaa kokonaislukujen lukemisen taidolla.

API-täydennys:

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().

Lukija.java, vaihe 3: rajoitettujen kokonaislukujen lukeminen

Täydennetään Lukijaa rajoitetulla välillä olevien kokonaislukujen lukemisen taidolla. Kuormitetaan yhä lisää metodia lueKokonaisluku.

API-täydennys:

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)

Lukija.java, vaihe 4: desimaalilukujen lukeminen

Täydennetään Lukija-luokkaa vielä desimaalilukujen lukemisen taidolla.

API-täydennys:

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

Suodattimia kirjallisuuden analysointiin

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.

LaskeMerkit.java

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
$

Suurenna.java

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
$

SuodataRiveja.java

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
$

RiveilleNumerot.java

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
$

VokKonsMuu.java

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
$

Kirjallisuudentutkimusta

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.

Yksi kahdeksi ja kaksi yhdeksi

Toteuta seuraavat ohjelmat "komentorivikomentoina" – virheistä annetaan selkeät ilmoitukset, mutta ohjelmat eivät kysele mitään. Tiedostojen nimet annetaan komentoriviparametreina.

JaaKahdeksi.java

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

YhdistaYhdeksi.java

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

Kirjallisuudentutkimuksen työkalu

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

Tekstilaboratorio, vaihe 1: komentotulkki

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
$

Operaatio merkkejä

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
$

Operaatio suurenna

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
$

Operaatio suodata

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
$

Operaatio numeroi

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
$

Operaatio vokaali

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
$

Eikö enää koskaan copy-pastea!

Keltainen

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... ;-)