Harjoitustyö 2

Julkaistu

Tehtävänanto ja muuta materiaalia

Esimerkkitiedostot

Oman ohjelman tulosteita kannattaa vertailla esimerkkitiedostoihin ennen WETO-palautuksia.

Jokaiseen esimerkkiin, error-esimerkki pois lukien, liittyy neljä tiedostoa. Ohjelmalle komentoriviparametrina annettava kuvatiedosto on file-alkuisessa tiedostossa. Input-alkuisessa tiedostossa ovat ohjelmalle annettavat komennot ja vastaavassa output-alkuisessa tiedostossa ovat komentoja vastaavat näyttötulosteet. Syötteet ja niitä vastaavat tulosteet on yhdistetty combination-alkuiseen tiedostoon.

Error-esimerkille ei ole annettu kuvatiedostoa, koska testissä annetaan virheellinen komentoriviparametri.

Esimerkiksi input_tree.txt-tiedoston komennot tuottavat output_tree.txt-tiedoston sisältämät tulosteet, kun ohjelmalle annetaan komentoriviparametrina file_tree.txt-tiedosto. Input_tree.txt– ja output_tree.txt-tiedostojen sisältö esitetään combination_tree.txt-tiedostossa yhdessä aivan kuin komennot olisi annettu komentoikkunassa. File-esimerkin voi ajaa omalla ohjelmalla seuraavasti:

java LakiHT2 file_tree.txt < input_tree.txt > out.txt

Yllä ohjelman tuloste kaapataan out.txt-tiedostoon uudelleenohjausmerkin > avulla.

Esimerkit ovat saatavilla kahdessa muodossa:

Esimerkeistä ei ole versioita eri merkistöille, koska tiedostoissa on vain 7-bittisen ASCII-merkistön ”näkyviä” merkkejä, joiden esitystapa on sama niin Latin 1 (ISO 8859-1) kuin UTF-8-koodatussa Unicode-merkistöissä.

Salaiset testit

Salaiset testit ovat julkisessa testauksessa käytettyjen esimerkkien tapaan saatavilla kahdessa muodossa:

Kysymyksiä ja vastauksia

1. Mistä aloittaisin?

Kokoa ohjelmaasi harjoitustyöhön liittyvien harjoitustehtävien ratkaisut. Harjoitustyön tehtävänannossa on mainittu joitakin hyödyllisiä tehtäviä. Kuudennen harjoituskerran ensimmäinen tehtävä on erityisen hyödyllinen, koska siinä tehdyllä operaatiolla saa ohjelmassa käyttöön tekstitiedostoon tallennetun kuvan. Myös toinen ja kolmas tehtävä liittyvät suoraan harjoitustyöhön. Tee main-operaatioon pääsilmukka, jossa kutsut jo harjoitusten yhteydessä tehtyjä operaatioita.

2. Voinko kutsua omaa operaatiota toisesta omasta operaatiosta?

Kyllä. Kaikkien omien operaatiokutsujen ei tarvitse lähteä main-operaatiosta. Oman operaation kutsuminen toisesta omasta operaatiosta on mahdollista ja täysin sallittua, kunhan kutsuketju ei veny liian pitkäksi. Esimerkiksi neljä peräkkäistä operaatiokutsua on vielä helposti ihmisen hahmotettavissa. Usein on järkevintä kutsua tiettyyn operaatioon liittyvää apuoperaatioita suoraan kyseisestä operaatiosta.

3. Miksi latausoperaationi kaatuu, vaikka koodi on kunnossa?

Tarkista, että käyttämäsi testikuvan rivit ovat samanpituisia ja että myös viimeisellä rivillä on rivinvaihto.

4. Miten saan selville millä rivillä try-catch-lausetta käyttävä operaatio kaatuu?

Voit haarukoita kaatumiskohdan paikan seuraamalla ohjelman etenemisistä tulostuslauseilla. On kuitenkin helpompaa kutsua catch-kohdassa printStackTrace-operaatiota parametrina saadun poikkeuksen kautta. Operaatio tulostaa näytölle samanlaisen ”poikkeuspinon” kuin Java-tulkki ohjelman kaatuessa.

Esimerkiksi:

// Siepataan mikä tahansa poikkeus.
catch (Exception e) {
   e.printStackTrace();
   merkit = null;
}

5. Kuinka tunnistaa parametrillinen suodatuskomento?

Esimerkiksi muotoa ”filter 5” olevan komennon voi tunnistaa String-luokan startsWith-operaatiolla antamalla parametrin arvoksi ”filter” tai mieluummin ”filter”-arvoisen vakion.

6. Onko kuvatiedosto merkki- vai lukumuodossa?

Kuvat ovat merkkeinä (katso tehtävänannon toinen kuva). Tehtävänannon leipätekstiin oli jäänyt kahteen kohtaan maininta kuvan lukuesityksestä tiedostossa, mistä pahoittelut.

7. Kuinka lasken reunan leveyden?

Reunan leveyden ja samalla mitan suodinikkunan keskipaikasta ikkunan reunalle saa jakamalla suodinikkunan sivun pituuden (int) kahdella, jolloin Java hävittää tarpeettomat desimaalit. Esimerkiksi:

int reunanLeveys = suotimenSivunPituus / 2;

Jos yllä suodin on esimerkiksi 3 x 3 -kokoinen, saadaan reunan leveydeksi 3 / 2 = 1 Javan laskusäännöillä.

8. Miksi en voi kopioida taulukkoa sijoittamalla?

Suodatuksessa tarvitaan uusi lukutaulukko (tuloskuva), jonka reunoilla on samat arvot kuin vanhassa lukutaulukossa (lähtökuva). Taulukon sisällön kopiointi toiseen taulukkoon ei onnistu tähän tapaan:

int rivlkm = vanhatLuvut.length;
int sarlkm = vanhatLuvut[0].length;
int[][] uudetLuvut = new int[rivlkm][sarlkm];
...
uudetLuvut = vanhatLuvut;

koska taulukkomuuttujia (viitteitä) sijoitettaessa vasemmanpuoleinen operandi liitetään samaan taulukkoon (olioon) kuin oikeanpuoleinen operandi. Sijoituksen lopputuloksena yhteen taulukkoon (olioon) liittyy kaksi muuttujaa (viitettä), jolloin suodatuksessa käytetään aiemmin suodatettuja arvoja ja ohjelma vaikuttaa toimivan omituisesti ilman näkyvää syytä.

Tee taulukon kopiointiin erillinen apuoperaatio, jossa taulukon merkit kopioidaan toiseen taulukkoon. Esimerkki operaation kutsusta:

int rivlkm = vanhatLuvut.length;
int sarlkm = vanhatLuvut[0].length;
int[][]uudetLuvut = new int[rivlkm][sarlkm];
...
boolean onnistui = kopioiTaulukko(vanhatLuvut, uudetLuvut);

Operaation voi toteuttaa myös siten, että se luo kohdetaulukon itse, kopioi merkit parametrina saamastaan taulukosta kohdetaulukkoon ja palauttaa viitteen uuteen taulukkoon. Esimerkki operaation kutsusta:

char[][] uudetLuvut = kopioiTaulukko(vanhatLuvut);

Kopiointia voi tehostaa molemmissa tapauksissa kopioimalla vain reuna-alkiot. Tällöin reunan leveys täytyy välittää operaatiolle parametrina.

9. Miten järjestää operaatiot?

Sijoita main-operaatio joko ohjelman alkuun tai loppuun. Muiden operaatioiden osalta voisi suositella, että yhteenkuuluvat operaatiot ovat lähellä toisiaan, jotta koodia olisi helpompi lukea. Koodia voi ryhmitellä myös pieniin apuoperaatioihin ja tärkeämpiin operaatioihin ja järjestää yhteenkuuluvat operaatiot lähelle toisiaan näiden kahden ryhmän sisällä. Muitakin vaihtoehtoja toki on.

10. Ideoita suodatuksen toteuttamiseen?

Suodatukseen tarvitaan nelinkertainen silmukka. Ensimmäinen silmukkapari käy läpi kaikki lähtökuvan alkiot paikoissa (i, j) , joihin voidaan asettaa suodinikkunan siten, että ikkunan keskipiste on paikassa (i, j) ilman, että suodinikkuna menee kuvan ulkopuolelle. Silmukoiden rajoja (alkuarvo ja yläraja) asetettaessa on huomioitava reunan leveys (7. kysymys).

Toinen silmukkapari laskee yhteen suodinikkunan ”alla” olevien alkioiden arvot. Tämänkin silmukkaparin rajojen muotoilussa on hyötyä reunan leveyden tuntemisesta. Toinen silmukkapari kannattaa eristää selkeyden vuoksi omaan operaatioon, jolle välitetään varsinaisesta suodattavasta operaatiosta parametrina lähtökuva, ulomman silmukkaparin määräämä paikka (i, j) ja reunan leveys. Operaatio palauttaa joko alkioiden summan tai suoraan keskiarvon.