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