(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 | z
NOTE: most of the students wrote a solution like
Exp ::= B | fn Ide => Exp
B ::= C | B C
C ::= Ide | (Exp)
Ide ::= x | y | z
This 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.