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

Ohjelmoinnin jatkokurssi: harjoitukset s2010: 6/6 (7.12.-10.12.)

(Muutettu viimeksi 6.12.2010, sivu perustettu 1.12.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:

// 2. harjoitukset, tehtävä 1.3, Oili Opiskelija

Huom: Ohjaajien eli pajamestarien sivulta löytyy hienoja testauksen apuvälineitä tehtävien tekemisen avuksi!

Kirjallisuuden jatkotutkimusta

Jatketaan kirjallisuudentutkimuksen välineiden laadintaa. Saattaa toisinaan olla kiinnostavaa selvittää, millaiset sanat seuraavat ja edeltävät toisiaan jossakin kielessä, kirjallisuuden lajissa tai yksittäisellä kirjailijalla.

Sanasuodatin.java, vaihe 1

Laadi luokka Sanasuodatin, jonka ilmentymä luodaan antamalla tekstitiedostoon liitetty File-olio.

Alustava API:

Esimerkki:

// luodaan File-olio tekstiTdsto
...
Sanasuodatin virta = new Sanasuodatin(tekstiTdsto);
int sanaLkm = 0;
while (virta.onVielaSanoja()) {
  String sana = virta.seuraava();
  sanaLkm = sanaLkm + 1;
}
...

Sanasuodatin.java, vaihe 2

Edellisen version "sanat" voivat sisältää mitä tahansa merkkejä, pilkkuja, pisteitä, yms. Kuitenkin kun sanoja tutkitaan, olisi hyvä voida samaistaa sanat lauseen keskellä ja lopussa – siis pilkuista ja pisteistä yms. pitäisi päästä eroon. Sanan sisällä sijaitsevat erikoismerkit on kuitenkin syytä säilyttää: siellä voi olla sanaan kuuluvia yhdysviivoja, heittomerkkejä, yms.

Toisinaan voi olla tarpeellista samaistaa myös isolla ja pienellä kirjoitetut sanat: esimerkiksi "Kissa" lauseen alussa ja "kissa" lauseen keskellä.

Kehittyneen version API:

Esimerkki:

// luodaan File-olio tekstiTdsto
...
Sanasuodatin virta = new Sanasuodatin(tekstiTdsto);
virta.isotPieniksi(true); 
int sanaLkm = 0;
while (virta.onVielaSanoja()) {
  String sana = virta.seuraava();
  sanaLkm = sanaLkm + 1;
}
...

SananSeuraajat.java

Toteuta luokka SananSeuraajat, tietorakenne, johon voidaan tallettaa pareja (sana, sanaluettelo). Luokka tarjoaa palvelut:

Vihjeitä: Seuraajaluettelotietorakenteen toteutuksessa voi käyttää sellaista HashMap-oliota, joka esittää assosiaatioita String-merkkijonon ja siihen liittyvän ArrayList<String>-olion välillä.

Käyttöesimerkki:

SananSeuraajat lelut = new SananSeuraajat();

lelut.lisaaSanalleSeuraaja("Maija", "nukke");
lelut.lisaaSanalleSeuraaja("Maija", "nalle");
lelut.lisaaSanalleSeuraaja("Maija", "kudin");

lelut.lisaaSanalleSeuraaja("Pekka", "auto");
lelut.lisaaSanalleSeuraaja("Pekka", "mittari");
lelut.lisaaSanalleSeuraaja("Pekka", "miekka");

System.out.println(lelut.mitkaSeuraavatSanaa("Maija"));
System.out.println(lelut.mitkaSeuraavatSanaa("Pekka"));
System.out.println(lelut.mitkaSeuraavatSanaa("Kalle"));

System.out.println(lelut);

Tulostus:

[nukke, nalle, kudin]
[auto, mittari, miekka]
null
{Maija=[nukke, nalle, kudin], Pekka=[auto, mittari, miekka]}

Toinen tapa tehdä SananSeuraajat.java

Edellä SananSeuraajat toteutettiin siten, että seuraajat esitettiin ArrayList<String>-olioina. Luontevampaa olisi ehkä esittää seuraajat joukkona, vaikkapa siis HashSet<String>-olioina. Ainoa muutos API-kuvauksessa on yhden metodin paluuarvon tyyppi:

Edellisen version käyttöesimerkki kelpaa tähän sellaisenaan.

SeuraajaAnalyysi.java

Laadi edellisten tehtävien luokkia Sanasuodatin ja SananSeuraajat käyttäen sovellus SeuraajaAnalyysi. Ohjelman tehtävä on selvittää annetussa tekstitiedostossa kaikkien sanojen välittömät seuraajasanat. Tässä tutkimuksessa halutaan samaistaa sanat joissa ainoat erot ovat kirjainten "koko", so. isot ja pienet kirjaimet tulkitaan kaikki pieniksi siten kuin Sanasuodatin-luokan kehittyneempi versio taitaa.

Ohjelma kysyy ensin tiedoston nimen ja tarjoaa sitten palvelun yksittäisen sanan seuraajien selvittämiseen.

Ohjelman testiversiossa myös koko seuraajaluettelon tulostaminen on hyödyllistä.

Testiaineistoa kirjallisuuden tutkimukseen:

Huomaa että Rautatie ja Huckleberry Finn ovat suoraan Gutenberg-projektin tarjoamassa muodossa. Jos haluat luotettavia tutkimustuloksia, tiedostoista kannattaa editoida pois turhat osat.

Käyttöesimerkki:

Minka tiedoston sanojen seuraajat haluat tutkia? Tyhjä lopettaa.
Ratatie.txt
Tiedostoa Ratatie.txt ei loydy!
Minka tiedoston sanojen seuraajat haluat tutkia? Tyhjä lopettaa.
Rautatie.txt
kissa
[maitoansa, pankon, oli]
kännykkä
null
Minka sanan seuraajat haluat tietaa? Tyhjä lopettaa.
hevonen
[huurussa, luimistaa, ja]
Minka sanan seuraajat haluat tietaa? Tyhjä lopettaa.

Kuu kasvaa

Luennoilla (luku XIII) tutustuttiin Kuulaskuri-luokkaa käyttävään graafiseen sovellukseen Kuu, joka näyttää nappia painellen kuukausien etenemisen. Kehitellään tätä sovellusta.

Nimetyt kuukaudet

Kurssimateriaalin luvussa VIII kehiteltiin Kuulaskuri-luokka osaamaan kuukausien nimet. Muokkaa graafinen sovellus Kuu näyttämään kuukauden numeron sijasta kuukauden nimi. Käytä laskennan logiikan toteuttamiseen (eli ns. mallina) luvun VIII luokkaa KuulaskuriPlus.

Uusi Kuu näyttää seuraavalta:

kuva maaliskuu.png

Kuulle vuodet

Eikö asiakkaille mikään riitä? Nyt ne haluavat vielä vuodetkin näkyviin laskurissaan. No, kunhan hinnasta sovitaan...

Kehittele Kuu osaamaan myös vuosiluvut. Aloitusajankohdaksi asetetaan tammikuu 2010.

Luvun VIII Kuulaskuri-versiot eivät ehkä ole sellaisinaan tähän tehtävään täysin käyttökelpoisia ainakaan ihan helposti – voit siis joutua kehittelemään myös itse laskennan logiikkaa, "kuumoottoria", jonkin verran. Mutta se näkyy sitten myös laskussa! [Itse täydentäisin KuulaskuriPlus-luokkaa aksessorilla, joka palauttaa kuukauden nimen. Aliluokka KuulaskuriPlusPlus tietenkin sitten perii tuonkin ominaisuuden...]

Aina vaan hienompi Kuu näyttää seuraavalta:

kuva maaliskuu2038.png

Kuu sovelmaksi

Muokkaa hienoimmasta Kuu-sovelluksestasi www-sivulla käytettävä Kuu-sovelma.

Tukkimiehen taskulaskin

Tukkimies pitää kirjaa vetämällä viivan vihkoon aina kun puu on kaadettu. Viivojen määrä on kaadettujen puiden eli tukkien määrä. Viivahan on tukin yksinkertaistettu kuva! Jos yöllä käy tukkivarkaita, tukkimies pyyhkii viivoja pois vihostaan.

Tukkimies laskee siis seuraavin numeroin yhdestä kymmeneen: |, ||, |||, ||||, |||||, ||||||, |||||||, ||||||||, |||||||||, ||||||||||.

Esimerkkejä tukkimiehen algebrasta &ndash tukkimies tarvitsee vain yhteen- ja vähennyslaskun:

| + | = ||
||| + |||| = |||||||
||||| - || = |||
||| - || = |
||| - ||| =

Kuten viimenen esimerkki osoittaa, nollaa tukkimies ei tunne: kun vaikkapa kolmesta vähennetään kolme, jäljelle ei jää mitään. Vihko on tyhjä. Tapaus ei silti ole virhetilanne.

Suurin luku, jonka tukkimies tuntee on |||||||||||||||||||||, kymmenjärjestelmässä siis 21. Tukkimies taitaa osata pelata venttiä?

Aritmetiikan virhetilanteet ja "arvot" virheen seurauksena ovat tyyppiä:

|| - ||| = ALIVUOTO!
||||||||||||||||||||| + || = YLIVUOTO!

Jos virhearvolla yritetään laskea, virhearvo ei muutu miksikään:

(|| - |||) + ||| = ALIVUOTO!

Tukkilaskin.java: lay-out

Toteuta ensin Tukkilaskimen lay-out:
(Tämä on vain kuva, mutta alempana tällä sivulla on myös (toivottavasti) toimiva Tukkilaskin-sovelma.)

kuva Tukkilaskin.png

Vihje: Tässä esimerkissä arvokenttä, numeronäppäin ("|") ja toimintonäppäimet (plus, miinus, on, clear) on toteutettu kukin omana paneelinaan (JPanel), joiden kunkin asemointi on GridLayout ((1,1), (1,1) ja (1,4)). Paneelit on koottu yhteen BorderLayout-asemoinnilla.

TLaskentalogiikka.java

Tukkimiehen aritmetiikan algebran toteuttaa TLaskentalogiikka-luokka. Logiikan peruskäsitteet ovat puskuri, muisti, yhteenlasku, vähennyslasku ja nollaus. Puskurin ja muistin maksimipituus on 21 ja ne voivat sisältää vain "yksijärjestelmän" numeromerkkeja "|".

Vihje: Aritmetiikan toteuttamisessa String-metodi length() lienee näppärä väline? Yhteenlaskukin on pelkkää katenointia...

Käyttöesimerkki:

TLaskentalogiikka lask = new TLaskentalogiikka();

System.out.println("lasketaan 3+2");

// kolmonen puskuriin:
System.out.println("kolmesti lisaaNumero");
lask.lisaaNumero();           // puskuri: | muisti:
lask.lisaaNumero();           // puskuri: || muisti:
lask.lisaaNumero();           // puskuri: ||| muisti:

System.out.println("plus");
lask.plus();                  // puskuri:  muisti: |||

// kakkonen puskuriin:
System.out.println("kahdesti lisaaNumero");
lask.lisaaNumero();          // puskuri: | muisti: |||
lask.lisaaNumero();          // puskuri: || muisti: |||

System.out.println("tulosOn");
lask.tulosOn();
System.out.println("Puskurissa on 2+3: " + lask.getPuskuri()); // puskuri: |||||

// lisätään summaan vielä ykkönen

System.out.println("plus");
lask.plus();                 // puskuri:  muisti: |||||
lask.lisaaNumero();          // puskuri: | muisti: |||||
lask.tulosOn();
System.out.println("Puskurissa on (2+3)+1: " + lask.getPuskuri()); // puskuri: ||||||

System.out.println("nollaa");
lask.nollaa();               // puskuri:  muisti:

System.out.println("Lasketaan 7-2");
// seitsemän puskuriin:
lask.lisaaNumero();   // puskuri: | muisti:
lask.lisaaNumero();   // puskuri: || muisti:
lask.lisaaNumero();   // puskuri: ||| muisti:
lask.lisaaNumero();   // puskuri: |||| muisti:
lask.lisaaNumero();   // puskuri: ||||| muisti:
lask.lisaaNumero();   // puskuri: |||||| muisti:
lask.lisaaNumero();   // puskuri: ||||||| muisti:
System.out.println("Puskurissa on 7: " + lask.getPuskuri()); // puskuri: |||||||

System.out.println("miinus");
lask.miinus();        // puskuri:  muisti: |||||||

// kaksi puskuriin:
lask.lisaaNumero();   // puskuri: | muisti: |||||||
lask.lisaaNumero();   // puskuri: || muisti: |||||||

lask.tulosOn();
System.out.println("Puskurissa on 7-2: " + lask.getPuskuri()); // |||||

lask.nollaa();
System.out.println("Kokorajoitus:");
lask.lisaaNumero(); lask.lisaaNumero(); lask.lisaaNumero();
lask.lisaaNumero(); lask.lisaaNumero(); lask.lisaaNumero();
lask.lisaaNumero(); lask.lisaaNumero(); lask.lisaaNumero();
lask.lisaaNumero(); lask.lisaaNumero(); lask.lisaaNumero();
lask.lisaaNumero(); lask.lisaaNumero(); lask.lisaaNumero();
lask.lisaaNumero(); lask.lisaaNumero(); lask.lisaaNumero();
lask.lisaaNumero();  // ||||||||||||||||||| muisti:
lask.lisaaNumero();  // |||||||||||||||||||| muisti:
lask.lisaaNumero();  // ||||||||||||||||||||| muisti:
lask.lisaaNumero();  // ||||||||||||||||||||| muisti:
lask.lisaaNumero();  // ||||||||||||||||||||| muisti:
lask.lisaaNumero();  // ||||||||||||||||||||| muisti:
lask.lisaaNumero();  // ||||||||||||||||||||| muisti:

System.out.println("Ylivuoto, lisätään tuohon 1:");
lask.plus();         // puskuri:  muisti: |||||||||||||||||||||
lask.lisaaNumero();  // puskuri: | muisti: |||||||||||||||||||||
lask.tulosOn();
System.out.println("Puskurissa on: " + lask.getPuskuri()); // puskuri: YLIVUOTO!

System.out.println("Alivuoto, lasketaan 1-2:");

lask.nollaa();       // puskuri:  muisti:
lask.lisaaNumero();  // puskuri: | muisti:
lask.miinus();       // puskuri:  muisti: |
lask.lisaaNumero();  // puskuri: | muisti: |
lask.lisaaNumero();  // puskuri: || muisti: |
lask.tulosOn();
System.out.println("Puskurissa 1-2: " + lask.getPuskuri()); // puskuri: ALIVUOTO!

Tukkilaskin.java

Toteuta tukkimiehelle toimiva taskulaskin muokkamalla pelkän lay-outin osaavasta ohjelmasta laskutaitoinen ohjelma. Laskentapalvelut saadaan TLaskentalogiikka-oliolta.

Huomaa miten tämä laskin poikkeaa tutuista laskimista: Plus- tai miinus-näppäimen painaminen tyhjentää näytön. Tyhjä näyttö tarkoittaa nollaa; "nolla" tukkimiehen lukujärjestelmässä on sama kuin "ei mitään".

Seuraavan tehtävän esimerkistä voi tutkailla, miten laskin käyttäytyy!

Tukkilaskin sovelmaksi

Tukkimiehellä ei aina ole kannettavaa metsällä mukanaan, mutta nettitaitoinen kännykkähän jokaiselta jo alkaa löytyä...

Muokkaa tukkimiehen taskulaskinsovelluksesta selaimella suoritettava sovelma (applet).

Tämän tapainen sen tulisi olla:

Selain ei ymmärrä Javaa!

Roomalaisen tukkimiehen taskulaskin: Calcolatore.java

Roomalainen tukkimies osaa eräitä lyhennysmerkintöjä tukkeja esittävien viivojen jonoille: Hän lyhentää viittä tarkoittavan luvun "|||||" muotoon "V" ja kymmentä tarkoittavan luvun "||||||||||" muotoon "X". Viivansakin hän kirjoittaa tavanomaista tukkimiestä tyylikkäämmin muotoon "I".

Muita varsinaisten roomalaisten numeroiden hienouksia roomalainen tukkimies ei tunne; esimerkiksi "IV" ja "IX" eivät ole hänelle lukuja vaan väärin muodostettuja ilmauksia. Hän kirjoittaa esimerkiksi luvun neljä muodossa "IIII". Kuuteentoista roomalainen tukkimies laskee seuraavaan tapaan: "I", "II", "III", "IIII", "V", "VI", "VII", "VIII", "VIIII", "X", "XI", "XII", "XIII", "XIIII", "XV", "XVI", ...

Myöskään "X":ää suuremmille luvuille roomalaisella tukkimiehellä ei ole lyhennysmerkintöjä. Esimerkiksi luvun 119 hän ilmaisee kirjoittamalla "XXXXXXXXXXXVIIII".

Luvut esitetään aina siten, että mahdolliset "X":t ovat alussa, niitä seuraa – jos seuraa – korkeintaan yksi "V". Luvun lopussa on korkeintaan neljä merkkiä "I". Ja kuten tavallisella tukkimiehellä, roomalaisellakin "nolla" on "ei mitään".

Muokkaa ensin TLaskentalogiikka roomalaiseksi logiikaksi RTLaskentalogiikka. Huomaa että laskennan sisäinen toteutus kannattaa säilyttää ennallaan: String-metodilla length() aritmetiikka on helpompaa kuin roomalaisia lyhenteitä käyttäen. Ainoastaan siis getPuskuri()-metodin palautusarvo pitää muuttaa roomalaiseen muotoon. Olisikohan mitään ideaa toteuttaa RTLaskentalogiikka TLaskentalogiikka-luokan aliluokkana...

Vihje: Kannattanee muistella String-metodia replace(...), ..., mahtaisikohan jokin tyyli replace("IIIIIIIIII", "X") olla käyttökelpoinen?

Ylivuodon käsittely on myös syytä harkita uudelleen: Käytössä olkoon edelleen 21 merkkipaikkaa, mutta lyhenteiden ansiosta suurin roomalaisen tukkimiehen laskimen esittämä luku on XXXXXXXXXXXXXXXXXXXXX eli meille desimaalikoille 210. Ja se siis on esitettävissä. Kuitenkin vaikkapa 209 eli XXXXXXXXXXXXXXXXXXXXVIIII aiheuttaa ylivuodon. Ratkaisevaa RTLaskentalogiikka-ylivuodon aiheutumisessa on siis lukuilmauksen merkkipituus, ei luvun varsinainen arvo.

Laskimen toteuttaminen muunnetulla logiikalla ei enää sitten paljon vaivaa aiheuta. Näppäimistö säilyy samana. Roomalaisessakin laskimessa on vain yksi numeronäppäin, mutta viiden painalluksen jälkeen näyttöön ilmestyy "V", yhdeksän painalluksen jälkeen "VIIII" ja kymmenen jälkeen "X", jne.

Tässä esimerkissä numeronäppäintä on painettu neljäkymmentäyhdeksän kertaa tai tuo luku on jonkin laskutoimituksen tulos:

kuva Calcolatore.png

Sovelmana jotakin seuraavanlaista:

Selain ei ymmärrä Javaa!

(Ei ole perusteellisesti testattu!)

Kurssikysely

Kurssikyselyyn vastaaminen

Vastaa kurssikyselyyn osoitteessa http://ilmo.cs.helsinki.fi/kurssit/servlet/Valinta. Muista myös lähettää lomake! Lähetysnäppäin on lomakkeen lopussa. Tähän kysymykseen vastataan yksilönä ja anonyymisti. Tehtävä myös rastitetaan opiskelijoittain, ei opintopiireittäin. Ja rastin saa kirjoittaa, jos on todella vastannut kyselyyn! Opiskelijaan luotetaan tässä-(kin) asiassa! Koetta koskeviin kysymyksiin ei tietenkään voi vastata, mutta kysely tehdään kuitenkin jo nyt, jotta saadaan varmistettua hyvä vastausprosentti. Vastauksilla ihan oikeasti on merkitystä laitoksen opetusta kehitettäessä!