========================================================================= 2. välikokeen (10.12.96) tarkastusselostus ========================================================================= Tarkastajat: 1. Harri Honkaharju 2. Tiina Auvinen 3. Hannu Turtia 4. Marko Orasaari 5. Satu Eloranta Joissakin tehtävissä on käytetty puolikaspisteitä. Loppupisteet on pyöristetty ylöspäin. ========================================================================= 1. ========================================================================= 1. Pascal-kielessä arvojen, muuttujien, parametrien ja funktioiden tyypit asettavat rajoituksia mm. sijoituslauseelle, parametrivälitykselle, syöttö- ja tulostusoperaatioille. Millaisia nämä rajoitukset ovat? Mitä hyötyä niis- tä on ohjelmoijalle? Onko niistä jotakin haittaa. Vihje: Keskeisiä käsitteitä ovat sijoitusyhteensopivuus ja identtisyys. (5 p) Moniste (Wikla 1993) s. 42 Tyyppien yhteensopivuus - Tyypit ovat identtiset, jos niiden nimet tarkoittavat samaa tyyppi- ilmausta (ei riitä, että ilmaukset ovat samanlaiset). - Esim: type Lukuja = 1..10; var eka,toka : Lukuja; - Ei: var eka : 1..10; toka: 1..10; - Kahta tyyppiä kutsutaan yhteensopiviksi, jos - ne ovat identtiset - ne ovat saman tyypin osavälejä tai toinen on toisen osaväli - ne ovat yhtä pitkiä merkkijonotyyppejä tai - joukkotyyppejä, joilla on yhteensopiva kantatyyppi. - Lauseke E tyyppiä T2 on sijoitusyhteensopiva tyypin T1 kanssa, jos jokin seuraavista on totta: - T1 ja T2 identtiset, eikä kumpikaan ole tiedostotyyppi tai sisällä tiedostotyyppiä komponenttinaan. - T1 tyyppiä REAL ja T2 INTEGER - T1 ja T2 ovat yhteensopivia järjestettyjä tyyppejä ja E:n arvo on suljetulla välillä, jonka T1 määrää - T1 ja T2 ovat yhteensopivia joukkotyyppejä ja kaikki E:n alkiot ovat suljetulla välillä, jonka kantatyyppi T1 määrää - T1 ja T2 ovat yhteensopivia merkkijonotyyppejä. - Taulukot ja tietueet ovat sijoitusyhteensopivia vain jos ne ovat identtisiä. - Todellinen arvoparametri oltava sijoitusyhteensopiva muodollisen arvoparametrin tyypin kanssa. - Lukuoperaatiossa oltava sijoitusyhteensopiva vastaavan muuttujan kanssa. Hyötyä: Pascal on vahvasti tyypitetty ohjelmointikieli, eikä salli muuttujalle antaa toisen tyypin mukaista arvoa. Rajoitus on virheitä ehkäisevä ja totuttaa hy- viin ohjelmointitapoihin. Haittaa: Muuttujia voidaan käsitellä eri tyyppisinä joko valmiiden tai itsemääritel- tyjen muunnosaliohjelmien avulla. Esimerkkinä mainittakoon Laskuharjoituk- sissa on käyty läpi valmiina olevat pyöristys- ja desimaalipisteen katkaisu funktiot ja tehtävänä ollut proseduuri, joka lukee kokonaisluvun merkkeinä, jo(t)ka kelvollisuuden toteamisen jälkeen on muunnettu vastaavaksi kokonais- luvuksi. Kokeneemmaalle ohjelmoijalle rajoitus voi tuntua turhan työn teettämiseltä, vrt. esim. C, joka on sallivampi. Keskiarvo 3.41 Vastauksia arvosteltaessa joutui käyttämään poikkeuksellisen paljon harkin- taa. Täydet pisteet sai, jos vastauksessa oli läpikäyty kaikki kolme määri- telmää, toi rajoitukset julki ja kommentoinut hyötyä ja haittoja. Jonkin/joidenkin kohtien pois jättäminen oli tiputtanut 1-3 pistettä riip- puen vastauksen muista ansioista. Kielellisiin seikkoihin ei juurikaan kiinnitetty huomiota, sikäli kun vas- taaja oli ymmärettävästi tuonut ajatuksensa ja ohjelmointitaitonsa näiltä osin esille. ========================================================================= 2. ========================================================================= Tässä muistin virkistämiseksi ensin tietorakenteet: Tehtävässä piti määritellä tyyppi tekstity haluamallaan tavalla. Jos oli valinnut standardipascalin packed array of char -tyypin, se piti lukea merkki kerrallaan! Tämä oli melko yleinen virhe B-kohdassa. Tässä esimerkissä on valittu turbon string -tyyppi. const MAX=100; type tekstity=string; tilaus=record nimi:tekstity; osoite:tekstity; pullolkm:1..255; end; taulu=array[1..MAX] of tilaus; var tilaukset: taulu; tilauslkm:0..MAX; ___________________________________________________ A-kohta: Tehtävästä sai kaksi pistettä, jos oli tehnyt sen muuten kuin binäärihaulla. Tässä kaksi esimerkkiä siitä. Toisessa vertaillaan for-silmukassa kaikkia nimiä ja toisessa repeat -until -silmukassa vertaillaan nimiä niin kauan kunnes nimi löytyy tai ollaan selattu kaikki. ____________________________________________________________ Function Asiakkaanindeksi(name:tekstity):integer; var I:integer; begin Asiakkaanindeksi:=0; for I:=1 to tilauslkm do if tilaukset[I].nimi=name then Asiakkaanindeksi:=I; end; ______________________________________________________________ Function Asiakkaanindeksi(name:tekstity):integer; var onko:boolean; I:integer; begin Asiakkaanindeksi:=0; onko:=false; I:=1; Repeat if tilaukset[I].nimi=name then onko:=true; I:=I+1; until onko or (I>tilauslkm); if onko then Asiakkaanindeksi:=I-1; end; _______________________________________________________ BINÄÄRIHAKU: Jos binäärihaun oli tehnyt oikein sai neljä pistettä. Function Asiakkaanindeksi(name:tekstity):integer; var vasen, oikea, keski:integer; begin oikea:=tilauslkm; vasen:=1; repeat keski:=(vasen+oikea) div 2; if name< tilaukset[keski].nimi then oikea:=keski-1 else vasen:=keski+1; until (name=tilaukset[keski].nimi) or (vasen>oikea); if name=tilaukset[keski].nimi then AsiakkaanIndeksi:=keski else Asiakkaanindeksi:=0; end; __________________________________________________________ B-KOHTA: Asiakas piti lisätä proseduurissa oikealle paikalleen taulukossa aakkosjärjestyksessä. Tiedot piti pyytää proseduurissa. Asiakas ei saanut jo olla taulukossa. Sen tarkistamiseen oli kätevää käyttää A-kohdan proseduuria. Pullojen lukumäärä oli vielä tarkistettava. malliesim: Procedure LisaaAsiakas; var lnimi, losoite:tekstity; ind,pullo:integer; begin Write('Anna lisõttõvõn asiakkaan nimi: '); readln(lnimi); if tilauslkm lnimi do begin tilaukset[ind+1]:=tilaukset[ind]; ind:=ind-1; end; with tilaukset[ind+1] do begin nimi:=lnimi; tilauslkm:=tilauslkm+1; Write('Anna osoite: '); readln(osoite); Write('Anna pullolkm: '); repeat readln(pullo); if (pullo<1) or (pullo>255) then Write('Anna lukumäärä uudelleen (0-255): '); until (pullo>0) and (pullo<256) ; pullolkm:=pullo; end; end else Writeln('Asiakas on jo talletettu!'); end; ____________________________________________________________________ USEIN TEHTYJÄ VIRHEITÄ: - Muuttujien nimiä oli käytetty virheellisesti esim. taulu[keski].nimi (vaikka taulu on tyyppi eikä muuttuja) tai tilaukset.nimi[keski]. - Binäärihaku oli muistettu suunnilleen sinne päin, muttei ihan oikein. - Tyyppi tekstity oli jätetty määrittelemättä. - Standardipascalin packed array of char -tyyppinen muuttuja oli luettu kokonaan kerrallaan. - B-kohdassa kutsuttaessa A-kohdan funktiosta oli jäänyt parametri pois. - loogisia virheitä oli montaa lajia - B -kohdassa piti asiakkaan tiedot kysyä proseduurin sisällä! Jotkut olivat välittäneet ne funktion parametreina. - Monet olivat laittaneet uuden asiakkaan tiedot taulukon viimeiseksi. Tällöin taulukko olisi pitänyt vielä järjestää! ______________________________________________________________________ YLEISTÄ KORJAUSPERUSTEISTA: Yleensä otin puoli pistettä per pikkuvirhe, jos idea oli muuten oikein. Jos B-kohdassa oli jättänyt taulukon järjestyksen tekemättä, sai vain puolet pisteistä eli 1,5 pistettä kolmesta. ========================================================================= 3. ========================================================================= Tehtävä jakautuu kahteen osaan: suunnitelma ja aliohjelmien koodi. Suunnitelmasta sai maksimissaan 1 pisteen ja aliohjelmista 4 pistettä. Suunnitelman täydellisyyttä ei ole vaadittu kovinkaan ankarasti. Hyvä suunnitelma määrittelee tiedoston rakenteen siten, että ohjelmoija voi niiden perusteella ohjelmoida aliohjelmat. Riittävä esimerkki voisi olla vaikka: Tiedostoon kirjoitetaaan tilauslkm ensimmäiselle riville. Sen jälkeen kirjoitetaan jokaisen käytössä olevan (1..tilauslkm) tietueen tiedot siten, että tietueiden kentät ovat kukin omalla rivillään. Selventävä esimerkki: 3 Ensio Eka Kotikuja 6 34 Tiina Toka Suurtie 4 C34 123 Kaisa Kolmas Kettupolku 5 21 Tiedoston rakenteella on useampia eri mahdollisuuksia. Yleisin ja luultavasti myös tallennusrakenne on tallettaa tieteiden kukin kenttä (siis nimi /osoite /tilaus) jokainen omalle rivilleen. Tilauslukumäärän voi tallettaa tiedostoon, jolloin se myös luetaan sieltä. Tilauslukumäärää ei tarvitse tallettaa tiedostoon, jolloin luettaessa päivitetään tilauslkm:ää sen perusteella montako riviä (jaettuna kolmella) tiedostosta luettiin. Kaikki toimivat tiedoston rakenteet on hyväksytty. Kahdesta yleisimmästä on alla Turbo-Pascalilla tehdyt esimerkit. Versio 1: Ei talleteta tilauslkm:ää. Jokainen tietue kenttä on omalla rivillään. Ei mitään eroittimia kahden tietueen välissä. Versio 2: Tilauslkm talletetaan ensimmäiselle riville.Muuten kuin versio 2. {----------------------------------------- Mallivastaus 2. välikokeen tehtävään 3 Turbo-Pascalia versio 1, joka ei talleta tilauslkm:ää tiedostoon -----------------------------------------} Procedure Turvaan; {1 versio} var TurvaTied:Text; {tekstitiedostomuuttuja} i:integer; begin Assign(TurvaTied,'Turva'); Rewrite(TurvaTied); for i:=1 to tilauslkm do begin writeln(TurvaTied,Tilaukset[i].nimi); writeln(TurvaTied,Tilaukset[i].osoite); writeln(TurvaTied,Tilaukset[i].pullolkm); end; Close(TurvaTied); end; {-------------------------------------------} Procedure Turvasta; {1 versio} var TurvaTied : Text; {tekstitiedostomuuttuja} begin Assign(TurvaTied,'Turva'); Reset(TurvaTied); tilauslkm:=0; while not eof(TurvaTied) do begin tilauslkm:=tilauslkm+1; readln(TurvaTied,tilaukset[tilauslkm].nimi); readln(TurvaTied,tilaukset[tilauslkm].osoite); readln(TurvaTied,tilaukset[tilauslkm].pullolkm); end; Close(TurvaTied); end; =================================== {----------------------------------------- Mallivastaus 2. v{likokeen tehtävään 3 Turbo-Pascalia 2. versio, joka tallettaa tiedostoon tilauslkm:n -----------------------------------------} Procedure Turvaan; {2 versio} var TurvaTied:Text; {tekstitiedostomuuttuja} i:integer; begin Assign(TurvaTied,'Turva2'); Rewrite(TurvaTied); writeln(TurvaTied,tilauslkm); for i:=1 to tilauslkm do begin writeln(TurvaTied,Tilaukset[i].nimi); writeln(TurvaTied,Tilaukset[i].osoite); writeln(TurvaTied,Tilaukset[i].pullolkm); end; Close(TurvaTied); end; {-------------------------------------------} Procedure Turvasta; {2 versio} var TurvaTied : Text; {tekstitiedostomuuttuja} i:integer; begin Assign(TurvaTied,'Turva2'); Reset(TurvaTied); Readln(TurvaTied,tilauslkm); for i:=1 to tilauslkm do begin readln(TurvaTied,tilaukset[i].nimi); readln(TurvaTied,tilaukset[i].osoite); readln(TurvaTied,tilaukset[i].pullolkm); end; Close(TurvaTied); end; ------------------------------------------------------------------------ Pascalin 2. välikoe tehtävä 3 (Tietorakenteen talletus ja lukeminen tekstitiedostosta Turva) Yleisiä virheitä: 1 piste pois: -Kytkentä ulkoiseen tiedosto nimeen puuttuu: Assign Turbossa, Program(input,output,TURVA) standardissa -var Tied:Text puuttuu tai mokattu pahasti [kaksi edellistä yhdessä vain yksi piste pois] -Suunnitelma puuttuu kokonaan. -rewrite / reset puuttuu/pielessä -writeln / readln -käskyssä taulu[i], eikä tilaukset[i] -Ei teksti, vaan binaaritiedosto 0,5 pistettä pois: -Turvaan tallettaa kaikki kentät (1..MAX) -Luetaan merkkijono standardissa yhdellä readln:lla eikä merkki kerrallan. -Tilauslkm:ää ei päivitetä lukiessa -Turhia kokonaisluku <-> merkkijono konversioita tiedostoon kirjoitettaessa / luettaessa. -Kirjoitetaan tai luetaan koko tietue writeln / readln - käskyssä. Kauneusvirheitä, joista ei ole vähennetty pisteitä: -Ei ole kerrottu mitä Pascalin murretta käytetään - puuttui todella monesta. Merkintäni "Ympäristö?" tarkoittaa tätä. -Ei talleteta, jos Tilauslkm = 0. (Tämä aiheuttaa virhetoiminnon, kun käyttäjä on poistanut kaikki tietueet) -Tiedoston levyasema ja/tai hakemisto (esim. A:) on kiinnitetty. Entä jos tietokoneessa ei olekaan A: -asemaa? -DOS- tiedostonimi (Turbossa) on yli 8+3 merkkiä. -File of Text - oikeat määrittelyt ovat Text tai File of char. -Ei lueta suoraan tilaukset muuttujan kenttiin, vaan käytetään tarpeetonta välimuuttujaa. -Puolipisteistä, väärinkirjoituksista yms. ei ole vähennetty. Miinuspisteiden yhteenlasku poikkeaa normaalista - useampi virhe vähentää pisteitä vähemmän kuin niiden summa. Seurauksena pisteet putosivat 4 pisteeseen nopeasti, mutta siitä alaspäin hitaammin. ========================================================================= 4. ========================================================================= Mallivastausehdotus ------------------- PROGRAM Herkkumehu(input,output,turva); TYPE tekstity = varying[40] of char; { Sparc-piirre } tilaus = record nimi:tekstity; lkm:integer; end; CONST KOMENNOT=['P','p','L','l','M','m','H','h','T','t','X','x']; MAX=200; VAR lopetus,ok,muutettu:boolean; komento:char; tilaukset:array[1..MAX] of tilaus; tilauslkm:integer; {muut määritykset kuten tehtävissä 2 ja 3} FUNCTION AsiakkaanIndeksi(nimi:tekstity):integer; begin {palauttaa asiakkaan indeksin taulukossa, 0 jos ei löydy} end; PROCEDURE Turvaan; begin { tallettaa tiedot levylle} end; PROCEDURE Turvasta; begin {lukee tiedot levyltä} end; PROCEDURE LisaaAsiakas; begin {lisää asiakkaan tietorakenteeseen} end; PROCEDURE Poista; var i,j:integer; nimi:tekstity; {sparc-piirre} begin write('Poistettavan nimi : '); readln(nimi); i:=AsiakkaanIndeksi(nimi); if i=0 then writeln('Tämän nimistä asiakasta ei löydy') else begin for j:=i to tilauslkm-1 do tilaukset[j]:=tilaukset[j+1]; tilauslkm:=tilauslkm-1; muutettu:=true; writeln('poisto onnistui'); end; end; PROCEDURE Maara; var n,i:integer; begin n:=0; for i:=1 to tilauslkm do n:=n+tilaukset[i].lkm; writeln('tilauksia on ',tilauslkm:1,' kappaletta, yht. ',n:1,' pulloa'); end; BEGIN {pääohjelma} muutettu:=false; lopetus:=false; writeln(' *** Herkkumehu v0.99a ***'); writeln; writeln(' L - lisää asiakas'); writeln(' P - poista asiakas'); writeln(' M - raportti tilausten määrästä'); writeln(' H - hae tiedot levyltä'); writeln(' T - Tallenna tiedot levylle'); writeln(' X - lopeta'); writeln; repeat repeat write('Komento> '); ok:=true; readln(komento); if not (komento in KOMENNOT) then begin ok:=false; write(' virheellinen komento - anna uudelleen'); end; until ok; case komento of 'P','p': Poista; 'L','l': LisaaAsiakas; 'M','m': Maara; 'H','h': begin muutettu:=false; Turvasta; end; {tässä voisi myös tarkistaa, onko tietoja muutettu ettei menetetä muutoksia kysymättä} 'T','t': begin muutettu:=false; Turvaan; end; 'X','x': lopetus:=true; end; until lopetus; if muutettu then begin write(' Tiedot muuttuneet, tallennetaanko (k/e) :'); readln(komento); if not (komento in ['e','E']) then Turvaan; end; writeln('Kiitos käynnistä, tervetuloa uudelleen...'); END. Arvosteluperusteita: ------------------- - Puolikas piste oli pienin käytetty yksikkö - Olen arvostanut puuttuvien toimintojen toteutuksen (tilauksen poisto ja kokonaismäärän kysely) ja pääohjelman pistemäärältään suunnilleen yhtä arvokkaiksi. Tilauksen poistoa olen painottanut hiukan enemmän kuin kokonaismäärän kyselyä - Tehtävän vaatimaksi komentontojen kuvailuksi olen hyväksynyt myös selkeän ohjelmakoodin, josta komennot ja niiden rakenne käy helposti vilkaisemalla selville. Puutteellisesta kuvailusta olen yleensä vähentänyt 0.5 p. - Tilauksen poisto: pieni virhe -0.5 p; isompi virhe joka antaa vaikutelman ettei asiaa ole täysin ymmärretty -1 p; puuttuva toteutus -1.5 p. - Tilausmäärän kysely: puuttuva -1 p, virhe -0.5 p. Tässä tehtävässä on hyväksytty pelkkä tilauslkm-muuttujan tulostaminen, vaikka tehtävän laatija lienee alunperin ajatellutkin pullomäärän laskemista. - Pääohjelma: tässä voi tehdä monenlaisia virheitä. Yleensä olen vähentänyt virheistä 0.5-1 p. Pienistä kirjoitusvirheistä en ole juuri rokottanut. Yleisimpiä virheitä: -------------------- (ei missään yleisyys tms. järjestyksessä) - Taulukon tiivistys poistossa: for i:=indeksi to tilauslkm tilaukset[i]:=tilaukset[i+1] =>taulukon ylivuoto kun tilauslkm=MAX (-0.5 p) - Taulukon tiivistys poistossa: for i:=indeksi to tilauslkm tilaukset[i+1]:=tilaukset[i] indeksit väärin päin, tuhoaa taulukon loppuosan (-1 p) - virheellisen komennon tarkistus unohdettu komentotulkissa (-0.5 p) - ei minkäänlaista toistorakennetta komentotulkissa, eli vain yksi komento suoritetaan ========================================================================= 5. ========================================================================= Tehtävä 5. Tietotyypit: type pointer = ^tietue; value = jotain; tietue = record data: value; link: pointer; end; a) Funktio 'pituus' laskee listan pituuden. Ratkaisu ilman rekursiota: function pituus(lista: pointer): integer; var luku: integer; begin luku := 0; while lista <> nil do begin luku := luku + 1; lista := lista^.link; end; pituus := luku; end; Rekursiivinen ratkaisu: function pituus(lista: pointer): integer; begin if lista = nil then pituus := 0 else pituus := 1 + pituus(lista^.link); end; b) Proseduuri 'poistanama' poistaa listalta ne tietueet, joiden data-kentässä on annettu arvo. Ratkaisut käyttävät apuproseduuria 'poista', joka poistaa listan ensimmäisen alkion (ja olettaa sen olevan olemassa). Apuproseduuri: procedure poista(var lista: pointer); var poistettava: pointer; begin poistettava := lista; lista := lista^.link; dispose(poistettava); end; Ratkaisu ilman rekursiota: procedure poistanama(var lista: pointer; arvo: value); var p,ed: pointer; begin ed := nil; p := lista; while p <> nil do if p^.data = arvo then begin p := p^.link; if ed = nil then poista(lista) else poista(ed^.link); end else begin ed := p; p := p^.link; end; end; Rekursiivinen ratkaisu, joka poistaa tietueet alkaen listan lopusta (toimenpide ensin loppulistalle, sitten tälle tietueelle): procedure poistanama(var lista: pointer; arvo: value); begin if lista <> nil then begin poistanama(lista^.link,arvo); if lista^.data = arvo then poista(lista); end; end; Rekursiivinen ratkaisu, joka poistaa tietueet alkaen listan alusta (toimenpide ensin tälle tietueelle, sitten loppu- listalle): procedure poistanama(var lista: pointer; arvo: value); begin if lista <> nil then if lista^.data = arvo then begin poista(lista); poistanama(lista,arvo); end else poistanama(lista^.link,arvo); end; =========================================================================