Oppimateriaali perustuu kevään 2010 kurssiversion materiaaliin ja Arto Wiklan ohjelmointisivustoon. Materiaalin copyright © (aakkosjärjestyksessä): Matti Luukkainen, Matti Paksula, Arto Vihavainen, Arto Wikla. Materiaalia saa vapaasti käyttää itseopiskeluun. Muu käyttö vaatii luvan.

Ohpe-harjoitukset s2010: 6/6 (11.-15.10.)

Sivu saattaa vielä muuttua!

(Muutettu viimeksi 6.10.2010, sivu perustettu 4.10.2010.)

Nämä harjoitukset liittyvät oppimateriaalin lukuihin III, IV ja IV:1-8.

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:

// 6. harjoitukset, tehtävä 2.2, Oili Opiskelija

Taulukoiden muistelua ja sitten rohkeasti eteenpäin

Tämä tehtäväsarja on kokonaisuus. Jos olet NetBeansin käyttäjä, voit tehdä sen yhteen projektiin.

taulukonPienin

Tee staattinen metodi private static int taulukonPienin(int[] taulukko), joka palautaa arvonaan taulukon pienimmän alkion.

int[] luvut = {8, 3, 7, 12, -3, 2, 4};	
System.out.println("Pienin alkio on " + taulukonPienin(luvut));
Pienin alkio on -3

taulukonPienimmanIndeksi

Tee staattinen metodi private static int taulukonPienimmanIndeksi(int[] taulukko), joka palautaa arvonaan taulukon pienimmän alkion indeksin. Entä jos pienin ei ole yksikäsitteinen?

int[] luvut = {8, 3, 7, 12, -3, 2, 4};	
System.out.println("Pienimmän indeksi on " + taulukonPieninIndeksi(luvut));
Pienimmän indeksi on 4

taulukkoTekstiksi

Tee staattinen metodi private static String taulukkoTekstiksi(int[] taulukko), joka palauttaa arvonaan taulukon sisällön merkkijonona seuraavan esimerkin mukaisesti.

int[] luvut = {8, 3, 7, 12, -3, 2, 4};	
System.out.println(taulukkoTekstiksi(luvut));
8,3,7,12,-3,2,4

vaihdaAlkiot

Tee staattinen metodi private static void vaihdaAlkiot(int[] taulukko, int vaihdettava1, int vaihdettava2), joka vaihtaa taulukon kahden alkion paikkaa annettujen indeksien vaihdettava1 ja vaihdettava2 perusteella. Mitä metodi tekee, jos annettu indeksi on virheellinen?

int[] luvut = {8, 3, 7, 12, -3, 2, 4};	

System.out.println(taulukkoTekstiksi(luvut));

vaihdaAlkiot(luvut, 1, 0);
System.out.println(taulukkoTekstiksi(luvut));

vaihdaAlkiot(luvut, 0, 3);
System.out.println(taulukkoTekstiksi(luvut));
8,3,7,12,-3,2,4
3,8,7,12,-3,2,4
12,8,7,3,-3,2,4

Suositus: Virheellinen indeksi tässä tehtävässä ilman eri toimenpiteitä aiheuttaa tavallisen ArrayIndexOutOfBoundsException-virheilmoituksen ja ohjelman päättymisen. Jos haluaa hieman täsmällisemmän ilmoituksen, voi "heittää poikkeuksena" sellaisen ArrayIndexOutOfBoundsException-olion, joka sisältää tarkempaa tietoa virheen syystä. Esim. tyyliin:

  throw new ArrayIndexOutOfBoundsException("Virheellinen vaihtoindeksi!");

pienimmanIndeksiAlkaenKohdasta

Tee staattinen metodi private static int pienimmanIndeksiAlkaeKohdasta(int[] taulukko, int alkaen), joka palauttaa arvonaan taulukon pienimmän alkion sisältävän indeksin ideksiväliltä alkaen — taulukon viimeinen indeksi.

int[] luvut = {8, 3, 7, 12, -3, 2, 4};

System.out.println(pienimmanIndeksiAlkaenKohdasta(luvut, 1));
System.out.println(pienimmanIndeksiAlkaenKohdasta(luvut, 5));
System.out.println(pienimmanIndeksiAlkaenKohdasta(luvut, 6));
4
5
6	

Vaihtojärjestäminen

Luennoilla tutustuttiin yksinkertaiseen vaihtojärjestämiseen. Siinä taulukon joka indeksikohtaan etsittiin "jäljellä olevista pienin". Aina kun havaittiin epäjärjestys, arvoja vaihdettiin.

Tätä algoritmia voidaan hivenen tehostaa tuossa viimeksi mainitussa kohdassa: etsitään edelleenkin "jäljellä olevista" pienin, mutta tehdäänkin tarvittaessa tuo vaihto vain "jäljellä olevista pienimmän" kanssa, ei aina, kun havaittiin epäjärjestys.

Tämän järjestämisalgoritmin nimi on vaihtojärjestäminen.

Toteuta vaihtojärjestäminen staattisena metodina public static void jarjesta(int[] taulukko) Käytä toteutuksessasi edellisissä tehtävissä ohjelmoituja staattisia metodeja.

Algoritmin toimintaa voidaan kuvailla seuraavaan luettelon tapaan. [Otetaan tässä vaiheessa mukaan testitarkoituksia varten aputulostus, joka tietenkin poistetaan lopullisesta tuotteesta. Usein testitulostukset on tapana "kommentoida pois" ohjelmatekstistä! Silloin ne on myös helppo palauttaa, jos/kun algoritmin myöhemmin havaitaan toimivan väärin!]

  1. Toista indekseille ind = 0, 1, 2, ..., taulukon pituus-2:
  2. [tulosta taulukon sisältö]

Kokeile metodin toimintaa seuraavalla esimerkillä:

int[] luvut = {8, 3, 7, 12, -3, 2, 4};
jarjesta(luvut);

Ohjelman pitäisi tulostaa seuraavaan tapaan:

8,3,7,12,-3,2,4
-3,3,7,12,8,2,4
-3,2,7,12,8,3,4
-3,2,3,12,8,7,4
-3,2,3,4,8,7,12
-3,2,3,4,7,8,12
-3,2,3,4,7,8,12

Mittaamista

Vaihtojärjestäminen ja yksinkertainen vaihtojärjestäminen

Luennolla mittailtiin kolmen järjestysalgoritmin nopeutta eri tilanteissa. Täydennä materiaalin antamaa mittausohjelmaa VertaileJarjAlgoritmeja.java edellisen tehtävän algoritmilla ja selvitä erityisesti, onko vaihtojärjestäminen merkittävästi nopeampi kuin luennoilla esitetty yksinkertainen vaihtojärjestäminen.

Onko se binäärihaku nyt niin hirveän hyvä kuitenkaan?

Luennolla väitettiin, että binäärihaku olisi muka jotenkin kovin nopea tapa hakea järjestetystä taulukosta vastausta kysymykseen, missä jos missään indeksissä sijaitsee kysytty arvo.

Ota edellisen tehtävän mittausohjelmasta mallia ja kehittele oma mittausohjelma, joka voisi keskustella käyttäjän kanssa seuraavaan tyyliin:

Peräkkäishaun ja binäärihaun vertailua.
Miten suuresta taulukosta haetaan?
100000
Montako hakua tehdään?
10000
Peräkkäishaku: 246 ms.
Binäärihaku:   31 ms.

Kivi-Paperi-Sakset

Tehdään vaiheittain kivi-paperi-sakset-peli. Pelissä on tarkoitus arvata, minkä kolmesta vaihtoehdosta (kivi, paperi tai sakset) vastustaja valitsee ja valita itse vaihtoehto, joka voittaa vastustajan valinnan. Kivi voittaa sakset, sakset voittavat paperin ja paperi kiven.

Tuomari.java

Tuomari pitää kirjaa ensimmäisen ja toisen pelaajan pisteistä sekä tasapelien määrästä. Toteuta Tuomari-luokka, jolla on metodi kirjaaSiirto(String ekanSiirto, String tokanSiirto), joka kirjaa siirron. Siirrot ovat "k" kivelle, "p" paperille, sekä "s" saksille. Jos yksikin parametri on virheellinen, metodi ei tee mitään.

Ohjelmoi luokkaan myös toString()-metodi olion tilanteen merkkiesitystä varten. Kokeile ja esittele Tuomari-luokan käyttöä seuraavalla Kps-ohjelmalla:

public class Kps {
  public static void main(String[] args) {

    Tuomari tuomari = new Tuomari();
    System.out.println(tuomari);
    System.out.println();
    
    tuomari.kirjaaSiirto("k", "p");
    System.out.println(tuomari);
    System.out.println();
    
    tuomari.kirjaaSiirto("k", "k");
    System.out.println(tuomari);
    System.out.println();
  }
}

Ohjelman tulostus on seuraavanlainen:

Pelitilanne 0-0
Tasapelejä 0

Pelitilanne 0-1
Tasapelejä 0

Pelitilanne 0-1
Tasapelejä 1

Kahden pelaajan Kivi-Paperi-Sakset

Laajenna pääohjelmaa Kps siten, että se kysyy syötteitä kahdelta pelaajalta. Peli jatkuu niin pitkään kunnes jompikumpi pelaajista antaa syötteeksi jonkin muun syötteen kuin "k", "p" tai "s". Käytä Tuomari-luokkaa pelitilanteen tulostamiseen jokaisen kierroksen jälkeen.

Keskustelun pitää näyttää seuraavanlaiselta:

Ensimmäisen pelaajan siirto: p
Toisen pelaajan siirto: p
Pelitilanne 0-0
Tasapelejä 1

Ensimmäisen pelaajan siirto: s 
Toisen pelaajan siirto: p
Pelitilanne 1-0
Tasapelejä 1

Ensimmäisen pelaajan siirto: s 
Toisen pelaajan siirto: k
Pelitilanne 1-1
Tasapelejä 1

Ensimmäisen pelaajan siirto: s
Toisen pelaajan siirto: riitti!

Kiitos!
Pelitilanne 1-1
Tasapelejä 1

Tietokone vastustajana: Tekoaly.java

Tee luokka Tekoaly, jolla on metodi getSiirto(). Voit itse päättää, millä perusteella siirto annetaan. Tekoaly-olio ei kuitenkaan saa tietää ennalta vastapelaajan siirtoa.

Laajenna luokkaa Kps siten, että peli kysyy aluksi pelataanko tietokonetta vastaan. Jos pelaaja vastaa "k" tai "kyllä", peli on tietokonetta vastaan. Muuten pelataan toista pelaajaa vastaan.

Pääohjelman tulostus on seuraavanlainen jos valitaan pelaaja pelaajaa vastaan.

Pelataanko tietokonetta vastaan: ei!

Ensimmäisen pelaajan siirto: p
Toisen pelaajan siirto: p
Pelitilanne 0-0
Tasapelejä 1

Ensimmäisen pelaajan siirto: s
Toisen pelaajan siirto: riitti!

Kiitos!
Pelitilanne 0-0
Tasapelejä 1

Jos valitaan vastustajaksi tietokone, tietokoneen valinnat tulostetaan myös.

Pelataanko tietokonetta vastaan: k

Ensimmäisen pelaajan siirto: p
Tietokone valitsi: k
Pelitilanne 1-0
Tasapelejä 0

Ensimmäisen pelaajan siirto: k
Tietokone valitsi: p
Pelitilanne 1-1
Tasapelejä 0

Ensimmäisen pelaajan siirto: riitti!

Kiitos!
Pelitilanne 1-1
Tasapelejä 0

Muistava tekoäly

Laajenna Tekoaly-luokkaa siten, että se pitää kirjaa vastustajan tekemistä siirroista. Metodi asetaSiirto(String siirto) asettaa vastustajan siirron tekoälyn muistiin. Toteuta tekoälyn muisti String-taulukkona, jonka koko voidaan määritellä Tekoaly-luokan konstruktorissa. Muistin täyttyessä tekoäly unohtaa aina vanhimman siirron.

Muuta myös Tekoaly-luokan getSiirto-metodia siten, että se käyttää muistissa olevaa taulukkoa seuraavan siirron tekemiseen. Kannattaa ehkä verrata vastustajan viimeistä siirtoa aikaisempiin siirtoihin ja pohtia minkä verran mitäkin siirtoa on tehty aikaisemman siirron jälkeen.

Esimerkiksi, jos vastustajan viimeisin nähty siirto on k, ja muistissa on ppkspkkspk, on vastustaja pelannut kiven jälkeen kerran kiven ja kaksi kertaa sakset. Tällöin tekoälyn ehkä kannattaa pelata kivi.

Saat toki kehitellä monimutkaisemmankin tekoälyn!

Suunnittelua ja toteutusta

Monet tehtävät edellä on tehty ohjatusti vaiheittain. Nyt on aika kokeilla omia siipiä! Pyri tekemään myös seuraavat tehtävät askelittain etenemällä.

KomentoriviParametrit.java

Tee sovellus, joka tulostaa komentoriviparametrit String-vertailujen antamassa "aakkosjärjestyksessä".

OnkoMukana.java

Tee ohjelma, joka ensin kysyy syötettävien kokonaislukujen lukumäärän, sitten lukee tuon määrän lukuja taulukkoon ja lopuksi tarjoaa seuraavan palvelun: Ohjelmalle annetaan lukuja yksi kerrallaan ja ohjelma selvittää, löytyykö luku taulukosta. Etsimiseen on käytettävä binäärihakua. Muista mitä binäärihaku edellyttää taulukon järjestykseltä!

LottoarvontaJaTarkistus.java

Lotto on numeroveikkaus, jossa arvotaan 7 numeroa ja 3 lisänumeroa 39 numerosta. Loton voittoluokat ovat 7 oikein, 6 ja lisänumero oikein, 6 oikein, 5 oikein ja 4 oikein. Tee ohjelma joka ensin arpoo oikean lottorivin. Sitten ohjelmalta voi kysellä, onko jokin lottorivi oikein. Kyselyitä voi olla useampia. Suunnittele itse, miten ohjelman toiminta päättyy. Satunnaisluvun väliltä 1-39 saat arvottua vaikkapa seuraavasti:

int arvottu = (int)(39*Math.random()) + 1;

Kyselija.java

Kyselija on olio, "laite", joka helpottaa tietokilpailun kysymysten tekemistä ja vastausten analysointia. Sen API on seuraavanlainen:

Käyttöesimerkki selventää konstruktorin ja aksessoreiden käyttöä:

  Kyselija kilpa = new Kyselija("Mitä on italian", "suomeksi?");

  kilpa.samaistaIsotJaPienet(true);
  boolean oikein;

  oikein = kilpa.kysy("tutti", "kaikki");
  if (oikein) 
    System.out.println("Hienoa, hyvä alku!");

  // kuvaruudulla tapahtuu:
  //
  // Kone:     Mitä on italian "tutti" suomeksi?
  // Käyttäjä: Kaikki
  // Kone:     OIKEIN!
  // Kone:     Hienoa, hyvä alku!

  kilpa.kysy("ferro", "rauta"); // Arvon palauttavaa metodia voi
                                // kutsua myös arvoa käyttämättä!
  // kuvaruudulla tapahtuu:
  //
  // Kone:     Mitä on italian "ferro" suomeksi?
  // Käyttäjä:              rAuTA
  // Kone:     OIKEIN!

  oikein = kilpa.kysy("matto", "hullu");
  if (!oikein)
    System.out.println("No mitäs nyt?");

  // kuvaruudulla tapahtuu:
  //
  // Kone:     Mitä on italian "matto" suomeksi?
  // Käyttäjä: matto
  // Kone:     VÄÄRIN! Oikea vastaus on "hullu".
  // Kone:     No mitäs nyt?

  System.out.println("Kysymyksiä oli " + kilpa.montakoKysymystä() +
                     ", oikeita vastauksia " + kilpa.montakoOikein() + ".");

  // kuvaruudulla tapahtuu:
  //
  // Kone:     Kysymyksiä oli 3, oikeita vastauksia 2.

  kilpa.kysy("madre", "äiti");
  kilpa.kysy("cavallo", "hevonen");
  kilpa.kysy("birra", "olut");
  // ... jne., jne...

Ohjelmoi myös sovellus, joka havainnollistaa Kyselija-luokan käyttöä tietokilpailun toteuttamisessa. Voit tietenkin valita itseäsi kiinnostavan aihepiirin. Huomaa että tämä tehtävä ei ole taulukkotehtävä vaikka tässä osassa onkin.

Kurssikysely

Vastaa kurssikyselyyn ja vakuuta ohjaaja siitä!

Vastaa kurssikyselyyn osoitteessa http://ilmo.cs.helsinki.fi/kurssit/servlet/Valinta. Valitse Ohjelmoinnin perusteet.

Muista myös lähettää lomake! Lähetysnäppäin on lomakkeen lopussa. Vastaukset ovat anonyymejä, joten ei tarvitse ujostella... Ja tästäkin tehtävästä silti saa "rastin", kunhan vain vakuuttaa ohjaajan siitä, että vastattu on.

Kyselylomakkeessa on joukko kohtia joihin ei voi syystä tai toisesta vastata. Jätä sellaiset kohdat väliin. Erityisesti koetta koskevat kysymykset ovat tällaisia. Samoin kysymys 15 "harjoitusryhmän pitäjästä" ei ole tässä kurssityylissä helppo. Vastaa siihen vain, jos olet selkeästi työskennellyt pääasiassa yhden ohjaajan kanssa.

Vastauksilla ihan oikeasti on merkitystä laitoksen opetusta kehitettäessä! Erityisen kiinnostavaksi ja tärkeäksi vastaamisen tekee nyt kokeiltu uudenlainen tapa järjestää kurssi.