Guido Krüger's Web Service

Programming Asssignments 11 - 13


(This page is available in German only. )

Aufgaben zu den Kapiteln 11 bis 13

1. [A] (String ist final). Die Klasse String wurde im JDK als final deklariert und kann daher nicht abgeleitet werden. Das schränkt ihre Anwendbarkeit und insbesondere ihre Erweiterbarkeit stark ein. Überlegen Sie sich, auf welche Weise ohne Ableitung neue String-Methoden entwickelt werden können und zeigen Sie deren Vor- und Nachteile auf.

2. [A] (Übergabe von Strings an Methoden). Strings werden in Java als Objekte dargestellt und damit als Parameter einer Methode per Referenz an diese übergeben. Demzufolge würde man erwarten, daß eine innerhalb der Methode ausgeführte Zuweisung an einen String-Parameter nach Ende des Methodenaufrufs den aktuellen Parameter verändert hätte und somit auch außerhalb der Methode sichtbar wäre. Tatsächlich ist dies keineswegs der Fall, sondern Änderungen an String-Argumenten, die innerhalb der Methode vorgenommen werden, bleiben lokal und sind außerhalb von ihr unsichtbar. Erklären Sie dieses Verhalten und begründen Sie, warum es vollkommen korrekt ist.
Aufg1102.java

3. [A] (String und StringBuffer). Kapitel 11 stellt sowohl die Klasse String als auch StringBuffer vor. Überlegen Sie, wann es sinnvoller ist, einen String zu verwenden und wann man einen StringBuffer einsetzen sollte. Begründen Sie Ihre Überlegungen.

4. [B] (Worte in einem String zählen). Schreiben Sie eine Methode wcnt, die ein Argument vom Typ String akzeptiert und die Anzahl der darin enthaltenen Worte zählt. Ein Wort soll dabei eine zusammenhängende Folge von Zeichen sein, die keine Leerzeichen, Tabulatoren oder Zeilenschaltungen enthält.
Aufg1104.java

5. [B] (Die B-Sprache). Ebenso wie Informatiker erfinden auch Kinder gerne spezielle "Sprachen", die sich lustig anhören und deren Verständnis nur Eingeweihten zugänglich ist. Das im englischsprachigen Raum bekannte "Pig-Latin" ähnelt dabei der hierzulande bekannten "B-Sprache", der wir diese Aufgabe widmen wollen.

Die B-Sprache funktioniert so, daß an alle zusammenhängenden Folgen von Vokalen innerhalb eines Wortes ein "b" plus die Wiederholung der Vokalfolge angehängt wird. Aus dem Satz "Meine Mutter ißt gerne Fisch" wird so beispielsweise "Meibeinebe Mubutteber ibißt gebernebe Fibisch". Nach einiger Übung lernt man problemlos, die B-Sprache fließend zu sprechen und zu verstehen.

Schreiben Sie ein Programm, das eingegebene Sätze in die B-Sprache übersetzt. Falls Sie noch genügend Zeit haben, realisieren sie auch die umgekehrte Übersetzung.
Aufg1105.java

6. [B] (Englischer Plural). Zu einem englischen Substantiv kann sehr leicht der Plural gebildet werden, wenn man die folgenden Regeln anwendet:

Schreiben Sie eine Methode englishPlural, die zu einem als String übergebenen englischen Substantiv den Plural bildet und an den Aufrufer zurückgibt.

7. [B] (Ersetzen eines Teilstrings). Obwohl die Klasse String in Java eine ganze Reihe nützlicher Methoden bietet, ist sie doch nicht ohne Schwachstellen. So fehlt in der aktuellen Version des JDK beispielsweise eine Methode, die es erlaubt, jedes Auftreten eines vorgegeben Teilstrings gegen einen anderen auszutauschen. Zwar gibt es die Methode replace, diese erlaubt es aber lediglich, ein einzelnes Zeichen gegen ein anderes auszutauschen.

Schreiben Sie eine Methode replaceString, die in einem gegebenen String alle Vorkommen eines Strings s1 gegen einen String s2 austauscht. s1 und s2 dürfen dabei beliebig (auch unterschiedlich) lang sein.
Aufg1107.java

8. [B] (Ganzzahlen formatieren). Zwar ist es in JAVA sehr leicht, eine Zahl oder einen String auf der Konsole auszugeben, aber C-Programmierer, die in JAVA programmieren, vermissen schmerzlich die Formatierungsmöglichkeiten, die ihnen die Familie der printf-Ausgabefunktionen in C zur Verfügung gestellt hat. Schreiben Sie eine Methode formattedInt, die zwei Argumente vom Typ int akzeptiert und einen formatierten String zurückgibt. Das erste Argument bezeichnet die in einen String zu konvertierende Zahl und das zweite die Breite des Feldes, in dem die Zahl rechtsbündig plaziert werden soll. So führt beispielsweise ein Aufruf von formattedInt(173,5) zur Rückgabe des Strings " 173" und ein Aufruf von formattedInt(-22055,8) liefert " -22055".

9. [B] (Kalender ausdrucken). Schreiben Sie ein Programm, das ein Kalenderblatt für einen vorgegebenen Monat ausdruckt. Tag, Monat und Jahr sollen dabei als Kommandozeilenargumente an das Programm übergeben werden. Die Darstellung des 14.9.97 könnte beispielsweise so aussehen:

     14. September 1997
----------------------------
 Mo  Di  Mi  Do  Fr  Sa  So
  1   2   3   4   5   6   7
  8   9  10  11  12  13 >14<
 15  16  17  18  19  20  21
 22  23  24  25  26  27  28
 29  30

Aufg1109.java

10. [B] (Zufallszahlen). Schreiben Sie eine statische Methode randomInt, die gleichverteilte ganzzahlige Zufallszahlen innerhalb eines vorgegebene Wertebereichs liefert. Obere und untere Grenze sollen als Argument an die Methode übergeben werden. So soll beispielsweise jeder Aufruf von randomInt(4,10) ein Zufallszahl im Bereich von 4 bis 10 liefern. Alle Zahlen in diesem Bereich sollen gleich wahrscheinlich sein.

11. [B] (Linksbündige Leerzeichen entfernen). Schreiben Sie eine Methode ltrim, die einen String als Argument akzeptiert, daraus die linksbündigen Leerzeichen entfernt und den so modifizierten String an den Aufrufer zurückgibt.

12. [B] (Nach wiederholten Zeichenketten suchen). Schreiben Sie eine Methode lookupPairs, die einen beliebigen String auf Teilzeichenketten untersucht, die mehr als einmal darin vorkommen. Der Rückgabestring soll jede dieser Zeichenketten genau einmal ausgeben. Die einzelnen Elemente sollen dabei durch ein Leerzeichen voneinander getrennt werden. Schätzen Sie ab, welches Laufzeitverhalten Ihr Verfahren hat. Ist es O(n*n) oder sogar noch besser?

13. [B] (Performance von String und StringBuffer). Um an eine bestehende Zeichenkette eine weitere anzuhängen, gibt es zwei Möglichkeiten:

  1. Die Zeichenkette wird als String gespeichert und der anzuhängende String wird mit Hilfe des +-Operators angehängt.
  2. Die Zeichenkette wird als StringBuffer gespeichert und der anzuhängende String wird mit Hilfe der append-Methode angehängt.
Schreiben Sie ein Programm, das beide Varianten implementiert und an einer repräsentativen Reihe von Testfällen durchspielt. Versuchen Sie, die Laufzeiten beider Varianten zu messen, vergleichen Sie die Ergebnisse und unternehmen Sie den Versuch einer Begründung.

14. [B] (Einfache Verschlüsselung). Eine einfache Methode, Texte zu verschlüsseln, besteht darin, jedes einzelne Zeichen gegen ein anderes auszutauschen. Angenommen, im Text kommen nur die Großbuchstaben 'A' bis 'Z' vor, dann könnte man ein Array von 26 Elementen verwenden, in dem die 26 Großbuchstaben in beliebiger Anordnung vorkommen. Das Verschlüsseln würde nun so funktionieren, daß jeder Buchstabe durch das Zeichen ersetzt wird, das in der Tabelle an seiner eigentlichen Position steht. Schreiben Sie ein Programm, das einen Text auf diese Weise ver- und wieder entschlüsseln kann.

15. [B] (Punkte beim Scrabble). In dem Kreuzworträtselspiel Scrabble werden die von den Spielern gefundenen Worte auf der Basis der darin enthaltenen Buchstaben bewertet. Jeder Buchstabe besitzt eine feste Punktzahl entsprechend der folgenden Liste:

So ergibt beispielsweise das Wort "FARM" 9 und das Wort "DOG" 5 Punkte (Die Original-Scrabble-Bewertung ist natürlich auf englische Worte ausgelegt). Bonusregelungen etc. sollen an dieser Stelle unberücksichtigt bleiben. Schreiben Sie eine Methode scrabbleScore, die den Punktewert eines als String übergebenen Wortes berechnet.

16. [B] (Wortpalindrome). Ein Palindrom ist ein Wort, das vorwärts und rückwärts gelesen dasselbe ergibt (abgesehen von Unterschieden in Groß- und Kleinschreibung). Beispiele für Palindrome sind "Uhu", "neben", "Ebbe", "nennen", "Renner" oder "Rotor". Schreiben Sie eine Methode isPalindrome, die ermittelt, ob ein als Argument übergebener String ein Palindrom ist.
Aufg1116.java

17. [B] (Satzpalindrome). Neben Wort-Palindromen gibt es auch ganze Sätze, die ein Palindrom ergeben, wenn man Interpunktion und Groß-/Kleinschreibung ignoriert. Beispiele dafür sind "Ein Neger mit Gazelle zagt im Regen nie", "Madam, I'm Adam" oder "A man, a plan, a canal: panama". Schreiben Sie eine Methode isSentencePalindrome, die genau dann true zurückgibt, wenn der übergebene String ein Satz-Palindrom ist. Falls Sie bei der Aufgabe zu den Wortpalindromen einen iterativen Lösungsansatz gewählt haben, versuchen Sie nun einen rekursiven und umgekehrt.

18. [B] (Einfache Wildcardsuche). Die Methode indexOf der Klasse String kann dazu verwendet werden, das erste Auftreten eines Strings innerhalb eines anderen Strings zu suchen. Implementieren Sie eine erweiterte Methode wildIndexOf, bei der auch das Wildcard "?" im Suchstring enthalten sein darf. Ein "?" steht dabei für das Auftreten eines einzelnen beliebigen Zeichens an dieser Stelle. Würden Sie beispielsweise mit dem Suchausdruck "t?r" in der Zeichenkette "Der Ball ist rund" suchen, so würde die Methode 11 zurückgeben. Falls der Suchbegriff nicht gefunden wurde, sollte -1 zurückgegeben werden.

19. [B] (Wortanfänge in Großbuchstaben konvertieren). Schreiben Sie eine Methode wordsToUpperCase, die in einem String den ersten Buchstaben in jedem Wort in Großschrift konvertiert.

20. [B] (Zeichen in einem String sortieren). Angenommen, ein String besteht nur aus den Großbuchstaben "A" bis "Z". Schreiben Sie eine Methode, die die Zeichen in dem String in aufsteigender Reihenfolge sortiert. Aus "HALLO" soll also beispielsweise "AHLLO" und aus "GURKENSUPPE" soll "EEGKNPPRSUU" werden. Kennen Sie ein Verfahren mit linearer Laufzeit?
Aufg1120.java

21. [B] (Buchstaben auf amerikanischen Telefonen). Bei amerikanischen Telefonen sind neben Zahlen auch Buchstaben auf den Tasten aufgedruckt (s. folgende Abbildung). Da somit jedem Wort eindeutig die zugehörige Telefonnummer zugeordnet werden kann (umgekehr stimmt dies nicht), bietet sich die Möglichkeit, anstelle von Nummern leichter merkbare mnemonische Kürzel im Kopf zu behalten:

   +---+ +---+ +---+
   ! 1 ! ! 2 ! ! 3 !
   !   ! !ABC! !DEF!
   +---+ +---+ +---+

   +---+ +---+ +---+
   ! 4 ! ! 5 ! ! 6 !
   !GHI! !JKL! !MNO!
   +---+ +---+ +---+

   +---+ +---+ +---+
   ! 7 ! ! 8 ! ! 9 !
   !PRS! !TUV! !WXY!
   +---+ +---+ +---+

   +---+ +---+ +---+
   !   ! ! 0 ! !   !
   !   ! !   ! !   !
   +---+ +---+ +---+
So wäre beispielsweise die Nummer 1-800-JAVA sehr leicht zu behalten, wenn es der Anschluß der JAVA-Serviceabteilung der Firma SUN wäre (die Vorwahl 1-800 ist das Gegenstück zur deutschen 0130, bezeichnet also eine Telefonnummer, die für den Anrufer kostenlos ist). In Wirklichkeit entspricht das Wort JAVA der Nummer 5282, die der Anrufer wählt, wenn er "JAVA" eingibt.

Bei der Vergabe dieser Kürzel kann allerdings oftmals die Nummer nicht frei gewählt werden, sondern es muß versucht werden, zu einer vorgegebenen Nummer einen leicht zu merkenden Begriff zu finden. Ihre Aufgabe ist es nun, dafür ein Programm zu schreiben. Das Programm erwartet eine numerische Telefonnummer als Eingabe und gibt dann alle Worte aus, die auf der Telefontastatur der vorgegebenen Nummer entsprechen.

22. [B] (Ein Problem rückwärts bearbeiten). Manchmal ist es einfacher ein Problem zu lösen, wenn die zu bearbeitende Datei oder der zu bearbeitende String rückwärts gelesen wird. Eine vormals sehr schwierige Lösung kann dadurch möglicherweise eine überraschende Wendung erfahren. Versuchen Sie diesen Ansatz am Beispiel der folgenden Aufgabenstellung.

Schreiben Sie ein Programm, das aus einem gültigen Java-Ausdruck die Namen und Position der darin enthaltenen Methodenaufrufe findet und als Array von Strings mit angehängter Position zurückgibt. Lautet der Ausdruck beispielsweise

showStatus("Zeile "+3*(i+line (x))+pack(line(y))+"/"+x*(pack((z))));
so soll Ihr Programm ein Array mit den folgenden 5 Strings zurückgeben:
"showStatus,0"
"line,25"
"pack,35"
"line,40"
"pack,56"
Versuchen Sie einen Lösungsansatz, bei dem der String rückwärts gelesen wird. Dabei sollte bei jeder öffnenden Klammer solange nach links weitergelesen werden, bis das erste Zeichen auftaucht, das weder ein Leerzeichen, Tabulator oder eine Zeilenschaltung ist. Handelt es sich bei diesem Zeichen um ein in Bezeichnern erlaubtes Zeichen (also um einen Buchstaben, eine Zahl oder einen Unterstrich), so wurde ein Methodenaufruf gefunden. Handelt es sich dagegen um ein Trennzeichen wie "*" oder "+", so wurde kein Methodenaufruf gefunden. Nehmen Sie der Einfachheit halber an, daß nur Methoden aus der aktuellen Klasse verwendet werden, also keine qualifizierten Namen mit darin enthaltenen Punkte vorkommen.

23. [B] Lottotip abgeben. Seit einiger Zeit können beim Lotto auch Zufallswetten abgegeben werden. Dabei werden die 6 Kreuze nicht mehr vom Spieler gemacht, sondern ein Zufallszahlengenerator wählt den vermeintlich glückbringenden Tip aus den 49 Zahlen aus.

Schreiben Sie ein Programm, das in der Lage ist, einen Tip für das Lotto 6 aus 49 abzugeben. Selbstverständlich muß der Tip gültig sein (darf also keine doppelten Zahlen enthalten), und die Wahrscheinlichkeit für das Auftreten jeder beliebigen Zahlenkombination sollte gleich sein.
Aufg1123.java

24. [B] Stack zur Prüfung korrekter Klammerung. Ein Stack wird häufig da verwendet, wo rekursive Strukturen, also Schachtelungen, untersucht werden müssen. Eine typische Anwendung besteht darin, die korrekte Klammerung von arithmetischen Ausdrücken zu prüfen, also festzustellen, ob zu jeder öffnenden Klammer eine zugehörige schließende Klammer existiert.

Schreiben Sie ein Programm, das einen als String vorgegebenen Ausdruck auf korrekte Klammerung untersucht, indem jede öffnende Klammer auf dem Stack abgelegt und bei Auftreten einer schließenden Klammer wieder entfernt wird. Der Ausdruck ist korrekt geklammert, wenn niemals versucht wird, eine Klammer vom leeren Stack zu entfernen und nach Ende des Ausdrucks keine Klammern mehr auf dem Stack sind.

25. [C] (Römische Zahlen). Lange bevor in Mitteleuropa die positionsorientierten Zahlensysteme in Mode kamen, gab es bereits das System der römischen Zahlen. Dieses basierte auf einem Alphabet der folgenden Ziffern:
ZifferWert
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

Jede römische Zahl setzt sich aus einer Folge römischer Ziffern zusammen. Der Wert der Zahl ergibt sich durch einfaches Addieren der Werte der einzelnen Ziffern. Einzige Ausnahme: steht eine kleinere vor einer größeren Ziffer, so wird der Wert der kleineren Ziffer nicht zum Ergebnis addiert, sondern von ihm subtrahiert. So ergibt beispielsweise die Ziffer MCMXCVII den Wert 1000 - 100 + 1000 - 10 + 100 + 5 + 1 + 1 = 1997.

Bei der Konstruktion einer römischen Ziffer zu einem gegebenen Wert sind einige Bildungsregeln (sozusagen eine sehr frühe Form eines Style-Guides) zu beachten:

  1. Normalerweise werden die Ziffern absteigend geordnet. Die Ziffer mit dem größten Wert steht also vorne, die mit dem kleinsten hinten.
  2. Es werden nie zwei kleinere Ziffern verwendet, wenn eine größere denselben Wert ergibt. Statt LL wird als immer C geschrieben.
  3. Es stehen nie mehr als drei gleichartige Ziffern nebeneinander. Wird eine vierte benötigt (für die Vierer- und Neunerstellen), so wird stattdessen eine I links neben die nächst größere Ziffer geschrieben. Dies ist die Einzige Ausnahme zu Regel 1. Beispiel: 4 wird nicht als IIII, sondern als IV aufgeschrieben und 9 nicht als VIIII, sondern als IX.
Schreiben Sie zwei Methoden romanToDecimal und decimalToRoman, die römische Ziffern in Dezimalzahlen umwandeln und umgekehrt. Verwenden Sie ein int zur Darstellung der Dezimalzahl und ein String zur Darstellung der römischen Zahl.

26. [C] Blocksatz. Java-Strings unterliegen keinen prinzipiellen Längenbeschränkungen und sind daher auch dafür geeignet, kleinere Textdateien komplett im Hauptspeicher halten und bearbeiten zu können. Die Trennung der einzelnen Zeilen erfolgt ebenso wie in der externen Datei dadurch, daß Zeilenschaltungen eingefügt werden. Eine typische Anwendung bestehtnun darin, einen mehrzeiligen Textstring so zu formatieren, daß er im Blocksatz dargestellt wird. Dabei wird jede Zeile innerhalb einer vorgegebenen Zeilenlänge so dargestellt, daß der darin enthaltenen Text sowohl links- als auch rechtsbündig dargestellt wird. Die zum Auffüllen erforderlichen Leerzeichen werden dabei so zwischen den Worten eingefügt, daß das Ergebnis möglichst harmonisch aussieht. Ist eine Zeile zu lang, so wird an einer Wortgrenze ein geeigneter Zeilenumbruch eingefügt. Die letzte Zeile des Textes bleibt unformatiert.

Schreiben Sie eine Methode leftRightAlign, die einen String und ein int als Argument erwartet. Der übergebene String soll dabei entsprechend der im zweiten Argument angegebenen Zeilenbreite im Blocksatz formatiert und an den Aufrufer zurückgegeben werden.

Soll beispielsweise der vorige Absatz im Blocksatz mit 40 Zeichen ausgegeben werden, so könnte dies etwa so aussehen:

Schreiben      Sie     eine      Methode
leftRightAlign, die einen String und ein
int   als    Argument   erwartet.    Der
übergebene     String     soll     dabei
entsprechend  der  im  zweiten  Argument
angegebenen  Zeilenbreite  im  Blocksatz
formatiert    und   an   den    Aufrufer
zurückgegeben werden.

Aufg1126.java

27. [C] (Galgenraten). In der Schule wurde oft das Spiel "Galgenraten" gespielt, bei dem es darum ging, einen vom Mitspieler ausgesuchten Begriff mit möglichst wenigen Versuchen zu erraten. Die Spielregeln waren dabei wie folgt:

Der Galgen besteht aus folgenden 10 Elementen:
  1. Der Grundlinie (dem Galgenhügel)
  2. Dem Stamm
  3. Dem Querträger
  4. Einer zusätzlichen Versteifung
  5. Dem Strick
  6. Dem Kopf des Gegners
  7. Seinem Körper
  8. Seinen Armen
  9. Seinem linken Bein
  10. Seinem rechten Bein
Der Galgen kann leicht mit normalen ASCII-Zeichen dargestellt wrden, wie folgende Abbildung zeigt (rechts mit nummerierten Elementen):
                                     3
     -------------             -------------
     | /        |              | /        |5
     |/         O              |/4        O6
     |         -+-             |         -+-8
     |          |              |          |7
     |         / \            2|        9/ \10
     |                         |
     |                         |
     |                         |
----------------------    ----------------------
                                     1
Schreiben Sie eine textorientierte Implementierung des Spiels "Galgenraten". Das Programm ist ihr Gegner, es wählt auf der Basis eines kleinen Katalogs ein Wort aus, das Sie erraten sollen. Nach jedem Rateversuch, den Sie unternehmen, zeigt das Programm sowohl den Zustand des teilweise erratenen Wortes als auch des Galgens an. Das Spiel ist beendet, wenn entweder das Wort erraten, oder der Galgen komplett gezeichnet ist.

28. [C] (Gewichtete Zufallszahlen). Entwickeln Sie eine Klasse WRandom, die in der Lage ist, gewichtete ganzzahlige Zufallszahlen zu produzieren. Im Gegensatz zu den gleichverteilten Zufallszahlen soll die Klasse die Möglichkeit bieten, Zufallszahlen zu produzieren, die mit unterschiedlicher Wahrscheinlichkeit auftreten. Die Klasse soll zwei Methoden addWeightedRange und nextNumber besitzen, die wie folgt verwendet werden:

Wird der Zufallszahlengenerator beispielsweise durch die beiden Aufrufe addWeightedRange(1,5,10) und addWeightedRange(21,30,50) initialisiert, so wird er Zufallszahlen aus den Bereichen 1 bis 5 und 21 bis 30 liefern. Die Wahrscheinlichkeit, daß eine Zahl aus dem Bereich 21 bis 30 auftritt, ist dabei fünfmal so hoch wie die, daß die Zahl aus dem Bereich 1 bis 5 genommen wird. Überlegen Sie sich auch eine geeignete Teststrategie und implementieren Sie eine Routine zum Testen der Klasse WRandom.

29. [C] (du). Auf UNIX-Anlagen gibt es ein Programm Programm du, das den Platzbedarf eines Verzeichnisses durch Summierung der Größe aller darin enthaltenen Dateien ermittelt. Dabei werden rekursiv auch Unterverzeichnisse und die darin enthaltenen Dateien und Unterverzeichnisse usw. berücksichtigt. Schreiben Sie ein Programm, das den Namen eines Verzeichnisses über die Kommandozeile übergeben bekommt und analog zu du den Platzbedarf für dieses Verzeichnis ermittelt.

30. [C] (Eine Nachricht entschlüsseln). Die verschlüsselte Nachricht

"rle gnagon ermer aisae mongase rue usnuhigas tnraumas anwrchta, frsd an eich is eaisam batt zu aisam usgahauanas usgaziafan vanwrsdalt."

soll entschlüsselt werden. Über den Text und seine Verschlüsselung ist folgendes bekannt:

Beispiel:

Die fünf häufigsten Buchstaben des Wortes "hoechstgeschwindigkeit" sind beispielsweise (h, e, i, c, s). Werden diese zu (i, h, s, e, c) vertauscht, so ergibt sich die Verschlüsselung "ioheictghceiwsndsgkhst".

Schreiben Sie ein Programm, das Sie beim Entschlüsseln der Nachricht unterstützt. Es soll die häufigsten Buchstaben ermitteln und die Möglichkeit bieten, diese interaktiv im Dialog zu vertauschen. Das jeweilige Entschlüsselungsergebnis soll auf dem Bildschirm angezeigt werden.
Aufg1130.java

31. [C] (Master Mind). Ein ebenfalls sehr bekanntes und oft gespieltes Spiel ist MasterMind (auch unter dem Namen Superhirn bekannt). Obwohl die Regeln sehr leicht zu erlernen sind, bietet es eine Vielzahl von taktischen Varianten.

Ihre Aufgabe besteht nun darin, das MasterMind-Spiel zu implementieren. Der Computer ist dabei Ihr Gegner, denkt sich eine Steckerstellung aus und bewertet jeden Ihrer Züge durch Vergabe weißer oder schwarzer Stecker. Verwenden Sie anstelle von Farben der Einfachheit halber die Ziffern von 0 bis 5. Schreiben Sie das Programm so modular, daß eine spätere Erweiterung auf eine grafische Darstellung möglich ist.

32. [C] (Implementierung einer Klasse IntSet). Verwenden Sie die Klasse Vector dazu, eine Klasse IntSet zu implementieren, die eine Menge von Ganzzahlen darstellt. Im Gegensatz zu einem normalen Vektor darf in einer Menge natürlich kein Element mehrfach vorkommen, was durch die Eingaberoutinen sichgestellt werden muß. Implementieren Sie die folgenden Methoden:

33. [C] (Bearbeiten von ini-Dateien). Während der Versionen 3.0 bis 3.11 waren ini-Dateien das wichtigste Instrument zur Konfiguration von Programmen unter MS-Windows. Mit Windows 95 haben sie durch die Einführung der Registry (einer Art hierarchischer Datenbank für Konfigurationseinträge) an Bedeutung verloren, werden aber trotzdem noch von verschiedenen Programmen verwendet. Das Format der ini-Dateien kann wie folgt beschrieben werden:

Ein Beispiel für eine gültige ini-Datei ist der folgende Auszug aus der System-Konfigurationsdatei system.ini:
[boot]
system.drv=system.drv
drivers=mmsystem.dll power.drv
user.exe=user.exe
gdi.exe=gdi.exe
sound.drv=mmsound.drv
dibeng.drv=dibeng.dll
comm.drv=comm.drv
shell=Explorer.exe

[386Enh]
ebios=*ebios
device=*vshare
device=*dynapage
device=*vcd
device=*vpd

[mci]
cdaudio=mcicda.drv
sequencer=mciseq.drv
waveaudio=mciwave.drv
Das Windows-API stellt die Funktion GetProfileString und GetPrivateProfileString zur Verfügung, um einen beliebigen Eintrag aus einer ini-Datei zu lesen. Sie haben nun die Aufgabe, eine etwas vereinfachte Version dieser Funktion sowie eine Methode zum Schreiben eines Konfigurationseintrags zu programmieren. Erstellen Sie dazu eine Klasse IniFile, und implementieren Sie die folgenden Methoden:

34. [C] (Hexdump). Schreiben Sie ein Programm, das eine als Argument in der Kommandozeile übergebene Datei als Hexdump ausgibt. Soll beispielsweise eine Datei mit dem Inhalt

The hex dump output
should look like this
als Hexdump ausgegegen werden, so ist die Ausgabe des Programmes:
0000  54 68 65 20 68 65 78 20-64 75 6D 70 20 6F 75 74   The hex dump out
0010  70 75 74 0D 0A 73 68 6F-75 6C 64 20 6C 6F 6F 6B   put..should look
0020  20 6C 69 6B 65 20 74 68-69 73 2E 0D 0A             like this..
Der Inhalt der Datei wird dabei zeilenweise zu jeweils 16 Byte mit ihrem hexadezimalen Bytewert und als ASCII-Text (nicht-druckbare Zeichen werden als "." dargestellt) ausgegeben. Die erste Spalte enthält die Adresse des ersten Bytes der Ausgabezeile, sie wird nach jeder Zeile um 16 Bytes weitergezählt.
Aufg1134.java

35. [C] (LineNumberWriter). Kapitel 13 erklärt, wie eigene Writer-Klassen angelegt und als Filter verwendet werden können. Entwickeln Sie eine Klasse LineNumberWriter, die als Ausgabefilter verwendet werden kann, um die Ausgabe eines Writers mit Zeilennummern zu versehen. Entwerfen Sie auch ein Beispielprogramm, an dem Sie die Funktionsfähigkeit Ihrer Klasse demonstrieren.

36. [D] (Eliza). Eines der ersten und ältesten Programme, das sich mit dem Thema Künstliche Intelligenz auseinandersetzte, war Eliza von Joseph Weizenbaum. Seine Aufgabe war es, mit Hilfe einer geschickten Fragetechnik die Rolle eines Psychiaters in einem Arzt-/Patientendialog zu übernehmen. Eliza hat mittlerweile Kultstatus und ist eine berühmt gewordene Inkarnation des Turing-Tests, bei dem es darum geht, einen menschlichen Gesprächspartner geschickt darüber im Unklaren zu lassen, ob sein Gesprächspartner ein Mensch oder eine Maschine ist.

Interessanterweise kann man eine brauchbare Implementierung von Eliza bereits mit relativ einfachen Mitteln erstellen. Das Programm beginnt den Dialog mit der Frage "Guten Tag. Bitte beschreiben Sie mir Ihr Problem". Die Antwort des Anwenders muß ein einfacher Satz sein, der nach folgenden Regeln beantwortet wird:

Regel 1
Falls die Anwort mit "Auf wiedersehen", "Tschüß" oder "Goodby" beginnt, antwortet Eliza "Auf wiedersehen, Sie schulden mir 150 DM".
Regel 2
Falls andernfalls die Antwort ein emotionales Schlüsselwort wie "Mutter", "Vater", "Sex" oder "Tod" enthält, reagiert das Programm mit einer vorprogrammierten Antwort, die zu dem Schlüsselwort paßt. Auf das Wort "Mutter" könnte das Programm beispielsweise mit Antworten der Art
Regel 3a
Falls in der Antwort des Anwender keine Schlüsselworte enthalten sind, sucht Eliza nach anderen Worten, die für die Antwort interessant sein könnten. Die gefundenen merkt es sich, um sie in Regel 3b anwenden zu können.
Regel 3b
Nachdem Regel 3a verarbeitet wurde, untersucht Eliza die dort gesammelten Worte. Falls mindestens 2 oder 3 davon vorhanden sind, baut Eliza eine Antwort aus einer zufälligen Aswahl daraus zusammen. Falls die Worte beispielsweise "Schule" und "Tochter" sind, könnte die Antwort lauten "Beschreiben Sie die Verbindung zwischen Schule und Tochter" oder "Sind Sie besorgt über die Spannung zwischen Tochter und Schule?". Falls die Anwendung der Regeln 3a und 3b zu keinem Erfolg führt, fährt das Programm mit jeweils 50%iger Wahrscheinlichkeit entweder mit Regel 4 oder Regel 5 fort.
Regel 4
Eliza sucht nun in der Antwort des Anwenders nach Pronomen, also Wörtern wie "mir", "dir", "mein" oder "dein". Falls ein solches gefunden wird, dreht Eliza seine Bedeutung um und formuliert eine Frage daraus. Aus "Mein Vater war ein Mörder" wird so "Ihr Vater war ein Mörder? Können Sie das näher erklären?".
Regel 5
Falls auch Regel 4 fehlschlägt (oder mit 50%iger Wahrscheinlichkeit nach einem Fehlschlag von Regel 3), gibt Eliza einfach eine Universalantwort des Typs
Es ist überraschend, wie echt ein solcher Dialog trotz der sehr einfachen Regeln auf den ersten Blick wirken kann, wenn die vordefinierten Antworten und Schlüsselworte sorgfältig gewählt wurden. Obwohl das Programm nicht einen Funken Intelligenz hat, sondern sich wie ein geschickt programmierter Papagei verhält, lassen sich viele Anwender durch den Verlauf des Dialogs täuschen und halten das Gespräch für authentisch. Versuchen Sie, Eliza nach der hier beschriebenen Methode zu implementieren und testen Sie sein Verhalten. Geben Sie eine Beispielsitzung an.

37. [D] CPU-Emulation mit MINIPU. Jeder Programmierer weiß, daß die von ihm produzierten Programme in die Maschinensprache des Prozessors übersetzt werden müssen, um von diesem ausgeführt werden zu können. Nur wenige wissen allerdings, wie man einen Prozessor in Assembler oder gar Maschinensprache programmiert. Wir wollen uns in dieser Aufgabe damit beschäftigen, eine kleine Prozessorsimulation und die zugehörigen Werkzeuge zu entwickeln.

Unser Prozessor, den wir im folgenden MINIPU nennen wollen, hat einen sehr einfachen Aufbau. Er besitzt ein einziges universell verwendbares Register (den Akkumulator ACC) und 4096 16 Bit breite Speicherworte, die für Programmcode und Daten verwendet werden können. Weiterhin gibt es einen ebenfalls 4096 Byte großen Stack, der Rücksprungadressen und Akkumulatorinhalte aufnehmen kann. Der Stack wächst von unten nach oben. Er wird über den Stackpointer SP referenziert, der auf das nächste freie Element zeigt. Unmittelbar nach der Initialisierung von MINIPU enthält SP den Wert 0.

Jedes Befehlswort ist 16 Bit lang. Die ersten 4 Bit kodieren den Befehlstyp, die übrigen 12 Bit bleiben entweder unbenutzt oder werden als Adresse verwendet. Der Programmablauf wird durch den Programmzähler PC gesteuert, der jeweils auf den nächsten auszuführenden Befehl zeigt. Die Ausführung eines MINIPU- Programms beginnt immer an Adresse 0. Soll am Anfang des Programms ein zusammenhängender Bereich für Daten reserviert werden, so muß als erster Befehl ein Sprung an den Anfang des Code-Bereichs verwendet werden.

Die folgende Tabelle gibt einen Überblick über den Befehlsvorrat von MINIPU:

MnemoCodierungBedeutung
HLT0000 ------------ Anhalten des Programms
LDA0001 xxxxxxxxxxxx Laden des Akkumulators mit dem Inhalt der Speicheradresse (ACC := (x))
STA0010 xxxxxxxxxxxx Speichern des Akkumulatorinhalts an der angegebenen Adresse ((x) := ACC)
ADD0011 xxxxxxxxxxxx Addieren eines Speicherworts zum Akkumulator (ACC := ACC + (x))
SUB0100 xxxxxxxxxxxx Subtrahieren eines Speicherworts vom Akkumulator (ACC := ACC - (x))
MUL0101 xxxxxxxxxxxx Multiplizieren eines Speicherwortsmit dem Akkumulator (ACC := ACC * (x))
DIV0110 xxxxxxxxxxxx Dividieren des Akkumulators durch ein Speicherwort (ACC := ACC / (x))
JMP0111 xxxxxxxxxxxx Sprung zur Adresse x (PC := x)
JZE1000 xxxxxxxxxxxx Sprung zur Adresse x falls der Akkumulator 0 enthält (PC := x falls ACC == 0)
JGT1001 xxxxxxxxxxxx Sprung zur Adresse x falls der Akkumulator einen Wert größer 0 enthält (PC := x falls ACC > 0)
PSH1010 ------------ Den Akkumulatorinhalt oben auf dem Stack ablegen ((SP) := ACC; SP := SP + 1)
POP1011 ------------ Das oberste Element vom Stack entfernen und im Akkumulator ablegen (SP := SP - 1; ACC := (SP))
JSR1100 xxxxxxxxxxxx Den Inhalt des Programmzählers oben auf dem Stack ablegen ((SP) := PC; SP := SP + 1)
RET1101 ------------ Das oberste Element vom Stack entfernen und es als Programmzähler verwenden (SP := SP - 1; PC := (SP))
INP1110 xxxxxxxxxxxx Ein 16-Bit-Wort als Dezimalzahl über die Tastatur einlesen und im Akkumulator ablegen. An Adresse x steht ein String, der zur Beschriftung der Eingabe verwendet werden kann.
OUT1111 xxxxxxxxxxxx Den Inhalt des Akkumulators als Dezimalzahl auf dem Bildschirm ausgeben. An Adresse x steht ein String, der zur Beschriftung der Ausgabe verwendet werden kann.
MINIPU kennt lediglich zwei Datentypen. Einer von beiden ist ein 16- Bit-Wort, das für alle internen Berechnungen und zur Ein-/Ausgabe verwendet wird. Bei dem anderen handelt es sich um eine Zeichenkette, die bei den Ein-/Ausgabebefehlen zur besseren Lesbarkeit mit ausgegeben wird. Eine Zeichenkette ist nach dem Verständnis von MINIPU eine Folge von Bytes, die mit einem Nullbyte abgeschlossen wird. Jede Speicherzelle kann daher zwei Zeichen der Zeichenkette aufnehmen, von denen das erste im High- und das Zweite im Low-Byte steht.

Ihre erste Aufgabe besteht nun darin, einen Emulator für den MINIPU- Prozessor zu schreiben. Schreiben Sie dazu eine geeignete Klasse MiniPU, die alle benötigten Datentrukturen und Funktionen implementiert. Die Methode startProgram soll dabei den Interpreter starten und das als Array übergebene Programm ausführen. Führen Sie den Programmlauf als separaten Thread aus und ermöglichen Sie es, ihn mit Hilfe eines Aufrufs von stopProgram zu unterbrechen. Integrieren Sie den Emulator in eine Testumgebung, die das interaktive Starten und Stoppen erlaubt und die Möglichkeit bietet, ein MINIPU-Maschinenprogramm von der Festplatte in den Speicher zu lesen.

Da die Programmierung in Maschinencode sehr aufwendig ist, sollten Sie weiterhin einen Assembler für MINIPU-Code entwickeln. Ein Assembler ist ein Programm, das mnemonische Befehlscodes in MINIPU-Maschinencode übersetzt. Er soll beispielsweise das folgende Programm, das in MINIPU-Assembler geschrieben wurde, in die interne Maschinendarstellung übersetzen:

0000 JMP 0005  ;Sprung an den Programmanfang
0001 DW  0002  ;Konstante 2
0002 DW  'Za'  ;String "Zahl "
0003 DW  'hl'
0004 DW  ' \0'
0005 INP 0002  ;Zahl einlesen
0006 MUL 0001  ;Mit 2 multiplizieren
0007 OUT 0002  ;Zahl ausgeben
0008 HLT       ;Programmende
Statten Sie den Assembler auch mit Fähigkeiten zum Lesen und Schreiben aus einer Datei aus, um die erstellten Programme auf Festplatte ablegen zu können. Erweitern Sie die Klasse MiniPU zusätzlich um die Fähigkeit, MINIPU-Programme aus einer Datei zu lesen und auszuführen.

38. [D] (Vokabeltrainer). Eine einfache und wirksame Methode, Vokabeln zu lernen, besteht darin, das Karteikastenprinzip anzuwenden. Dabei steht jede Vokabel auf einer Karteikarte, die sich in einem von vier Karteikästen befindet. Auf der Rückseite der jeweiligen Karte steht ihre Übersetzung. Zu Beginn des Lernvorgangs befinden sich alle Karten in Kasten eins. Nun werden die Karten aus diesem Kasten nacheinander abgefragt. Bei einer richtigen Antwort landet die betreffende Karte in Kasten zwei, bei einer falschen Antwort verbleibt sie in Kasten eins. Nach dem ersten Durchlauf befinden sich also alle Karten, die richtig übersetzt wurden, in Kasten zwei, während alle fehlerhaften in Kasten eins geblieben sind.

Nach dem gleichen Prinzip werden nun in weiteren Durchgängen die vier Karteikästen mehrfach durchgearbeitet. Immer, wenn eine Vokabel richtig übersetzt wurde, wandert sie in den nächsten Kasten, andernfalls geht es einen Kasten zurück. Befinden sich alle Karteikarten in Kasten vier, so wurden jede mindestens dreimal nacheinander richtig übersetzt und man kann davon ausgehen, daß das Erlernte "sitzt".

Wichtig für den Lernerfolg ist eine geschickte Strategie für die Reihenfolge, in der die Kästen bearbeitet werden. Bei insgesamt 10 Durchgängen wären etwa die Kastenfolgen 1-2-3-1-2-3-1-4-2-1 oder 1-1-2-1-3-2-1-2-3-4 denkbar. Schreiben Sie ein Programm, das es ermöglicht, Vokabeln mit Hilfe des Karteikastenprinzips zu lernen. Speichern Sie die zu lernenden Vokabeln in einer Textdatei, die beim Aufruf des Programms geladen und beim Beenden mit den veränderten Kasteninformationen zurückgeschrieben wird. Das Programm soll folgende Features bieten:

39. [D] (edlin). Einer der ersten Texteditoren, die unter MS-DOS zur Verfügung standen, war EDLIN. Wegen seiner zeilenorientierten Arbeitsweise und der umständlichen Bedienung war er bei vielen Anwendern gefürchtet. Seit der Version 5.0 ist er aus dem Betriebssystem verschwunden und wurde durch den leistungsfähigeren Texteditor EDIT ersetzt.

Ihre Aufgabe besteht nun darin, EDLIN wieder zum Leben zu erwecken und eine stark vereinfachte Version nachzuprogrammieren. Da es an dieser Stelle zu aufwendig wäre, alle Details der echten EDLIN-Implementierung zu implementieren, gehen Sie entsprechend der folgenden, vereinfachten Spezifikation vor:

Obwohl die Fähigkeiten und der Bedienungskomfort von EDLIN nicht mit modernen Texteditoren konkurrieren kann, lassen sich alle wesentlichen Veränderungen an einer Textdatei bereits mit den hier implementierten Kommandos ausführen. Naheliegende Erweiterungen dieser Aufgabe würden darin bestehen, weitere Funktionen, wie Suchen/Ersetzen, Kopieren/Einfügen, Einfügen des Inhalts einer externen Datei, usw. zu realisieren.
© 1995-2004 Guido Krüger - Last updated 31 Dec 2003 - Back to top-level page