Data- och Programstrukturer Provmoment: Ladokkod: Tentamen ges för: 7,5 högskolepoäng Omtentamen NDP011 Systemarkitektprogrammet Namn: (Ifylles av student) Personnummer: (Ifylles av student) Tentamensdatum: Tid: 2014-11-17 09.00 – 13.00 Hjälpmedel: Inga hjälpmedel Totalt antal poäng på tentamen: För att få respektive betyg krävs: G >= 22 VG >= 34 45 poäng Allmänna anvisningar: Numrera sidorna samt börja varje ny uppgift på nytt blad. Skriv personnummer på försättsblad och alla inlämnade sidor. Var tydliga, kortfattade och konkreta i era svar. Skriv tydligt (oläsligt = fel)! På programmeringsuppgifterna är principen viktigare än syntaxen. Poängavdrag kan ges för onödigt komplicerade eller ostrukturerade lösningar. Kommandon från bibliotek (även Roberts genlib och strlib) får normalt användas fritt. Om du är osäker skriv ner vad kommandot förväntas göra. Inkluderingar behöver aldrig anges om det inte framgår av frågan. Rättningstiden är som längst tre veckor Viktigt! Glöm inte att skriva namn på alla blad du lämnar in. Lycka till! Ansvarig lärare: Telefonnummer: Anders Gidenstam 033 435 4214 Karl Jansson 033 435 4399 1. En symboltabell har ett gränssnitt med operationerna: void Insert(symtabADT table, string key, void *value) Sätter in angivet nyckel, värde par (key, value) i symboltabellen. Om nyckeln redan finns så uppdateras det associerade värdet till value. void* Lookup(symtabADT table, string key) Slår upp nyckeln (key) i symboltabellen och returnerar värdet som hör ihop med nyckeln eller konstanten NOT_FOUND om värdet inte finns. a) Symboltabeller kan implementeras med ett antal olika datastrukturer. Två möjliga datastrukturer är hashtabell och binärt sökträd (utan balansering). Förklara i ord och bild hur symboltabbellen fungerar med respektive datastruktur (hashtabell och binärt sökträd) som underliggande implementation. (4p) c) Förklara begreppen lastfaktor (eng. load factor) och balansering i kontexten av en hashtabell respektive ett binärt sökträd. (2p) d) Tidskomplexitet har använts under kursen för att beskriva hur algoritmer och datastrukturer skalar. Ange tidskomplexiteten för Insert och Lookup när en hashtabell med closed hashing respektive ett balanserat binärt sökträd används för den underliggande implementationen. (4p) 2 2. Betrakta följande rekursiva funktion: public void func(string a, int length) { if (length == 1) { printf(a); return; } for (int i = 0; i < length; i++) { swap(a, i, length-1); func(a, length-1); swap(a, i, length-1); } } public void swap(string a, int i, int j) { char c; c = a[i]; a[i] = a[j]; a[j] = c; } a) Visa i ord och bild hur resultatet av följande anrop till void func(string a,int length) beräknas. func("abc",3); En lämplig nivå är att visa vilka funktionsanrop som sker, deras argument och returvärden. (3p) b) Vad är det för problem som funktionen ämnar lösa? Och vad är den rekursiva ideen som funktionen använder för att lösa problemet? (2p) 3 3. I denna uppgift skall en rekursiv funktion implementeras som vänder på en sträng som skickas som parameter till funktionen. Om ”ABCD” skickas till funktionen så skall funktionen exempelvis returnera ”DCBA”: Skriv en funktion med prototypen string ReverseString(string str); som returnerar en bakvänd version av str. En eller flera hjälpfunktioner och eller wrapperfunktioner kan behövas. För full poäng behöver funktionen även vara svansrekursiv (tail-recursive). (5p) 4 4. En prioritetskö (eng. priority queue) är en abstrakt samlingsdatatyp (ADT) med operationerna void Enqueue(pqueueADT pqueue, int newValue) Sätter in angivet värde (newValue) i prioritetskön. Alla heltalsvärden är giltiga datavärden. int DequeueMax(pqueueADT pqueue) Tar bort och returnerar elementet med högst prioritet i prioritetskön. En möjlig implementation av en prioritetskö är en hög (eng. heap) lagrad i ett fält (eng. array) likt den i laboration 2 men som här är av en förbestämd storlek. Nedan följer typdeklarationer för en sådan: typedef struct pqueueCDT *pqueueADT; #define MAXSIZE 128 struct pqueueCDT { int numElements; int elements[MAXSIZE]; }; a) Implementera funktionen void Enqueue(pqueueADT pqueue, int newValue) för en prioritetskö implementerad som en hög (eng. heap) i C. (4p) b) Implementera funktionen int DequeueMax(pqueueADT pqueue) för en prioritetskö implementerad som en hög (eng. heap) i C. (4p) c) Ange och förklara respektive operations tidskomplexitet (worst-case) uttryckt i antalet element i prioritetskön, N. (2p) 5 5. Som du förmodligen minns från laboration 3 så är MFL ett minimalt lat funktionellt språk som kan köras i en interpretator. Ett uttryck i språket parsars då till en abstrakt syntaxträd som sedan evalueras av en evalueringsfunktion, valueADT Eval(expADT exp, environmentADT env). Eval-funktionen kan brytas ner i underfunktioner utifrån typen på rotnoden i uttrycket exp. I den här uppgiften skall du implementera ett par av dessa underfunktioner i C. Grammatiken/syntaxen för MFL och användbara delar av gränssnitten till interpretatorns övriga delar listas nedanför denna uppgift. Du kan anta att funktionerna i gränssnitten fungerar som de skall (förutom de delar av Eval som du skall implementera själv, förstås). Om ett fel som omöjliggör evaluering av noden upptäcks skall funktionen void Error(string msg) anropas vilken antas avbryta evalueringen. a) Implementera underfunktionen till Eval static valueADT EvalCompound(expADT exp, environmentADT env) i C som evaluerar en nod i syntaxträdet av typen CompoundExp (dvs. heltalsuttryck med operator). Alla deluttryck som evalueras, evalueras i den aktuella miljön. Funktionen skall arbeta mot gränssnitten för expADT, environmentADT och valueADT utan kännedom om den faktiska implementationen. (5p) b) Implementera underfunktionen till Eval static valueADT EvalFuncCall(expADT exp, environmentADT env) i C som evaluerar en nod i syntaxträdet av typen CallExp (dvs. ett funktionsanrop). För att beräkna värdet på ett funktionsanrop måste uttrycket i funktionens kropp evalueras i en miljö där det formella argumentet (en identifierare) är satt till det faktiska argumentet i anropet. För att kunna göra det måste först funktionsvärdet som anropet görs på beräknas. Sedan måste en ny miljö skapas som innehåller funktionens formella argument definierat som uttrycket för det faktiska argumentet samt de definitioner som var synliga där funktionsvärdet skapades. Notera också att miljön som det faktiska argumentet skall evalueras i (när/om det behövs) är den där funktionsanropet sker, vilket man får ta hänsyn till när det formella argumentet definieras. Därefter kan funktionsanropet evalueras genom att beräkna värdet på uttrycket i funktionens kropp i den nya miljön. Funktionen skall arbeta mot gränssnitten för expADT, environmentADT och valueADT utan kännedom om den faktiska implementationen. (5p) Vänd för uppgift c)! 6 c) Parsermodulen för MFL interpretatorn exporterar funktionen expADT ParseExp(scannerADT scanner) som i sin tur består av ett antal underfunktioner för var och en av grammatikens regler: static expADT ReadE(scannerADT scanner); static expADT ReadT(scannerADT scanner); static expADT ReadC(scannerADT scanner); static expADT ReadF(scannerADT scanner); Din uppgift är att implementera underfunktionen till ParseExp, static expADT ReadT(scannerADT scanner); som är ansvarig för att tolka sin input enligt reglerna för symbolen T i grammatiken (som följer nedanför uppgiften) i C. Funktionen skall arbeta mot gränssnittet för expADT utan kännedom om den faktiska implementationen. Om ett syntaxfel upptäcks skall funktionen void Error(string msg) anropas vilken antas avbryta parsningen. Du skall också anta att de andra funktionerna (readE, readC och readF) är färdiga och kan användas vid behov. (5p) 7 MFL Grammatik/Syntax Def ::= identifier = E // Only in files and in the // :define command. E ::= T + E E ::= T – E E ::= T T ::= C * T T ::= C / T T ::= C C ::= F (E) C ::= F F F F F F ::= ::= ::= ::= ::= // Call to function. (E) if E RelOp E then E else E func (identifier) { E } integer identifier // If expression // Function expression RelOp ::= = RelOp ::= < RelOp ::= > MFL Kodexempel Definitioner: # Example definitions x = 42 zero = 0 id = func (x) { x } fac = func (n) { if n = 0 then 1 else n*fac(n-1) } Beräkning av uttryck: MFL interpreter (type ":help" for help) > 1+3*4 13 > :load test.mfl > x 42 > fac(4) 24 > fac <function with one argument> > func (x) { 2*x } <function with one argument> > func (x) { 2*x }(3) 6 > 8 MFL Modulgränssnitt för exp-modulen typedef struct expCDT *expADT; typedef enum { FuncExp, IfExp, CallExp, ConstExp, IdentifierExp, CompoundExp } exptypeT; /* expADT constructor functions */ expADT NewFuncExp(string arg, expADT body); expADT NewIfExp(expADT lhs, char relOp, expADT rhs, expADT ifPart, expADT elsePart); expADT NewCallExp(expADT func, expADT arg); expADT NewIntegerExp(int n); expADT NewIdentifierExp(string id); expADT NewCompoundExp(char op, expADT lhs, expADT rhs); /* expADT selector functions */ exptypeT ExpType(expADT exp); /* For FuncExprExp */ string GetFuncFormalArg(expADT exp); expADT GetFuncBody(expADT exp); /* For IfExp */ expADT GetIfLHSExpression(expADT exp); expADT GetIfRHSExpression(expADT exp); char GetIfRelOp(expADT exp); expADT GetIfThenPart(expADT exp); expADT GetIfElsePart(expADT exp); /* For FuncCallExp */ expADT GetCallExp(expADT exp); expADT GetCallActualArg(expADT exp); /* For ConstExp */ int ExpInteger(expADT exp); /* For IdentifierExp */ string ExpIdentifier(expADT exp); /* For CompoundExp */ char ExpOperator(expADT exp); expADT ExpLHS(expADT exp); expADT ExpRHS(expADT exp); 9 MFL Modulgränssnitt för env-modulen typedef struct environmentCDT *environmentADT; environmentADT NewEnvironment(); environmentADT NewClosure(environmentADT parent); valueADT GetIdentifierValue(environmentADT env, string name); void DefineIdentifier(environmentADT env, string name, expADT exp, environmentADT context); MFL Modulgränssnitt för value-modulen typedef enum { FuncValue, IntValue } valuetypeT; typedef struct valueCDT *valueADT; /* valueADT constructor functions */ valueADT NewFuncValue(string formalArg, expADT body, environmentADT closure); valueADT NewIntegerValue(int val); /* valueADT selector functions */ valuetypeT ValueType(valueADT value); /* For IntValue */ int GetIntValue(valueADT value); /* For FuncValue */ environmentADT GetFuncValueClosure(valueADT value); string GetFuncValueFormalArg(valueADT value); expADT GetFuncValueBody(valueADT value); Modulgränssnitt för scannerADT scannerADT stöder (bl.a.) följande operationer: string ReadToken(scannerADT scanner); bool MoreTokensExist(scannerADT scanner); void SaveToken(scannerADT scanner, string token); 10