1 - Nebenläufigkeit in Java Implementierungsfehler 1. Fehler in Klasse Stick, Zeile 5 Beim Zurücklegen des Sticks fehlt der Aufruf von notify(), ohne diesen Aufruf wird ein einmal wartender Philosoph nie aufgeweckt. Richtig wäre also: public synchronized void put() { isUsed = false; notify(); } 2. Fehler in Klasse Stick, Zeile 8 Hier wird in der Abfrage if statt while benutzt. Dies kann dazu führen dass ein Philosoph 2 zunächst wartet da sein Nachbar 1 isst, dieser den Stick dann zurückgibt und Philosoph 1 aufweckt, sich den Stick aber auch sofort wieder nimmt (da er sehr hungrig ist) und dann beide Philosophen denselben Stick haben. Richtig wäre also: public synchronized void take() { while (isUsed) { try { wait(); } catch (InterruptedException e) {} } isUsed = true; } 3. Fehler in Klasse Philosopher, Zeile 28 - 30 Das wait auf dem rechten Stick macht zum einen keinen Sinn, da das anschließende right.take() unter Umständen ebenfalls warten würde. Zum anderen wird auch noch der linke Stick behalten. Richtig wäre also: left.take(); if (right.isUsed()) { left.put(); } else { right.take(); System.out.println("Eating."); } left.put(); right.put(); Hauptprogramm 1 2 3 4 5 6 7 8 9 10 public static void main(String[] args) { int philCount = 3; Stick[] sticks = new Stick[philCount]; for (int i = 0; i < philCount; ++i) { sticks[i] = new Stick(); } for (int i = 0; i < philCount; ++i) { new Philosopher(sticks[i], sticks[(i + 1) % philCount]).start(); } } 1 Die Klasse Stick bleibt unverändert, die Klasse Philosopher muss nun aber noch von der Klasse Thread erben public class Philosopher extends Thread und die Methode philosophize muss in run umbenannt werden. Alternativ könnte die Klasse Philosopher auch das Interface Runnable implementieren public class Philosopher implements Runnable dann müssen die Philosophen aber beispielsweise mittels new Thread(new Philosopher(sticks[i], sticks[(i + 1) % philCount])).start(); gestartet werden. Auch hier muss die Methode philosophize in run umbenannt werden. 2 - Listen in Haskell Rekursive Variante: 1 2 any1 _ [] = False any1 p (x:xs) = p x || any1 p xs Variante mit foldr: 3 any2 p = foldr (\x b -> p x || b) False oder 4 any2' p xs = foldr (||) False (map p xs) Variante mit List Comprehension: 5 any3 p xs = not $ null [ () | x <- xs, p x ] Die Typsignatur ist jeweils: 6 any1, any2, any2', any3 :: (a -> Bool) -> [a] -> Bool Zum Schluss der Test auf die Quadratzahleigenschaft: 7 8 isSquare :: Integer -> Bool isSquare n = any (\x -> x * x == n) [ 2 .. n `div` 2 ] 3 - Datentypen in Haskell Teil a) 1 2 -- |Natural numbers with 0 data NatN = Zero | Pos Nat 3 4 5 -- |Natural numbers without 0 in binary representation data Nat = IHi | O Nat | I Nat 6 7 8 zero :: NatN zero = Zero 9 10 11 seven :: NatN seven = Pos (I (I IHi)) 12 13 14 fortytwo :: NatN fortytwo = Pos (O (I (O (I (O IHi))))) 2 Teil b) 15 data A a = B () | A (a -> a) 16 17 value1, value2, value3 :: A Bool 18 19 20 21 value1 = B () value2 = A id value3 = A not Teil c) 22 23 24 data Exp = Num Int | Add Exp Exp | Mul Exp Exp Die einfache Variante: 25 26 27 28 instance Show Exp where show (Num n) = show n show (Add e1 e2) = "(" ++ show e1 ++ " + " ++ show e2 ++ ")" show (Mul e1 e2) = "(" ++ show e1 ++ " * " ++ show e2 ++ ")" Die bessere Variante: 29 30 31 32 33 34 instance Show Exp where show (Num n) = show n show (Add e1 e2) = show e1 show (Mul e1 e2) = showParen e1 where showParen e@(Add _ _) = showParen e = ++ " + " ++ show e2 ++ " * " ++ showParen e2 "(" ++ show e ++ ")" show e 4 - Do-Notation in Haskell Teil a) 1 2 3 4 5 6 7 8 readNonEmpty :: IO String readNonEmpty = getLine >>= \str -> (if null str then putStrLn "Please insert a non-empty string" >> readNonEmpty else return str) >>= \nonEmpty -> return nonEmpty Teil b) 9 10 11 12 13 14 15 readNonEmpty2 :: IO String readNonEmpty2 = do str <- getLine if null str then do putStrLn "Please insert a non-empty string" readNonEmpty2 else return str 3 5 - Resolution Wir erstellen zunächst den SLD-Baum: ?- delete(1, [1,0+1], Xs). / \ Regel (2) / \ Regel (3) { X1 -> 1 / \ { X1 -> 1 , Y1 -> 1 / \ , Y1 -> 1 , Xs1 -> [0+1] / \ , Xs1 -> [0+1] , Xs -> [0+1] / \ , Xs -> [1|Ys1] } / \ } / \ ?- 1 = 1. ?- delete(1, [0+1], Ys1). | / \ {} | Regel (2) / \ Regel (3) | { X2 -> 1 / \ { X2 -> 1 ?- . , Y2 -> 0+1 / \ , Y2 -> 0+1 % Erfolg mit Xs = [0+1] , Xs2 -> [] / \ , Xs2 -> [] , Ys1 -> [] / \ , Ys1 -> [0+1|Ys2] } / \ } / \ ?- 1 = 0+1. ?- delete(1, [], Ys2). % Fehlschlag | | Regel (1) | { Ys2 -> [] } | ?- . % Erfolg mit Xs = [1, 0+1] Berechnung der Lösung nach dem Suchverfahren von Prolog: ?- delete(1, [1,0+1], Xs). (*) |- {X1 -> 1, Y1 -> 1, Xs1 -> [0+1], Xs ?- 1 = 1. |- {} ?- . Ausgabe: Xs = [0+1] Rücksetzen, 3. Regel bei (*) |- {X1 -> 1, Y1 -> 1, Xs1 -> [0+1], Xs ?- delete(1, [0+1], Ys1). (**) |- {X2 -> 1, Y2 -> 0+1, Xs2 -> [], Ys1 ?- 1 = 0+1. Fehlschlag Rücksetzen, 3. Regel bei (**) |- {X2 -> 1, Y2 -> 0+1, Xs2 -> [], Ys1 ?- delete(1, [], Ys2). |- {Ys2 -> []} (1. Regel) Ausgabe: Xs = [1,0+1] -> [0+1]} (2. Regel) -> [1|Ys1]} -> []} (2. Regel) -> [0+1|Ys2]} Um nur das erste Vorkommen des 1. Parameters aus der Liste zu entfernen, fügen wir einen Cut bei der 2. Regel hinzu: 1 2 3 deleteFirst(_, [] , [] ) . deleteFirst(X, [Y|Xs], Xs ) :- X = Y, !. deleteFirst(X, [Y|Xs], [Y|Ys]) :- deleteFirst(X, Xs, Ys). 4 Um alle Vorkommen des 1. Parameters aus der Liste zu entfernen, müssen wir die 2. Regel anpassen, s dass nach dem Löschen des 1. Vorkommens die Restliste nach weiteren Vorkommen durchsucht wird. Außerdem muss in der 3. Regel zusätzlich sichergestellt werden, dass der 1. Parameter von delete und das 1. Listenelement ungleich sind: 4 5 6 deleteAll(_, [] , [] ) . deleteAll(X, [Y|Xs], Ys ) :- X = Y, deleteAll(X, Xs, Ys). deleteAll(X, [Y|Xs], [Y|Ys]) :- X \= Y, deleteAll(X, Xs, Ys). 6 - Verwandtschaftsbeispiel in Prolog Der Vollständigkeit halber betrachten wir zunächst noch einmal die vorgegebenen Verwandtschaftsbeziehungen aus der Vorlesung: 1 2 3 4 ehemann(christine, ehemann(maria , ehemann(monika , ehemann(angelika , heinz ). fritz ). herbert). hubert ). 5 6 7 8 9 10 11 mutter(herbert , mutter(angelika, mutter(hubert , mutter(susanne , mutter(norbert , mutter(andreas , christine). christine). maria ). monika ). monika ). angelika ). 12 13 14 15 16 17 18 maennlich(heinz ). maennlich(fritz ). maennlich(herbert). maennlich(hubert ). maennlich(norbert). maennlich(andreas). Um alle Großmütter zu bestimmen, müssen wir alle Mütter von Personen ermitteln, die selbst Eltern sind. Daher definieren wir uns zunächst ein Hilfsprädikat vater{.prolog} und dann eine Relation elternteil: 19 vater(Kind, Vater) :- mutter(Kind, Mutter), ehemann(Mutter, Vater). 20 21 22 elternteil(Kind, Elternteil) :- mutter(Kind, Elternteil). elternteil(Kind, Elternteil) :- vater(Kind, Elternteil). 23 24 oma(Oma) :- elternteil(_, Elternteil), mutter(Elternteil, Oma). Alle Onkel (außer den angeheirateten) einer Person erhalten wir, indem wir alle Brüder der Eltern dieser Person bestimmen. Dazu definieren wir uns zunächst eine Relation für Geschwister. Geschwister sind Personen, die dieselbe Mutter haben. Zusätzlich beachten muss man aber noch, dass es sich bei den beiden Geschwistern um unterschiedliche Personen handeln muss. 25 26 27 geschwister(Geschwister1, Geschwister2) :mutter(Geschwister1, Mutter), mutter(Geschwister2, Mutter), Geschwister1 \= Geschwister2. Alle Brüder einer Person erhalten wir, indem wir zusätzlich nur männliche Geschwister betrachten: 28 bruder(Person, Bruder) :- geschwister(Person, Bruder), maennlich(Bruder). 29 30 31 onkel(Person, Onkel) :elternteil(Person, Elternteil), bruder(Elternteil, Onkel). 5 Um die Liste aller Kinder einer Person zu bestimmen, können wir erneut auf das Prädikat elternteil zurückgreifen. Allerdings wollen wir nun nur eine nicht-deterministische Lösung erhalten. Daher kapseln wir den Nicht-Determinismus mit Hilfe des Metaprädikats findall: 32 kinderVon(Person, Kinder) :- findall(Kind, elternteil(Kind, Person), Kinder). 6