Tentamen Programmeringsteknik II 2013-06-05 Skrivtid: 1400-1700 Hjälpmedel: Java-bok (vilken som helst) Tänk på följande Skriv läsligt! Använd inte rödpenna! Skriv bara på framsidan av varje papper. Börja alltid ny uppgift på nytt papper. Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din kod på alla papper. Skriv inte längst upp i vänstra hörnet - det går inte att läsa där efter sammanhäftning. Fyll i försättssidan fullständigt. Det är principer och idéer som är viktiga. Skriv så att du övertygar rättaren att du har förstått dessa även om detaljer kan vara felaktiga. Programkod skall vara läslig dvs den skall vara vettigt strukturerad och indenterad. Namn på variabler, metoder, funktioner, klasser etc skall vara beskrivande men kan ändå hållas ganska korta. Såvida inget annat anges så får man bygga på lösningar till föregående uppgifter även om dessa inte har lösts. Det är tillåtet att införa hjälpmetoder och hjälpklasser. Uttrycket skriv en metod som skall alltså inte tolkas så att lösningen inte får struktureras med hjälp av flera metoder. Uppgifterna kommer inte i svårighetsordning. Betygsgränser: 15 ger säkert 3, 21 ger säkert 4, 26 ger säkert 5. Lycka till! 1
Uppgifter 1. Nedanstående figur visar noderna i ett binärt sökträd med heltal som nycklar. 12 3 23 1 7 17 31 5 20 42 a) Rita hur trädet ser ut efter inlägg av nycklarna 22, 21 och 15 (i den givna ordningen). (2p) 12 3 23 1 7 17 31 5 15 20 42 22 21 b) Skriv en klass Node som skall representera noderna i trädet samt en klass BST som representerar trädet. Klasserna skall innehålla nödvändiga instansvariabler och lämpliga konstruktorer. (3p) 2
Snyggast är att göra noden som en inre klass till BST-klassen. Då blir den dold för omvärlden men BST-klassen har full tillgång till den. Om man gör nodklassen som en egen självständig klass måste man ha getmetoder för dess attribut. BST-klassen behöver ingen konstruktor eftersom dess enda attribut root blir null automatiskt. public class BST { private Node root; private static class Node { private int key; private Node left; private Node right; private Node(int key, Node left, Node right) { this.key = key; this.left = left; this.right = right; c) Skriv en metod public int largest() i klassen BST som returnerar den största nyckeln i trädet. (2p) public int largest() { if (root == null) { throw new RuntimeException("Empty tree"); else { Node n = root; while (n.right!= null) { n = n.right; return n.key; Alternativt en rekursiv lösning d) Skriv en metod String tostring() i klassen BST som returnerar en sträng med noderna i inorder. För trädet ovan skall alltså strängen {1 3 5 7 12 17 20 23 31 42 returneras. (3p) 3
public String tostring() { return "{" + tostring(root) + ""; private static String tostring(node n) { if (n!=null) return tostring(n.left) + " " + n.key + tostring(n.right); else return ""; 2. Betrakta följande klasshierarki och testmetod: public class Animal { public final String s = "Animal"; public String speak() { return s + " Growl "; public class Cat extends Animal { public final String s = "Cat"; public String speak() { return s + " Miaow "; public class Dog extends Animal { public final String s = "Dog"; public String speak() { return s + " Woof "; public class Test { public static void main(string[] args) { Animal a1 = new Cat(); System.out.println(a1.speak() + a1.s); Animal a2 = new Dog(); System.out.println(a2.speak() + a2.s); Dog a3 = new Labrador(); System.out.println(a3.speak() + a3.s); Animal a4 = new Labrador(); System.out.println(a4.speak() + a4.s); Cat a5 = new Animal(); System.out.println(a5.speak() + a5.s); Dog a6 = new Labrador(); System.out.println(a6.speak() + a6.s); public class Labrador extends Dog { public final String s = "Labrador "; Om någon eller några konstruktioner i main-metoden är illegala så stryk dessa. Ange sedan vad metoden skriver ut. (4p) Stryk raderna: Cat a5 = new Animal(); System.out.println(a5.speak() + a5.s); Utskrift: Cat Miaow Animal Dog Woof Animal Dog Woof Dog Dog Woof Animal Dog Woof Dog 3. Din kompis har följande klass som representerar en lista: 4
public class List { private Node first; private static class Node { private int value; private Node next; private Node(int value, Node next) { this.value = value; this.next = next; public List() { first = null; private List(Node n) { first = n; public String tostring() { String res = ""; for (Node n = first; n!= null; n = n.next) { res = res + n.value; if (n.next!= null) { res = res + ", "; return "{" + res + ""; private static Node insert(int k, Node n) { if (n==null k<n.value) { return new Node(k, n); else if (k>n.value) { n.next = insert(k, n.next); return n; public void insert(int k) { first = insert(k, first); Hen har fått i uppgift av sin programmeringslärare att skriva en metod som kontrollerar om två listor är lika. Hen har skrivit följande metod (och hjälpmetod): public boolean equals(list other) { return equals(first, other.first); private static boolean equals(node n1, Node n2) { boolean result = false; if (null == n1 && null == n2) { result = true; else if(null == n1 null == n2) { result = false; else if(n1.value == n2.value) { result = true; equals(n1.next, n2.next); else { result = false; equals(n1.next, n2.next); return result; Din kompis har testkört metoden med listan {0, 1, 5 och {0, 3, 5 och metoden har returnerat true. a) Varför blir resultatet felaktigt? (2p) Metoden baserar sitt svar på jämförelsen av första elementen i listorna. Om dessa är lika anses listorna lika (result sätts till true). Metoden anropar visserligen rekursivt equals på resten av listorna men svaret från dessa anrop tas ej emot. b) Vad skulle metoden returnera om ni testade med listorna {3, 5 och {0, 3, 5? Med listorna {1, 2, 3 och {4, 5, 6? (2p) Eftersom svaret baseras endast på de första elementen ger båda jämförelserna false som resultat. 5
c) Skriv om metoden så att den ger rätt resultat. (3p) private static boolean equals(node n1, Node n2) { boolean result = false; if (null == n1 && null == n2) { result = true; else if(null == n1 null == n2) { result = false; else if(n1.value == n2.value) { result = equals(n1.next, n2.next); else { result = false; return result; En lite kompaktare lösning: private static boolean equals(node n1, Node n2) { boolean result = false; if (null == n1 && null == n2) { return true; else if(null == n1 null == n2) { return false; else { return n1.value==n2.value && equals(n1.next, n2.next); d) Hjälpmetoden equals(node n1, Node n2) ovan är deklarerade som static. Vad händer om man tar bort ordet static i metodhuvudet? (1p) Tekniskt sett så innebär ordet static att metoden hör till klassen som helhet - inte till enskilda objekt. Metoden rör inga instansvariabler och kan (bör) därför deklareras static men koden fungerar ändå om denna deklaration utelämnas. 4. I bilagan finns kod som delvis implementerar en kalkylator. (Det är väsentligen samma kod som gavs till obligatorisk uppgift 5. ) Av utrymmesskäl är vissa delar som saknar betydelse för uppgiften utelämnade. a) Metoden statement i klassen Calculator har försetts med ett nytt kommando: undef. Efter kommandot skall en följd av variabelnamn komma. Uppgiften för kommandot är att ta bort dessa ur variabellistan (mappen) så att dessa variabler blir odefinierade. Exempel: 6
Marvin$ java Calculator Symbolic calculator version 2013-06-05 Input : 1=x=y=z=u Parsed: 1.0=x=y=z=u Input : y Parsed: y Input : undef y Input : y Parsed: y *** Evaluation error: Undefined variable: y Input : undef x a u *** Evaluation error: Variable a is not defined Input : vars E : 2.718281828459045 PI : 3.141592653589793 ans : 1.0 u : 1.0 z : 1.0 Input : Skriv klart metoden performundef() i klassen Calculator som utför detta. Om en variabel inte finns skall metoden resultera i ett evalueringsfel och hanteringen av kommandot avbrytas. Tips: Klassen TreeMap innehåller metoden remove(object key) som tar bort posten med angiven nyckel (key). (3p) public void performundef() { tokenizer.nexttoken(); while (tokenizer.isword()) { if (variables.containskey(tokenizer.getword())) { variables.remove(tokenizer.getword()); else { throw new EvaluationException("Variable " + tokenizer.getword() + " is not defined"); tokenizer.nexttoken(); b) De trigonometriska funktionerna (sin, cos... ) förutsätter att argumentet är givet i radianer. Ibland kan man ju vilja uttrycka sig i grader. För att kunna ange att man vill använda grader har kommandot set lagts in i metoden statement. Kommandot skall följas antingen av ordet degrees eller ordet radians. Exempel: Input : sin(0.5*pi) Parsed: sin(0.5*pi) Input : sin(90) Parsed: sin(90.0) Result: 0.8939966636005579 Input : set degrees Input : sin(90) Parsed: sin(90.0) Input : sin(30) Parsed: sin(30.0) Result: 0.49999999999999994 Input : set radians Input : sin(90) Parsed: sin(90.0) Result: 0.8939966636005579 Input : set nygrader *** Syntax error: Expected radians or degrees *** The error occurred at token nygrader just after token set Input : sin(0.5*pi) Parsed: sin(0.5*pi) Input : Skriv klart metoden performset() i klassen Calculator som skall se till att gra- 7
der respektive radianer används enligt exemplet ovan. Skriv också de övriga kompletteringarna av koden som krävs! Tips: Grader (g) kan räknas om till radianer (r) med formeln r = πg/180 (5p) Svårigheten i denna uppgift ligger i att överföra informationen från metoden performset i klassen Calculator till det ställe där beräkningarna faktiskt görs dvs metoderna sin och cos i klassen Symbolic. Det bästa sättet är att förse klassen Symbolic med en flagga som anger om grader eller radianer skall användas. Denna flagga sätts med en set-metod som alltså anropas från performset. Eftersom metoderna i klassen Symbolic alla är static måste såväl flaggan som dess set-metod deklareras static I Calculator: public void performset() { tokenizer.nexttoken(); if (tokenizer.gettoken().equals("radians")) { Symbolic.setRadians(true); else if (tokenizer.gettoken().equals("degrees")) { Symbolic.setRadians(false); else { throw new SyntaxException("Expected radians or degrees "); tokenizer.nexttoken(); I Symbolic private static boolean useradians = true; public static void setradians(boolean value) { useradians = value; public static Sexpr sin(sexpr operand) { if (!operand.isconstant()) { return new Sin(operand); else if (useradians) { return new Constant(Math.sin(operand.getValue())); else { return new Constant(Math.sin(operand.getValue()*Math.PI/180)); och på motsvarande sätt i cos 8