Tentamen Programmeringsteknik II för STS 2007-03-09 Skrivtid: 1500-2000 Inga hjälpmedel. Tänk på följande OBS: För betygen 4, 5 och VG kommer något högre gränser än normalt användas. 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 namn 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. Lycka till! 1
Uppgifter 1. Följande nycklar är givna: 12, 8, 5, 7, 17, 31, 14, 9, 13, 21 a) Använd nycklarna (i given ordning) för att bygga upp ett binärt sökträd. Hur många jämförelser krävs i genomsnitt för att återfinna en lagrad nyckel i just detta träd? b) Använd nycklarna (i given ordning) för att konstruera en hashtabell med länkad kollisionshantering. Tabellstorleken skall vara 7. Hur många jämförelser krävs krävs i genomsnitt för att upptäcka att en given nyckel inte finns lagrad i just denna tabell? (Du får göra rimliga antaganden om hashfunktionen) c) Hashtabeller och binära sökträd är två alternativa implementationer av avbildningar. Vad är fördelarna med hashtabeller och vad är fördelarna med binära sökträd? 2. Nedan finns en klass som implementerar länkade listor med strängar. Listorna skall vara sorterade med den minsta först. class LinkedList { protected static class ListNode { String key; ListNode next; ListNode(String k, ListNode n) { key = k; next = n; protected ListNode head; public String tostring() { /* Returnerar en sträng i enlighet med körexemplet public boolean containskey(string k) { return containskey(k, head); protected static boolean containskey(string k, ListNode h) { /* Returnerar true om listan som börjar i h innehåller k, annars false public boolean equals(linkedlist l) { return equals(head, l.head); public boolean equals(listnode p, ListNode q) { /* Returnerar true om listorna som börjar i p respektive q innehåller exakt samma nycklar, annars false 2
public void add(string k) { public void remove(string k) { /* Tar bort nod med nyckel k ur listan. Om k ej finns ändras ingenting public LinkedList copy() { /* Skapar en kopia av denna LinkedList LinkedList l = new LinkedList(); l.head = copy(head); return l; protected static ListNode copy(listnode h) { /* Skapar en kopia av listan som börjar med h och returnerar första noden i den listan public static void main(string[] args) { LinkedList l = new LinkedList(); l.add("k"); l.add("x"); l.add("a"); l.add("b"); l.add("z"); System.out.println(l); System.out.println( "x: " + l.containskey("x")); System.out.println( "r: " + l.containskey("r")); LinkedList p = l.copy(); if (l.equals(p)) System.out.println("Listorna är lika. OK!"); else System.out.println("Listorna är ej lika. Skumt!"); p.remove("x"); if (l.equals(p)) System.out.println("Listorna är lika. Skumt!"); else System.out.println("Listorna är ej lika. OK!"); /* Output: (a, b, k, x, z) x: true r: false Listorna är lika. OK! Listorna är ej lika. OK! a) Implementera hjälpmetoden containskey(string k, ListNode h) som skall returnera true om nyckeln k finns i den lista som börjar med h annars false. 3
b) Implementera hjälpmetoden copy(listnode h) som skall returnera en kopia av den lista som börjar i h. c) Implementera tostring()-metoden som skall returnera en sträng i enlighet med körexemplet. Observera att kommatecken skall sättas mellan elementen men inte före första eller efter sista. d) Implementera hjälpmetoden equals(listnode p, ListNode q) som skall returnera true om de listor som börjar i p och q innehåller exakt samma nycklar annars false. e) Implementera metoden remove(string k) som skall ta bort noden med nyckel k ur listan. Om sådan nod inte finns så skall listan vara oförändrad. 3. Designen av standardklassen StreamTokenizer kan tyckas lite gammalmodig. Ett mer objektorienterat sätt vore att låta den skapa objekt av de enheter ( tokens ) som den hittar. Eftersom man behöver enheter av olika typ ( ord, tal, tecken,... ) så behöver man en klasshierarki med en basklass och olika subklasser för de olika typer av enheter som man vill ha. Denna uppgift går på att dels definiera en sådan klasshierarki och dels egen tokenizer som delar upp inputströmmen i objekt ur denna hierarki. Så här ser en del av klassen Tokenizer ut: public class Tokenizer { StreamTokenizer st; Token current; // Aktuell token public Tokenizer(InputStream in) { InputStreamReader isr = new InputStreamReader(in); st = new StreamTokenizer( new BufferedReader( new InputStreamReader(in))); public Token nexttoken() throws IOException { // Reads and returns the next token public Token getcurrent() { return current; /** A small test program. Reads tokens up to ; and tests some methods public static void main(string [] args) throws IOException { Tokenizer tnz = new Tokenizer(System.in); int nw = 0; // The number of words double sum = 0.0; // The sum of the numbers Token t; // Will hold the latest read token 4
System.out.print("Input: "); while ( true ) { t = tnz.nexttoken(); if (t.isachar() && t.getchar()== ; ) break; // Leave loop if ; found else if (t.isaword()) nw++; // Count the words else if (t.isanumber()) sum = sum + t.getvalue(); // Sum the numbers System.out.println("Token : " + t); System.out.println("Number of read words: " + nw); System.out.println("Sum of read numbers: " + sum); t.getvalue(); // Cause an exception Här följer en testkörning av av ovanstånde klass: vega$ java Tokenizer Input: Hej hopp! 12 4 ++ 8 x ; Token : Hej Token : hopp Token :! Token : 12.0 Token : 4.0 Token : + Token : + Token : 8.0 Token : x Number of read words: 3 Sum of read numbers: 24.0 Exception in thread "main" TokenizerException: Does not have a value: ; at Token.getValue(Token.java:19) at Tokenizer.main(Tokenizer.java:62) vega$ Som synes så utnyttjar klassen standardklassen StreamTokenizer a) Definiera en klasshierarki med en basklass Token och subklasserna AWord för att representera ett ord (dvs ett String-objekt), ANumber för att representera reella tal (dvs en double) samt AChar för att representera enskilda tecken som inte bildar ord eller tal. (Man kan naturligtvis tänka sig fler subklasser som t ex AnEOL men det behöver du inte göra här.) Klasserna (åtminstone de flesta) behöver var sin konstruktor. Alla klasserna utom basklassen Token skall ha en lämplig tostring-metod (se körexemplet nedan). Det skall finnas metoder för att avgöra vilken typ av objekt det är (isaword, isanumber, isachar). Det behövs också någon form av get-metoder för att komma åt ordet, talet eller tecknet (se main-metoden för exempel på användning). Vid felaktig användning av en get-metod skall ett undantag kastas (se sista raden i main-metoden samt utskrifterna från körexemplet). (5p) 5
b) Skriv färdig metoden nexttoken() som hämtar nästa element från strömmen st. Elementet skall dels lagras i attributet current och dels returneras som värde. Metoden skall bygga på de vanliga metoderna och attributen i klassen StreamTokenizer (dvs nexttoken(), ttype, TT_WORD, TT_NUMBER,... ) (5p) 4. 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 utan 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 hantering av division med noll som ett undantag av typen EvalError (finns definierad i paketet). Programmet skall alltså upptäcka försök att dividera med noll, diagnostisera det och fortsätta med nästa uttryck i enlighet med körexemplet nedan. (3p) b) Skriv färdigt metoden eval i klassen Assignment c) Skriv tostring-metoden i klassen BinaryOp så att den returnerar ett Stringobjekt med korrekt utsatta parenteser i enlighet med testkörningen. (3p) d) Implementera operationen trunkering med hjälp av den unära operatorn ~. Denna operator skall ha samma prioritet som unärt minus och den skall returnera det evaluerade argumentet utan eventuell decimaldel (men resultatet är fortfarande av typ double). Se testkörningen! (6p) Så här skall en körning av programmet se ut: Welcome! End with "q"? 1/0; 1.0/0.0 : *** Division med noll? 2*(3=x); 2.0*(3.0=x) : 6.0? x*(x+x); x*(x+x) : 18.0? ~7/6; ~7.0/6.0 : 1.1666666666666667? ~(7/6); ~(7.0/6.0) : 1.0? q Bye! 6