LUNDS TEKNISKA HÖGSKOLA Institutionen för datavetenskap Tentamen EDAA5 Programmering, grundkurs 2018-0-07, 08:00-13:00 Hjälpmedel: Snabbreferens för Scala & Java. Instruktioner Skriv din anonymkod + personlig identifierare här: Om du skriver icke-anonymt ange personnummer + namn i stället. Tillåtet hjälpmedel: Snabbreferens för Scala & Java. Uppgift 1 i del A består av deluppgifter som du ska besvara genom att fylla i en tabell i detta häfte. Del B innehåller uppgift 2, 3,... med svar i form av programkod som du ska skriva på separata papper. Skriv bara på ena sidan av varje inlämnat blad. Skriv din anonymkod + personlig identifierare (eller personnummer + namn om du skriver icke-anonymt) överst på varje inlämnat blad. Det ska tydligt framgå vilken (del)uppgift du löser. Detta häftet ska lämnas in tillsammans med ifyllt omslag och svaren på uppgifterna i del B. Preliminär poängfördelning Maximalt ges 100p, varav uppgift 1 omfattar 20p och resterande uppgifter omfattar 80p. För godkänt krävs 50p. Om du på uppgift 1 erhåller färre än 10p, kan din tentamen komma att underkännas utan att resterande uppgifter bedöms. Poäng och delpoäng som anges ovan och i uppgifterna är preliminära och kan komma att justeras när den slutliga bedömningen fastställs. Upplysningar För att vara tentamensberättigad ska du vara godkänd på alla obligatoriska laborationer och projekt, samt ha genomfört diagnostisk kontrollskrivning. Om du tenterar utan att vara tentamensberättigad annulleras din skrivning. För att undvika att någon skrivning annulleras av misstag kommer alla som, enligt institutionens noteringar, tenterat utan att vara tentamensberättigade att kontaktas via epost. Felaktigheter i institutionens noteringar kan därefter påtalas fram till nästa tentamenstillfälle då resterande skrivningar annulleras. Lösningar läggs ut på kursens hemsida senast dagen efter tentamen. Resultatet läggs in i Ladok när rättningen är klar. 1
2(9) Del A: Uttryck och värden. Uppgift 1, totalt 20 poäng. Följande kod finns kompilerad utan kompileringsfel och tillgänglig på din classpath: 1 trait Player { def name: String } 2 case class Student(name: String, program: String) extends Player 3 case class Teacher(name: String) extends Player 5 case class Scores(data: Map[Player, Vector[Int]] = Map()){ 6 def sum(p: Player): Int = data(p).sum 7 def add(kv: (Player, Int)): Scores = 8 copy(data = data.updated(kv._1, data.getorelse(kv._1, Vector()) :+ kv._2)) 9 } 10 11 sealed trait Hand { def iswin(other: Hand): Boolean } 12 case object Sten extends Hand { def iswin(other: Hand) = other == Sax } 13 case object Sax extends Hand { def iswin(other: Hand) = other == Påse } 1 case object Påse extends Hand { def iswin(other: Hand) = other == Sten } 15 16 class Game { 17 var scores = Scores() 18 19 def play(a: (Player, Hand), b: (Player, Hand)): Game = { 20 def points(test: Boolean): Int = if (test) 1 else 0 21 scores = scores.add(a._1 -> points(a._2.iswin(b._2))) 22 scores = scores.add(b._1 -> points(b._2.iswin(a._2))) 23 this 2 } 25 } Du ska fylla i tabellen på nästa sida enligt följande. Antag att du skriver in nedan kod i Scala REPL rad för rad. För varje variabel med namn x1... x10, ange statisk typ (alltså den typ kompilatorn härleder), samt det värde variabeln får efter initialisering, eller sätt i stället kryss i rätt kolumn om det blir ett kompileringsfel respektive exekveringsfel. Vid frånvaro av fel, svara på samma sätt som Scala REPL skriver ut typ respektive värde, enligt exempel x0 i tabellen. 1 val g = new Game 2 val ps = Vector[Player](Student("Kim", "D"), Student("Kim", "D"), Teacher("Kim")) 3 val hemlis: Map[Player, Hand] = Map(ps(0) -> Sten, ps(1) -> Sax, ps(2) -> Påse) 5 val x0 = 1.0 + 1 6 val x1 = ps.size 7 val x2 = hemlis.size 8 val x3 = { val p: Player = new Student("Oddput", "E"); p.program } 9 val x = Påse iswin Påse 10 val x5 = Sten iswin hemlis(ps(3)) 11 val x6 = Sax iswin hemlis(3) 12 val x7 = Scores().add(ps(0) -> x0.toint).sum(ps(0)) 13 val x8 = g.play(ps(2) -> hemlis(ps(2)), ps(1) -> hemlis(ps(1))).scores.sum(ps(0)) 1 val x9 = ps.indices.foreach { i => 15 g.play(ps(i) -> hemlis(ps(i)), ps(2) -> hemlis(ps(2))) 16 } 17 val x10 = g.scores.sum(ps(0))
3(9) Vid kompileringsfel sätt kryss. Vid exekveringsfel sätt kryss. Ange statisk typ som kompilatorn härleder om ej kompilerings- eller exekveringsfel. Ange det värde som tilldelas vid exekvering, med samma format som vid utskrift av värdets tostring, om ej kompilerings- eller exekveringsfel. x0 Double 2.0 x1 x2 x3 x x5 x6 x7 x8 x9 x10
(9) Del B. Implementation. Uppgift 2, 3,, 5, totalt 80 poäng. System för flervalfrågetest: övergripande beskrivning av indata och utdata Du ska implementera ett system för hantering av flervalsfrågetest. Flervalsfrågorna läses från fil och används sedan av en användare som vill träna på frågorna. Vid uppstart läser systemet en fil kallad questions.txt med ledtext och svarsalternativ för varje fråga enligt nedan exempel: 1 # Vilket alternativ beskriver bäst resultatet av detta uttryck? 2 # Vector(println(1+1)) 3 + Vector(()): Vector[Unit] - 2: Int 5 - (): Unit 6 - körtidsfel 7 8 # Vilket/vilka av alternativen nedan ger värdet true? 9 + true && true 10 + true false 11 - true && (1 / 0 == 1 / 0) 12 13 # Vilket/vilka uttryck ger värdet true givet funktionen f nedan? 1 # def f(x: Int): Int = if (x > 0) x * f(x - 1) else 1 15 + f(0) == 1 16 + f(1) == 1 17 - f(2) == 0 18 + f(5) == 120 19 + f(100) <= Int.MaxValue Du kan anta att indata uppfyller dessa krav: De olika frågorna är separerade med dubbla radbrytningar och det finns minst en fråga i indatafilen. Varje fråga börjar med en eller flera rader med begynnelsetecknet # som innehåller frågans ledtext. Efter raderna med ledtext kommer exakt en rad per svarsalternativ. Svarsalternativrader som börjar med tecknet + är rätt medan de som börjar med tecknet - är fel. Det finns minst två och max tio svarsalternativ, där minst ett är rätt och minst ett är fel. Alla svarsalternativ är unika. På nästa sida visas en exempelkörning med användarens indata efter prompten "> " fram till radslut. Följande krav gäller för interaktionen med användaren (för exakta utskriftsformat, se exempelkörningen): Varje fråga har ett unikt nummer (index) som börjar på 0. Systemet ska ställa en fråga i taget där ordningen på svarsalternativen är slumpmässig. Efter varje ställd fråga ska systemet invänta användarens svar. Varje siffra 0...9 som finns i användarens svar ska, oavsett ordning, tolkas som ett valt alternativ. Efter att användaren svarat ska antalet poäng för svaret beräknas och skrivas ut. Poängberäkning per fråga ska ske enligt följande definitioner och regler: Låt r respektive f beteckna det totala antalet korrekta respektive felaktiga alternativ som erbjuds. Således motsvarar r + f det totala antalet möjliga svarsalternativ som visas. Varje korrekt alternativ som ingår i användarens svar bidrar med 1/r delpoäng till poängsumman. Varje felaktigt alternativ som ingår i svaret bidrar med 1/f delpoäng till poängsumman. På så sätt blir det 0 poäng om alla eller inga svarsalternativ ingår i användarens svar. Nästa fråga ska väljas enligt dessa regler: Om det finns ännu ej ställda frågor ska nästa fråga väljas bland dessa, enligt ordningen i indata-filen questions.txt. Om alla frågor ställts minst en gång ska systemet som nästa fråga välja den som för närvarande har lägst medelpoäng. Systemet ska registrera det totala antalet gånger som varje fråga ställts, samt för varje fråga löpande beräkna medelvärdet av de poäng som hittills erhållits för respektive svar. Om användaren svarar :q ska inga fler frågor ställas, statistik skrivas ut och programmet avslutas.
5(9) I exemplet nedan väljs frågorna och beräknas poängen för varje fråga enligt reglerna på föregående sida. Eftersom fråga med index 1 får lägst poängmedelvärde, efter att alla frågor besvarats en gång, är det den som väljs som nästa fråga på raderna 53 nedan. 1 > scala QuizControl 2 3 *** Besvara fråga 0: 5 Vilket alternativ beskriver bäst resultatet av detta uttryck? 6 Vector(println(1+1)) 7 8 0: körtidsfel 9 1: 2: Int 10 2: Vector(()): Vector[Unit] 11 3: (): Unit 12 Ange heltal för alternativ eller :q för quit. 13 > 2 1 Helt rätt! 15 Du fick 1.0 poäng! 16 17 *** Besvara fråga 1: 18 19 Vilket/vilka av alternativen nedan ger värdet true? 20 21 0: true false 22 1: true && true 23 2: true && (1 / 0 == 1 / 0) 2 Ange heltal för alternativ eller :q för quit. 25 > 1 2 26 Rätt alternativ: 0 1 27 Du fick -0.5 poäng! 28 29 *** Besvara fråga 2: 30 31 Vilket/vilka uttryck ger värdet true givet funktionen f nedan? 32 def f(x: Int): Int = if (x > 0) x * f(x - 1) else 1 33 3 0: f(1) == 1 35 1: f(5) == 120 36 2: f(100) <= Int.MaxValue 37 3: f(0) == 1 38 : f(2) == 0 39 Ange heltal för alternativ eller :q för quit. 0 > 0 1 Rätt alternativ: 0 1 2 3 2 Du fick 0.25 poäng! 3 *** Besvara fråga 1: 5 6 Vilket/vilka av alternativen nedan ger värdet true? 7 8 0: true && true 9 1: true false 50 2: true && (1 / 0 == 1 / 0) 51 Ange heltal för alternativ eller :q för quit. 52 > :q 53 Fråga 0 besvarad 1 ggr, poängmedelvärde: 1.0 5 Fråga 1 besvarad 1 ggr, poängmedelvärde: -0.5 55 Fråga 2 besvarad 1 ggr, poängmedelvärde: 0.25 56 Hejdå!
6(9) Systemets komponenter Följande delvis färdiga komponenter ingår i systemet, där du ska göra klart de delar som saknas: Modulen QuizModel innehåller case-klasserna Question med fråga och rätt/fel svarsalternativmängder samt Randomized med svarsalternativ i slumpad ordning (se vidare uppg. 2). Modulen QuizIO har hjälpmetoder för indata och utdata (se vidare uppg. 3). Java-klassen QuizState registrerar antalet gånger varje fråga besvarats samt beräknar löpande medelpoängen per fråga (se vidare uppg. ). Modulen QuizControl innehåller ett huvudprogram och en läs-skriv-loop som interagerar med användaren (se vidare uppg. 5). Uppgift 2: QuizModel (30p) Modulen QuizModel nedan innehåller följande oföränderliga case-klasser: Question har en frågetext prompt och två mängder right och wrong med korrekta respektive felaktiga svarsalternativ. Randomized har en sekvens alts med alla svarsalternativ i en slumpad ordning samt en mängd isright med index för alla svar i alts som är korrekta. 1 object QuizModel { 2 case class Question(prompt: String, right: Set[String], wrong: Set[String]){ 3 def randomize(): Randomized =??? // 15p } 5 6 case class Randomized(alts : Vector[String], isright: Set[Int]){ 7 def showalts: String = 8 alts.zipwithindex.map(p => s"${p._2}: ${p._1}").mkstring("\n") 9 10 def showright: String =??? // 5p 11 12 def points(answeredalternatives: Set[Int]): Double =??? // 10p 13 } 1 } Du ska implementera metoderna randomize, showright och points enligt nedan krav: Metoden randomize ska ge en Randomized-instans där attributet alts innehåller alla svarsalternativ i slumpvis ordning och mängden isright innehåller index för de alternativ i alts som är korrekta. Tips: använd metoden scala.util.random.shuffle som tar en sekvens och returnerar dess element i slumpvis ordning. Metoden showright ska ge en sträng med index för de korrekta svarsalternativen i alts sorterade i växande indexordning och separerade med blanktecken. Denna sträng används för att ge återkoppling till användaren efter besvarad fråga (se utskrift från exempelkörning efter texterna "Rätt alternativ: " på föregående sida).
7(9) Metoden points ska med hjälp av alts och isright beräkna poängvärdet för användarens svar. Poängen beräknas enligt tidigare beskrivna regler. Parametern answeredalternatives innehåller index för de svarsalternativ i alts som ingår i användarens svar. Tips: Du kan ha nytta av en lokal funktion som breäknar delpoäng för ett svarsalternativ. Uppgift 3: QuizIO (20p) Modulen QuizIO används av QuizControl för att skapa en frågesekvens ur en indatafil, samt för att begära indata från användaren. Den delvis färdiga implementation återfinns nedan: 1 object QuizIO { 2 import QuizModel.Question 3 object FromFile { 5 def matrixfromsource(file: String): Seq[Seq[String]] = 6 scala.io.source.fromfile(file).getlines.mkstring("\n") 7.split("\n\n").toSeq.map(_.split('\n').toSeq) 8 9 def loadquestions(file: String): Vector[Question] = { //20p 10 def picklinesstartingwith(init: String, xs: Seq[String]): Seq[String] =??? 11??? 12 } 13 } 1 15 object FromUser { 16 val prompt = "Ange heltal för alternativ eller :q för quit.\n> " 17 def read(): String = scala.io.stdin.readline(prompt) 18 } 19 } Du ska implemnetera metoden loadquestions som med hjälp av matrixfromsource ska skapa en frågesekvens. Parametern file innehåller indatafilens namn. Frågorn i sekvensen som returneras ska ligga i samma ordning som i indatafilen. Tips vid implementationen: Genom att anropa matrixfromsource får du från en fil med namnet file en sekvens med sekvenser av strängar baserad på tidigare beskrivning av indataformat och körningsexempel. Du ska implementera och använda den lokal hjälpfunktionen picklinesstartingwith som ur en sekvens filtrerar ut de rader som börjar med specifika begynnelsetecken init ur en sekvens xs, enligt funktionshuvudet ovan. Funktionen ska också plocka bort dessa begynnelsetecken från varje rad och även ta bort ev. inledande blanktecken ur raderna i den returnerade strängsekvensen.
8(9) Uppgift : QuizState (Java, 20p) Java-klass QuizState nedan håller reda på antalet gånger varje fråga besvarats och löpande medelvärde för svarspoängen per fråga. Denna klass används i uppgift 5, där varje användares svar medför ett anrop av den färdiga metoden addresult där parametern qix anger frågans index och parametern points innehåller poängen som användaren fått för sitt svar. Du ska i denna uppgift implementera metoden nextquestionindex som ska ge frågenumret (index) för nästa fråga som ska ställas enligt reglerna på sidan. 1 public class QuizState { 2 private int[] counts; 3 private double[] means; 5 public QuizState(int nbrofquestions){ 6 counts = new int[nbrofquestions]; 7 means = new double[nbrofquestions]; 8 } 9 public int getsize(){ 10 return counts.length; 11 } 12 13 public int getcount(int qix){ 1 return counts[qix]; 15 } 16 17 public double getmean(int qix){ 18 return means[qix]; 19 } 20 21 public void addresult(int qix, double points){ 22 counts[qix] += 1; 23 means[qix] = means[qix] + (points - means[qix])/counts[qix]; 2 } 25 26 public int nextquestionindex(){ /*??? */ } // 20p 27 } Du ska skapa och använda dessa privata hjälpmetoder: indexoffirstcountzero som söker reda på index för första fråga som ännu ej besvarats, och som returnerar ett heltal utanför indexgränserna om ingen obesvarad fråga finns. indexofminmean som ger index för den fråga som har lägst löpande medelvärde (du kan anta att det finns minst en fråga).
9(9) Uppgift 5: QuizControl (10p). Scala-modulen QuizControl innehåller ett huvudprogram. Du ska implementera metoden loopuntilquit som används av huvudprogrammet enligt nedan. Du ska använda attributen questions och state samt metoden askuser för att åstadkomma den fråga-svar-loop som kravställs på sidan och illustreras i exempelkörningen. Du ska använda nextquestionindex för att välja nästa fråga. 1 object QuizControl { 2 import QuizModel._ 3 import QuizIO._ 5 val questions: Vector[Question] = FromFile.loadQuestions("questions.txt") 6 val state = new QuizState(questions.length) 7 8 def showstate(): Unit = for (i <- questions.indices) { 9 println(s"fråga $i besvarad ${state.getcount(i)} ggr, " + 10 s"poängmedelvärde: ${state.getmean(i)}") 11 } 12 13 def askuser(q: Question): Option[Double] = { 1 val alt = q.randomize() 15 println(s"\n${q.prompt}\n\n${alt.showalts}") 16 val ans = FromUser.read() 17 if (ans == ":q") None 18 else { 19 val ansalts = ans.filter(_.isdigit).map(_ - '0').toSet 20 val pts = alt.points(ansalts) 21 if (pts < 1.0) println(s"rätt alternativ: ${alt.showright}") 22 else println("helt rätt!") 23 println(s"du fick $pts poäng!") 2 Some(pts) 25 } 26 } 27 28 def loopuntilquit(): Unit =??? // 10p 29 30 def main(args: Array[String]): Unit = { 31 loopuntilquit() 32 showstate() 33 println("hejdå!") 3 } 35 } Tips: Din kod behöver bland annat skriva ut rubriken med frågans nummer och undersöka resultatet av askuser. Studera utskrifstformatet i exempelkörningen noga. Du behöver också uppdatera state genom anrop av addresult.