| Question | Mark |
| 1. Regular expressions [15 marks] | |
| 2. Finite state machines [15 marks] | |
| 3. Algorithm complexity [15 marks] | |
| 4. Data representation [15 marks] | |
| 5. Digital logic [15 marks] | |
| 6. Prolog [15 marks] | |
| 7. Lisp [15 marks] | |
| Exam Total (Best 6) [90 marks] |
Question 1: Regular expressions [15]
Sample solution
^[AEIOU]([A-Z]{2})+$
|
Sample solution
They will each be 4 hex characters long,
starting with the character that indicates the instruction type
and then following one of three patterns:
input, output, halt, and clear end with 000
skipcond ends with one of 000, 400, 800
the other instructions end with any three hex digits
hence:
^(([567A]000) | (8[048]00) | ([0-49B-E][0-9A-F]{3}))$
|
Question 2: Finite state machines [15]
| Current State | Symbol | New State |
| Start | x | Q1 |
| Start | y | Q2 |
| Start | z | Q3 |
| Q1 | x | Q1 |
| Q1 | y | Q3 |
| Q1 | z | Q2 |
| Q2 | x | Q3 |
| Q2 | y | Q2 |
| Q2 | z | Q1 |
| Q3 | x | Q1 |
| Q3 | y | Q2 |
| Q3 | z | Accept |
| Accept | x | Accept |
| Accept | y | Q1 |
| Accept | z | Q3 |
Sample solution |
Sample solution
One simple possibility is to have five states:
start --> fetch --> decode --> execute --> accept
^ /
\__________________/
We go from start to fetch once the program is loaded and begins running,
we go from fetch to decode once the next instruction has been fetched,
we go from decode to execute once decoding is completed,
we go from execute to accept after a halt instruction has been executed,
otherwise once an instruction execution completes we go back to fetch
The instruction register would be updated during the fetch instruction.
The program counter would be updated during decoding,
but could also be updated by certain instructions during execution
(e.g. by a jump instruction).
|
Question 3: Algorithm complexity [15]
input
store N
L, load N
subt Y
store N
output
skipcond 0
jump L
halt
N, hex 0
Y, hex 10
|
Sample solution
O(N) - the code reads and stores N, then repeatedly subtracts 10
from N until the result is negative. This takes roughly
N/10 iterations, thus O(N), linear
|
(defun f (N)
(cond
((not (numberp N)) 0)
((< N 1) 0)
(t (+ N (f (/ N 2))))))
|
Sample solution
O(logN) - the recursion quits when N < 1,
the recursive calls cut N in half each time, rounding down,
thus at most 1 + log2(N) calls
|
Question 4: Data representation [15]
Suppose file "hexStuff.cl" contains the definition of a lisp function, gethex, described below.
gethex takes an integer, N, as a parameter and returns an 8-character string that is the hex representation of N (two's complement).
For instance (gethex 18) would return "00000012", and (gethex -1) would return "FFFFFFFF".
Show the output that would be produced if we ran the following lisp script:
#! /usr/bin/gcl -f
(load "hexStuff.cl")
(defun product (X Y)
(if (and (integerp X) (integerp Y))
(let ((result (* x y)))
(format t "~A * ~A is ~A~%" (gethex X) (gethex Y) (gethex result))
result)))
(format t "Final: ~A~%" (gethex (+ 17 (product -3 -2))))
|
Sample solution
Note that it's essentially displaying
VAL1 * VAL2 is PRODUCT
Final RESULT
where VAL1 is the hex for -3, VAL2 is the hex for -2,
PRODUCT is the hex for 6 (i.e. (-3)*(-2)), and
RESULT is the hex for 23 (i.e. 6+17)
Thus the output is
FFFFFFFD * FFFFFFFE is 00000006
Final 00000017
|
Question 5: Digital logic [15]
Based on the truth table below, answer questions 1-3.
|
|
Part 3 sample solution |
Question 6: Prolog [15]
Sample solution extremes(L,Sm,Lg) :- smallest(L,Sm), largest(L,Lg). smallest([N], N) :- number(N). smallest([H|T], H) :- number(H), smallest(T,S), H < S. smallest([H|T], S) :- number(H), smallest(T,S), H >= S. largest([N], N) :- number(N). largest([H|T], H) :- number(H), largest(T,L), H > L. largest([H|T], L) :- number(H), largest(T,L), H =< L. |
% memRequest(FreeMem, RequestSize, Chosen, MemAfter) % -------------------------------------------------- % given a list of available memory segments, FreeMem, and % a request for a block of memory of a specific size, RequestSize, % determine the chosen block, Chosen, and % the updated memory list, MemAfter % % memory segments are described as pairs: [ size, address] % where size is the number of available bytes % and address is the memory address of the start of the segment memRequest([[S,A]|T], R, [S,A], T) :- posint(S), posint(A), posint(R), R =< S. memRequest([H|T], R, C, [H|MA]) :- memRequest(T, R, C, MA). memRequest(M, _, noneAvail, M). posint(X) :- integer(X), X > 0. |
Sample solution Note that for a request like memRequest(L, N, C, M), memRequest is finding the first free block that is at least as big as N, unifying that block's [Size,Address] list with C, and unifying M with everything that's left in L after removing C So in this query N is 2048, and the first big enough chunk is the one [4096,2000], so the query unifies C with [4096,2000] and MA with the rest, i.e. [[128,1000], [2048,8000], [512,9000]] |
Question 7: Lisp [15]
Write a lisp function, extremes, that expects a list, L, as its only parameter.
If L is not a list, or is an empty list, then extremes returns nil, otherwise extremes returns a list of two values: the smallest value in L and the largest value in L.
For example, (extremes '(3 12 4 1)) returns (1 12)
Sample solution
(defun extremes (L)
(cond
((not (listp L)) nil)
((null L) nil)
(t (list (smallest L) (largest L)))))
|
Sample solution #! /usr/bin/gcl -f (load "final.pl") ; <-- I really should have named it final.cl!!! (format t "Please enter a list~%") (defvar L (read)) (defvar result (extremes L)) (format t "Extremes of ~A are ~A~%" L result) |