Lösningar till Tentamen i Programmeringsteknik II och MN2 2006-03-10 1. En nyckel har en unik plats i såväl binära sökträd som i hashtabeller. Dubletter skall alltså inte lagras. Det blir ogörligt att söka och veta när man hittat rätt nyckel annars. a) 17 12 29 11 14 21 31 3 Det krävs 1 jämför för att återfinna 17, 2 för 12 och 29 etc. Om alla nycklar är lika sannolika att bli eftersökta blir det genomsnittliga antalet jämförr (1 + 2 + 2 + 3 + 3 + 3 + 3 + 4) 8 = 21 8 Vanliga fel: i. Dubletter inlagda dvs 17 och 14 finns med två gånger i trädet ii. Division med 10 i stället för med 8. Standardantagandet vid analys är att varje lagrad nyckel är lika sannolik att bli eftersökt. iii. Enstaka nycklar hamnat fel. b) En hashtabell med 7 länkade listor. Nycklar 17 12 14 29 17 31 11 3 14 21 Hashfunktion %7 3 5 0 1 3 4 3 0 0 1 ¹ 14 ¹ 21 ¹ 29 2 3 4 5 ¹ 17 ¹ 31 ¹ 3 ¹ 11 ¹ 12 6 Vi antar att hashfunktionen sprider efterfrågade nycklar jämnt över tabellen dvs att det är lika stor sannolikhet att leta i varje lista. Vi har inte hållit listorna sorterade (vilket man skulle kunna göra). Det innebär att man måste leta till slutet på en lista innan man kan upptäcka att en viss nyckel inte finns 1
i listan. Om man med jämförr menar nyckeljämförr så krävs således i genomsnitt 2 + 1 + 0 + 3 + 1 + 1 + 0) 7 = 8 7 jämförr (eftersom det är 7 listor). Vanligen räknar man dock antalet försök (det krävs 1 försök att leta i en tom lista, 2 att leta i en lista med ett element etc). Det gonomsnittliga antalet försök som krävs för att upptäcka att en given nyckel inte finns blir då (3 + 2 + 1 + 4 + 2 + 2 + 1) 7 = 15 7. (Se utdelat material om hashtabeller). Vanliga fel: i. Dubletter inlagda. ii. Division med 8 eller 10 i stället för med 7. Vid misslyckad sökning skall genomsnittet bildas över antalet platser i tabellen, inte över antalet lagrade nycklar. iii. Några har sagt att man måste leta igenom alla listor för att veta att en viss nyckel inte finns. Så är det inte varje nyckel hör till exakt en lista. 2. Du har fått i uppdrag att skriva ett program som ska användas för att framställa kartor. En karta ska kunna innehålla ett antal olika slags objekt såsom vägar, stigar, järnvägar, kraftledningar, sjöar, skogar, stenar, hus osv. Vid ett projektmöte med din uppdragsgivare kommer ni fram till att man kan dela upp objekten i olika kategorier med gemensamma egenskaper. Vägar, stigar, järnvägar och kraftledningar kan beskrivas som en följd av punkter som binds samman av räta linjer. Sjöar och skogar kan beskrivas som polygoner. Stenar och hus (åtminstone i de kartskalor som är aktuella) kan beskrivas som punkter. Något som är unikt för varje typ av objekt är dock färgen. Vägar ska t.ex. ritas med röd färg, järnvägar med svart, sjöar ska vara blåa och skogar gröna. a) Analysera problemet och räkna upp ett antal förslag på möjliga klasser och metoder. (1 p) Här gäller det att analysera uppgiften och hitta ord som möjligen kan bli klasser och metoder. Det viktiga är inte att omedelbart hitta rätt förslag, utan att skapa något att utgå från i det fortsatta arbetet. En tumregel är att klasser ofta känns igen som substantiv och metoder som verb, men om man är osäker är det bättre att sätta upp ordet på båda ställena. När man sedan går vidare kommer man att upptäcka att inte alla förslag kvalificerar sig som klasser eller metoder i den slutgiltiga designen, men de kan ändå ha bidra till tankearbetet. Ett förslag till svar på uppgiften finns i tabell 1. b) Välj ut vilka av förslagen som ska bli klasser och rita ett klassdiagram där olika relationer (framför allt arv) mellan klasserna framgår. (2 p) Ett förslag till klassdiagram finns i figur 1. c) Vilka attribut (instansvariabler) och metoder ska finnas i de olika klasserna? Beskriv kortfattat i klartext (inte programkod) vad de olika metoderna ska göra eller om de ska vara abstrakta. (2 p) 2
Karta 1 innehåller * KartObjekt 3 Figur 1: Förslag på klassdiagram för kartprogrammet. public paint() public paint() protected getcolor() LinjeObjekt PolygonObjekt PunktObjekt private int[] xcoord private int[] xcoord private int xcoord private int[] ycoord private int[] ycoord private int ycoord public paint() public paint() public paint() Väg Järnväg Sjö Sten protected getcolor() protected getcolor() protected getcolor() protected getcolor() Stig Kraftledning Skog Hus protected getcolor() protected getcolor() protected getcolor() protected getcolor()
Klasser Program Kartor Objekt Vägar Stigar Järnvägar Kraftledningar Sjöar Skogar Stenar Hus Kategorier Egenskaper Linjer Polygoner Punkter Färger Metoder Framställa Innehålla Dela upp Beskriva Binda samman Ge färg Rita Tabell 1: Förslag på möjliga klasser och metoder till kartprogrammet. Förslag på attribut och metoder finns i figur 1. Om en metod eller klass är abstrakt, anges det med kursiv stil. Metoden paint() ritar upp en Karta eller ett enskilt KartObjekt och metoden getcolor() returnerar den färg ett visst KartObjekt ska ritas med. 3. Följande klass innehåller två fel. 1 public class Trubbel{ 2 3 private int anumber; 4 5 public Trubel(int n){ 6 anumber=n; 7 8 9 public int getnumber(){ 10 return n; 11 12 a) Vid första kompileringsförsöket fås följande felmeddelande Trubbel.java:5: invalid method declaration; return type required public Trubel(int n){ ^ 1 error Förklara vad det beror på och hur man ska lösa problemet. (1 p) 4
Det beror på ett stavfel. Konstruktorn måste heta exakt likadant som klassen, dvs Trubbel och inget annat. Rätta alltså till stavfelet, så fungerar det bättre! b) Efter att ha rättat till det första felet görs ett nytt kompileringsförsök, vilket resulterar i ett annat felmeddelande. Trubbel.java:10: cannot find symbol symbol : variable n location: class Trubbel return n; ^ 1 error Förklara vad det beror på och hur man ska lösa problemet. (1 p) Parametern n till konstruktorn har förväxlats med instansvariabeln anumber. Det är endast i konstruktorn man kommer åt n, men anumber är tillgänglig överallt. Därför bör man returnera anumber i stället! 4. Nedanstående klass används för att implementera en avbildning från strängar till heltal (dvs en map ).... a) Implementera tostring()-metoden som skall returnera en sträng i enlighet med körexemplet. (2p) i. Iterativ lösning: public String tostring() { String res ="["; for ( ListNode l=head; l!=null; l=l.next ) res = res + l.tostring(); // ListNode s tostring return res + "]"; ii. Rekursiv lösning: public String tostring() { return "[" + tostring(head) + "]"; protected static String tostring(listnode l) { if (l==null) return ""; return l.tostring() + tostring(l.next); b) Implementera get-metoden som skall returnera värdet som hör ihop med en given nyckel eller kasta ett undantag om nyckeln inte finns. Se kommentarer i koden. (2p) 5
i. Iterativ lösning: public int get(string k) throws NoSuchKeyException { ListNode l = head; while (l!=null && k.compareto(l.key)>0) l = l.next; if (l==null k.compareto(l.key)!=0) throw new NoSuchKeyException(k); return l.value; ii. Rekursiv lösning: public int get(string k) throws NoSuchKeyException { return get(k, head); protected static int get(string k, ListNode l) throws NoSuchKeyException { if ( l==null k.compareto(l.key)<0 ) throw new NoSuchKeyException(k); if (k.compareto(l.key)==0) return l.value; return getrecursive(k, l.next); c) Implementera hjälpmetoden protected static ListNode add enligt kommentarerna i koden. (3p) protected static ListNode add(string k, int v, ListNode h) { if ( h==null k.compareto(h.key)<0 ) return new ListNode(k, v, h); if ( k.compareto(h.key)==0 ) { h.value = v; return h; { h.next = add(k, v, h.next); return h; 5. Följande klass används för att representera en mängd med heltal. Mängden är organiserad som ett binärt sökträd. se tentan a) Implementera height-metoden som returnerar höjden på trädet dvs det största antalet noder på en väg från roten till ett löv. (2p) 6
public int height() { return height(root); protected static int height(node r) { if ( r==null ) return 0; return 1 + max( height(r.left), height(r.right)); protected static int max(int x, int y) { if ( x>y ) return x; return y; b) Implementera hjälpmetoden isomorph som skall returnera true om de träd som den får som parametrar är isomorfa, annars false. Två träd är isomorfa om de har exakt samma förgreningsstruktur ( ser lika ut om man ritar dem ) oavsett datainnehåll i noderna. (3p) protected static boolean isomorph(node u, Node v) { if (u==null && v==null) return true; if( u==null v==null ) return false; return isomorph(u.left, v.left) && isomorph(u.right, v.right); c) Implementera equals-metoden som returnerar true om två träd innehåller exakt samma nycklar, annars false. (3p) i. Enklaste lösning: Utnyttja tostring public boolean equals(treeset t) { return tostring().equals(t.tostring()); ii. Man kan naturligtvis också kolla dels om alla nycklar i det första trädet finns i det andra och dels om alla i det andra finns i det första. boolean equals(treeset t) { return subset(root, t.root) && subset(t.root, root); static boolean subset(node t, Node u) { /* true om alla noder i t finns i u annars false */ if ( t==null ) return true; // Tomma mängden är en delmängd av varje mängd 7
if (contains(t.data,u)) return subset(t.left, u) && subset(t.right, u); return false; static boolean contains(int k, Node u) { if (u==null) return false; if ( k==u.data ) return true; if (k<u.data) return contains(k, u.left); return contains(k, u.right); iii. Ytterligare sätt är att traversera båda träden i inorder och lägga in nodernas data i någon linjär struktur som lista eller array och sedan jämföra dessa (detta är ju faktiskt vad som görs när man använder tostring-metoden) 6. I programbilagan Parser.java m.fl. finns ett program som läser aritmetiska uttryck, representerar dem internt i ett träd, evaluerar trädet och skriver värdet (mycket likt del 2 av inlämningsuppgiften men lite färre funktioner bl.a.) Vissa delar av koden är utelämnade. (Observera att metoden som hette prim i inlämningsuppgiften heter factor här.) a) Implementera eval-funktionen i klassen Variable. (3p) public double eval(map<string, Double> h) { if ( h.containskey(ident) ) return h.get(ident); return 0; b) Varje försök att anropa funktionerna exp och log ger syntaxfel med diagnosen Unknown function. Vad beror det på? Rätta felet (felen)! (2p) Felet är att man i ident testar namnen (textsträngarna) med == som jämför referenserna i stället för strängarnas innehåll. Använd alltså equals (eller compareto): if (id.equals("exp")) return new Exp(prim()); if (id.equals("log")) return new Log(prim()); throw new SyntaxError("Unknown function: " + id); 8
c) Implementera restoperatorn % definierad på exakt samma sätt som i Java. Restoperatorn skall ha samma prioritet som multiplikation och division och ge felutskrift om argumentet är negativt. Du skall således skriva en klass Modulo som subklass till Binop med de metoder som behövs för evaluering, utskrift etc. Du skall också modifiera Parser-klassen så att operatorn känns igen och att uttrycksträdet kan byggas med Moduloojekt. (3p) Komplettering av term-metoden: if (st.ttype== / ) prod = new Division(prod,factor()); prod = new Modulo(prod,factor()); Klass för modulo-operationen: public class Modulo extends Binop { public Modulo(Sexpr x, Sexpr y) { super(x,y); public int priority() { return 20; public String getname() { return "%"; public double eval(map<string, Double> h) { return left.eval(h)%right.eval(h); (Texten om felutskrift är rena gallimatiasen som skall ignoreras. Eventuell feltestning skall göras i eval-metoden och på om andra argumentet evalueras till 0) d) Som bl a framgår av körningsexemplet ovan hanteras inte evalueringsfel av typ division med noll och felaktigt argument till log Skapa en undantagsklass för evalueringsfel och se till att undantag av denna typ kastas när sådana fel uppstår. Komplettera också main-metoden så att sådana fel hanteras på ett vettigt sätt. (2p) Felklass public class EvalError extends RuntimeException { public EvalError(String msg) { super(msg); 9
Ändring i eval för Division public double eval(map<string, Double> h) { double d = right.eval(h); if (d==0.0) throw new EvalError("Division by zero"); return left.eval(h)/d; 7. a) Beskriv hur man kan göra för att implementera det binära sökträdet från uppgift 5 i C. Skriv de deklarationer som behövs. (2p) typedef struct Node { int data struct Node *left, *right; Node, *link; link cons(int d, link l, link r) { // En konstruktor link l = (link) malloc(sizeof(node)); l->data = d; l->left = l; l->right = r; return l; b) Skiv en C-funktion för att beräkna höjden på trädet. (2p) int max(int a, int b) { return a>b?a:b; int height(link r) { if ( r==null ) return 0; return 1 + max(height(l->left), height(l->right)); 10