Data- och Programstrukturer Provmoment: Ladokkod: Tentamen ges för: Omtentamen NDP011 Systemarkitektprogrammet 7,5 högskolepoäng Namn: (Ifylles av student) Personnummer: (Ifylles av student) Tentamensdatum: 2014-08-26 Tid: 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) 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: Anders Gidenstam Karl Jansson Telefonnummer: 033 435 4214 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. Symboltabeller kan implementeras med ett antal olika datastrukturer. a) För en symboltabell implementerad som ett binärt sökträd så påverkas operationernas tidskomplexitet av trädets balans. Definiera vad ett balanserat binärt sökträd är. b) För en symboltabell implementerad som en hashtabell är lastfaktor (eng. load factor) ett viktigt begrepp. Förklara vad detta begrepp innebär i kontexten av en hashtabell. c) När en hashtabell implementeras kan olika strategier användas för att hantera krockar som kan inträffa vid insättning; Ange och definiera två olika sådana strategier. 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 respektive ett balanserat binärt sökträd används för den underliggande implementationen. (4p) 2
2. Betrakta följande version av ackermanns funktion: int ack(int m, int n){ if(m == 0) return n+1; else if(n == 0) return ack(m-1,1); return ack(m-1,ack(m,n-1)); } a) Visa i ord och bild hur resultatet av följande anrop till int ack(int m,int n) beräknas. ack(2,1); En lämplig nivå är att visa vilka funktionsanrop som sker, deras argument och returvärden. b) När man exekverar rekursiva funktioner som saknar giltiga basfall så kan en krasch som kallas stack overflow inträffa. Förklara vad som händer när en stack overflow krasch inträffar. c) Kommer ackermanns funktion ovan att kunna leda till en stack overflow krasch, varför? varför inte? (1p) 3
3. Binärsökning kan användas för att snabbt söka igenom en sorterad array av element genom att halvera sökmängden för varje jämförelse av ett arrayelement. I denna uppgift skall en rekursiv implementation av binärsökning av integer-värden anges: Skriv en funktion med prototypen bool binarysearch(int key, int array[]); som returnerar true om key återfinns i array och false om key inte återfinns i array. En eller flera hjälpfunktioner och eller wrapperfunktioner kan behövas. (5p) 4
4. En dubbeländad kö (eng. deque) är en ADT som kan bete sig som en kö åt båda hållen, eller, mer lätt begripligt, båda ändarna beter sig som stack:ar, dvs. man kan lägga till och ta bort element i båda ändarna. Ändarna sitter ihop så att det som push:as på ena sidan kan pop:as från den andra när alla element närmare den änden har pop:ats. Gränssnittet till en dequeadt är givet nedan. /* File: deque.h This interface defines a double-ended queue. */ /* Type: dequeelementt */ typedef double dequeelementt; /* Type: dequeadt */ typedef struct dequecdt *dequeadt; /* Function: NewDeque * Usage: deque = NewDeque(); */ dequeadt NewDeque(void); /* Function: FreeDeque * Usage: FreeDeque(deque); */ void FreeDeque(dequeADT deque); /* Function: PushLeft * Usage: PushLeft(deque, element); * This function pushes the specified element onto the left end of the deque.*/ void PushLeft(dequeADT deque, dequeelementt element); /* Function: PushRight * Usage: PushRight(deque, element); * This function pushes the specified element onto the right end of the deque.*/ void PushRight(dequeADT deque, dequeelementt element); /* Function: PopLeft * Usage: element = PopLeft(deque); * This function pops the leftmost element from the deque and returns * that value. If the deque is empty when PopLeft is called, * the function calls Error with an appropriate message. */ dequeelementt PopLeft(dequeADT deque); /* Function: PopRight * Usage: element = PopRight(deque); * This function pops the rightmost element from the deque and returns * that value. If the deque is empty when PopRight is called, * the function calls Error with an appropriate message. */ dequeelementt PopRight(dequeADT deque); /* Functions: DequeIsEmpty, DequeIsFull * Usage: if (DequeIsEmpty(deque))... * if (DequeIsFull(deque))... * This functions test whether the deque is empty or full. * A dynamic deque will never become full (since we assume the system memory * will never be exhausted). */ bool DequeIsEmpty(dequeADT deque); bool DequeIsFull(dequeADT deque); 5
Ett sätt att implementera en dubbeländad kö är med två stackar. Definitionen av dequecdt blir då struct dequecdt { stackadt leftend; stackadt rightend; }; a) Förklara i ord och bild hur en sådan implementation skulle kunna fungera. Hur ser man till att det som push:as på ena sidan kan pop:as från den andra sidan vid behov? b) Implementera funktionen void PushLeft(dequeADT deque, dequeelementt element); i C för dequecdt definitionen ovan. Funktionen skall arbeta mot gränssnittet för stackadt utan kännedom om den faktiska implementationen av stackarna. c) Implementera funktionen dequeelementt PopRight(dequeADT deque); i C för dequecdt definitionen ovan. Var noggrann med specialfallen. Funktionen skall arbeta mot gränssnittet för stackadt utan kännedom om den faktiska implementationen av stackarna. (4p) d) Ange och motivera värstafallstidskomplexiteten som en funktion av deque:ns storlek, N, (i O() mening) för din implementation av PopRight enligt din kod i d) och/eller din beskrivning i a). stackadt stöder följande operationer: void Push(stackADT stack, stackelementt element); stackelementt Pop(stackADT stack); bool StackIsEmpty(stackADT stack); stackadt NewStack(void); void FreeStack(stackADT stack); Värstafallstidskomplexiteten för Push och Pop på en stackadt kan antas vara O(1) för den implementation som används här. 6
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 EvalIf(expADT exp, environmentadt env) i C som evaluerar en nod i syntaxträdet av if-uttryckstyp. Ett if-uttryck evalueras genom att först evaluera villkoret och sedan den relevanta grenen. Alla deluttryck som evalueras, evalueras i den aktuella miljön. (5p) b) Implementera underfunktionen till Eval static valueadt EvalFuncExp(expADT exp, environmentadt env) i C som evaluerar en nod i syntaxträdet av funktionsuttryckstyp (Notera: inte funktionsanropstyp). Ett funktionsuttryck evalueras till ett anropningsbart funktionsvärde som behöver känna till miljön/kontextet där det definierades. (5p) 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 ReadC(scannerADT scanner); som är ansvarig för att tolka sin input enligt reglerna för symbolen C 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, readt och readf) är färdiga och kan användas vid behov. 7
MFL Grammatik/Syntax Def ::= identifier = E E ::= T + E E ::= T E E ::= T // Only in files and in the // :define command. T ::= C * T T ::= C / T T ::= C C ::= F (E) C ::= F F ::= (E) F ::= if E RelOp E then E else E F ::= func (identifier) { E } F ::= integer F ::= identifier // Call to function. // 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