Ohjelmointi 1/2009, demo t091 vastauksia

1. Java-koodin toiminta, tehtävän pisteytys

Matti Välimäki

1a)
Ohjelma tulostaa rivin
1 1 2 3 5 8 13 21 34 55 89

Näin käy, koska ensin tulostetaan merkkijono "1"
ja aina muuttujan a arvo välilyönnin jälkeen.
Jokaisen kierroksen tulostuksen jälkeen lasketaan
kaksi edellistä lukua yhteen ja päivitetään niiden
summa a:n arvoksi. Näin syntyy niin sanottu Fibonaccin
lukusarja. Muuttuja d toimii säiliömuuttujana,
johon a: vanha arvo laitetaan talteen siksi aikaa, kun
sen uusi arvo lasketaan. Tämä tehdään siksi, ettei
a:n vanhempi arvo katoaisi, vaan se voidaan
laskutoimituksen jälkeen sijoittaa muuttujaan b.

// in English
The program prints the line
1 1 2 3 5 8 13 21 34 55 89

The first "1" is printed before the loop and
on every iteration thereafter the value of the
variable a is printed. After the printing on each
iteration the value of a is updated to a = a+b
meaning the previous two numbers are added together
to form the next. This creates the so called Fibonacci
sequence. The variable d works as a container
variable in which the old value of a is stored so it 
isn't lost when a is updated but can be set as the
value of the variable b.

1b) Arvosteluperusteet

Lähtöpisteet = 3
Yritetty sijoittaa String = char -0.5p
Ei käsitelty erikoistapausta (pituus < 7) -0.75p
Ei käsitelty erikoismerkkejä -0.75p
Käsitelty Stringiä tai StringBuilderia taulukkona -0.5p
Yritetty käyttää primitiivimuuttujalla metodeja -0.5p
Luodaan paljon olioita -0.25p
Pienistä virheistä -0.25p/tapaus
Muista virheistä tapauskohtaisesti

1. Java-koodi

1   package tentti09;
2   
3   /**
4    * Käyttäjätunnusgeneraattori
5    * @author vesal
6    *
7    */
8   public class Kayttajatunnus {
9   
10      /**
11       * Muuttaa kirjaimen käyttäjätunnukseen kelpaavaksi.
12       * Kirjain muutetaan pieneksi ja skandeista poistetaan pisteet.
13       * Tuntemattomat kirjaimet ja mahdolliset välimerkit korvataan 
14       * 1-merkillä.
15       * @param c muutettava kirjain
16       * @return käyttäjätunnukseen kelpaava kirjain
17       * @example
18       * <pre name="test">
19       *   muutaKirjain(' ') === '1';
20       *   muutaKirjain('2') === '1';
21       *   muutaKirjain('A') === 'a';
22       *   muutaKirjain('B') === 'b';
23       *   muutaKirjain('a') === 'a';
24       *   muutaKirjain('Ä') === 'a';
25       *   muutaKirjain('Ö') === 'o';
26       *   muutaKirjain('ö') === 'o';
27       * </pre>
28       */
29      public static char muutaKirjain(char c) {
30          final String mitka = "åäö";
31          final String miksi = "aao";
32          char lc = Character.toLowerCase(c);
33          int i = mitka.indexOf(lc);
34          if ( i >= 0 ) return miksi.charAt(i);
35          if ( lc < 'a' ) return '1';
36          if ( lc > 'z' ) return '1';
37          return lc;
38      }
39      
40      /**
41       * Aliohjelma muuttaa nimen käyttäjätunnukseksi. 
42       * Käyttäjätunnus muodostetaan yhdistämällä käyttäjän 
43       * etunimen ensimmäinen kirjain ja sukunimen ensimmäiset 
44       * seitsemän merkkiä, jolloin käyttäjätunnuksesta tulee 8 merkkiä pitkä. 
45       * Jos sukunimessä ei ole seitsemää merkkiä, sukunimi otetaan mukaan 
46       * kokonaisuudessaan. 
47       * Kirjaimet ä, ö ja å korvataan kirjaimilla a, o ja a. 
48       * Käyttäjätunnus sisältää ainoastaan pieniä kirjaimia.
49       * @param etunimi muutettavan nimen etunimi
50       * @param sukunimi muutettvan nimen sukunimi
51       * @return nimien perusteella muodostettu käyttäjätunnus
52       * @example
53       * <pre name="test">
54       *   teeTunnus("Martta","Hänninen") === "mhannine"
55       *   teeTunnus("Åke","Ek") === "aek"
56       *   teeTunnus("Björn","Björnström") === "bbjornst"
57       * </pre>
58       */
59      public static String teeTunnus(String etunimi, String sukunimi) {
60          String alku = "";
61          if ( etunimi.length() > 0 ) alku = ""+muutaKirjain(etunimi.charAt(0));
62          int pituus = Math.min(8-alku.length(),sukunimi.length());
63          StringBuilder tunnus = new StringBuilder(alku);
64          for (int i=0; i<pituus; i++)
65              tunnus.append(muutaKirjain(sukunimi.charAt(i)));
66          return tunnus.toString();
67      }
68  
69      
70      
71      /**
72       * @param args
73       */
74      public static void main(String[] args) {
75          // TODO Auto-generated method stub
76  
77      }
78  
79  }
80  
[JavaDoc-muoto] [Alkuperäinen java]

2. Matriisin transpoosi

1   package tentti09;
2   
3   /**
4    * Luodaan matriisin transpoosi.
5    * Algoritmi:
6    * <pre>
7    * 0. olkoon matriisi a
8    * 1. luodaan matriisi jossa rivejä yhtä paljon kuin a:ssa rivejä 
9    *    ja sarakkeita yhtä paljon kuin a:ssa rivejä
10   * 2. käydään läpi kaikki a:n rivit indeksillä ir
11   * 3.   jokaisella rivillä käydään läpi kaikki sarakkeet indeksillä is
12   * 4.   tulosmatriisi[is][ir] = a[ir][is]    
13   * </pre>
14   * 
15   * @author vesal
16   * @version 17.12.2009
17   */
18  public class Transpoosi {
19  
20      /**
21       * Luodaan matriisin transpoosimatriisi.
22       * Oletetaan kaikki rivit yhtä pitkiksi.
23       * @param a transponoitava matriisi
24       * @return a:n transpoosi
25       * @example
26       * <pre name="test"> 
27       * #import java.util.Arrays;
28       *   double[][] a1 = {{1,2},{3,4}};
29       *   Arrays.deepToString(transpoosi(a1)) === "[[1.0, 3.0], [2.0, 4.0]]";
30       *   double[][] a2 = {{1,2,3},{4,5,6}};
31       *   Arrays.deepToString(transpoosi(a2)) === "[[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]]";
32       * 
33       * </pre>
34       */
35      public static double[][] transpoosi(double a[][]) {
36          double[][] at = new double[a[0].length][a.length];
37          for (int ir=0; ir < a.length; ir++)
38              for (int is=0; is<a[0].length; is++)
39                  at[is][ir] = a[ir][is];
40          return at;
41      }
42  }
43  
[JavaDoc-muoto] [Alkuperäinen java]

2. tehtävän pisteytys

Irene Venäläinen:

Tehtävä 2

  a. kohta

Vastauksessa tuli esittää yksiselitteisesti mihin kohtaan
mikäkin matriisin alkio sijoitetaan transpoosissa. Erityisesti
oli tärkeä mainita, että rivin i sarakkeen j alkio sijoitetaan
transpoosissa riville j sarakkeeseen i ja operaatio suoritetaan
kaikille matriisin alkioille.

Tehtävässä ei vähennetty pisteitä, jos ei ollut maininnut, että
n x m -matriisin alkiot tallennetaan m x n -matriisiin, jos
algoritmi oli esitelty muuten selkeästi. Tästä maininnasta sai
kuitenkin pisteitä, jos muu algoritmi ei ollut tarpeeksi selkeä.

Yleisiä virheitä:

Liian ympäripyöreä kuvaus:
  "Muutetaan rivi sarakkeeksi." ei riitä, täytyy käydä läpi alkiotasolla.
  "Jne." ei auta mikäli ei ole selkeästi esitetty mitä pitää tehdä.
"Esim." esimerkit oli annettu jo tehtävänannossa.
Oletettu, että matriisi on tietyn kokoinen.
Algoritmin käsite hukassa.
Oli käytetty indeksejä, muttei esitelty niitä.


  b. kohta

Oikeasta esittelyrivistä 1 p
Taulukon luonti 1 p
Taulukon läpikäynti 1 p
Alkion sijoitus 0.5 p
Taulukon palautus 0.5 p

Puuttuva sulku -0.25 p
Indeksit väärin -0.5 p
taulukko.length() -0.25 p
taulukko[].length -0.25 p

Toistuvista virheistä vähennettiin vain kerran. Puuttuvista
puolipisteistä ei vähennetty pisteitä.

3. Java-kielen rakenteesta

Martti Hyvönen:

Tehtävä 3: Mallivastaukset ja arviointiperusteet
=================================================
Yleistä

Mun mielestä tehtävä meni hyvin. Kuitenkin vain yksi sai täydet
6 pistettä. Toisaalta kukaan ei jäänyt nollille, vaan huonoin
tulos oli 1 piste.

Monet olivat selittäneet asioita aivan liian laajasti.
Riittiköhän aika muihin tehtäviin? Kysymykset olivat kyllä
sellaisia, että niistä pystyi selittämään laajastikin, mutta
olisi pitänyt pystyä tiivistämään vastaus ja löytämään
oleellinen. Pitkistä vastauksista ei kuitenkaan tullut miinusta,
mutta lyhyestä ja ytimekkäästä saattoi saada plussaa :)

Tärkeintä oli osata selittää asiat. Vastausten ei siis tarvinnut
juuri alla olevien "mallivastausten" kaltaisia. Monella oli
paljon parempia vastauksia kuin alla olevat :)

a) Parametrit viedään ohjelman käynnistyskäskyn jälkeen
   välilyönnillä erotettuina. Esimerkiksi ohjelmalla HelloWorld
   voitaisiin antaa parametrit käynnistyksen yhteydessä
   seuraavasti:

    java HelloWorld parametri1 parametri2 jne...

   Parametrit saadaan selville pääohjelman args-taulukosta.
   Jokainen parametri on siinä oma alkionsa.

Pisteytyksestä:

0,5 pistettä sai kun osasi selittää, joko esimerkin avulla tai
ilman, että Java-ohjelmalle viedään parametreja komentorivillä
käynnistyksen yhteydessä. Parametreja voidaan syöttää useita ja
ne erotellaan toisistaan välilyönneillä.

Vähennyksiä tuli, jos sain kuvan, että parametrit tarvitsevat
edellään "-"-merkin. Näin ei siis ole.

Toiset 0,5 pistettä sai kun osasi selittää, että käyttäjän
antamat parametrit saadaan selvillä ohjelmakoodissa pääohjelman
parametrina olevasta merkkijonotaulukosta (String[]), jonka nimi
on usein args.

Yleisin vähennys taisi olla jos ei ollut muistanut mainita tuota
pääohjelmaa (main-aliohjelmaa). Niitä ei siis saada selville
mistä tahansa ohjelmakoodissa, vaan nimenomaan pääohjelman
parametrina olevasta String-taulukosta.

b) Oliotietotyyppisessä muuttujassa sijaitsee ainoastaan viite
   siihen kohtaan tietokoneen muistia jossa varsinanen olion arvo/
   arvot (data) sijaitsee.  Alkeistietotyyppiset muuttujat
   sisältävät itse arvon. (0,75p)

Erovaisuuksiahan on vaikka kuinka, joten täytyi osata löytää
oleellisin, joka on mielestäni tuo. Jos osasi selittää tuon
hyvin sai siis heti 0,75 pistettä. Jos selityksessä oli jotain
hämminkiä saattoi saada vain 0,5 pistettä tai jos oli kunnolla
hämminkiä niin saattoi tulla vain 0,25 pistettä.

Jos oli selittänyt viitteiden sijaan jostain muusta
erovaisuudesta/eroavaisuuksista sai 0,25 pistettä.

Erittäin hyvin selitetyt eroavaisuudet, mutta viiteasiaa ei
ollut kuitenkaan mainittu niin saattoi saada jopa 0,5 pistettä.

Yhtäläisyyksien keksiminen oli yllättävän vaikeaa. Yrittäneet
kuitenkin palkittiin! Paitsi jos sanoi jotain tyyliin "molemmat
ovat tietotyyppejä". Kun oli luetellut jotain järkeviä ja
oikeita yhtäläisyyksiä sai siis 0,25p. Hyväksyin mm. seuraavia:

"Molempia voi viedä aliohjelmille parametreiksi."

"Molemmat ovat tiedon väliaikaiseen tallentamiseen tarkoitettuja
ohjelmointikielen rakenteita."

"Molempia voidaan käyttää samalla tavalla muuttujan nimen avulla."

jne.

c) Täytyy muistaa, että olioita vertailtaessa ==-operaattorilla
   verrataan olioiden viitteitä, eikä niiden olioiden arvoja
   joihin viitteet osoittavat. Kun halutaan vertailla siis kahta
   merkkijonoa, täytyy käyttää equals-metodia. (0,5p)

   Että käyttää kahta ==-merkkiä, eikä =-merkkiä joka on
   sijoitusoperaattori. (0,5p)

   Liukulukuja vertailtaessa kannattaa ottaa huomioon, että
   luvut ovat samoja tietyllä tarkkuudella. Ei saisi siis
   käyttää ==-operaattoria ollenkaan. (0,25p)

   Kokonaislukuja ja desimaalilukuja verrattaessa Java muuttaa
   arvot samaksi tyypiksi (autoboxing) ja tämä voi aiheuttaa
   ongelmia kun vertailee liuku- ja kokonaislukuja keskenään. 
   (0,25p)

   Kaikki alkeistietotyypit eivät ole keskenään vertailtavissa
   (esim boolean ja int). Huom! Lukuarvoja voi kuitenkin
   vertailla keskenään. (0,25p)

Pisteitä saa kuten yllä on merkattu, mutta kuitenkin maksimissaan yhden pisteen.

d) Ylivuoto tarkoittaa muuttujan muistialueen ylittymistä (0,5p).
   Ylivuoto pitää ottaa huomioon valitsemalla riittävän suuri
   tietotyyppi haluamamme tiedon talletukseen. (0,5p)

Hyväksyin tietenkin myös matemaattisemman ja tarkemman
selityksen ylivuodosta, jossa selitettiin ylivuotoa
binäärilukujen yhteenlaskun avulla.

e)
   i. double (tai float) mansikoidenMaaraKiloina
	täytyy olla liukuluku, koska mansikoitahan voi olla vaikka 
        1,5kg
  ii. String henkiloTunnus
	täytyy olla merkkijono, sillä henkilötunnus sisältää myös
        merkkejä
 iii. int (short) vuosiluku
	kokonaisluku ja int riittää vallanmainiosti
  iv. boolean onkoKurssillaTilaa
	koska kurssilla joko on tilaa tai ei ole (vaihtoehtoja 
        siis vain kaksi) on luentevinta käyttää boolean-tietotyyppiä

Jos oli unohtanut muuttujien nimet menetti heti 0,25 pistettä.
Jos oli unohtanut perustelut menetti 0,25 pistettä. Hyvin
perustellen hyväksyin joitakin muitaikin tietotyyppejä, kuin
ylläolevia. Jos ei kuitenkaan ollut perustellut mitään oli aika
heikoilla muissa kuin yllämainituissa tietotyypeissä. Onko
kurssilla tilaa tuli kuitenkin olla boolean, vaikka perusteli
mitä. Suurin osa olikin osannut tuon oikein.

Kaikki olivat osannut nimetä muuttujat oikein. Muutamia huonoja
nimiä oli, mutta yksittäinen huono nimi ei vaikuttanut
pisteisiin, jos tehtävä oli muuten oikein. Ääkkösten
käyttämisestä en sakottanut, vaikka sitä ei suositellakkaan.

iii-kohdassa hyväksyin myös long:n, vaikka perustelut olivatkin
vääriä :). Intin lukualue on -2147483648 - 2147483647, joten
eiköhän tuo riitä vuosilukuihin ihan hyvin. Itseasiassa short
riittäisi myös hyvin, mutta eipä sitä juurikaan taideta käyttää
kuitenkaan. short-hyväksyttiin siis tietenkin myös.

f) String on muuttumaton ja StringBuilder muuttuva merkkijono.
   String:n arvoa ei siis voi muuttaa luomisen jälkeen, vaan
   muuttaminen onnistuu ainoastaan luomalla uusi olio.
   StringBuilderia-oliolla on sen sijaan monipuoliset metodit
   merkkijonon muuttelua varten. StringBuilderia sanotaan joskus
   myös dynaamiseksi merkkijonoksi.

Yhden pisteen vastaukseen riitti tällä kertaa jos osasi,
jotenkin selittää, että String:ä ei voi muuttaa ja
StringBuilderia voi. Tämä tehtävä olikin osattu erinomaisesti.
Moni oli jopa tainnut aavistaa ja laittaa vastauksen
lunttilappuunsa :)

4. Yleistä Java-tietoa

Markus Pohjola

Tehtävä 4:
==========

Tehtävässä neljä pyydettiin vastaamaan lyhyesti kolmeen
kysymykseen, jotka sisälsivät tarkentavia alakysymyksiä.

A (2p):

Hyvien koodauskäytäntöjen, muuttujien, aliohjelmien yms.
tarkoituksenmukaisen nimeämisen ja ohjelman fiksun paloittelun
lisäksi koodia on syytä kommentoida. Alla on lueteltu hyvässä
vastauksessa esiintyviä seikkoja. Täysiä pisteitä varten kaikkia
seikkoja ei ollut tarkoitus mainita, vaan vastauksen
kokonaisuudella oli merkitystä.

Mitä on kommentointi ja miksi koodia kannattaa kommentoida:

- kommentointi on koodin sekaan kirjoitettua tekstiä, 
  jota kääntäjä ei huomioi
- koodia on helpompi ymmärtää
- myös muut kuin ohjelman kirjoittaja ymmärtävät koodia
- koodin pariin on helpompi palata myöhemmin, koska ajan 
  myötä koodin toiminta mahdollisesti unohtuu
- koodia on helpompi päivittää ja käyttää uudelleen
- JavaDoc-tyylisten kommentien avulla voidaan luoda koko luokan 
  ja sen osien toiminnan kuvaava standardoitu API-dokumentaatio
- on olemassa erikseen yksi- ja moniriviset kommentit:

 // Tämä on yksirivinen kommentti

 ja

 /*  Tämä on kommentti,
     joka on useammalla rivillä.
  */

Ei vaadittu vastaukseen, mutta on hyvä tiedostaa myös seuraavat seikat:
- kommentointi pakottaa ohjelmoijan ajattelemaan koodia uudella tavalla,
  koska koodin toiminta täytyy selittää omin sanoin
- koodin toimintaa selittäessä täytyy ajatella muita kehittäjiä, 
  koska kommentien pitää olla hyödyllisiä ja ymmärrettäviä

Anna esimerkki Javadoc-tyylisestä kommentista aliohjelmaan, joka
tutkisi onko sana palindromi (sama sana luki sen kumminkin päin
tahansa) vai ei:

Vastauksessa täytyy tulla ilmi, että aliohjelma vastaanottaa
parametrina (@param-määre) tutkittavan sanan ja sanan
"palindromisuus" tulee jollain tavalla ilmi. Vastauksessa
odotettiin aliohjelman paluuarvon, joka kertoo sanan
palindromisuuden, esittelyä @return-määreen avulla. Vastaukseksi
hyväksyttiin kuitenkin myös tilanne, jossa aliohjelma ei
palauttanut tietoa sanan palindromisuudesta, mutta jossa
aliohjelman kommentissa oli kerrottu tuloksen tulostamisesta
ruudulle.

Alla esimerkki:

/**
  * Tutkii onko parametrina annettu sana palindromi eli
  * sama sana luki sen kumminpäin tahansa.
  *
  * @param sana tutkittava sana
  * @return palauttaa true, jos tutkittava sana on palindromi. Muulloin false.
  */

Yleisiä virheitä:

- vastattu ranskalaisilla viivoilla
- sama asia kerrottu useamman kerran
- liian ylimalkainen vastaus
- aliohjelman javadoc-kommentti puuttuu tai on virheellinen
- aliohjelman sijaan annettu esimerkki luokan javadoc-kommentista

B (2p):

Mitä toistorakenteita Java-kielessä on ja miten ne eroavat
toisistaan? Anna jokaiseen mielekäs käyttökohde.

Toistorakenteen eli silmukan avulla voidaan toistaa samaa
ohjelman lohkoa useamman kerran. Javassa on kolme erilaista
silmukkaa, jotka ovat määrämuotoinen toisto eli for-silmukka,
alkuehtoinen toisto eli while-silmukka ja loppuehtoinen toisto
eli do-while-silmukka. Alla kunkin silmukan syntaksi:

for (laskurin alustus; ehto; laskurin päivitys) {
  lause 1;
  ...
  lause n;
}

while (ehto) {
  lause 1;
  ...
  lause n;
}

do {
  lause 1;
  ...
  lause n;
} while (ehto)

Silmukoiden erot toisiinsa nähden ja käyttökohteet:

for:
- käytetään kun tiedetään kuinka monta kertaa silmukka täytyy suorittaa
- esimerkiksi taulukon läpikäynti

while:
- käytetään kun ei olla varmoja tarviiko silmukkaa suorittaa kertaakaan
- esimerkiksi: while (talon sisällä on kylmä) { lämmitäTaloa(); }

do-while:
- vastaa while-silmukkaa, mutta käytetään kun tiedetään, 
  että silmukka suoritetaan ainakin yhden kerran
- esimerkiksi käyttäjän antaman syötteen validointi ja 
  syötteen antamisen toistaminen, jos syöte oli virheellinen

Yleisiä virheitä:

- toistorakenteet sekoitettu ehto-lauseisiin
- switch-case-valintarakenne esitelty toistorakenteena
- vastauksesta puuttuu mielekäs käyttökohde
- vain osa toistorakenteista esitelty
- saman aliohjelman käyttäminen useamman kerran esitelty toistorakenteena

C (2p):

Kuinka Java-koodi muuttuu suoritettavaksi ohjelmaksi? Mikä on
Java-virtuaalikoneen tehtävä? Mitä hyötyä Javan
virtuaalikoneesta on?

Tehtävän ratkaisu löytyy luentomonisteen viidennestä kappaleesta
(http://kurssit.it.jyu.fi/ITKP102/moniste/html/moniste.html#o5Lahdekoodista_prosessorille).

Täydet pisteet saa kun mainitsee seuraavat seikat:

- ensiksi luodaan java-päätteinen lähdekooditiedosto, joka on tekstidokumentti
- lähdekooditiedosto käännetään java-kääntäjällä tavukoodiksi
- kääntämisen yhteydessä tarkistetaan lähdekoodin syntaksi

Virtuaalikoneen tehtävä:

- kääntämisen yhteydessä syntynyt class-päätteinen 
  tavukooditiedosto suoritetaan virtuaalikoneella (JVM)
- virtuaalikone tulkkaa tavukoodia ja suorittaa sen 
  kohdekoneen prosessorilla

Mitä hyötyä virtuaalikoneesta on:

- sama Java-ohjelma toimii kaikissa käyttöjärjestelmissä, 
  joihin on asennettu virtuaalikone
- Java-ohjelmasta ei tarvitse tehdä omaa versiota kullekin 
  käyttöjärjestelmälle

Yleisiä virheitä:

- lähdekoodin syntaksin tarkistamista ei ole kerrottu
- virtuaalikone ja java-kääntäjä sekoitettu keskenään
- ylimalkainen vastaus; esimerkiksi: "Tehdään eka java-tiedosto, 
  joka muuttuu class-tiedostoksi, joka sitten käynnistetään."









5. Java-koodi

1   package tentti09;
2   
3   
4   import java.util.Scanner;
5   
6   /**
7    * Ohjelma kysyy kaksi nimeä ja laskee nimien perusteella
8    * kaksinumeroisen rakkausprosentin. Rakkausprosentti lasketaan
9    * laskemalla "LOVES"-sanan jokaisen kirjaimen lukumäärät kummassakin
10   * nimessä. Esimerkiksi nimissä "Lotta" ja "Esko" on yhteensä
11   * <pre>
12   *  -  1 kpl L-kirjainta
13   *  -  2 kpl O-kirjaimia
14   *  -  0 kpl V-kirjainta
15   *  -  1 kpl E-kirjainta
16   *  -  1 kpl S-kirjainta.
17   * </pre>
18   * Suur- ja pienaakkosilla ei ole merkitystä. 
19   * Kirjaimien lukumäärät summataan vierekkäisten kirjaimien lukumäärien 
20   * kanssa yhteen, jolloin jäljelle jää neljä lukumäärää. 
21   * Jos lukumäärien summasta tulee kaksi- tai useampinumeroinen, 
22   * niin silloin luvun numerot summataan yhteen 
23   * (esimerkiksi luvusta 12 tulee 3). 
24   * Vierekkäisten kirjaimien lukumäärien summaamista jatketaan kunnes 
25   * jäljellä on kaksi lukua. Nämä kaksi lukua muodostavat rakkausprosentin.  
26   * @author Ohj1 tuntiopet
27   * @version 16.12.2009
28   */
29  public class Rakkauslaskuri {
30  
31      /** Sana jonka kirjaimiin verrataan */
32      public static final String RAKKAUSSANA = "LOVES";
33  
34      
35      /**
36       * Laskee yhteen luvun numerot
37       * @param luku tutkittava luku
38       * @return luvun numeroiden summa
39       * @example
40       * <pre name="test">
41       *   summaaNumerot(0) === 0
42       *   summaaNumerot(12) === 3
43       *   summaaNumerot(123) === 6
44       *   summaaNumerot(78) === 15
45       * </pre>
46       */
47      public static int summaaNumerot(int luku) {
48          int summa = 0;
49          int n = luku;
50          while ( n > 0 ) {
51              summa += n % 10;
52              n = n / 10;
53          }
54          return summa;
55      }
56      
57      /**
58       * Muuttaa luvun yksinumeroikseksi laskemalla luvun numerot
59       * yhteen niin kauan että tulos on yksinumeroinen.
60       * @param luku tutkittava luku
61       * @return luvun numeroiden summa (aina yksi numero)
62       * @example
63       * <pre name="test">
64       *   yksiNumeroinenSumma(1)   === 1;
65       *   yksiNumeroinenSumma(12)  === 3;
66       *   yksiNumeroinenSumma(123) === 6;
67       *   yksiNumeroinenSumma(78)  === 6;
68       * </pre>
69       */
70      public static int yksiNumeroinenSumma(int luku) {
71          int tulos = luku;
72          while ( tulos > 9 ) tulos = summaaNumerot(tulos);
73          return tulos;
74      }
75      
76      /**
77       * Laskee taulukon vierekkäiset luvut yhteen ja palauttaa
78       * uuden taulukon, jossa on vierekkäisten lukujen summa.
79       * Jos summan luvusta tulee kaksi tai useampinumeroinen, 
80       * sen numerot lasketaan yhteen kunnes se on yksinumeroinen.
81       * @param a laskettava taulukko
82       * @return vierekkäisten summa (yhtä lyhyempi kuin a)
83       * @example
84       * <pre name="test">
85       * Arrays.toString(laskeVierekkaisetYhteen(new int[]{1}))     === "[]";
86       * Arrays.toString(laskeVierekkaisetYhteen(new int[]{1,1}))   === "[2]";
87       * Arrays.toString(laskeVierekkaisetYhteen(new int[]{2,3,5})) === "[5, 8]";
88       * </pre>
89       */
90      public static int[] laskeVierekkaisetYhteen(int[] a) {
91          int[] a2 = new int[a.length - 1]; // Lukujen määrä vähenee aina yhdellä.
92          // Käydään kerralla läpi kaksi vierekkäistä alkiota.
93          // Lopetetaan kun jälkimmäinen (j) on lopussa.
94          for (int i = 0, j = 1; j < a.length; i++, j++) {
95              a2[i] = yksiNumeroinenSumma(a[i] + a[j]);
96          }
97          return a2;
98      }
99  
100     /**
101      * Laskee kuinka monta mitäkin sanan kirjainta löytyy jonosta.
102      * @param sana jonka kirjaimia tutkitaan 
103      * @param jono josta kirjaimia etsitään
104      * @return kirjainten lukumäärät kokonaislukutaulukossa
105      * @example
106      * <pre name="test">
107      * #import java.util.Arrays;
108      *   Arrays.toString(laskeKirjaimet("apu","saapas"))  === "[3, 1, 0]";
109      *   Arrays.toString(laskeKirjaimet("apu",""))        === "[0, 0, 0]";
110      *   Arrays.toString(laskeKirjaimet("Apua","saapas")) === "[0, 1, 0, 3]";
111      * </pre>
112      */
113     public static int[] laskeKirjaimet(String sana,String jono) {
114         int[] a = new int[sana.length()];
115         for (int si = 0; si < a.length; si++) {
116             char c = sana.charAt(si);
117 
118             int lkm = 0;
119             for (int ji=0; ji<jono.length(); ji++)
120                 if ( jono.charAt(ji) == c ) lkm++;
121 
122             a[si] = lkm;
123         }
124         return a;
125     }
126 
127     /**
128      * Lasketaan sanojen rakkausprosentti.
129      * @param nimi1
130      * @param nimi2
131      * @return nimien rakkausprosentti
132      * 
133      * @example
134      * <pre name="test">
135      * laskeRakkausprosentti("Lotta","Esko") === 86
136      * laskeRakkausprosentti("Viivi-Loviisa","Lasse-Sylvester") === 39
137      * laskeRakkausprosentti("SUSU","VOSSO") === 99
138      * </pre>
139      */
140     public static int laskeRakkausprosentti(String nimi1, String nimi2) {
141         int[] luvut = laskeKirjaimet(RAKKAUSSANA, (nimi1 + nimi2).toUpperCase());
142 
143         while (luvut.length > 2) {
144             luvut = laskeVierekkaisetYhteen(luvut);
145         }
146         return luvut[0]*10 + luvut[1];
147     }
148 
149     /**
150      * Lasketaan rakkausprosentti käyttäjän syötteistä
151      * @param args ei käytössä
152      */
153     public static void main(String[] args) {
154         System.out.println("Ohjelma laskee kahdesta nimestä rakkausprosentin.");
155         Scanner scan = new Scanner(System.in);
156         System.out.print("Anna ensimmäinen nimi: ");
157         String nimi1 = scan.nextLine();
158         System.out.print("Anna toinen nimi: ");
159         String nimi2 = scan.nextLine();
160         System.out.printf("Rakkausprosenttinne on %d!%n",
161         laskeRakkausprosentti(nimi1, nimi2));
162     }
163 
164 }
165 
[JavaDoc-muoto] [Alkuperäinen java]

5. tehtävän pisteytys

Ville Lahtinen

MALLIVASTAUKSET

T1: summa
T2: tulos
T3: a[i] + a[j]
T4: a2
T5: if (jono.charAt(ji) == c) lkm++;
T6: public static int laskeRakkausprosentti(String nimi1, String nimi2) {
        int[] luvut = laskeKirjaimet(RAKKAUSSANA, (nimi1 + nimi2).toUpperCase());

        while (luvut.length > 2) {
            luvut = laskeVierekkaisetYhteen(luvut);
        }

        return luvut[0]*10 + luvut[1];
    }

Bonustehtävä

Maksimiprosenttiin tarvittavat kirjaimet voi laskea nurinpäin
esimerkiksi seuraavasti.

      9   9
     / \ / \
    5   4   5
   / \ / \ / \
  3   2   2   3
 / \ / \ / \ / \
2   1   1   1   2
L   O   V   E   S

Oikea määrä kirjaimia on esimerkiksi nimissä "Lasse" ja "Viola".

PISTEYTYSPERUSTEET

T1 - T5: Maksimissaan 0,4 pistettä/kohta
- Kohdissa T1, T2 ja T4 ei hyväksytty kuin täsmälleen oikea
  vastaus.
- Kohdassa T3 hyväksyttiin myös vastaukset a[i] + a[i+1] ja
  a[j-1] + a[j].
- Kohdassa T5 hyväksyttiin vertailuoperaattorin sijaan myös
  equals-metodien käyttö, vaikka niitä ei char-muuttujille voi
  käyttääkään.

T6: Maksimissaan 4 pistettä.
- Aliohjelman esittely oikein 0,5 pistettä
- Kirjaimien lukumäärän laskenta oikein 0,5 pistettä. Jos pien-
  ja suuraakkosia ei huomioitu, vähennettiin 0,25 pistettä.
- laskeVierekkaisetYhteen-metodia kutsuttiin silmukassa 0,5 pistettä
- laskeVierekkaisetYhteen-metodia kutsuttiin oikea määrä (eli
  silmukkaa käytiin läpi oikea määrä) 0,5 pistettä
- Silmukan ehto muodostettiin RAKKAUSSANA-vakion kirjaimien
  lukumäärän perusteella 0,5 pistettä
- laskeVierekkaisetYhteen-metodin kutsu ja paluuarvon sijoitus
  oikein 0,5 pistettä
- Aliohjelman palautusarvon tyyppi oikein 0,5 pistettä
- Aliohjelman palauttama arvo laskettiin oikein 0,5 pistettä.

Kohtien T1 - T6 pisteet laskettiin yhteen ja pyöristettiin 0,25
pisteen tarkkuuteen.

Bonustehtävässä hyväksyttiin kaikki vastaukset, joilla
rakkausprosentiksi tuli 99.