(1) (2) (3) (4) Exp Exp __ Exp Exp / \ / \ / / | \ / \ Exp Exp Exp Exp fn Ide => Exp __ Exp Exp / \ \ / / \ | / \ / / | \ | Exp Exp Ide Ide Exp Exp x Exp Exp fn Ide => Exp Ide | | | | | | | | | | | Ide Ide z x Ide Ide Ide Ide x Ide z | | | | | | | x y y z y z y
Exp ::= A | fn Ide => Exp A ::= B | A fn Ide => Exp B ::= C | B C C ::= Ide | (Exp) Ide ::= x | y | zNOTE: most of the students wrote a solution like
Exp ::= B | fn Ide => Exp B ::= C | B C C ::= Ide | (Exp) Ide ::= x | y | zThis second solution is almost correct, but not completely, since it does not allow to generate strings like x fn y => y, while they are generated by the grammar given in the text. However, since, admittedly, this case was tricky to recognize, no points of penalty have been given for this error.
C / \ C1 C3 | C2
class Queue { private class Element { int info; Element next; Element(int n) { info = n; next = null; } } private Element first; private Element last; Queue() { first = null; } public synchronized boolean is_empty() { ------------ return (first == null); } public synchronized void insert(int n) { ------------ if (first == null) { first = last = new Element(n); notify(); } else --------- { last = last.next = new Element(n); } } public synchronized int pop() throws InterruptedException { ------------ --------------------------- while (is_empty()) wait(); -------------------------- int n = first.info; first = first.next; return n; } }NOTE 1: notify() (instead of notifyAll()) is sufficient here, because only consumers can be in the wait set.
NOTE 2: The assumption that the consumers checks is_empty() before calling pop() is not sufficient, because in a multi-threaded context such checks needs to be done atomically (i.e. in a critical section) with the consumption. Hence the necessity of the instruction while (is_empty()) wait(); and of the instruction notify();.
NOTE 3: Several students wrote a solution of the following kind:
class Queue { private class Element { int info; Element next; Element(int n) { info = n; next = null; } } private Element first; private Element last; private boolean in_use; ----------------------- Queue() { first = null; in_use = false; } --------------- public boolean is_empty() { return (first == null); } public void insert(int n) { while (in_use) wait(); ---------------------- in_use = true; -------------- if (first == null) { first = last = new Element(n); } else { last = last.next = new Element(n); } in_use = false; notifyAll(); ---------------------------- } public int pop() { while (in_use) wait(); ---------------------- in_use = true; -------------- int n = first.info; first = first.next; in_use = false; notifyAll(); ---------------------------- return n; } }In other words, they propose to simulate a lock mechanism, instead of using the lock mechanism available in Java (via the synchronized mode). This solution cannot work in Java, because there is no guarrantee that the two consecutive instructions while (in_use) wait(); in_use = true; are executed atomically. In other words, the simulation of a lock mechanism presents the same problem of interference as the methods which update a shared object.
fun sum_bintree(leaf(x)) = x | sum_bintree(node(t1,t2)) = sum_bintree(t1) + sum_bintree(t2);
fun map_fun_list [] _ = [] | map_fun_list (f::l) x = (f x)::(map_fun_list l x);
delete_repetitions([],[]). delete_repetitions([X|L],K) :- occurs(X,L), delete_repetitions(L,K). delete_repetitions([X|L],[X|K]) :- not(occurs(X,L)), delete_repetitions(L,K). occurs(X,[X|_]). occurs(X,[Y|L]) :- not(X=Y), occurs(X,L).NOTE: the test not(X=Y), in the second clause of occurs, serves to eliminate repetitions of the answer.