LUNDS TEKNISKA HÖGSKOLA Institutionen för datavetenskap Tentamen EDAA45 Programmering, grundkurs 2017-04-20, 14:00-19:00 Instruktioner Skriv din anonymkod + personlig identifierare här: Tillåtet hjälpmedel: Snabbreferens för Scala & Java. Del A innehåller uppgifter med korta svar som du anger direkt i detta häfte, vilket ska lämnas in tillsammans med omslaget och svaren på del B. Del B innehåller uppgifter med svar i form av programkod som du ska skriva på separat papper. Skriv bara på ena sidan av varje inlämnat blad. Skriv din anonymkod + personlig identifierare överst på varje inlämnat separat papper. Det ska tydligt framgå vilken (del)uppgift du löser. Preliminär poängfördelning Maximalt ges 100p, varav del A omfattar 20p och del B omfattar 80p. För godkänt krävs 50p. Om du på del A erhåller färre än 10p, kan din tentamen komma att underkännas utan att del B 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 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(11) Del A. Evaluera uttryck. Totalt 20p. 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 u1... u10, 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 u0 i tabellen. Det förekommer varken kompilerings- eller exekveringsfel på raderna till och med deklarationen av u0 nedan, medan efterföljande rader kan innehålla fel. 1 sealed trait Color 2 case object Red extends Color 3 case object Green extends Color 4 case object Blue extends Color 5 6 class Egg(val weight: Int) { 7 private var isunboiled = true 8 def boil(): Unit = { isunboiled = false } 9 def isboiled =!isunboiled 10 } 11 class EasterEgg(var color: Color, override val weight: Int) extends Egg(weight) 12 13 val (egg1, egg2, egg3) = (new Egg(60), new EasterEgg(Red, 60), new Egg(60)) 14 val eggs = Vector(egg1, egg2, egg3, egg2) 15 val eggmap = Map('a' -> egg1, 'b' -> egg2, 'c' -> egg3) 16 17 val u0 = 40 + 2.0 18 19 val u1 = egg1 == egg3 20 val u2 = egg1.weight == egg2.weight 21 val u3 = eggs.map(_.weight).sum 22 val u4 = (eggmap.keyset + 'c').size 23 val u5 = eggmap.mapvalues(_.color).apply('c') 24 val u6 = egg2.color match { 25 case Red if egg2.weight > 60 => "Glad Påsk" 26 case _ => 42 27 } 28 val u7 = eggmap('d').weight == Blue 29 val u8 = scala.util.try { eggs(42).weight }.getorelse(42) 30 val u9 = { egg2.weight = egg2.weight + 12; egg2.weight - 30 } 31 val u10 = { eggmap('c').boil(); eggs(2).isboiled }
3(11) Vid kompileringsfel sätt kryss. Vid exekveringsfel sätt kryss. Ange statisk typ som kompilatorn härleder om ej kompileringsfel eller exekveringsfel. Ange dynamiskt värde som tilldelas vid exekvering om ej kompileringsfel eller exekveringsfel. u0 Double 42.0 u1 u2 u3 u4 u5 u6 u7 u8 u9 u10
4(11) Del B. Skriva kod. Totalt 80p. Uppgift B1: Registrering, 25p. (words 8p, freqmap 5p, terms 4p, cosinesimilarity 8p) Vid databehandling av naturligt språk (eng. Natural Language Processing, NLP) vill man ofta beräkna hur lika två texter är. Ett vanligt mått på likhet är cosinesimilarity som beräknas med hjälp av frekvenserna för de termer som ingår i respektive text. Du ska implementera de saknade delarna, markerade???, i nedan objekt enligt efterföljande instruktioner. 1 object NLP { 2 val ispunctuation = Set('.', '-', ',', ';', '?', '!') // skiljetecken 3 val isstopword = Set("om", "i", "till", "och", "med", "för", "är", "alla") 4 5 case class Doc(text: String) { 6 /** Strängen text uppdelad i ord. */ 7 val words: Vector[String] =??? 8 9 /** En tabell med frekvensen för alla ord i words. */ 10 val freqmap: Map[String, Int] =??? 11 12 /** Ger frekvensen för word i words, eller 0 om ordet saknas. */ 13 def freq(word: String): Int = freqmap.getorelse(word, 0) 14 15 /** Unika ord i words som ej är stoppord. */ 16 val terms: Set[String] =??? 17 18 /** Den Euklidiska normen av sekvensen av termer. */ 19 val norm: Double = math.sqrt(terms.toseq.map(t => freq(t) * freq(t)).sum) 20 } 21 22 /** Returnerar ett mått på likheten mellan a och b. */ 23 def cosinesimilarity(a: Doc, b: Doc): Double =??? 24 } Likhetsmåttet cosinesimilarity mellan två texter a och b beräknas så här: summera över alla förekommande termer produkten av frekvensen för varje term i a respektive b, och dividera sedan med produkten av den så kallade Euklidiska normen för respektive text. Eller mer precist uttryckt: f A (t i ) f B (t i ) t cosinesimilarity(a, b) = i T A B där f A (t i ) betecknar frekvensen (antalet förekomster) av termen t i i texten a. Mängden T innehåller alla unika termer som förekommer i a och/eller b. Den Euklidiska normen A beräknas som roten ur summan av kvadraten av alla termers frekvenser i a, enligt attributet norm som är givet i koden ovan. Exempel: Låt oss beräkna cosinesimilarity för dessa två texter som beskriver olika filmer: val a = Doc("Hårdkokt drama om kodare i Eslöv. Ingen bugg går säker!") val b = Doc("Drama om hårdkokt kodare i Lackalänga - är alla kodare snälla?") Dela upp texterna i vektorer av ord (attributet words i case-klassen Doc ovan), genom att i tur och ordning göra följande: (1) byt ut alla tecken som finns i ispunctuation mot blanktecken, (2) omvandla alla stora bokstäver till små bokstäver, (3) dela upp texten i en array av strängar genom att splittra vid alla blanka, (4) ta bort ev. tomma strängar, (5) omvandla till en vektor med strängar. Vi får då följande resultat för a.words respektive b.words:
5(11) Vector("hårdkokt","drama","om","kodare","i","eslöv","ingen","bugg","går","säker") Vector("drama","om","hårdkokt","kodare","i","lackalänga","är","alla","kodare","snälla") Beräkningen av likhetsmått mellan texter sker efter borttagning av små, korta ord, t.ex. prepositioner, så kallade stoppord. Ett ord som inte är stoppord kallas för en term och det är dessa som ska används vid beräkningen av cosinesimilarity. Implementera attributet term i case-klassen Doc genom att använda isstopword ovan. Vidare behöver du den mängd, som i formeln kallas T, med alla termer t i som ingår i a och/eller b, i vårt exempel: eslöv, snälla, säker, går, lackalänga, ingen, drama, hårdkokt, bugg, kodare. Beräkna cosinesimilarity genom att summera över alla t i produkten av frekvensen för t i i respektive text, och dividera med produkten av normerna. Vårt exempel ger: f A (t i ) f B (t i ) t i T A B = 1 0 + 0 1 + 1 0 + 1 0 + 0 1 + 1 0 + 1 1 + 1 1 + 1 0 + 1 2 12 8 1 2 4 + 2 2 = 1 2 Testprogrammet nedan visar hur cosinesimilarity används för att rekommendera filmer. 1 object NixFlix { 2 import NLP._ 3 4 val filmdatabase: Map[String, Doc] = Map( 5 "house of nerds" -> 6 Doc("Hårdkokt drama om kodare i Eslöv. Ingen bugg går säker!"), 7 "pink is the new black" -> 8 Doc("Action om hårdkokt hackerfängelse i Åstorp; olämplig för barn."), 9 "designated developer" -> 10 Doc("Drama om hårdkokt kodare i Lackalänga - är alla kodare snälla?"), 11 "snabba cachen" -> 12 Doc("Komedi om hårdkokt algoritm, med coola kodare i Eslöv.") 13 ) 14 15 def main(args: Array[String]): Unit = { 16 val favorite = "House of Nerds".toLowerCase 17 println(s"din favoritfilm är: ${favorite.capitalize}") 18 val others = filmdatabase.keyset - favorite 19 val recommended: Seq[(String, Double)] = others.map { f => 20 (f, cosinesimilarity(filmdatabase(f), filmdatabase(favorite))) 21 }.toseq.sortby(_._2).reverse 22 println("\ndin topplista med liknande filmer på NixFlix:") 23 recommended.foreach { 24 case (f, s) => println(s"${f.capitalize.padto(25,' ')} Likhet: $s") 25 } 26 } 27 } 1 Din favoritfilm är: House of nerds 2 3 Din topplista med liknande filmer på NixFlix: 4 Designated developer Likhet: 0.4999999999999999 5 Snabba cachen Likhet: 0.43301270189221935 6 Pink is the new black Likhet: 0.14433756729740646
6(11) Uppgift B2: Allokeringsalgoritm, 30p. (distanceto 2p, nextschool 4p, add 13p, allocate 11p) Priset i ekonomisk vetenskap till Alfred Nobels minne tilldelades år 2012 Alvin E. Roth och Lloyd S. Shapley för forskning om algoritmer som kan användas på en marknad där individer handlar med olika resurser enligt givna regler, så att allokeringen blir stabil, med vilket menas att ingen individ har något att vinna på ytterligare handel. Ett exempel på en situation där pristagarnas resultat kan användas är allokering av elever till skolor. Antag att följande grundförutsättningar gäller: Varje elev har sorterat alla skolor i ordning efter vilka den helst vill gå på. Varje skola har ett begränsat antal lediga platser större än noll. Det totala antalet lediga platser på skolorna motsvarar det totala antalet elever, så att varje elev kan få en plats på någon skola. Närhetsprincipen ska gälla: en elev som bor närmare har förtur. I nedan exempel finns tre skolor och sex elever. Skolans position och elevernas bostad, visas i rutnätet till vänster. Elevernas skolval visas i tabellen till höger. Varje skola har 2 lediga platser. 6 5 4 3 2 Maj Fäladsgården Anna Patrik Gustav Tunaskolan Sandra Elev Maj Gustav Sandra Patrik Anna Roy Skolpreferenser Tunaskolan, Järnåkraskolan, Fäladsgården Tunaskolan, Fäladsgården, Järnåkraskolan Järnåkraskolan, Tunaskolan, Fäladsgården Järnåkraskolan, Fäladsgården, Tunaskolan Tunaskolan, Järnåkraskolan, Fäladsgården Tunaskolan, Fäladsgården, Järnåkraskolan 1 Järnåkraskolan Roy 0 0 1 2 3 4 5 6 Du ska implementera nedan allokeringsalgoritm, som baseras på nobelpristagarnas forskningsresultat: Indata :Elever med sina skolval Utdata :Skolor med allokerade elever 1 studentbuffer en förändringsbar sekvens med alla elever 2 while finns elever i studentbuffer do 3 student ta ut första eleven ur studentbuffer 4 school skolan som är näst i tur bland de skolor som student önskat 5 Skriv ut allokeringsmeddelande enl. exempel i senare testprogram 6 bumpedopt school.add(student) // add returnerar ev. utknuffad student, se nedan 7 Om bumpedopt innehåller en elev, lägg till den sist i studentbuffer och skriv ut enl. exempel 8 end Varje skola har en egen, privat samling med preliminärt allokerade elever som inte visas i algoritmen ovan. Metoden add lägger till en elev i skolans samling. Om det blir fullt på skolan blir den elev som är längst bort av med sin plats och denna utknuffade (eng. bumped) elev returneras inkapslad i en Option. Om någon elev blev utknuffad så läggs den sist i studentbuffer, se rad 7 ovan.
7(11) Objektet SchoolAllocation nedan innehåller en påbörjad lösning som du ska utgå ifrån. 1 object SchoolAllocation { 2 3 case class Pos(x: Double, y: Double) { 4 /** Ger avståndet mellan denna punkt och punkten other */ 5 def distanceto(other: Pos): Double =??? 6 } 7 8 class Student(val name: String, val prefs: Seq[School], val pos: Pos) { 9 /** Returnerar skolorna i prefs i ordning en och en, vid varje anrop. 10 * Vid anrop fler gånger än antalet skolor i prefs, kastas undantag. */ 11 def nextschool: School =??? 12 } 13 14 class School(val name: String, maxstudents: Int, val pos: Pos) { 15 import scala.collection.mutable.arraybuffer 16 17 private var current: ArrayBuffer[Student] = ArrayBuffer.empty 18 19 /** Ger en vektor med studenter som är allokerade till denna skola. */ 20 def allocation: Vector[Student] = current.tovector 21 22 /** Lägger till student. Om det blir för många elever så knuffas den elev 23 * som bor längst bort ut. Returnerar ev. utknuffad elev. */ 24 def add(student: Student): Option[Student] =??? 25 } 26 27 /** Allokerar students till schools enligt given algoritm och skriver ut 28 * meddelande vid varje preliminärt allokerad/utknuffad elev. */ 29 def allocate(students: Seq[Student], schools: Seq[School]): Unit =??? 30 31 } Implementera de metoder vars implementationer saknas, markerade med???, enligt nedan krav och tips: distanceto ska beräknas enl. Pytagoras sats, t.ex. med hjälp av math.hypot Du kan förutsätta att nextschool inte kommer att anropas fler gånger än det finns skolor i prefs add ska lägga till en elev och eventuellt knuffa ut den elev som bor längst bort från skolan om inte alla får plats efter tillägg. Eventuellt utknuffad elev ska returneras i ett Option-objekt. Tips: Metoden add kan implementeras på flera olika sätt. Beroende på hur du väljer att göra, kan du ha nytta av några av dessa metoder som fungerar på en ArrayBuffer, här kallad xs: xs.append(x) xs.remove(i) xs.insert(i, x) lägger till elementet x sist i xs tar bort elementet på platsen med index i och returnerar det borttagna elementet (efterföljande element flyttas till lägre index) sätter in elementet x på platsen med index i (efterföljande element flyttas till högre index) Testprogrammet på nästa sida testar objektet SchoolAllocation enligt exemplet på föregående sida. Utskriften från körningen visar hur de meddelande som ska skrivas ut av allocate ska se ut.
8(11) 1 object TestSchoolAllocation { 2 import SchoolAllocation._ 3 4 val (f, j, t) = ( 5 new School("Fäladsgården", 2, Pos(1,5)), 6 new School("Järnåkraskolan", 2, Pos(1,1)), 7 new School("Tunaskolan", 2, Pos(4,3)) 8 ) 9 10 val schools: Seq[School] = Seq(f, j, t) 11 12 val students: Seq[Student] = Seq( 13 new Student("Maj", Seq(t,j,f), Pos(1.5, 5.5)), 14 new Student("Gustav", Seq(t,f,j), Pos(4.5, 4.5)), 15 new Student("Sandra", Seq(j,t,f), Pos(3.5, 2.5)), 16 new Student("Patrik", Seq(j,f,t), Pos(0.5, 2.5)), 17 new Student("Anna", Seq(t,j,f), Pos(2.5, 4.5)), 18 new Student("Roy", Seq(t,f,j), Pos(3.5, 0.5)) 19 ) 20 21 def main(args: Array[String]): Unit = { 22 allocate(students, schools) 23 println("\nallokering:") 24 for (school <- schools) { 25 print(school.name + ": ") 26 println(school.allocation.map(_.name).mkstring(", ")) 27 } 28 } 29 }
9(11) Testprogrammet ovan kan ge följande utskrift, beroende på hur add implementeras: 1 Allokera Maj till Tunaskolan 2 Allokera Gustav till Tunaskolan 3 Allokera Sandra till Järnåkraskolan 4 Allokera Patrik till Järnåkraskolan 5 Allokera Anna till Tunaskolan 6 Maj utknuffad från Tunaskolan 7 Allokera Roy till Tunaskolan 8 Roy utknuffad från Tunaskolan 9 Allokera Maj till Järnåkraskolan 10 Maj utknuffad från Järnåkraskolan 11 Allokera Roy till Fäladsgården 12 Allokera Maj till Fäladsgården 13 14 Allokering: 15 Fäladsgården: Roy, Maj 16 Järnåkraskolan: Sandra, Patrik 17 Tunaskolan: Gustav, Anna
10(11) Uppgift B3. Matris, Java, 25p. (put 7p, count 11p, isfull 7p) Spelet Fyra-i-rad (eng. Connect 4) är ett sällskapsspel för två personer som påminner om luffarschack. Varannan gång lägger deltagarna en bricka (eng. marker) i spelet, som är ett lodrät placerat fackverk enligt bilden till höger. Brickorna fylls på uppifrån i separata kolumner och trillar ned i en stapel. Brickorna får ej flyttas efter placering. Den som först får fyra av sina brickor i rad (vågrätt, lodrätt eller diagonalt) vinner. Spelet slutar oavgjort om det inte längre finns plats för fler brickor. https://en.wikipedia.org/w/index.php?curid=52250308 Java-klassen Grid nedan representerar rutnätet i ett Fyra-i-rad-spel. Du ska implementera metoderna med /*??? */ enligt instruktioner på nästa sida. Spelarnas brickor representeras av olika tecken, t.ex. 'X' och 'O', medan en ledig ruta representeras av ett blanktecken. 1 public class Grid { 2 public static final int rows = 6; 3 public static final int cols = 7; 4 5 private char[][] grid = new char[rows][cols]; 6 7 public Grid() { 8 for (int r = 0; r < rows; r++){ 9 for (int c = 0; c < cols; c++){ 10 grid[r][c] = ' '; 11 } 12 } 13 } 14 15 public char get(int row, int col){ 16 if (row >= 0 && row < rows && col >= 0 && col < cols) { 17 return grid[row][col]; 18 } else { 19 return ' '; 20 } 21 } 22 23 public int put(int col, char marker){ /*??? */ } 24 25 private int count(int startrow, int startcol, 26 int deltarow, int deltacol, char ch){ /*??? */} 27 28 public boolean iswinnerat(int row, int col, char marker){ 29 if (grid[row][col]!= marker) { 30 return false; 31 } else { 32 return count(row, col, 1, 0, marker) >= 3 // up-down 33 count(row, col, 0, 1, marker) >= 3 // right-left 34 count(row, col, 1, 1, marker) >= 3 // diagonal 1 35 count(row, col, 1, -1, marker) >= 3; // diagonal 2 36 } 37 } 38 39 public boolean isfull(){ /*??? */ } 40 }
11(11) Krav och tips: Positionen längst ned till vänster ska motsvara index (rad, kolumn) = (0, 0) i matrisen grid. Metoden put ska placera brickan marker på den första lediga raden i kolumn col och returnera index för den rad som brickan hamnade på. Om det ej finns plats för fler brickor i denna kolumn ska -1 returneras. Den privata hjälpmetoden count används av den givna metoden iswinner för att räkna hur många brickor som finns i rad i en viss ledd (upp-ner, vänster-höger, diagonalt på två olika håll) från en viss utgångsposition som ges av parametrarna startrow och startcol. Den ledd som count räknar i ges av parametrarna deltarow respektive deltacol. Brickan på utgångspunkten ska inte räknas. Tips: Använd i sekvens två loopar som vardera räknar brickor genom att först addera och sedan subtrahera deltavärdena från utgångspunkten enligt nedan exempel: 5 4 3 2 1 0 O O X X X O X O X X O O O 0 1 2 3 4 5 6 Antag att spelet ser ut som till vänster och att count anropas med ch = 'X' samt startrow = 1 och startcol = 2 motsvarande positionen som är inramad i bilden. Om deltarow = 1 och deltacol = 0 ska count räkna antalet brickor i rad först uppåt och sedan nedåt varpå resultatet ska bli 1 (brickan på startpositionen ska ej räknas). Om deltarow = 1 och deltacol = 1 ska count räkna antalet marker i rad i ena diagonalen både uppåt höger och nedåt vänster och resultatet ska bli 3. Metoden isfull ska ge true om brädet är fullt annars false. Tips: Det räcker att kolla om översta raden är full. Nedan körning i Scala REPL visar hur Java-klassen Grid ska kunna användas: 1 scala> val g = new Grid 2 g: Grid = Grid@4bd31064 3 4 scala> def show = { 5 for (r <- 0 until Grid.rows) yield { 6 for (c <- 0 until Grid.cols) yield g.get(r, c) 7 }.mkstring 8 }.reverse.mkstring("\n") 9 show: String 10 11 scala> (g.put(0,'x'),g.put(0,'o'),g.put(0,'x'),g.put(0,'o'),g.put(0,'x'),g.put(0 12,'O')) 13 res0: (Int, Int, Int, Int, Int, Int) = (0,1,2,3,4,5) 14 15 scala> (g.put(0,'x'),g.put(1,'x'),g.put(1,'o'),g.put(2,'x'),g.put(2,'o'),g.put(3 16,'X')) 17 res1: (Int, Int, Int, Int, Int, Int) = (-1,0,1,0,1,0) 18 19 scala> show 20 res2: String = 21 "O 22 X 23 O 24 X 25 OOO 26 XXXX " 27 28 scala> (g.iswinnerat(0,1,'x'), g.iswinnerat(1,0,'o'), g.isfull) 29 res3: (Boolean, Boolean, Boolean) = (true,false,false)