Ohjelmointi 1, tentti 19.4.2013 Arviointiraportti Antti-Jussi Lakanen Yleistä --- * Tentti oli hieman tyypillistä haastavampi. Pistekeskiarvo niiden osalta ketkä jättivät jotain tarkastettavaa oli (ilman demoja) 12.2. Demojen kanssa keskiarvoksi tuli 16.2. * Lukiolaisten pistekeskiarvo 9.2 (piti tehdä vain 2 tehtävää). * Tehtäväkohtaiset pistekeskiarvot: - Tehtävä 1: 2.3 - Tehtävä 2: 4.6 - Tehtävä 3: 1.9 - Tehtävä 4: 3.4 Tehtävä 1 --- Monelle vaikeahko tehtävä. Ei sisältänyt kuitenkaan mitään semmoista, mitä kurssin tiedoilla ei olisi ollut mahdollista tehdä. Tyypillisiä virheitä ja vähennyksiä: * Silmukassa menty läpi "väärää asiaa", esim merkit[i], kun piti olla merkit[numero], missä int numero = int.Parse(numerot[i].ToString()); - 1.5 p. * numerot- ja jono-muuttujien pituuksien tarkastelu puuttuu, -1 p. * Ei parsittu lukua merkkijonosta lainkaan, -1 p. * Väärä silmukkarakenne, esim. yritetty käyttää indeksiä foreach-silmukassa, -1 p. * Yritetty parsia int kirjoittamalla "jono".Parse, -0.25 p. * Yritetty parsia char-merkki int.Parsella, -0.25 p. (ks. vinkki tentin lopussa) * Oleelliset pikkuvirheet esim vertailuoperaattori vs sijoitus, -0.25 p. * Epäoleelliset pikkuvirheet, ei vähennyksiä. public static bool T9(String[] merkit, String numerot, String jono) { if (numerot.Length != jono.Length) return false; for (int i = 0; i < numerot.Length; i++) { if (!Char.IsDigit(numerot[i])) return false; // bonus int luku = int.Parse(numerot[i].ToString()); if (merkit[luku].IndexOf(jono[i]) < 0) return false; // Ylläoleva rivi voidaan kirjoittaa myös seuraavaan muotoon: // if (!(merkit[luku].Contains(jono[i].ToString()))) return false; } return true; } Tehtävä 2 --- Tehtävä osattiin aika hyvin. Tentin helpoin tehtävä. Suurimmat vaikeudet !-operaattorin käytössä. (1) C#-kielessä int-muuttujaan voidaan lisätä seuraavasti i++; ++i; i = i + 1; i += 1; * Oikeista 0.25 p. kustakin * Vääristä ei rokotettu, joskin myös pienistä kirjoitusvirheistä oltiin myös aika pikkutarkkoja, esim i =+ 1 vs i += 1 * int i = 5; i = 6; ei ole i:n arvon kasvattamista, vaan uudelleenasettamista. Tästä ei siis pisteitä tullut. (2) Operaattori == vertailee kahta arvoa ja palauttaa totuusarvon. Operaattori = tekee sijoituksen. Vrt. int i = 5; // Sijoitus if (i == 0) { /* Jos i:n arvo on nolla niin tehdään jotain... */ } (3) Muuttujan n arvo lauseen double n = (7 % 3) * (4 + 6) / 3; jälkeen on 3.0. Sulut lasketaan ensin: 7 % 3 == 1, 4 + 6 == 10. Sitten kerto- ja jakolaskut vasemmalta oikealle. 1 * 10 == 10, 10 / 3 == 3, kyseessä on int-jakolasku, joten desimaaliosa häviää. Tulos sijoitetaan double-muuttujaan, joten n:n arvo on 3.0. Moni oli laskenut tuon 10 / 3 väärin. Ilman sulkuja double n = 7 % 3 * 4 + 6 / 3; arvo on 6.0. Laskut vastaavasti kuin äsken, nyt yhteenlasku vain tehdään viimeisenä. Aloitetaan vasemmalta oikealle: 7 % 3 == 1, 1 * 4 == 4 6 / 3 == 2 (huom! tämä ennen yhteenlaskua) 4 + 2 == 6 Sijoitus double-muuttujalle, n:n arvoksi tulee 6.0. * 10 / 3 laskettu väärin, -0.5 p. (4) !-operaattori on ns. looginen negaatio -operaattori, joka muuttaa totuusarvoisen operandin arvon päinvastaiseksi. Ts. true-arvosta tulee false, ja falsesta true. Esimerkiksi bool isAlive = true; if (!isAlive) { // tämä lohko tapahtuu vain jos isAlive-muuttujan arvo on false } * Selitys ei riittävän kuvaava, esim "joku ei ole jotakin", -0.5 p. * Esimerkki vaillinainen tai väärin, -0.25 tai -0.5 p. vakavuudesta riippuen * Pelkkä esimerkki, ei selitystä -0.5 p. (5) return-lause on C#-kielen avainsana. return-lausetta käytetään aliohjelmasta poistumiseen (void-aliohjelma) tai palauttamaan arvo aliohjelmasta. * Ei kerrottu mistä "palautetaan", -0.5 p. (6) Kun int a = 3; int b = 5; niin lausekkeen b % a < b - a arvo on false. Perustelu: b % a == 2, b - a == 2, 2 < 2 == false. Tehtävä 3 --- Tentin hankalin tehtävä johtuen varmaan siitä, että rekursiota ei kovin paljon käsitelty kurssilla. (a) Rekursiossa aliohjelma tai funktio kutsuu itseään uudestaan tiettyjen ehtojen perusteella. Rekursiossa on oltava jokin alkuarvo tai -arvoja (ohjelmoinnissa tätä/näitä kutsutaan myös lopetusehdoksi). * Tämä hokema muistettiin aika hyvin. Asian ymmärtäminen on kuitenkin eri asia, kuten b-kohdasta näkyy. Tyypillisiä virheitä: * Ei selitetty kunnolla mikä määrittyy itsensä avulla (esim. "lauseke" ei kelpaa), -0.5 p. * Määrittely liian väljää, esim. "algoritmi, joka ratkaisee itse itsensä", -0.5 p. tai -1.0 p. (b) * Isoa hajontaa vastauksissa. Tehtävässä näkyi se, ettei rekursion käsitettä oltu sisäistetty. Toki kurssilla oli verrattain vähän asiaa rekursiosta, siksi tehtäväkin oli "perustasoa". Tässä virheistä tyypillisimmät. * Fibonacci-funktio ei ota yhtään parametria, -1 p. * Fibonacci(0) tai Fibonacci(1) ei määritelty, -0.25 p. * Funktio määritelty muuten väärin, esim. F(1):llä tuli jotain muuta kuin mitä piti, -0.25 p. /// /// Antaa n:nen luvun Fibonaccin lukujonosta. /// Iteratiivinen toteutus. /// /// n /// Fibonacci(n) public static int Fibonacci(int n) { int fibo = 0; int fiboM1 = 1; // F_1 = 1 int fiboM2 = 0; // F_0 = 0 for (int i = 3; i < 100; i++) { fibo = fiboM1 + fiboM2; Console.WriteLine(fibo); fiboM2 = fiboM1; fiboM1 = fibo; } return fibo; } /// /// Antaa n:nen luvun Fibonaccin lukujonosta. /// Rekursiivinen toteutus. /// /// n /// Fibonacci(n) /// ///
///  Rekursio.FibonacciRec(0) === 0;
///  Rekursio.FibonacciRec(2) === 1;
///  Rekursio.FibonacciRec(4) === 3;
///  Rekursio.FibonacciRec(5) === 5;
///  Rekursio.FibonacciRec(6) === 8;
/// 
///
public static int FibonacciRec(int n) { if (n == 0) return 0; if (n == 1) return 1; return FibonacciRec(n - 1) + FibonacciRec(n - 2); } Tehtävä 4 --- A-osassa oli paljon virheitä, B-osa meni paremmin. B-osan kaltaisia tehtäviä on ollut demoissakin aika paljon joten sitä varten oli selvästi harjoiteltu. (a) Tulostaa Antti-Jussi Lakanen Antti Lakanen Miksi? Kun funktiolle viedään StringBuilder-olioviite, funktiossa muutetaan oliota, johon pääohjelman paikallinen muuttuja (sb) viittaa. Koska StringBuilder-olioita voi muuttaa, näkyvät LisaaLoppuun- funktiossa tehdyt muutokset myös pääohjelman sb-muuttujassa. Toisin on silloin, kun viedään String-olioviite. Nythän on aluksi niin, että pääohjelman s-muuttuja ja LisaaLoppuun-funktion jono- muuttuja viittaavat samaan olioon koneen keskusmuistissa. Sitten funktiossa tehdään jono += lisattava; . Muistetaan, että String-olioita ei voi muuttaa. Mitä tässä tapahtuu, on, että luodaan uusi olio, johon jono-muuttuja laitetaan viittaamaan. Huomaa, että mitään muutoksia ei ole tehty pääohjelman s-muuttujalle, se viittaa edelleen siihen samaan olioon, johon se alunperinkin viittasi. Niinpä jono-muuttuja ja olio johon se viittaa "kuolevat" samalla hetkellä kun LisaaLoppuun-funktion suoritus päättyy. * Yllättävän moni sanoi että String-oliosta tulostetaan "Antti", koska String-oliota ei voi muokata. On totta, ettei String-oliota voi muokata, mutta kun sanomme String jono = "jokinJono"; jono = "ihanToinenJono"; niin emme muuta sitä jono-oliota, johon ylempänä viittaamme (koska se ei ole mahdollista), vaan teemme ihan uuden olion, ja laitamme jono-muuttujan viittaamaan siihen. Siis: String-oliota ei voi muuttaa, mutta String-tyyppistä muuttujaa kyllä voi! * Jos vastauksessa oli että String-olio tulostaa Antti-Jussi Lakanen mutta kohtuullisin perusteluin niin -1.5 p. * Jos osattu antaa tulostukset oikein, mutta perusteluissa pieniä puutteita, -1 p. * Annettu (oikeat) tulostukset ilman perusteluja, -1.5 p. * Annettu osittain oikeat tulostukset ilman perusteluja, -2 tai -2.5 p. (b) * Ei huomioitu laskevaan päättyvää lukusarjaa, esim [3,2,1], -0.5 p. * Nollamittaista taulukkoa ei erikseen käsitelty, palauttaa 1, -0.25 p. * Nollamittaista taulukkoa ei käsitelty, heittää poikkeuksen, -0.5 p. * Dokumentaatio puuttuu, -0.5 p. * Osa dokumentaatiosta puuttuu, tapauksesta riippuen -0.25 p. tai -0.5 p. * Palautettu pituuden sijasta osajono, -1.0 tai -1.5 p. toteutuksen oikeellisuudesta riippuen * Käsitellään taulukkoa indeksirajan ulkopuolelta, -0.5 p. * Epäoleelliset kirjoitusvirheet, ei vähennyksiä /// /// Antaa pisimmän laskevan osajonon pituuden. /// /// Luvut /// Pisimmän laskevan osajonon pituus. /// ///
///  int[] luvut = { 5, 3, 4, 4, 2, 0, 1, 2, 3, 3, 2 };
///  Osajono.PisinLaskevaJono(luvut) === 4;
///  int[] luvut2 = { 5, 4, 3, 2, 1 };
///  Osajono.PisinLaskevaJono(luvut2) === 5;
///  int[] luvut3 = { 1, 2, 3 };
///  Osajono.PisinLaskevaJono(luvut3) === 1;
///  int[] luvut4 = { 3 };
///  Osajono.PisinLaskevaJono(luvut4) === 1;
///  int[] luvut5 = { };
///  Osajono.PisinLaskevaJono(luvut5) === 0;
///  int[] luvut6 = { 3, 3, 3 };
///  Osajono.PisinLaskevaJono(luvut6) === 3;
/// 
///
public static int PisinLaskevaJono(int[] luvut) { if (luvut.Length == 0) return 0; int pisin = 1; int pituusNyt = 1; for (int i = 1; i < luvut.Length; i++) { if (luvut[i] <= luvut[i - 1]) pituusNyt++; else pituusNyt = 1; if (pituusNyt > pisin) pisin = pituusNyt; } return pisin; }