Osa 2

Loogiset operaatiot

Materiaalin esimerkeissä ja tehtävissä käytetyt ehtolauseet ovat tähän mennessä käyttäneet yksinkertaisia lausekkeita, joilla on tarkasteltu ehtolauseeseen ja toistolauseeseen liittyvän lähdekoodin suorittamista. Esim.

int luku = 10;

if (luku == 0) { // lauseke
    System.out.println("Suoritetaan jos luku == 0 on totta");
}
int luku = 2;

if (luku % 2 == 0) {
    System.out.println("Luku on parillinen");
}
Esimerkkitulostus

Luku on parillinen

Ehtolauseen lauseke voi koostua useammasta osasta, joissa käytetään loogisia operaatioita ja &&, tai ||, sekä ei !.

  • Kahdesta lausekkeesta koostuva lauseke, joka yhdistetään ja-operaatiolla, on totta jos ja vain jos yhdistettävistä lausekkeista molemmat evaluoituvat todeksi.
  • Kahdesta lausekkeesta koostuva lauseke, joka yhdistetään tai-operaatiolla, on totta jos jompikumpi tai molemmat yhdistettävistä lausekkeista evaluoituvat todeksi.
  • Loogista operaatiota ei käytetään totuusarvon muuntamiseen truesta falseksi tai falsesta trueksi.

Seuraavassa yhdistetään &&:lla eli ja-operaatiolla kaksi yksittäistä ehtoa. Koodilla tarkistetaan, onko muuttujassa oleva luku suurempi kuin 4 ja pienempi kuin 11, eli siis välillä 5-10:

System.out.println("Onkohan luku väliltä 5-10: ");
int luku = 7;

if (luku > 4 && luku < 11) {
    System.out.println("On! :)");
} else {
    System.out.println("Ei ollut :(")
}
Esimerkkitulostus

Onkohan luku väliltä 5-10: On! :)

Seuraavassa annetaan ||:n eli tai-operaation avulla kaksi vaihtoehtoa, onko luku pienempi kuin 0 tai suurempi kuin 100. Ehto toteutuu jos luku täyttää jommankumman ehdon:

System.out.println("Onkohan luku pienempi kuin 0 tai suurempi kuin 100");
int luku = 145;

if (luku < 0 || luku > 100) {
    System.out.println("On! :)");
} else {
    System.out.println("Ei ollut :(")
}
Esimerkkitulostus

Onkohan luku pienempi kuin 0 tai suurempi kuin 100 On! :)

Seuraavassa käännetään ! ei-operaatiolla lausekkeen luku > 4 tulos. Ei-operaatio merkitään lauseketta ennen niin, että käännettävä lauseke rajataan suluilla, ja ei-operaatio lisätään sulkuja ennen.

int luku = 7;

if (!(luku > 4)) {
    System.out.println("Luku ei ole suurempi kuin 4.");
} else {
    System.out.println("Luku on suurempi tai yhtäsuuri kuin 4.")
}
Esimerkkitulostus

Luku on suurempi tai yhtäsuuri kuin 4.

Alla on kuvattuna lausekkeiden toimintaa kun lausekkeissa on loogisia operaatioita.

lukuluku > 0luku < 10luku > 0 && luku < 10!(luku > 0 && luku < 10)luku > 0 || luku < 10
-1falsetruefalsetruetrue
0falsetruefalsetruetrue
1truetruetruefalsetrue
9truetruetruefalsetrue
10truefalsefalsetruetrue
Loading

Ehtolauseiden suoritusjärjestys

Tutustutaan klassiseen ohjelmointiongelmaan:

'Kirjoita ohjelma, joka kysyy käyttäjältä lukua yhden ja sadan väliltä ja tulostaa luvun. Jos luku on kolmella jaollinen, luvun sijaan tulostetaan "Fizz". Jos luku on viidellä jaollinen, luvun sijaan tulostetaan "Buzz". Jos luku on sekä kolmella että viidellä jaollinen, luvun sijaan tulostetaan "FizzBuzz"'.

Ohjelmoija lähtee ratkaisemaan tehtävää lukemalla ongelmakuvauksen, ja luomalla ohjelmakoodia ongelmakuvausta seuraten. Koska ohjelman suoritusehdot esitellään ongelmassa annetussa järjestyksessä, muodostuu ohjelman rakenne järjestyksen perusteella. Ohjelman rakenne muodostuu seuraavien askelten perusteella:

  • Tee ohjelma, joka lukee luvun käyttäjältä ja tulostaa sen.
  • Jos luku on jaollinen kolmella, tulosta luvun sijaan merkkijono "Fizz".
  • Jos luku on jaollinen viidellä, tulosta luvun sijaan merkkijono "Buzz".
  • Jos luku on jaollinen kolmella ja viidellä, tulosta luvun sijan merkkijono "FizzBuzz".

Jos-tyyppiset ehdot on helppo toteuttaa if - else if - else -valintakäskyjen avulla. Alla oleva koodi on toteutettu yllä olevien askelten perusteella, mutta se ei kuitenkaan toimi oikein, kuten alla olevista esimerkeistä huomataan.

Scanner lukija = new Scanner(System.in);

int luku = Integer.valueOf(lukija.nextLine());

if (luku % 3 == 0) {
    System.out.println("Fizz");
} else if (luku % 5 == 0) {
    System.out.println("Buzz");
} else if (luku % 3 == 0 && luku % 5 == 0) {
    System.out.println("FizzBuzz");
} else {
    System.out.println(luku);
}
Esimerkkitulostus

3 Fizz

Esimerkkitulostus

4 4

Esimerkkitulostus

5 Buzz

Esimerkkitulostus

15 Fizz

Edellisessä lähestymistavassa ongelmana on se, että ehtolauseiden läpikäynti lopetetaan ensimmäiseen ehtoon, jonka arvo on totta. Esimerkiksi luvulla 15 tulostetaan merkkijono "Fizz", sillä luku on kolmella jaollinen (15 % 3 == 0).

Yksi lähestymistapa yllä olevan ajatusketjun kehittämiseen on ensin etsiä vaativin ehto ja toteuttaa se. Tämän jälkeen toteutettaisiin muut ehdot. Yllä olevassa esimerkissä ehto "jos luku on jaollinen kolmella ja viidellä" vaatii kahden tapauksen toteutumista. Nyt ajatusketju olisi muotoa.

  1. Tee ohjelma, joka lukee luvun käyttäjältä.
  2. Jos luku on jaollinen kolmella ja viidellä, tulosta luvun sijan merkkijono "FizzBuzz".
  3. Jos luku on jaollinen kolmella, tulosta luvun sijaan merkkijono "Fizz".
  4. Jos luku on jaollinen viidellä, tulosta luvun sijaan merkkijono "Buzz".
  5. Muulloin ohjelma tulostaa käyttäjältä luetun luvun.

Nyt ongelmakin tuntuu ratkeavan.

Scanner lukija = new Scanner(System.in);

int luku = Integer.valueOf(lukija.nextLine());

if (luku % 3 == 0 && luku % 5 == 0) {
    System.out.println("FizzBuzz");
} else if (luku % 3 == 0) {
    System.out.println("Fizz");
} else if (luku % 5 == 0) {
    System.out.println("Buzz");
} else {
    System.out.println(luku);
}
Esimerkkitulostus

2 2

Esimerkkitulostus

5 Buzz

Esimerkkitulostus

30 FizzBuzz

Loading

Toistolauseen ehto

Olemme tähän mennessä käyttäneet toistolausetta, jonka suluissa on totuusarvo true, jolloin toistoa on jatkettu ikuisesti (tai kunnes toistolauseessa päädytään komentoon break). Toistolauseen sulut, joihin olemme tähän mennessä asettaneet aina arvon true sisältävät oikeastaan ehtolausekkeen, aivan samalla tavalla kuin if-komentoa seuraavat sulut. Arvon true voi korvata lausekkeella, joka evaluoidaan ohjelman suorituksen yhteydessä. Lauseke määritellään täsmälleen samalla tavalla kuin ehtolauseen (if) lauseke.

Seuraavassa esimerkissä tulostetaan luvut 1, 2, ..., 5. Kun luku-muuttujan arvo on yli 5, while-ehto ei ole enää voimassa ja toistaminen lopetetaan.

int luku = 1;

while (luku < 6) {
    System.out.println(luku);
    luku++;
}

Lue ylläoleva "niin pitkään kuin muuttujan luku arvo on pienempi kuin 6, tulosta muuttujan luku arvo ja kasvata muuttujan luku arvoa yhdellä".

Loading...

Yllä muuttujan luku arvoa kasvatetaan yhdellä aina kun toistolauseen lohko suoritetaan.

Alla on video toistolauseen käytöstä.

Loading
Loading
Loading

Toistolauseen suoritus ei lopu heti kun toistolauseen ehtolauseke voisi evaluoitua todeksi. Toistolauseen ehtolauseke evaluoidaan aina kun saavutaan toistolauseen alkuun, eli (1) kun ohjelman seuraava suoritettava lause on toistolause, ja (2) kun toistolauseeseen liittyvän lohkon sisältämän ohjelmakoodin suoritus on saatu loppuun.

Tarkastellaan seuraavaa toistolausetta.

int luku = 1;

while (luku != 2) {
    System.out.println(luku);
    luku = 2;
    System.out.println(luku);
    luku = 1;
}

Ohjelman tulostus seuraavanlainen:

Esimerkkitulostus
1 2 1 2 1 2 ...

Vaikka muuttujan luku arvo on välillä 2, toistolauseen suoritus ei lopu koskaan.

Toistolauseen ehto tarkistetaan silloin kun toistolauseen toistaminen aloitetaan sekä silloin kun koodin suoritus on päässyt toistolauseen lopettavaan aaltosulkuun asti. Mikäli toistolauseen ehdon lauseke on evaluoituu todeksi eli muotoon true, suoritus jatkuu toistolauseen alusta. Mikäli lauseke evaluoituu epätodeksi eli muotoon false, suoritus siirtyy toistolausetta seuraavaan lauseeseen.

Vaikka muuttujan luku arvo on ylläolevassa toistolauseessa välillä 2, ei se ole koskaan 2 toistolauseen lopussa. Lopussa ehto luku != 2 on aina totta, ja suoritus jatkuu..

Eräs yleinen ongelmatyyppi on "tee jotain tietty määrä kertoja". Näissä ohjelmissa esiintyy toisto, jonka jokaisella toistokerralla tehdään haluttu toiminnallisuus sekä muutetaan kertojen lukumäärää laskevaa laskurimuuttujaa.

Seuraava ohjelma laskee tulon 4*3 hieman kömpelöllä tavalla eli summana 3 + 3 + 3 + 3:

int tulos = 0;

int i = 0;
while (true) {
  tulos += 3; // tarkoittaa samaa kuin tulos = tulos + 3;
  i++;  // tarkoittaa samaa kuin i = i + 1;

  if (i == 4) {
    break;
  }
}

System.out.println(tulos);

Saman toiminnallisuuden voi toteuttaa myös seuraavasti.

int tulos = 0;

int i = 0;
while (i < 4) {
  tulos += 3; // tarkoittaa samaa kuin tulos = tulos + 3;
  i++;  // tarkoittaa samaa kuin i = i + 1;
}

System.out.println(tulos);

Mitä enemmän ohjelmassa on muuttujia, sitä haastavampaa ohjelman askeleittaisen suorituksen seuraaminen on. Ohjelman ymmärtämisen kannalta suorituksen seuraaminen on kuitenkin tärkeää.

Yksi näppärä tapa muuttujien arvojen tarkasteluun toistolauseessa on taulukko. Seuraavaan taulukkoon on kirjoitettu auki edellisen esimerkin muuttujien tulos ja i arvot kullakin toistolauseen ehdon i < 4 vertailuhetkellä.

tulos i i < 4
0 0 true
3 1 true
6 2 true
9 3 true
12 4 false

Toistolauseen suoritus loppuu kun muuttujan tulos arvo on 12 ja muuttujan i arvo on 4 (ehto i < 4 on tällöin epätotta).

Loading...
Loading
Loading
Loading
Loading

Tarkastellaan seuraavaksi ohjelmaa, joka lukee käyttäjältä kokonaislukuja. Ohjelma käsittelee negatiiviset luvut epäkelpoina lukuina, positiiviset luvut hyväksyttävinä lukuina, sekä nollan lukemisen lopettamista ilmaisevana lukuna. Kun käyttäjä syöttää nollan, ohjelma tulostaa hyväksyttyjen lukujen summan, hyväksyttyjen lukujen lukumäärän sekä epäkelpojen lukujen lukumäärän.

Alla on kuvattuna eräs mahdollinen ratkaisu, joka ei kuitenkaan ole tyylin kannalta ideaali.

Scanner lukija = new Scanner(System.in);

System.out.print("Anna lukuja, negatiiviset luvut eivät kelpaa: ");
int summa = 0;
int hyvaksytytLuvut = 0;
int epakelvotLuvut = 0;

while (true) {
    int luettu = Integer.valueOf(lukija.nextLine());

    if (luettu == 0) {
        System.out.println("Hyväksyttävien lukujen summa: " + summa);
        System.out.println("Hyväksyttyjä lukuja: " + hyvaksytytLuvut);
        System.out.println("Epäkelvot luvut: " + epakelvotLuvut);
        break;
    }

    if (luettu < 0) {
        epakelvotLuvut++;
        continue;
    }

    summa += luettu;
    hyvaksytytLuvut++;
}

Yllä kuvatussa lähestymistavassa toistolauseen päättymisen jälkeen tapahtuva laskenta on toteutettu toistolauseen sisälle. Lähestymistapa ei ole suositeltava, sillä se johtaa helposti hyvin monimutkaiseen ohjelman rakenteeseen. Jos toistolauseen lopettamisen yhteydessä pitäisi tehdä muutakin — esimerkiksi lukea lisää syötteitä — asetettaisiin kyseinenkin toiminnallisuus helposti ehtolauseen sisälle. Lisätoiminnallisuuden kertyessä, ohjelma muuttuisi yhä vaikeammin ja vaikeammin luettavaksi.

Pitäydytään seuraavassa toistolauseen muodossa:

Scanner lukija = new Scanner(System.in);

// toistolauseessa tarvittavien muuttujien luominen

while (true) {
    // syötteen lukeminen

    // toistolauseesta poistuminen -- break

    // epäkelpojen syötteiden rajaaminen pois -- continue

    // hyväksyttävien syötteiden käsittely
}

// toistolauseesta poistumisen jälkeen suoritettava toiminnallisuus

Toisin sanoen, oleva ohjelma on selkeämpi jos toistolauseesta poistumisen jälkeen tehtävät asiat ovat toistolauseen ulkopuolella.

Scanner lukija = new Scanner(System.in);

System.out.print("Anna lukuja, negatiiviset luvut eivät kelpaa: ");
int summa = 0;
int hyvaksytytLuvut = 0;
int epakelvotLuvut = 0;

while (true) {
    int luettu = Integer.valueOf(lukija.nextLine());

    if (luettu == 0) {
        break;
    }

    if (luettu < 0) {
        epakelvotLuvut++;
        continue;
    }

    summa += luettu;
    hyvaksytytLuvut++;
}

System.out.println("Hyväksyttävien lukujen summa: " + summa);
System.out.println("Hyväksyttyjä lukuja: " + hyvaksytytLuvut);
System.out.println("Epäkelvot luvut: " + epakelvotLuvut);
Pääsit aliluvun loppuun! Jatka tästä seuraavaan osaan:

Muistathan tarkistaa pistetilanteesi materiaalin oikeassa alareunassa olevasta pallosta!