Ohjelmointi 1, tentti 16.5.2014. Tentaattori Antti-Jussi Lakanen.

Arviointiraportti

Yleistä

Tentti (http://users.jyu.fi/~anlakane/ohjelmointi1/tentit/2014-05-16-tentti2.pdf) oli vaikeudeltaan keskitasoa. Demopisteet lasketaan vielä tuohon päälle arvosanaa laskettaessa. Tehtäväkohtaiset pistekeskiarvot löytyvät alta kunkin tehtävän kohdalta.

Omasta tehtäväpaperista saa kopion saa Antti-Jussilta, huone C414.2. Jos en ole paikalla, laita sähköpostia tai soita 0500 603111. Papereihin on merkitty virheet, jotka voin käydä kanssasi läpi, tarvittaessa tarkastajan kanssa.

Ensimmäinen uusinta on 16.5.2014, ja toinen uusinta 13.6.2014.

Tehtävä Teki Keskiarvo Tarkastaja
T1 X X Antti-Jussi Lakanen
T2 X X Antti-Jussi Lakanen
T3 X X Antti-Jussi Lakanen
T4 X X Antti-Jussi Lakanen
T5 X X Antti-Jussi Lakanen
Yht X

Tehtävä 1

  • Teki: x opiskelijaa
  • Keskiarvo: x.x pistettä
  • Palautteet: mittaa x.x, vaikea x.x, arvio x.x, aika x min
  • Tarkastaja: Antti-Jussi Lakanen

Malliratkaisu

// Tehdään kokonaislukutaulukko, jossa viisi alkiota:
// 5, 11, -4, 0, 1
// Huomaa, että viimeinen luku on 1 koska 5/3 on int-lukuna 1.
int[] luvut = { 5, 10 + 1, -4, 0, 5 / 3 };

// Tehdään totuusarvoja sisältävä lista, johon laitetaan
// "valmiiksi" kaksi alkiota: true ja false
List<bool> totuudet = new List<bool>() { true, false };

// Lisätään totuudet-listaan true, koska
// luvut[0] on 5
// luvut[1] on 11 ja 11/2 on 5, joten 5 == 5
totuudet.Add(luvut[0] == (luvut[1] / 2));

// Lisätään totuudet-listaan false, koska
// totuudet.Count on 3 ja luvut.Length on 5, ja
// niinpä 3 == 5 palauttaa false
totuudet.Add(totuudet.Count == luvut.Length);

// Järjestetään luvut-taulukko.
// Taulukko nyt {-4, 0, 1, 5, 11}
Array.Sort(luvut);

// Lisätään totuudet-listaan true, koska
// luvut[2] on 1 ja luvut[0] + luvut[3] on 1
// niinpä 1 != 1 palauttaa false
totuudet.Add(luvut[2] != (luvut[0] + luvut[3]));

// Tulostetaan True, False, True, False, False
// Huomaa, että totuusarvoja tulostettaessa eka kirjain tulee isolla
// Pisteiden kannalta ei väliä kirjoittiko tulostuksen pienillä vai
// isoilla kirjaimilla.
foreach (bool arvo in totuudet)
{
    Console.WriteLine(arvo);
}

Yleiset huomiot

Tehtävä meni pääsääntöisesti aika hyvin. Vähän laiskasti tai epämääräisesti oli kommentoitu rivejä, tai ei oltu ihan uskallettu kertoa, että mitä arvoja bool-listaan todella ollaan lisäämässä. Mitenkään valtavan ankara en kuitenkaan ollut, jos vaikutelma kokonaisuudesta oli se, että ymmärrettiin mitä oltiin tekemässä.

Pisteytys ja virheet

  • Muiden pääohjelman rivien kommentoinnista enintään 4 pistettä yhteensä, mutta tulostuksesta ja sen oikeellisuudesta 2 pistettä.
  • Piti siis tietää että mitä arvoja bool-listaan menee, muuten ei täysiä pisteitä tullut.

Lisätään truetai false, vertailemalla totuudet-listan alkioiden määrää luvut-taulukon alkioiden määrään.

  • Yllä esimerkki kommentista, joka on totta, mutta jossa olisi pitänyt vielä mainita, että kumpi arvo listaan todella lisätään.

Tehtävä 2

  • Teki: x opiskelijaa
  • Keskiarvo: x.x pistettä
  • Palautteet: mittaa x.x, vaikea x.x, arvio x.x, aika x min
  • Tarkastaja: Antti-Jussi Lakanen

Malliratkaisu

/// @author anlakane
/// @version 16.5.2014
/// <summary>
/// Lasketaan palavien hehkulamppujen määrä, ja tulostetaan
/// palavat lamput kun mennään sata kierrosta.
/// </summary>
public class T2
{
    /// <summary>
    /// Kaksinkertainen silmukka, jolla
    /// saadaan aikaan sata kierrosta.
    /// </summary>
    public static void Main()
    {
        bool[] lamput = new bool[101];
        for (int i = 1; i < lamput.Length; i++)
        {
            for (int j = i; j < lamput.Length; j++)
            {
                if (j % i == 0) lamput[j] = !lamput[j];
            }
        }

        int summa = 0;
        StringBuilder lamputPaalla = 
          new StringBuilder("Sytytettyinä ovat lamput nro: ");
        String eteen = "";
        for (int i = 0; i < lamput.Length; i++)
        {
            if (lamput[i])
            {
                summa++;
                lamputPaalla.Append(eteen + i);
                eteen = ", ";
            }
        }
        lamputPaalla.Append(".");
        Console.WriteLine("Sytytettyjä lamppuja yht.: " + summa + " kpl.");
        Console.WriteLine(lamputPaalla);
    }
}

Yleiset huomiot

Tehtävä osoittautui vaikeaksi, ja tehtävänanto melko monine vaiheineen hieman hankalaksi ymmärtää.

Pisteytys ja virheet

  • Olin melko armollinen, vaikka kokonaisuus ei toiminutkaan. Hyvästä yrityksestä sai pisteitä.
  • Jos oli kuitenkin tehty ihan väärää asiaa, niin siitä ei annettu pisteitä. Tällaisia olivat esimerkiksi seuraavat: silmukassa mentiin läpi väärää asiaa, lamppuja sytytettiin "miten sattuu", päällä olevien lamppujen kokonaismäärä laskettu täysin epäloogisesti, jne.

Tehtävä 3

  • Teki: x opiskelijaa
  • Keskiarvo: x.x pistettä
  • Palautteet: mittaa x.x, vaikea x.x, arvio x.x, aika x min
  • Tarkastaja: Antti-Jussi Lakanen

Malliratkaisu

  • Kohta 1. int on kokonaislukutyyppi, ja bool totuusarvoinen tyyppi. Esimerkiksi int munIka = 31;, tai bool peliKaynnissa = false;.
  • Kohta 2. Muuttujat nimetään C#:ssa nk. camelCasing-säännön mukaan, siis ensimmäinen kirjain pienellä ja seuraavien sanojen ekat isolla. Ks. esimerkkejä kohdassa 1. Vakiot nimetään suuraakkosin, ja sanojen väliin tulee alaviiva, esimerkiksi int PALLOJA_ALUKSI = 10;.
  • Kohta 3. Esimerkki funktiosta ja sen käytöstä.
public static int Summa(int[] luvut)
{
  int summa = 0;
  foreach(int luku in luvut)
  {
    summa += luku;
  }
  return summa;
}

Ja tätä voi kutsua (esimerkiksi pääohjelmasta käsin)

int[] lukuja = {4, 5, 6, 1, 0};
int munSumma = Summa(lukuja);
  • Kohta 4. Vertailuoperaattori palauttaa true tai false. Esimerkkejä: == on yhtäsuuruusoperaattori, jolla voi vertailla esimerkiksi kokonaislukujen tai totuusarvojen yhtäsuuruutta. Vastaavasti != on erisuuruusoperaattori, esimerkiksi 1 != 1 palauttaa false, sillä 1 ei ole erisuuri kuin 1. Edelleen, < on "pienempi kuin" -operaattori, esimerkiksi 5 < 4, palauttaa false.
  • Kohta 5. Rekursiivisella aliohjelmalla tarkoitetaan sellaista aliohjelmaa, joka tarvitsee itseään ratkaistaakseen ongelman. Rekursiivinen aliohjelma siis kutsuu itse itseään.
  • Kohta 6. 19.125_10 --> 10011.001_2
19 = 2 * 9, jj 1   | 0.125 * 2 = 0.25
 9 = 2 * 4, jj 1   | 0.25  * 2 = 0.5
 4 = 2 * 2, jj 0   | 0.5   * 2 = 1
 2 = 2 * 1, jj 0   |  
 1 = 2 * 0, jj 1   |    
Kokonaisosa 10011  | Desimaaliosa 100 
(luetaan alh->ylos)| (luetaan ylh->alas)

Siis koko luku 10011.001

Yleiset huomiot

  • Meni pääsääntöisesti tosi hyvin. Ei isompia ongelmia.

Pisteytys ja virheet

  • Kohta 1: Piti olla esimerkit tietotyypeistä (esimerkiksi int, bool, char, double, jne.) sekä niiden käytöstä (esim int luku = 0;, char kirjain = 'a', tms.) Jos ei ollut esimerkkiä niin puoli pistettä pois.
  • Kohta 3: Funktio palauttaa arvon, joten paluuarvon tyyppinä ei voi olla void vaan jotain muuta. Tätä eivät kaikki muistaneet.
  • Kohta 3: Jos funktion palauttamaa arvoa ei kutsun yhteydessä sijoitettu mihinkään, niin siitä pikku vähennys -0.25 p.
  • Kohta 6: Jos laskutoimituksissa tai tulkinnassa virheitä niin melko armottomasti vähennyksiä tästä.

Tehtävä 4

  • Teki: x opiskelijaa
  • Keskiarvo: x.x pistettä
  • Palautteet: mittaa x.x, vaikea x.x, arvio x.x, aika x min
  • Tarkastaja: Antti-Jussi Lakanen

Malliratkaisu

Tulosmatriisi näyttää tältä

1  1  1   1   1   1
1  2  3   4   5   6
1  3  6  10  15  21
1  4 10  20  35  56
1  5 15  35  70 126
1  6 21 105 126 252

Joten reittejä oikeaan alakulmaan on 252 kappaletta, ja jokaiseen soluun vievien reittien määrä saadaan laskettua summaamalla rivi-1 ja sarake-1, missä rivi ja sarake merkkaavat sitä paikkaa (indeksiä) missä ollaan menossa, kun rivi ja sarake > 0. Miksi näin? Siksi, että ainoat mahdollisuudet tulla kuhunkin soluun on yläkautta (rivi-1) tai vasemmalta (sarake-1), joten nämä summaamalla saadaan kaikkien mahdollisten reittien lukumäärä.

Alla olevassa koodissa on pituuden säästämiseksi jätetty kirjoittamatta Tayta-funktio sekä luokka, ja Main-pääohjelman dokumentaatio. Koko ohjelma dokumentaatioineen löytyy täältä: http://users.jyu.fi/~anlakane/ohjelmointi1/tentit/2014-05-16/T4.cs

public static void Main()
{
  int[,] matriisi = Tayta(6, 6, 1);
  for (int i = 1; i < matriisi.GetLength(0); i++)
  {
    for (int j = 1; j < matriisi.GetLength(1); j++)
    {
      matriisi[i, j] = LaskeReittienLukumaara(matriisi, i, j);
    }
  }

  Console.WriteLine(matriisi[5, 5]);
}

/// <summary>
/// Funktio palauttaa matriisin tietyn paikan 
/// (rivi, sarake) reittien lukumäärän, kun solusta toiseen 
/// "liikkuminen" voi tapahtua ainoastaan oikealle ja alaspäin. 
/// Esimerkiksi, soluun (1, 1) voi tulla kahta eri reittiä; 
/// ensin oikealle ja alas, tai sitten ensin alas ja vasta 
/// sitten oikealle. 
/// </summary>
/// <param name="matriisi">Matriisi</param>
/// <param name="rivi">Rivi</param>
/// <param name="sarake">Sarake</param>
public static int LaskeReittienLukumaara(int[,] matriisi, int rivi, int sarake)
{
    if (rivi == 0)
    {
        return matriisi[rivi, sarake-1];
    }

    if (sarake == 0)
    {
        return matriisi[rivi-1 , sarake];
    }

    return matriisi[rivi - 1, sarake] + matriisi[rivi, sarake - 1];
}

Yleiset huomiot

Tässä tehtävässä kävi vähän niin, että joko saatiin selkeästi kiinni, mistä on kysymys, tai sitten ei lainkaan ymmärretty. Tämä teki pisteytyksestä vähän "binääristä", eli joko aika lailla 6 p. tai 0 p. Vaikeuksia oli joillakin saada kiinni siitä, mitä ollaan tekemässä (algoritminen ajattelu). Kuten edellisessä kohdassa sanottiin, oleellista oli huomata, että solun reittien määrä saadaan summaamalla yläsolu + vasen solu. Ohjelmoinnin kannalta tehtävä oli aika helppo, tästä lisää alla.

Varsinainen kirjoitettava osa tässä tehtävässä oli Main-pääohjelman kaksi sisäkkäistä silmukkaa, jonka sisään tuli LaskeReittienLukumaara-funktiokutsu ja arvojen sijoitus sopivasti matriisi-muuttujaan. LaskeReittienLukumaara-funktio itsessään oli hyvin lyhyt ja simppeli, joten sinänsä tehtävä oli ohjelmointimielessä helppo.

Pääohjelman sisäkkäiset silmukat voidaan aloittaa riviltä 1 ja sarakkeesta 1, koska ekalle vaaka- ja pystyriville voidaan todellakin tulla vain yhtä reittiä pitkin, joten niiden reittien lukumäärää ei tarvitse "uudestaan" laskea pääohjelmassa.

Pisteytys ja virheet

  • Kuten sanottu, pisteytys meni aika lailla 0 p. tai 6 p. Epäoleellisista pikkuvirheistä ei sakotettu. Dokumentaatiot puuttuivat joltakin, siitä -1 p.

Tehtävä 5

  • Teki: x opiskelijaa
  • Keskiarvo: x.x pistettä
  • Palautteet: mittaa x.x, vaikea x.x, arvio x.x, aika x min
  • Tarkastaja: Antti-Jussi Lakanen

Malliratkaisu

using System;
using System.Text;
using System.Linq;
using System.Collections.Generic;

/// @author anlakane
/// @version 16.5.2014
/// <summary>
/// Lotto-ohjelma, joka arpoo ja tulostaa 7 
/// varsinaista numeroa ja 3 lisänumeroa väliltä 1-39.
/// </summary>
public class T5
{
    /// <summary>
    /// Arvotaan ja tulostetaan numerot.
    /// </summary>
    public static void Main()
    {
        int[] pallot = new int[39];

        // Taulukon paikkaan nro i tulee aina luku i+1
        // eli paikkaan 0 arvo 1, paikkaan 1 arvo 2, ...,
        // paikkaan 38 (viimeinen paikka) luku 39.
        for (int i = 0; i < pallot.Length; i++)
        {
            pallot[i] = i + 1;
        }

        Sekoita(pallot);

        Console.WriteLine("Varsinaiset numerot:");
        for (int i = 0; i < 7; i++)
        {
            Console.WriteLine(pallot[i]);
        }

        Console.WriteLine("Lisänumerot:");
        for (int i = 7; i < 10; i++)
        {
            Console.WriteLine(pallot[i]);
        }
    }

    /// <summary>
    /// Aliohjelma sekoittaa annetun kokonaislukutaulukon
    /// alkiot keskenään Fisher-Yates-algoritmilla
    /// </summary>
    /// <param name="luvut">Sekoitettava taulukko.</param>
    public static void Sekoita(int[] luvut)
    {
        Random r = new Random();

        for (int i = luvut.Length - 1; i > 0; i--)
        {
            int j = r.Next(i);
            int apu = luvut[j];
            luvut[j] = luvut[i];
            luvut[i] = apu;
        }
    }
}

Yleiset huomiot

Hieman yllättäen taulukon (tai listan), jossa on lukuja peräkkäin, tekeminen oli yllättävän haastavaa. Näkyi esimerkiksi ehdotus int[] luvut = new int[1, ..., 39], joka on valitettavasti väärin. Pitää muistaa, että hakasulkujen sisään tulee taulukon koko, eikä alkioiden arvoja. Sivujuonteena mainitsen vain, että olisi hirveän kiva jos muoto int[] luvut = {1..30} (huomaa aaltosulut) toimisi C#:ssa, tähän tyyliin perättäisten lukujen jono tehdään monissa kielissä, esimerkiksi Haskellissa.

Vielä kerran: Jos pitää toistaa useita kertoja jotakin juttua (esimerkiksi laittaa monta lukua tai arvoa taulukkoon), niin silloin tarvitaan toistorakennetta eli silmukoita. Ks. alla.

int luvut = new int[39]; // Tehdään taulukko, jonka pituus 39
for(int i = 0; i < luvut.Length; i++)
{
  // Laitetaan kuhunkin taulukon alkioon arvo i + 1 
  // (tarkempi selitys malliratkaisussa)
  luvut[i] = i + 1; 
}

Jos ei päästy kiinni siihen, miten luvut saadaan taulukkoon, niin herkästi ne jäi sitten myös sekoittamatta (ja tulostamatta). Eli tässäkin tehtävässä tuppasi olemaan hivenen kaksihuippuinen arvostelujakauma.

Pisteytys ja virheet

  • Ei oltu laitettu lukuja 1-39 taulukkoon lainkaan tai oli laitettu väärin, -1 p.
  • Muut virheet tapauskohtaisest.