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 For string xyzyxxx the state sequence would be Start -> Q1 -> Q3 -> Accept -> Q1 -> Q1 -> Q1 (string not accepted) |
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) |