Tehtävät viikolle 1

Pakolliset tehtävät on merkitty harmaalla taustavärillä. Pakollisuus tarkoittaa, että kyseiset tehtävät ovat erityisen oleellisia ja niiden tekeminen on hyvin suositeltavaa. Jos joskus jokin "pakollinen" tehtävä jää tekemättä, kurssi ei kuitenkaan kaadu siihen.

Muutama huomio

Tavarat ja matkalaukku

Tässä tehtäväsarjassa tehdään luokat Tavara ja Matkalaukku, joiden avulla käsitellään matkalaukussa olevia tavaroita.

Tavara-luokka

Tee luokka Tavara, josta muodostetut oliot vastaavat erilaisia tavaroita. Tallennettavat tiedot ovat tavaran nimi ja paino (kg).

Lisää luokkaan seuraavat metodit:

Seuraavassa on luokan käyttöesimerkki:

public class Main {
    public static void main(String[] args) {
	Tavara kirja = new Tavara("Aapiskukko", 2);
        Tavara puhelin = new Tavara("Nokia 3210", 1);

        System.out.println("Kirjan nimi: " + kirja.haeNimi());
        System.out.println("Kirjan paino: " + kirja.haePaino());

        System.out.println("Kirja: " + kirja);
        System.out.println("Puhelin: " + puhelin);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

Kirjan nimi: Aapiskukko
Kirjan paino: 2
Kirja: Aapiskukko (2 kg)
Puhelin: Nokia 3210 (1 kg)

Matkalaukku-luokka

Tee luokka Matkalaukku, johon liittyy maksimipaino.

Lisää luokkaan seuraavat metodit:

Tallenna tavarat seuraavaan ArrayList-rakenteeseen:

ArrayList<Tavara> tavarat = new ArrayList<Tavara>();

Luokan Matkalaukku tulee valvoa, että sen tavaroiden yhteispaino ei ylitä maksimipainoa. Jos maksimipaino ylittyisi uuden tavaran vuoksi, metodi lisaaTavara ei saa lisätä tavaraa.

Seuraavassa on luokan käyttöesimerkki:

public class Main {
    public static void main(String[] args) {
	Tavara kirja = new Tavara("Aapiskukko", 2);
        Tavara puhelin = new Tavara("Nokia 3210", 1);
	Tavara tiiliskivi = new Tavara("tiiliskivi", 4);

        Matkalaukku matkalaukku = new Matkalaukku(5);
        System.out.println(matkalaukku);

        matkalaukku.lisaaTavara(kirja);
        System.out.println(matkalaukku);

        matkalaukku.lisaaTavara(puhelin);
        System.out.println(matkalaukku);

        matkalaukku.lisaaTavara(tiiliskivi);
        System.out.println(matkalaukku);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

0 tavaraa (0 kg)
1 tavaraa (2 kg)
2 tavaraa (3 kg)
2 tavaraa (3 kg)

Kielenhuoltoa

Ilmoitukset "0 tavaraa" ja "1 tavaraa" eivät ole kovin hyvää suomea – paremmat muodot olisivat "ei tavaroita" ja "1 tavara". Tee tämä muutos luokkaan Matkalaukku.

Nyt edellisen ohjelman tulostuksen tulisi olla seuraava:

ei tavaroita (0 kg)
1 tavara (2 kg)
2 tavaraa (3 kg)
2 tavaraa (3 kg)

Kaikki tavarat

Lisää luokkaan Matkalaukku seuraavat metodit:

Seuraavassa on luokan käyttöesimerkki:

public class Main {
    public static void main(String[] args) {
	Tavara kirja = new Tavara("Aapiskukko", 2);
        Tavara puhelin = new Tavara("Nokia 3210", 1);
	Tavara tiiliskivi = new Tavara("tiiliskivi", 4);

        Matkalaukku matkalaukku = new Matkalaukku(10);
        matkalaukku.lisaaTavara(kirja);
        matkalaukku.lisaaTavara(puhelin);
	matkalaukku.lisaaTavara(tiiliskivi);

	System.out.println("Matkalaukussa on seuraavat tavarat:");
	matkalaukku.tulostaTavarat();
	System.out.println("Yhteispaino: " + matkalaukku.yhteispaino() + " kg");
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

Matkalaukussa on seuraavat tavarat:
Aapiskukko (2 kg)
Nokia 3210 (1 kg)
Tiiliskivi (4 kg)
Yhteispaino: 7 kg

Raskain tavara

Lisää vielä luokkaan Matkalaukku metodi raskainTavara, joka palauttaa painoltaan suurimman tavaran. Jos yhtä raskaita tavaroita on useita, metodi voi palauttaa minkä tahansa niistä. Metodin on tarkoitus palauttaa viittaus olioon.

Seuraavassa on luokan käyttöesimerkki:

public class Main {
    public static void main(String[] args) {
	Tavara kirja = new Tavara("Aapiskukko", 2);
        Tavara puhelin = new Tavara("Nokia 3210", 1);
	Tavara tiiliskivi = new Tavara("tiiliskivi", 4);

        Matkalaukku matkalaukku = new Matkalaukku(10);
        matkalaukku.lisaaTavara(kirja);
	matkalaukku.lisaaTavara(puhelin);
	matkalaukku.lisaaTavara(tiiliskivi);

	Tavara raskain = matkalaukku.raskainTavara();
	System.out.println("Raskain tavara: " + raskain);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

Raskain tavara: tiiliskivi (4 kg)

Satunnainen tavara

Lisää luokkaan Matkalaukku metodi poimiTavara, joka palauttaa matkalaukusta satunnaisen tavaran. Metodin ei ole tarkoitus poistaa tavaraa matkalaukusta, vaan ainoastaan palauttaa viittaus olioon.

Seuraavassa on luokan käyttöesimerkki:

public class Main {
    public static void main(String[] args) {
	Tavara kirja = new Tavara("Aapiskukko", 2);
        Tavara puhelin = new Tavara("Nokia 3210", 1);
	Tavara tiiliskivi = new Tavara("tiiliskivi", 4);

        Matkalaukku matkalaukku = new Matkalaukku(10);
        matkalaukku.lisaaTavara(kirja);
	matkalaukku.lisaaTavara(puhelin);
	matkalaukku.lisaaTavara(tiiliskivi);

	Tavara satunnainen = matkalaukku.poimiTavara();
	System.out.println("Satunnainen tavara: " + satunnainen);
    }
}

Ohjelman mahdollisia tulostuksia ovat seuraavat:

Satunnainen tavara: Aapiskukko (2 kg)
Satunnainen tavara: Nokia 3210 (1 kg)
Satunnainen tavara: tiiliskivi (4 kg)

Lastiruuma

Tässä tehtäväsarjassa tehdään luokka Lastiruuma, joka vastaa matkalaukkuja sisältävää lastiruumaa.

Lastiruuma-luokka

Tee luokka Lastiruuma, johon liittyvät seuraavat metodit:

Tallenna matkalaukut sopivaan ArrayList-rakenteeseen.

Luokan Lastiruuma tulee valvoa, että sen matkalaukkujen yhteispaino ei ylitä maksimipainoa. Jos maksimipaino ylittyisi uuden matkalaukun vuoksi, metodi lisaaMatkalaukku ei saa lisätä matkalaukkua.

Seuraavassa on luokan käyttöesimerkki:

public class Main {
    public static void main(String[] args) {
	Tavara kirja = new Tavara("Aapiskukko", 2);
	Tavara puhelin = new Tavara("Nokia 3210", 1);
	Tavara tiiliskivi = new Tavara("tiiliskivi", 4);

	Matkalaukku matinLaukku = new Matkalaukku(10);
	matinLaukku.lisaaTavara(kirja);
	matinLaukku.lisaaTavara(puhelin);

	Matkalaukku pekanLaukku = new Matkalaukku(10);
	pekanLaukku.lisaaTavara(tiiliskivi);

	Lastiruuma lastiruuma = new Lastiruuma(1000);
	lastiruuma.lisaaMatkalaukku(matinLaukku);
	lastiruuma.lisaaMatkalaukku(pekanLaukku);

	System.out.println(lastiruuma);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

2 matkalaukkua (7 kg)

Kaikki tavarat

Lisää luokkaan Lastiruuma metodi tulostaTavarat, joka tulostaa kaikki lastiruuman matkalaukuissa olevat tavarat.

Seuraavassa on luokan käyttöesimerkki:

public class Main {
    public static void main(String[] args) {
	Tavara kirja = new Tavara("Aapiskukko", 2);
	Tavara puhelin = new Tavara("Nokia 3210", 1);
	Tavara tiiliskivi = new Tavara("tiiliskivi", 4);

	Matkalaukku matinLaukku = new Matkalaukku(10);
	matinLaukku.lisaaTavara(kirja);
	matinLaukku.lisaaTavara(puhelin);

	Matkalaukku pekanLaukku = new Matkalaukku(10);
	pekanLaukku.lisaaTavara(tiiliskivi);

	Lastiruuma lastiruuma = new Lastiruuma(1000);
	lastiruuma.lisaaMatkalaukku(matinLaukku);
	lastiruuma.lisaaMatkalaukku(pekanLaukku);

	System.out.println("Ruuman matkalaukuissa on seuraavat tavarat:");
	lastiruuma.tulostaTavarat();
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

Ruuman matkalaukuissa on seuraavat tavarat:
Aapiskukko (2 kg)
Nokia 3210 (1 kg)
tiiliskivi (4 kg)

Paljon tiiliskiviä

Testataan vielä, että lastiruuman toiminta on oikea eikä maksimipaino pääse ylittymään. Tee ohjelma, joka lisää lastiruumaan 100 matkalaukkua, joissa jokaisessa on yksi tiiliskivi. Tiiliskivien painot ovat 1, 2, 3, ..., 100 kg.

Ohjelman runko on seuraava:

public class Main {
    public static void main(String[] args) {
	Lastiruuma lastiruuma = new Lastiruuma(1000);
	// 100 matkalaukun lisääminen
	System.out.println(lastiruuma);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

44 matkalaukkua (990 kg)

Sanakirja

Tässä tehtäväsarjassa tehdään sanakirja, josta voi hakea suomen kielen sanoille käännöksiä englannin kielelle. Sanakirjan tekemisessä käytetään HashMap-tietorakennetta.

Sanan hakeminen

Sanakirjan sanat tallennetaan seuraavaan HashMap-rakenteeseen:

HashMap<String, String> sanakirja = new HashMap<String, String>();

Sanakirjassa on aluksi kolme sanaa:

sanakirja.put("apina", "monkey");
sanakirja.put("banaani", "banana");
sanakirja.put("cembalo", "harpsichord");

Tee ohjelma, joka kysyy käyttäjältä suomenkielisen sanan ja ilmoittaa sen englanninkielisen vastineen:

Anna sana: banaani
Käännös: banana
Anna sana: selleri
Tuntematon sana!

Toimintovalikko

Lisää ohjelmaan toimintovalikko, jossa on kaksi komento: komento käännä kääntää sanan suomesta englanniksi ja komento lopeta poistuu ohjelmasta.

Tervetuloa käännösohjelmaan!

Komennot:
käännä     käännös suomesta englanniksi
lopeta     ohjelman lopetus

Komento: käännä
Anna sana: cembalo
Käännös: harpsichord

Komento: käännä
Anna sana: oboe
Tuntematon sana!

Komento: käännä
Anna sana: apina
Käännös: monkey

Komento: lopeta
Hei hei!

Sanan lisääminen

Lisää ohjelmaan komento lisää, jonka avulla käyttäjä voi lisätä uuden sanan sanakirjaan.

Tervetuloa käännösohjelmaan!

Komennot:
käännä     käännös suomesta englanniksi
lisää      uuden sanan lisääminen
lopeta     ohjelman lopetus

Komento: käännä
Anna sana: selleri
Tuntematon sana!

Komento: lisää
Suomeksi: selleri
Käännös: celery

Komento: käännä
Anna sana: selleri
Käännös: celery

Komento: lopeta
Hei hei!

Kohti testauksen automatisointia

Ohjelman testaaminen käsin on toivottoman työlästä. Syötteen antaminen on kuitenkin mahdollista automatisoida, esim. seuraavassa esitettävällä tekniikalla.

Lisää pääohjelmasi alkuun metodikutsu automatisoiSyote():

    public static Scanner lukija = new Scanner(System.in);

    public static void main(String[] args) {       
        automatisoiSyote();
          
        ...
    }

Kopioi metodi ohjelmatiedostoosi mainin jälkeen (mutta mainin ulkopuolelle):

    public static void main(){
       // ...
    }

    private static void automatisoiSyote() {
        String syote = "käännä\n" + "apina\n"  +
                       "käännä\n" + "juusto\n" +
                       "lisää\n"  + "juusto\n" +     "cheese\n" +
                       "käännä\n" + "juusto\n" +
                       "lopeta\n";
        
        lukija = new Scanner( new ByteArrayInputStream(syote.getBytes()) );
    }

Joudut myös lisäämään ohjelmatiedostosi ylälaitaan importin:

import java.io.ByteArrayInputStream;

Metodi korvaa Javan Scannerin, eli syötteen lukemisesta huolehtivan olion versiolla, jolle syöte annetaan merkkijonona. Merkkijonomuuttujan syote sisältö siis "simuloi" käyttäjän antamaa syötettä. Rivinvaihto syötteeseen merkitään \n:llä. Jokainen yksittäinen rivinvaihtomerkkiin loppuva osa syote-merkkijonossa siis vastaa käyttäjän yhteen nextLine()-komentoon antamaa syötettä.

Testityötettä on helppo muuttaa, esim. seuraavassa syötetään lisää uusia sanoja sanakirjaan:

        String syote = "lisää\n"  + "juusto\n" +     "cheese\n" +
                       "lisää\n"  + "olut\n"   +     "beer\n" +
                       "lisää\n"  + "kirja\n"  +     "book\n" +
                       "lisää\n"  + "tietokone\n" +  "computer\n" +
                       "lisää\n"  + "auto\n"   +     "car\n" +                 
                       "lopeta\n";

Kun haluat testata ohjelmasi toimintaa jälleen käsin, kommentoi pois mainin alussa oleva metodikutsu automatisoiSyote()

Ohjelman toiminnan oikeellisuus pitää edelleen tarkastaa itse ruudulta. Tulostus voi olla aluksi hieman hämmentävää, sillä automatisoitu syöte ei näy ruudulla ollenkaan.

Lopullinen tavoite on automatisoida myös ohjelman tulostuksen oikeellisuden tarkastaminen niin hyvin, että ohjelman testaus ja testituloksen analysointi onnistuu "nappia painamalla". Palaamme aiheeseen myöhemmin kurssin aikana.

Kaikki sanat

Lisää ohjelmaan vielä komento kaikki, joka tulostaa koko sanakirjan sisällön.

Tervetuloa käännösohjelmaan!

Komennot:
kaikki     kaikkien sanojen listaus
käännä     käännös suomesta englanniksi
lisää      uuden sanan lisääminen
lopeta     ohjelman lopetus

Komento: kaikki
apina = monkey
banaani = banana
cembalo = harpsichord

Komento: lopeta
Hei hei!

Sanakirjan siistiminen

Sanakirjaohjelmasi toimii nyt. Koodisi ei ehkä kuitenkaan ole maksimaalisen siistiä ja jos ohjelmaa laajennettaisiin uusilla toiminnoilla, uhkaisi koodi muuttua yhä sekavammaksi.

Pidetään sanakirjan toiminnallisuus aluksi ennallaan, mutta parannellaan ohjelman rakennetta.

Luokka Sanakirja

Kirjoitit todennäköisesti koko ohjelmakoodin mainin sisään. Aloitetaan siivoaminen siirtämällä sanakirjatoiminnallisuus omaan luokkaan nimeltä Sanakirja. Luokalla on aluksi seuraavat metodit:

Tässä vaiheessa siis ei vielä tueta komentoa jonka avulla on mahdollista tulostaa koko sanakirjan sisältö.

Tee muutos ja varmista että ohjelmasi toimii edelleen. Tehtävän 3.4 kuvaamasta testauksen automatisoinnista voi nyt olla apua.

Luokan Sanakirja viimeistely

Lisää sanakirjalle vielä seuraava metodi

Testaa, että myös komento kaikki toimii uudella sanakirjalla.

Pääohjelma siistiksi

Nyt sanakirja on saatu eristettyä pääohjelman seasta omaksi selkeän vastuun omaavaksi luokakseen. Olemme toimineet tässä Ohpen materiaalin luvussa 17 esitellyssä hengessä, eli eristäneet koodista käsitteen omaksi olioksi.

Siistitään vielä pääohjelmaa.

Tehdään siistiminen jakamalla pääohjelman toiminnallisuus selkeisiin (staattisiin) apumetodeihin, siten että main kutsuu yksittäisiä metodeja jotka huolehtivat erillisten komentojen toimenpiteiden suorittamisesta.

Pyri siihen, että ohjelmasi main näyttää suunilleen seuraavanlaiselta:

    public static void main(String[] args) {       
        automatisoiSyote();

        Sanakirja sanakirja = alusta();
        tulostaTervehdysJaKomennot();

        while ( true ) {
            System.out.print("Komento: ");
            String komento = lukija.nextLine();

            if ( komento.equals("käännä")) {
                kaannos( sanakirja );
            } else if ( komento.equals("lisää") ) {
                lisays(sanakirja);
            } else if ( komento.equals("kaikki") ) {
                kaikkienTulostus(sanakirja);
            } else if ( komento.equals("lopeta") ) {
                break;
            }

            System.out.println("");
        }
    }

Esim. metodi alusta luo sanakirjaolion ja laittaa sinne alustavan sisällön sekä palauttaa sanakirjaolion pääohjelmalle. Metodin sisältö voi olla seuraava:

    private static Sanakirja alusta() {
        Sanakirja sanakirja = new Sanakirja();
        sanakirja.lisaa("apina", "monkey");
        sanakirja.lisaa("banaani", "banana");
        sanakirja.lisaa("cembalo", "harpsichord");
        return sanakirja;
    }

Sanojen lukumäärä

Tekemiemme muutosten jälkeen uuden komennon lisääminen on helppoa. Toteuta ohjelmaan komento lukumäärä, jonka avulla käyttäjä voi tarkastaa sanakirjassa olevien käännösten määrän.

Tee laajennus siten, että ohjelman rakenne pysyy siistinä: lisää luokalle Sanakirja sopiva metodi, esim. int lukumaara(), ja lisää pääohjelmalle uusi apumetodi jota kutsutaan whilen sisältä suorittamaan varsinainen tulostustoimenpide.

Sanalla monta käännöstä, osa 1

Tehdään ohjelmaan radikaalihko muutos: mahdollistetaan tilanne jossa sanalla on useita käännöksiä.

Edellä käytetty HashMap jossa sanaa vastaa yksi käännös ei ole tässä tilanteessa paras mahdollinen. Parempi ratkaisu on käyttää Sanakirja-luokassa HasMap:ia, jossa yksittäistä sanaa vastaa ArrayList:illinen merkkijonoja:

public class Sanakirja {
   private HashMap  <String, ArrayList < String > > sanakirja;

   // ...

Muuta nyt Sanakirja-luokan sisäistä rakennetta siten, että yhtä sanaa kohti voi olla useita käännöksiä. Tässä tehtävässä riittää että saat toimimaan metodit

Huomaa, että laajennus ei vaikuta ollenkaan muuihin ohjelmanosiin kuin Sanakirja-luokkaan. Jos emme olisi putsanneet koodia, olisi muutoksia tehtävä pääohjelman sekaan ja riski virheen tekemiselle olisi huomattavasti suurempi.

Huom: kun sanalle lisätään ensimmäinen käännös, täytyy ensin luoda sanaa vastaavat käännökset tallettava ArrayList-olio seuraavaan tyyliin:

    public void lisaa(String sana, String kaannos){
        if ( sanakirja.get(sana)==null )
            sanakirja.put(sana, new ArrayList());
    
        // ...
    }

Sanalla monta käännöstä, osa 2

Viimeistele edellisessä tehtävässä aloittamasi muutos siten, että muutkin Sanakirja-luokan metodit toimivat uudessa tilanteessa.

Alkuluvut

Alkuluku on kokonaisluku (2 tai suurempi), joka on jaollinen vain 1:llä ja itsellään.

Esimerkiksi luku 7 on alkuluku, koska se ei ole jaollinen luvuilla 2–6. Samoin luku 11 on alkuluku, koska se ei ole jaollinen luvuilla 2–10.

Luku 10 ei ole alkuluku, koska se on jaollinen 2:lla ja 5:llä. Myöskään luku 75 ei ole alkuluku, koska se on jaollinen 3:lla, 5:llä, 15:llä ja 25:lla.

Alkuluvun tarkistaminen

Tee ohjelma, joka tarkistaa, onko käyttäjän antama kokonaisluku alkuluku.

Anna luku: 7
Luku on alkuluku.
Anna luku: 10
Luku ei ole alkuluku.

Ensimmäiset alkuluvut

Tee ohjelma, joka tulostaa ensimmäiset alkuluvut käyttäjän syötteen mukaisesti.

Kuinka monta? 5
2 3 5 7 11
Kuinka monta? 10
2 3 5 7 11 13 17 19 23 29

Alkuluvut rajaan asti

Tee ohjelma, joka tulostaa tiettyä lukua pienemmät alkuluvut käyttäjän syötteen mukaisesti.

Yläraja? 10
2 3 5 7
Yläraja? 20
2 3 5 7 11 13 17 19

Alkuluvut välillä 1–1000000

Tee ohjelma, joka laskee, kuinka monta alkulukua on välillä 1–1000000.

Vihje: Erastotheneen seulasta voi olla hyötyä.

Alkulukupalvelu

Et kai kirjoittanut edellisten tehtävien koodia suoraan mainiin? Entä jos tarvitsisit alkulukutunnistusta jossain muussa ohjelmassa?

Alkulukupalvelu kannattaakin eristää omaksi luokaksi. Näin alkulukupalvelu on helppo ottaa käyttöön jossain muussa ohjelmassa.

Tee luokka Alkulukupalvelu jota voi käyttää seuraavaan tapaan:

    public static void main(String[] args) {
        Alkulukupalvelu alkuluvut = new Alkulukupalvelu(10000);

        if ( alkuluvut.onkoAlkuluku(97) )
            System.out.println("luku 97 on alkuluku");
        else
            System.out.println("luku 97 ei ole alkuluku");

        System.out.println("37 ensimmäistä alkulukua:");
        for ( int luku : alkuluvut.ensimmaisetAlkuluvut(37) ) {
            System.out.println(luku);
        }

        System.out.println("alkuluvut väliltä 2-10000");
        for ( int luku : alkuluvut.alkuluvutAsti(10000) ) {
            System.out.println(luku);
        }
    }

Metodit siis ovat

Konstruktorissa kerrotaan isoin luku joonka palvelu pystyy käsittelemään. Eli jos on luotu alkuluvut = new Alkulukupalvelu(10000), esim. metodin onAlkuluku ei tarvitse toimia jos parametri on suurempi kuin 10000. Myös muut metodit toimivat ainoastaan alkulukupalvelun alustuksen yhteydessä annetuun rajaan asti.

Pyri tekemään Alkulukupalvelun metodeista mahdollisimman selkeitä. Käytä tarvittaessa olion sisäisiä apumetodeja selkeyttämään koodia. Apumetodit kannattaa määritellä private-näkyvyysmääreellä, jolloin ne eivät näy olion ulkopuolelle.

Kirjainneliö

Seuraava tehtävä on varsin haastava. Ohjelman rakenne kannattaa suunnitella kynän ja paperin avulla ennen koodaamista!

Kirjainneliö

Tee ohjelma, joka tulostaa kirjainneliön seuraavien esimerkkien mukaisesti. Voit olettaa, että kerrosten määrä on 1–26.

Kerrokset: 2
AAA
ABA
AAA
Kerrokset: 3
AAAAA
ABBBA
ABCBA
ABBBA
AAAAA
Kerrokset: 4
AAAAAAA
ABBBBBA
ABCCCBA
ABCDCBA
ABCCCBA
ABBBBBA
AAAAAAA