Beitrag zur Lösung der Aufgaben des 22. Bundeswettbewerbs für

Transcription

Beitrag zur Lösung der Aufgaben des 22. Bundeswettbewerbs für
Beitrag zur Lösung der Aufgaben des 22.
Bundeswettbewerbs für Informatik
Ronny Harbich
Stendal, im Winter 2003
2
Inhaltverzeichnis
ALLGEMEINE HINWEISE...................................................................................................4
Voraussetzungen ............................................................................................................................................................... 4
Lösungsprogramme.......................................................................................................................................................... 4
Verstehen des Quelltexts .................................................................................................................................................. 4
AUFGABE 1 ........................................................................................................................5
Lösungsidee ....................................................................................................................................................................... 5
Programm-Dokumentation.............................................................................................................................................. 6
Der Backtracking-Algorithmus....................................................................................................................................... 6
Nutzungsgrenzen ............................................................................................................................................................ 6
Hinweise ......................................................................................................................................................................... 6
Programmablauf-Protokoll.............................................................................................................................................. 6
Aufbau ............................................................................................................................................................................ 7
Funktionsweise ............................................................................................................................................................... 7
Probeläufe des Programms ............................................................................................................................................. 8
Programm-Text............................................................................................................................................................... 11
AUFGABE 2 ......................................................................................................................14
Lösungsidee ..................................................................................................................................................................... 14
Programm-Dokumentation............................................................................................................................................ 14
Hinweise ....................................................................................................................................................................... 15
Nachteile der Strategie.................................................................................................................................................. 15
Erweiterungen des Zellenservers .................................................................................................................................. 15
Programmablauf-Protokoll............................................................................................................................................ 15
Aufbau .......................................................................................................................................................................... 15
Funktionsweise ............................................................................................................................................................. 17
Programm-Text............................................................................................................................................................... 17
AUFGABE 3 ......................................................................................................................24
Lösungsidee ..................................................................................................................................................................... 24
Programm-Dokumentation............................................................................................................................................ 24
Nutzungsgrenzen .......................................................................................................................................................... 25
Programmablauf-Protokoll............................................................................................................................................ 25
Aufbau .......................................................................................................................................................................... 25
Funktionsweise ............................................................................................................................................................. 25
Probeläufe des Programms ........................................................................................................................................... 26
Programm-Text.............................................................................................................................................................. 27
AUFGABE 4 ......................................................................................................................35
3
Lösungsidee ..................................................................................................................................................................... 35
Programm-Dokumentation............................................................................................................................................ 36
Vektor-Klasse ............................................................................................................................................................... 36
Fisch-Klasse.................................................................................................................................................................. 36
Darstellung des Modells ............................................................................................................................................... 37
Nutzungsgrenzen .......................................................................................................................................................... 37
Programmablauf-Protokoll............................................................................................................................................ 37
Aufbau .......................................................................................................................................................................... 38
Probeläufe des Programms ........................................................................................................................................... 39
Programm-Text.............................................................................................................................................................. 40
AUFGABE 5 ......................................................................................................................42
Schwächen einer Wort-für-Wort-Übersetzung............................................................................................................ 42
Leistungsfähigkeit von Sprachübersetzungsprogrammen .......................................................................................... 42
Fundamentale Schwierigkeiten maschineller Übersetzung ........................................................................................ 44
4
Allgemeine Hinweise
Voraussetzungen
Es wurden Lösungsvorschläge für die Aufgaben 1, 2, 3 und 4 angefertigt. Die für die ersten drei Aufgaben
verwendete Programmiersprache ist Microsoft Visual Basic .NET. Um die Lösungsprogramme ausführen zu
können, muss die Microsoft .NET Framework 1.1 auf einer Microsoft Windows 98, SE, NT4, 2000, XP oder
2003 Plattform installiert sein. Es wird an dieser Stelle ausdrücklich empfohlen, Windows XP oder 2003 zu
benutzen, da alle Programme nur auf diesen beiden Plattformen ausgiebig getestet worden sind. Auf der
beiliegenden CD befindet sich die Installationsroutine zu .NET Framework 1.1 im „dotNetFramework“Verzeichnis.
Die Dokumentation ist in ihrer Gesamtheit als PDF-Datei auf der CD im Verzeichnis „Dokumentation“
vorhanden. Um sie betrachten zu können, muss Adobe Reader 6.0 installiert sein. Diese kostenlose
Software befindet sich ebenfalls auf CD im Verzeichnis „Adobe Reader 6.0“.
Lösungsprogramme
Alle Anwendungen sind auf der CD im Verzeichnis „Lösungsprogramme“ vorhanden, wo sie mit Hilfe ihrer
Namen in verschiedene Ordner unterteilt worden sind. In einem solchen Ordner befindet sich der gesamte
kompilierfähige Quelltext und eine Ausführbare Programmdatei im Unterverzeichnis „bin“. Der Quelltext
sollte mit Microsoft Visual Studio .NET 2003 betrachtet werden.
Verstehen des Quelltexts
Generell sollte der mit Microsoft Visual Basic .NET erstellte Quelltext der Lösungsprogramme leicht verständlich sein. Kommt es aber trotzdem vor, dass einzelne Schlüsselwörter oder Umgebungsmethoden
unklar sind, gibt es die Möglichkeit im Internet auf http://msdn.microsoft.com/ Hilfe zu erlangen.
5
Aufgabe 1
Lösungsidee
Es soll der maximale Gewinn pro Tausch einer Währung aus einer Wechselkurstabelle, wie in Tabelle 1
veranschaulicht, heraus gesucht werden. Um dies zu verwirklichen, müssen folgende Schritte durchgeführt
werden:
1. Zunächst einmal wird die angegebene
Nr. Ausgangswährung
Endwährung
Kurs
Ausgangswährung in der Tabelle gesucht.
Bärentaler
Mausmark
1 : 2,70
1
2. Sobald diese Währung gefunden wurde,
2
Entenpeseta
Krötendollar
1 : 0,75
wird die Nummer des Tauschs in einer
3
Entenpeseta
Froschkrone
1 : 0,70
Liste notiert und die der Währung zu4
Entenpeseta
Wolfspfund
1
: 1,80
gehörige Endwährung in der Spalte der
5
Froschkrone
Krötendollar
1
: 1,10
Ausgangswährungen gesucht.
6
Froschkrone
Entenpeseta
1
: 1,10
3. Nun erfolgt die Wiederholung des zweiten
7
Krötendollar
Wolfspfund
1
:
2,00
Schritts solange, bis eine Endwährung mit
8
Krötendollar
Wolfspfund
1
:
1,90
der eingegebenen Ausgangswährung
9
Krötendollar
Froschkrone
1
:
1,05
übereinstimmt.
10
Mausmark
Entenpeseta
1 : 1,30
4. Anschließend ist es Notwendig den
11
Mausmark
Krötendollar
1 : 0,90
Gewinn pro Tausch eines Tauschvorgangs
12
Wolfspfund
Entenpeseta
1 : 0,70
zu errechnen. Dies geschieht, indem die
13
Wolfspfund
Bärentaler
1 : 0,20
Wechselkurse der Endwährungen der
einzelnen in der Liste nummerierten
14
Wolfspfund
Mausmark
1 : 0,50
Tausche miteinander multipliziert, mit Eins
Tabelle 1 - Wechselkurse
subtrahiert und durch die Anzahl der Tausche dividiert werden.
5. Da es möglich ist, dass mehrere gleiche Ausgangswährungen auftreten, muss nach dem Finden eines
Tauschvorgangs zu denjenigen Ausgangswährungen, welche mehrfach vorgekommen sind,
zurückgekehrt werden. Von dort aus werden dann jeweils die Schritte 2,3 und 4 ausgeführt. Während
des Zurückkehrens ist darauf zu achten, dass kein Tausch mehr als einmal von statten geht, da es sonst
zu einem immer wieder periodisch auftretenden Tauschen kommen würde.
6. Nachdem nun alle möglichen Tauschvorgänge überprüft worden sind, wird aus den gültigen
Tauschvorgängen derjenige herausgesucht, der einen maximalen Gewinn pro Tausch aufweist.
Beispiel
Es soll der maximale Gewinn pro Tausch für Entenpeseta herausgefiltert werden:
Schritt 1: Die Währung Entenpeseta wird bei Tausch Nr. 2 gefunden.
Schritt 2: Die Endwährung ist Krötendollar.
Schritt 3: Krötendollar wird gesucht und bei Tausch Nr. 7 gefunden
Wolfspfund wird gesucht und bei
Tausch Nr. 12 gefunden
Endwährung bei Tausch Nr. 12 ist Entenpeseta und entspricht somit der
Ausgangswährung.
Schritt 4: Berechnung des Gewinns pro Tausch (in %) für den letzteren Tauschvorgang:
( 0, 75 2, 00 0, 70 1) 100 = 5 %
3
3
Schritt 5: Der letztere Tauschvorgang weist unter anderem bei Tausch Nr. 7 mehrere Möglichkeiten auf.
Statt bei Tausch Nr. 12 aufzuhören, wird nun ein Schritt zurückgegangen und von Tausch Nr. 7 auf
Tausch Nr. 13 verwiesen.
Schritt 2: Die Endwährung Bärentaler wird bei Tausch Nr. 1 gefunden.
Schritt 3: Mausmark wird gesucht und bei Tausch Nr. 10 gefunden
Endwährung bei Tausch Nr. 10 ist
Entenpeseta und entspricht somit der Ausgangswährung.
Schritt 4: Berechnung des Gewinn pro Tausch (in %) für den letzteren Tauschvorgang:
( 0, 75 2, 00 0, 20 2, 70 1, 30 1) 100 = 53 %
5
50
An dieser Stelle wird aufgrund der vielen Möglichkeiten kein weiterer Tauschvorgang dargestellt.
Schritt 6: Schlussendlich findet eine Überprüfung des Gewinns pro Tausch der zwei gefundenen
gültigen Tauschvorgänge statt. Der erst gefundene Tauschvorgang zeigt einen Gewinn pro Tausch von
5
% auf, wohingegen der zweit gefundene Tauschvorgang nur einen geringeren Gewinn pro Tausch
3
53
% verzeichnen kann. Somit ist der erste Tauschvorgang derjenige, welcher den vorerst
von
50
maximalen Gewinn pro Tausch aufzeigt.
6
Bei diesem Beispiel ist darauf zu achten, dass nicht alle Möglichkeiten in betracht gezogen worden sind
und somit der gefundene Tauschvorgang nicht den realen maximalen Gewinn pro Tausch der Tabelle 1
darstellt!
Programm-Dokumentation
Das Programm zur Aufgabe 1 verwendet, wie in der Lösungsidee bereits erkennbar, einen rekursiven
Algorithmus. Im Speziellen ist dieser ein so genannter Backtracking-Algorithmus. Als Backtracking werden in
der Regel Algorithmen bezeichnet, welche durch Ausprobieren aller Möglichkeiten bestimmte Probleme
lösen können.
Bevor der fertige Backtracking-Quelltext beschrieben wird, soll ein Ablaufprogramm für ein besseres
Verständnis sorgen:
Der Backtracking-Algorithmus
- Rekursive Prozedur (Währungsname, Nummernliste)
- Schleife, die alle Ausgangswährungen aufzählt
- Wenn aktuelle Ausgangswährung gleich Währungsname ist, dann
- Wenn aktuelle Tauschnummer nicht in Nummernliste vorhanden ist, dann
- Füge aktuelle Ausgangswährung in Nummernliste ein
- Wenn Endwährung des aktuellen Tauschs mit eingegebener Ausgangswährung übereinstimmt,
dann
- Berechne Gewinn pro Tausch des aktuellen Tauschs
- Wenn aktueller Gewinn pro Tausch größer ist, als maximaler Gewinn pro Tausch, dann
- Maximaler Gewinn pro Tausch entspricht aktuellem Gewinn pro Tausch
- Ende
- Ansonsten
- Rekursiver Aufruf mit Argumenten: Endwährung des aktuellen Tauschs, Kopie von Nummernliste
- Ende
- Backtrack: entferne letzten Eintrag aus Nummernliste (hier wird ein Schritt zurückgegangen)
- Ende
- Ende
- Ende (Rekursionsende)
- Ende
Nutzungsgrenzen
Es kann eventuell zum Versagen der rekursiven Prozedur kommen, da der Stapelspeicher nicht unbegrenzt
groß ist. Eine System.StackOverflowException wäre die Folge.
Hinweise
Der Lösungsalgorithmus arbeitet mit Zeichenkettenvergleiche, das heißt es werden keine für die Währungsnamen repräsentativen Zahlen verglichen, sondern die Währungsnamen als solche selbst.
Programmablauf-Protokoll
Im Folgenden soll nun dem Leser Aufbau und Funktionsweise des Programms verdeutlicht werden.
7
Aufbau
Abbildung 1 - Aufbau des Lösungsprogramms
Das Lösungsprogramm ist, wie in Abbildung 1 - Aufbau des LösungsprogrammsAbbildung 1 erkennbar, in
zwei verschiedenen Bereichen aufgeteilt worden. Der Linke ist für das Sammeln, Laden und Speichern von
Wechselkursdaten verantwortlich, wohingegen der Rechte ausschließlich Rechen- und Ausgabeaufgaben
übernimmt.
Der Daten-Bereich enthält die Tabelle, in der alle Wechselkurse enthalten sind. Weiterhin befinden sich dort
Dateneingabe-Felder, die zum Erstellen und Verändern der Wechselkurse notwendig sind. Unter diesen
Feldern ist eine ganze Reihe von Schaltflächen vorhanden, die unter anderem für die Bearbeitung der
Tabelle benötigt werden.
Der rechte Bereich ist ähnlich aufgebaut, bietet aber dennoch einen sehr unterschiedlichen Funktionsumfang
an. Von oben beginnend wird zunächst eine weitere Tabelle sichtbar. Diese enthält Ergebnisse, nachdem
ein Rechenvorgang erfolgreich beendet wurde. Außer dieser Tabelle sind ein Datenfeld, ein Status-Bereich
und weitere Schaltflächen zu erkennen.
Funktionsweise
Der Benutzer muss, bevor er einen Rechenvorgang starten möchte, die Wechselkurs-Tablle mit Daten
auffüllen, indem er einen der beiden folgenden Schritte ausführt:
1. Einladen einer Wechselkurs-Tabelle
1.1. Nachdem das Programm gestartet wurde, sind zunächst alle Schaltflächen im Daten-Bereich
deaktiviert. Jetzt muss der Datei-Öffnen-Dialog aufgerufen werden, indem der Benutzer die
Schaltfläche „Laden“ betätigt.
1.2. Anschließend sucht sich der Benutzer eine Tabellen-Datei im XML-Format aus und beendet dann
den Dialog über die „Öffnen“-Schaltfläche. Hinweis: Es wurden bereits Tabellen, wie aus Aufgabe 1,
erzeugt und abgespeichert. Diese befinden sich im Programmverzeichnis.
1.3. An dieser Stelle sollte das Programm die Wechselkurse aus der Datei einlesen und in der Tabelle
im Daten-Bereich darstellen.
2. Erstellen einer benutzerdefinierten Wechselkurs-Tabelle
2.1. Um einen neuen Wechselkursdaten-Eintrag zu erstellen, muss der Benutzer zunächst alle
Datenfelder mit gültigen Informationen ausfüllen. Das bedeutet im Einzelnen:
2.1.1. Die Felder im Bereich „Ausgangswährung“ müssen mit dem Wert und den Namen der
gewünschten Ausgangswährung versehen werden. Dabei ist darauf zu achten, dass dem
Wert-Feld eine Zahl größer Null und dem Name-Feld eine nicht leere Zeichenfolge
übergeben wird. Das Lösungsprogramm überprüft die Eingaben und wird gegebenenfalls
entsprechende Hinweise zur Fehlerbehebung ausgeben.
2.1.2. Analog dazu müssen die Felder der Endwährung ausgefüllt werden.
2.2. Jetzt fügt der Benutzer den Eintrag der Tabelle zu, indem er die „Hinzufügen“-Schaltfläche betätigt.
8
2.3. Nachdem alle gewünschten Einträge erstellt worden sind, wird an dieser Stelle ausdrücklich
empfohlen, die erstellte Wechselkurs-Tabelle abzuspeichern. Die geschieht im Dateil so:
2.3.1. Es muss die Schaltfläche „Speichern“ betätigt werden, um den Datei-Speichern-Dialog zu
öffnen.
2.3.2. Nun sollte der Benutzer einen gültigen Datei-Namen eingeben und die „Speichern“Schaltfläche betätigen. Anschließend schreibt das Programm die Daten der Tabelle in die
ausgewählten XML-Datei.
3. Manipulieren einer Wechselkurs-Tabelle
3.1. Ändern eines Eintrags
3.1.1. Um einen Eintrag zu ändern, wählt der Benutzer den entsprechenden Eintrag aus der
Wechselkurs-Tabelle aus.
3.1.2. Nun variiert er die Informationen in den Datenfeldern. Hierbei ist natürlich auf richtige
Eingabeformate zu achten.
3.1.3. Letztendlich betätigt der Benutzer die „Ändern“-Schaltfläche und das Programm sollte nun
die Änderungen übernehmen.
3.2. Löschen von Einträgen
3.2.1. Der Benutzer wählt einen oder mehere Einträge aus:
3.2.1.1. Um mehrere Eintäge zu markieren, sollte der Benutzer einen Eintrag auswählen, die
UMSCHALT-Taste gedrückt halten und mit der Maus einen weiteren Eintrag selektieren.
Nun sollten alle Einträge zwischen erst- und letztgewähltem Eintrag markiert sein.
3.2.2. Zum Schluss betätigt er die „Löschen“-Schaltfläche und alle markierten Einträge werden
gelöscht.
3.3. Verschieben von Einträgen
3.3.1. Der Benutzer wählt einen oder mehere Einträge aus.
3.3.2. Um Einträge nach oben zu verschieben, muss der Benutzer die „Hoch“-Schaltfläche
drücken.
3.3.3. Möchte der Benutzer Einträge nach unten bewegen, so betätigt er die „Runter“-Schaltfläche.
Nachdem der Benutzer nun eine Tabelle erstellt beziehungsweise eingeladen hat, ist es möglich den
Rechenvorgang in wenigen Schritten vorzubereiten und zu starten:
1. Vorbereiten des Rechenvorgangs
1.1. Bevor der Benutzer den Rechenvorgang startet, ist es erforderlich, die gesuchte Ausgangswährung
in das Währungs-Feld im Bereich „Eintellungen“ einzugeben. Hierbei ist es nicht notwendig auf die
Groß- und Kleinschreibung zu achten.
2. Rechenvorgang starten
2.1. Um den Rechenvorgang zu starten, muss der Benutzer nur noch die „Rechnen“-Schaltfläche betätigen.
2.2. Nun sollte der Benutzer warten, bis der Rechenvorgang abgeschlossen ist. Sei er aus
irgendwelchen Gründen gewillt den Vorgang zu beenden, so muss der Benutzer die „Abbrechen“Schaltfläche bedienen. Details über die Rechnung erfährt er im Status-Bereich, wobei „Pfade“ die
Anzahl der gefundenen gültigen Tauschvorgänge und „Gewinn“ den derzeit maximalen Gewinn pro
Tausch darstellen.
3. Ergebnisse
3.1. Nachdem der Rechenvorgang erfolgreich abgeschlossen wurde, kann der Benutzer die Resultate in
der Tabelle im rechten Bereich des Programms auswerten. Dort ist der Tauschverlauf des
Tauschvorgangs mit dem maximalen Gewinn pro Tausch und weitere Einzelheiten wie Gewinn,
Berechungszeit und so weiter, zu erkennen.
3.2. Die Ergebnisse können über die „In Zwischenablage“-Schaltfläche in die Windows-Zwischenablage
kopiet werden.
Probeläufe des Programms
Zu Beginn wird die Wechselkurs-Tabelle aus Aufgabe 1 verwendet, um das Funktionieren des Programms
nachzuweisen. Dazu wird, wie oben beschrieben, die Datei „Aufgabe 1.xml“ aus dem Programmverzeichnis
in das Lösungsprogramm eingeladen. Anschließend werden die verschiedenen Ausgangswährungen
eingetragen und die jeweiligen Rechenvorgänge gestartet. Nachdem alle Rechnungen abgeschlossen sind,
werden folgende Ergebnisse ausgegeben:
1. Ausgangswährung Bärentaler
Nr.
Ausgangswährung
Endwährung
Kurs
1
Bärentaler
Mausmark
1 : 2,70
10
Mausmark
Entenpeseta
1 : 1,30
4
Entenpeseta
Wolfspfund
1 : 1,80
13
Wolfspfund
Bärentaler
1 : 0,20
9
Währung: Bärentaler
Gewinn: 0,26
Gewinn pro Tausch: 6,59 %
Tausche: 4
Pfade: 473
2. Ausgangswährung Entenpeseta
Nr.
Ausgangswährung
4
Entenpeseta
12
Wolfspfund
Endwährung
Wolfspfund
Entenpeseta
Kurs
1 : 1,80
1 : 0,70
Endwährung
Krötendollar
Froschkrone
Kurs
1 : 1,10
1 : 1,05
Endwährung
Froschkrone
Krötendollar
Kurs
1 : 1,05
1 : 1,10
Endwährung
Entenpeseta
Wolfspfund
Bärentaler
Mausmark
Kurs
1 : 1,30
1 : 1,80
1 : 0,20
1 : 2,70
Endwährung
Entenpeseta
Wolfspfund
Kurs
1 : 0,70
1 : 1,80
Währung: Entenpeseta
Gewinn: 0,26
Gewinn pro Tausch: 13,00 %
Tausche: 2
Pfade: 82
3. Ausgangswährung Froschkrone
Nr.
Ausgangswährung
5
Froschkrone
9
Krötendollar
Währung: Froschkrone
Gewinn: 0,15
Gewinn pro Tausch: 7,75 %
Tausche: 2
Pfade: 229
4. Ausgangswährung Krötendollar
Nr.
Ausgangswährung
9
Krötendollar
5
Froschkrone
Währung: Krötendollar
Gewinn: 0,15
Gewinn pro Tausch: 7,75 %
Tausche: 2
Pfade: 81
5. Ausgangswährung Mausmark
Nr.
Ausgangswährung
10
Mausmark
4
Entenpeseta
13
Wolfspfund
1
Bärentaler
Währung: Mausmark
Gewinn: 0,26
Gewinn pro Tausch: 6,59 %
Tausche: 4
Pfade: 234
6. Ausgangswährung Wolfspfund
Nr.
Ausgangswährung
12
Wolfspfund
4
Entenpeseta
Währung: Wolfspfund
Gewinn: 0,26
Gewinn pro Tausch: 13,00 %
Tausche: 2
10
Pfade: 72
Als nächstes wird eine Tabelle verwendet, die die Wechselkurse der Währungen Euro, US-Dollar,
Schweizer-Franken und Britisch-Pfund vom 02.08.2003 beinhaltet. In diesem Beispiel soll nur der Euro
behandelt werden. Die Tabelle hierzu befindet sich in der „Euro.xml“-Datei im Programmverzeichnis. Das
Programm gibt folgende Lösung aus:
Ausgangswährung Euro
Wechselkurs-Tabelle:
Nr.
Ausgangswährung
1
EUR
2
EUR
3
EUR
4
USD
5
USD
6
USD
7
GBP
8
GBP
9
GBP
10
CHF
11
CHF
12
CHF
Ergebnis-Tabelle:
Nr.
2
9
11
4
Endwährung
USD
GBP
CHF
EUR
GBP
CHF
EUR
USD
CHF
EUR
USD
GBP
Ausgangswährung
EUR
GBP
CHF
USD
Kurs
1 : 1,12
1 : 0,70
1 : 1,54
1 : 0,90
1 : 0,62
1 : 1,38
1 : 1,44
1 : 1,60
1 : 2,21
1 : 0,65
1 : 0,73
1 : 0,45
Endwährung
GBP
CHF
USD
EUR
EUR
USD
GBP
CHF
EU, Euro
US, Dollar
UK, Pound
Swiss, Franc
Kurs
1 : 0,70
1 : 2,21
1 : 0,73
1 : 0,90
Währung: EUR
Gewinn: 0,02
Gewinn pro Tausch: 0,41 %
Tausche: 4
Pfade: 99
Ein weiteres Beispiel soll zeigen, dass das Lösungsprogramm auch mit Wechselkurs-Tabellen
zurechtkommen kann, in welchen Tauschvorgänge bestimmter Währungen nicht gewinnbringend
gewechselt werden können. Die Tabelle hierzu befindet sich in der „Kein Gewinn.xml“-Datei im Programmverzeichnis. Das Programm gibt folgende Lösung aus:
Ausgangswährung Entenpeseta
Wechselkurs-Tabelle:
Nr.
Ausgangswährung
1
Bärentaler
2
Entenpeseta
3
Entenpeseta
4
Entenpeseta
5
Froschkrone
6
Froschkrone
7
Krötendollar
8
Krötendollar
9
Krötendollar
10
Mausmark
11
Mausmark
Ergebnis:
Währung: Entenpeseta
Endwährung
Mausmark
Krötendollar
Froschkrone
Wolfspfund
Krötendollar
Entenpeseta
Wolfspfund
Wolfspfund
Froschkrone
Entenpeseta
Krötendollar
Kurs
1 : 2,70
1 : 0,75
1 : 0,70
1 : 1,80
1 : 1,10
1 : 1,10
1 : 2,00
1 : 1,90
1 : 1,05
1 : 1,30
1 : 0,90
11
Gewinn: 0,00
Gewinn pro Tausch: 0,00 %
Tausche: 0
Pfade: 3
Das Resultat ist so zu interpretieren, dass zwar drei gültige Tauschvorgänge vorhanden sind aber keiner
dieser Gewinne bringen würde.
Programm-Text
Zunächst einmal wurde eine Klasse erstellt, die einen Tausch mit seinen zugehörigen Daten beschreibt:
' Tausch-Klasse
Public Class ListViewItemEx
' private Variablen
Private p_FromName As String
Private p_ToName As String
Private p_FromValue As Single
Private p_ToValue As Single
'
'
'
'
Ausgangswährungsname
Endwährungsname z.B.
Ausgangswährungswert
Endwährungswert z.B.
' öffentlich Eigenschaften
Public Property FromName() As String
End Property
Public Property ToName() As String
End Property
Public Property FromValue() As Single
End Property
Public Property ToValue() As Single
End Property
z.B. Entenpeseta
Krötendollar
z.B. 1
0,75
' verweist auf p_FromName
' verweist auf p_ToName
' verweist auf p_FromValue
' verweist auf p_ToValue
' Konstruktor ohne Argumente
Public Sub New()
End Sub
' Konstruktor mit Argumente
' Werte der Argumente werden den zugehörigen Variablen übergeben
Public Sub New(ByVal fromname As String, ByVal toname As String, _
ByVal fromvalue As Single, ByVal tovalue As Single)
End Sub
' erstellt eine Kopie des Tausch-Objekts
' neues Tausch-Objekt erhält dieselben Eigenschaften wie vorhandenes TauschObjekt
Public Overrides Function Clone() As Object
End Function
End Class
Nun befindet sich im Programm eine Wechselkurstabelle. Das heißt es gibt ein Array, das eine
Auflistung von Tausch-Objekten enthält:
' Array mit Tausch-Objekten vom Typ ListViewItemEx
LstExchangeTable.Items
Der Kern des Programms besteht aus der Backtracking-Prozedur „Calcs“:
' private Klassen-Variablen
Private TH_Count As Integer
' zählt gültige Tauschvorgänge
Private TH_MaxChange As Single
' größter Gewinn pro Tausch
Private TH_Currency As String
' eingegebene Ausgangswährung
Private TH_History As ArrayList
' Liste, welche die Tauschnummern, des
Tauschvorgangs mit maximalen Gewinn pro Tausch, speichert
' Backtracking-Prozedur (rekursiv)
' Argument fromname: aktuell zu suchende Währung
12
' Argument AryHistory: Liste, welche die Tauschnummern des aktuellen
Tauschvorgangs speichert
Private Sub Calcs(ByVal fromname As String, ByVal AryHistory As ArrayList)
Dim i, k As Integer ' Schleifen-Variablen
' Schleife durchläuft alle Ausgangswährungen
For i = 0 To LstExchangeTable.Items.Count – 1
' überprüft, ob Name der aktuellen Ausgangswährung mit fromname
übereinstimmt
If CType(LstExchangeTable.Items(i), _
ListViewItemEx).FromName.ToLower = fromname Then
' überprüft, ob aktuelle Ausgangswährung bereits in AryHistory
enthalten ist, um periodische Tausche zu vermeiden
If AryHistory.IndexOf(i) = -1 Then
' da aktuelle Ausgangswährung nicht in AryHistory enthalten ist,
wird die aktuelle Tauschnummer hinzugefügt
AryHistory.Add(i)
' überprüft, ob aktuelle Endwährung der eingegebenen Ausgangswährung
entspricht
If CType(LstExchangeTable.Items(i), _
ListViewItemEx).ToName.ToLower = TH_Currency Then
Dim p As Single = 1 ' Gesamtgewinn eines Tauschvorgangs
' Schleife durchläuft alle in der Liste AryHistory aufgeführten
Tausche
For k = 0 To AryHistory.Count - 1
With CType(LstExchangeTable.Items(AryHistory(k)), _
ListViewItemEx)
' Multiplikation der Kurswerte der einzelnen Tausche
p *= .ToValue / .FromValue
End With
Next
' Zahl der gültigen Tauschvorgänge wird um 1 erhöht
TH_Count += 1
' ausrechnen des Gewinns pro Tausch
p = (p - 1) / AryHistory.Count
' überprüft, ob aktueller Gewinn pro Tausch größer ist als schon
gefundener Gewinn pro Tausch
If p > TH_MaxChange Then
' kopiert aktuelle Liste der Tauschnummern und übergibt die
Kopie an TH_History
TH_History = AryHistory.Clone
' aktueller Gewinn pro Tausch wird an TH_MaxChange übergeben
TH_MaxChange = p
End If
' überprüft, ob aktuelle Endwährung der eingegebenen Ausgangswährung
nicht entspricht
Else
' hier erfolgt der rekursive Aufruf
' als Argumente werden aktuelle Endwährung und eine Kopie der
aktuellen Liste der Tauschnummern übergeben
13
Calcs(CType(LstExchangeTable.Items(i), _
ListViewItemEx).ToName.ToLower, AryHistory.Clone)
End If
' entferne letzte Tauschnummer (backtrack)
AryHistory.RemoveAt(AryHistory.Count - 1)
End If
End If
Next
' an dieser Stelle ist das Rekursionsende erreicht
End Sub
14
Aufgabe 2
Lösungsidee
Für das Lösen dieser Aufgabe wurde eine Liste der sich aus dem Text ergebenen Sprachformeln angelegt:
1. Grenzwert für ein Schadstoff (bei einem Handy)
2. einige (zufällig ausgewählte Nachrichten an Handys in der Funkzelle)
3. gelegentlich/ innerhalb kurzer Zeit (Handy empfängt Gelb-Nachrichten)
4. einzelne/ etliche (Handy empfängt Gelb-Nachrichten)
5. innerhalb kurzer Zeit (Server empfängt Gelb-Nachrichten)
6. eine größere Anzahl (Server empfängt Gelb-Nachrichten)
Für das Lösungsprogramm dieser Aufgabe wurden die Vorteile der objektorientierten Programmierung
genutzt, indem die Alarmstrategie in eine Klassen-Struktur umgewandelt wurde.
Programm-Dokumentation
Die Realisierung der Alarmstrategie soll im Folgenden beschrieben werden. Für jedes im Strategieplan aufgeführte Objekt wurde eine Klasse erstellt. Dies bedeutet im Einzelnen, dass für Sensoren, Handys, Server
und Nachrichten Klassen erstellt wurden, die an dieser Stelle näher erläutert werden sollen:
Die Sensor-Klasse enthält Informationen über die Art, momentane Konzentration und Grenzwert des ihr
zugeordneten Schadstoffs.
Zur Handy-Klasse gehören eine Liste aller im Handy eingebauten Sensoren, sowie eine Liste der
ankommenden Gelb-Nachrichten von einer Server-Klasse. Weiterhin beinhaltet sie eine Eigenschaft, die
es erlaubt die Anzahl der empfangenen Gelb-Nachrichten, bis ein Alarm ausgelöst wird, zu regulieren.
Da das Handy seinen Benutzer erst dann alarmieren darf, wenn die Anzahl der empfangenen GelbNachrichten in einer bestimmten Zeitspanne erreicht ist, wurde auch hierfür eine Eigenschaft erstellt.
Die für den Server angefertigte Klasse enthält zwei Listen: eine, die die in der Server-Funkzelle befindlichen Handys speichert und eine weitere, die alle Handy-Gelb-Nachrichten speichert. Ähnlich wie bei
der Handy-Klasse sind auch hier Eigenschaften vorhanden, die die Anzahl der Gelb-Nachrichten von
den Handys in einer bestimmten Zeitspanne registrieren, um dann je nach Bedarf einen RotNachrichten-Alarm auszulösen.
Die Nachrichten-Klasse stellt alle Gelb- und Rot-Nachrichten dar, die in den anderen Klassen benötigt
werden.
Als nächstes soll die Kommunikation zwischen den Klassen anhand einer schematischen Darstellung erläutert werden:
Abbildung 2 - Kommunikation zwischen den Klassen
1. Am Anfang eines Szenarios stehen, wie in Abbildung 2 erkennbar, die Sensoren der Handys. Bemerkt
die Sensor-Klasse, dass ein festgelegter Grenzwert eines Schadstoffs überschritten wird, meldet sie es
der Handy-Klasse.
15
2. Sobald die Handy-Klasse eine Meldung von der Sensor-Klasse erhalten hat, warnt sie den Benutzer und
erstellt eine Nachrichten-Klasse.
3. Diese sendet die Handy-Klasse an die ihr zugehörige Server-Klasse. Empfängt diese nun die festgelegte
Anzahl von Nachrichten innerhalb des vorgegebenen Zeitraums, so erstellt sie Rot-Nachrichten,
ansonsten Gelb-Nachrichten.
4. Anschließend sendet die Server-Klasse Nachrichten an die Handy-Klasse.
5. Empfängt eine Handy-Klasse eine Rot-Nachricht oder die ihr vorgegebene Anzahl von Gelb-Nachrichten
in dem ihr festgelegten Zeitintervall, so wird der Benutzer gewarnt.
Hinweise
Letztendlich wurden die Klassen so miteinander Verknüpft, dass das System die ganze Alarmstrategie
darstellt und somit sich jedes Element, wie zum Beispiel ein Handy, mit ihr bedienen lässt. Das
Lösungsprogramm enthält zwar die oben aufgezeigte Klassenstruktur, nutzt aber nicht alle Fähigkeiten
dieser aus. Ein Beispiel hierfür ist das Behandeln von Nachrichten, die von Nachbar-Servern ausgelöst
werden können, in der Server-Klasse.
Nachteile der Strategie
Ein Problem könnte sich ergeben, wenn ein Benutzer künstlich wiederholt Schadstoffe zum Sensor
herbeiführen und wegnehmen würden, sodass es zu einem massenhaften Senden von Gelb-Nachrichten an
den Funkzellen-Server kommt. Dieser würde dann alle anderen Handy-Besitzer sinnloser weise warnen. Ein
Beispiel: Der Benutzer eines Schadstoff erkennenden Handys könnte Gummi verbrennen. Die Sensoren des
Handys müsste er dann nur noch in Nähe der dabei freigesetzten Schadstoffe bringen und schon sendet das
Handy Gelb-Nachrichten. Entfernt der Benutzer anschließend sein Handy aus dem Schadstoffbereich und
bringt es danach wieder in Berührung mit diesen, sendet das Handy erneut Gelb-Nachrichten. Der Server
der Funkzelle empfängt diese Nachrichten und sendet Rot-Nachrichten aus, die aber die anderen HandyBenutzer unnötig veranlasst auf Schadstoffe in ihrer Umgebung zu achten. Als Lösung für dieses Problem
könnte der Server überwachen von welchem Handy eine Gelb-Nachricht ausgeht und bei einem zu häufigen
Empfangen dieser, das Handy ignorieren.
Ein weiteres Problem entstünde, wenn sehr viele Benutzer ihre Handys zum Nachrichtensenden, wie zuvor
beschrieben, veranlassen würden. Dies könnte eine Überlastung des Servers herbeiführen und somit die
Alarmstrategie zum Erliegen bringen. Um dies zu verhindern könnten die Schadstoff erkennenden Handys,
so modifiziert werden, dass ein zu häufiges Senden von Nachrichten unterbunden wird. Aber auch dies ist
keine optimale Lösung, da manche Benutzer die Handys manipulieren könnten.
Erweiterungen des Zellenservers
Die Funkzelle eines Servers kann relativ groß sein. Werden nun durch einen Alarm Rot-Nachrichten an die
Handys dieser Funkzelle gesendet, wissen die Benutzer zwar, dass eine erhöhte Schadstoffbelastung
vorliegt aber nicht genau wo. Um dieses Manko zu beheben, könnte in den Handys ein Empfänger für das
globale Positionbestimmungssystem, GPS, eingefügt werden. Jetzt ist es möglich, dass das Handy nicht nur
Informationen über den Schadstoff sendet, sondern auch Informationen über seine exakte Lage. Die
Aufgabe des Zellenserver besteht nun darin, dass die empfangenen Positionsdaten so ausgewertet werden,
dass bei einem Senden von Rot-Nachrichten nicht nur Schadstoff-Informationen sondern auch ein genaues
Gebiet der aufgetretenen Schadstoffe mit gesendet wird.
Programmablauf-Protokoll
Dem Leser soll an dieser Stelle Aufbau und Funktionsweise des Programms verdeutlicht werden.
Aufbau
Das Lösungsprogramm ist in zwei Bereiche aufgeteilt worden:
16
Abbildung 3 – Simulationsaufbau: Handys mit 10 Sensoren
In Abbildung 3 wird der Bereich „Handys mit 10 Sensoren“ ausschließlich für die Simulation der
Alarmstrategie eines Handys mit Mikrosensoren für 10 verschiedene Schadstoffe genutzt. Zunächst wurde
eine Liste erstellt, welche alle Sensoren mit ihren Schadstoff-Namen und ihren Grenzwerten anzeigt.
Weiterhin befinden sich ein Sensor-Nachricht-Bereich und ein Rot-Nachricht-Bereich auf dem Fenster. Auf
ein Gelb-Nachricht-Bereich wurde in dieser Simulation, aufgrund der Tatsache, dass nur ein Handy zur
Verfügung steht verzichtet. Ferner wurden Auswahl-Felder für Sensor-Nummer und Konzentrations-Wert,
sowie eine „Übernehmen“-Schaltfläche erstellt, um eine Schadstoffbelastungsänderung zu simulieren. Im
rechten Bereich der Anwendung befinden sich zwei Textfelder, die das Empfangen und Senden von
Nachrichten des Servers anzeigen.
Abbildung 4 - Simulationsaufbau: Server mit 100 Handys und 10 Nachbarn
17
Der Bereich „Server mit 100 Handys und 10 Nachbarn“ in Abbildung 4 wird hingegen für die Simulation der
Alarmstrategie eines Zellenservers für 100 in seiner Zelle aktive Handys und 10 benachbarte Zellen
verwendet. Auf der linken Seite befindet sich eine Tabelle mit den Handys und zwei Schaltflächen, um die
Alarmstrategie zu simulieren. Auf der rechten Seite hingegen sind drei Listen angeordnet, welche diverse
Nachrichten anzeigen.
Funktionsweise
Der Benutzer muss, um die Handy-Simulation ablaufen zu lassen, folgende Schritte ausführen:
1. Zuerst wird der Bereich „Handys mit 10 Sensoren“ ausgewählt.
2. Das Programm hat bereits nach dem Start ein Handy mit Sensoren und deren Schadstoffgrenzwerten
zufällig erstellt. In der Simulation werden die Grenzwerte und aktuelle Schadstoffbelastung mit Zahlen
von Eins bis Zehn bewertet. Der Benutzer kann, wenn er es denn wünscht, über die Schaltfläche „Neue
Schadstoffgrenzwerte (zufällig)“ mit Hilfe von Pseudo-Zufallszahlen die Grenzwerte automatisch
verändern lassen.
3. Als nächstes sollte der Benutzer in dieser Simulation eine Sensor-Nummer im Bereich „Sensor“
auswählen und eine neue momentane Schadstoffbelastung einstellen. Anschließend betätigt dieser die
„Übernehmen“-Schaltfläche und wenn nun ein Grenzwert überschritten wird, alarmiert das Handy den
Benutzer mit einer Sensor-Nachricht und mit einem akustischen Signal. Weiterhin erscheint der
Schadstoffname im „Empfangene Gelb-Nachrichten“-Bereich.
4. Um das Senden und Empfangen einer Rot-Nachricht zu simulieren, muss der Benutzer den aktuellen
Konzentrationswert eines Schadstoffs innerhalb von zehn Sekunden, fünfmal verändern.
Wenn der Benutzer das Ausführen einer Server-Simulation wünscht, so sind die nachstehenden Schritte
durchzuführen:
1. Zu Beginn wird der Bereich „Server mit 100 Handys und 10 Nachbarn“ ausgewählt.
2. Es ist schon eine Liste von 100 Handys mit jeweils drei verschiedenen Sensoren und zufällig erstellten
Schadstoffgrenzwerten vorhanden. Möchte der Benutzer die Simulation von neuem starten, so muss er
nur die „Neue Handys erstellen (zufällig)“-Schaltfläche bedienen.
3. An dieser Stelle sollte der Bediener der Anwendung mehrmals die „Konzentrationswerte ändern
(zufällig)“-Schaltfläche benutzen. Dies bewirkt, dass das Programm die Konzentrationswerte der
Schadstoffe bei 5% zufällig ausgewählten Handys ändert.
4. Alle Handys, die jetzt eine Grenzwertüberschreitung feststellen, senden an den Funkzellen-Server eine
Gelb-Nachricht. Das Empfangen dieser kann in der „Empfangene Gelb-Nachrichten“-Liste, welche die
letzten zehn ankommenden Gelb-Nachrichten anzeigt, beobachtet werden. Außerdem sendet der Server
Gelb-Nachrichten an zufällig ausgewählte Handys.
5. Nachdem die Schaltfläche mehrere male bedient wurde, kann es vorkommen, dass diverse Handys so
viele Gelb-Nachrichten an den Server geschickt haben, dass dieser eine Rot-Nachricht an alle Handys
und Warnungen an seine Nachbarn sendet. Hinweis: Der Server muss innerhalb von zwanzig Sekunden
zwanzig Nachrichten erhalten haben, um Rot-Nachrichten zu versenden.
6. Weiterhin ist es möglich, dass Handys, die innerhalb von zehn Sekunden acht Gelb-Nachrichten
empfangen haben, den Benutzer warnen.
Programm-Text
Der nachfolgende zum Teil verkürzte Quelltext ist nur ein Auszug aus dem vollständigen Programm:
' Ereignis-Delegaten
Public Delegate Sub OnMessageAlert(ByVal w As Message)
Public Delegate Sub OnSensorAlert(ByVal s As Sensor)
Public Delegate Sub OnHandyAlert(ByVal h As Handy, ByVal w As Message)
Public Delegate Sub OnServerAlert(ByVal srv As Server, ByVal w As Message)
' Klasse, die einen Sensor darstellt
Public Class Sensor
' Klasse, die eine Auflistung von Sensoren darstellt
Public Class SensorList
End Class
' private Variablen
Private p_Pollutant As String ' Name eines Schadstoffs
Private p_Limit As Single ' Grenzwert eines Schadstoffs
Private p_Concentration As Single ' momentane Konzentration eines Schadstoffs
18
Private AlertEvent As OnSensorAlert ' Ereignis-Variable (für OnSensorAlertDelegate)
' öffentlich Eigenschaften
Public ReadOnly Property Pollutant() As String
End Property
Public Property Limit() As Single
Set(ByVal Value As Single)
p_Limit = Value
VerifyConcentration()
End Set
End Property
Public Property Concentration() As Single
Set(ByVal Value As Single)
p_Concentration = Value
VerifyConcentration()
End Set
End Property
' Konstruktor
' Argumente: pollutant - Schadstoff-Name
'
: limit - Schadstoff-Grenzwert
Public Sub New(ByVal pollutant As String, ByVal limit As Single)
p_Pollutant = pollutant
p_Limit = limit
End Sub
' überprüft, ob eine Grenzwertüberschreitung vorliegt
Private Sub VerifyConcentration()
' wenn momentane Schadstoff-Konzentration größer ist als SchadstoffGrenzwert, dann
If p_Concentration >= p_Limit Then
' löse Sensor-Alarm aus
AlertEvent.Invoke(Me)
End If
End Sub
' erstellt eine Nachrichten-Klasse mit Schadstoff-Name
Public Function ToMessage() As Message
Return New Message(p_Pollutant)
End Function
End Class
' Klasse, die ein Handy darstellt
Public Class Handy
' Klasse, die eine Auflistung von Handys darstellt
Public Class HandyList
End Class
' Ereignis-Deklarationen
' wird ausgelöst, wenn ein Sensor Alarm schlägt
Public Event OnHandySensorAlert(ByVal h As Handy, ByVal w As Message)
' wird ausgelöst, wenn ein Handy viele Gelb-Nachrichten erhalten hat
Public Event OnHandyYellowAlert(ByVal h As Handy, ByVal w As Message)
' wird ausgelöst, wenn ein Handy eine Rot-Nachricht erhalten hat
Public Event OnHandyRedAlert(ByVal h As Handy, ByVal w As Message)
' private Variablen
Private p_Sensors As Sensor.SensorList ' Sensoren-Liste
Private p_MaxMessages As Integer ' maximale Anzahl von eingehenden GelbNachrichten
Private p_Duration As TimeSpan ' Zeitintervall
19
Private AlertEvent As OnHandyAlert ' Ereignis-Variable (für OnHandyAlertDelegate)
Private Messages As Message.MessageList ' Nachrichten-Liste
' öffentlich Eigenschaften
Public Property Sensors() As Sensor.SensorList
End Property
Public Property MaxMessages() As Integer
End Property
Public Property Duration() As TimeSpan
End Property
' Konstruktor
Public Sub New()
p_Sensors = New Sensor.SensorList(AddressOf SensorAlert)
p_Duration = New TimeSpan(0, 0, 10) ' Zeitspanne auf 10 Sekunden setzen
p_MaxMessages = 8
Messages = New Message.MessageList(AddressOf MessageAlert)
End Sub
' wird aufgerufen, wenn ein Server eine Gelb-Nachricht sendet
' Argumente: srv - Verweis zum Server, welcher die Nachricht gesendet hat
'
w - Gelb-Nachricht
Public Sub OnServerYellowAlert(ByVal srv As Server, ByVal w As Message)
Dim i As Integer ' Schleifen-Variable
Dim s As String = w.Pollutant.ToLower ' Name des Schadstoffs in
Kleinbuchstaben
' Schleife von 0 bis Anzahl aller unterschiedlicher empfangener
Nachrichten minus 1
For i = 0 To Messages.Count - 1
' wenn Schadstoff schon vorhanden, dann
If s = Messages(i).Pollutant.ToLower Then
Messages(i).OnMessage()
Return ' verlässt die Methode
End If
Next
Dim warn As Message = w.Clone ' Kopie der Nachricht
warn.Duration = p_Duration
warn.MaxMessages = p_MaxMessages
Messages.Add(warn) ' fügt die Nachricht der Liste hinzu
warn.OnMessage() ' löst ein Nachrichten-Ereignis aus
End Sub
' wird aufgerufen, wenn ein Server eine Rot-Nachricht sendet
' Argumente: srv - Verweis zum Server, welcher die Nachricht gesendet hat
'
w - Gelb-Nachricht
Public Sub OnServerRedAlert(ByVal srv As Server, ByVal w As Message)
RaiseEvent OnHandyRedAlert(Me, w) ' löst ein OnHandyRedAlert-Ereignis aus
End Sub
' wird aufgerufen, wenn ein Sensor Alarm schlägt
' Argumente: s - Verweis zum Sensor, der Alarm geschlagen hat
Private Sub SensorAlert(ByVal s As Sensor)
RaiseEvent OnHandySensorAlert(Me, s.ToMessage) ' löst ein
OnHandySensorAlert-Ereignis aus
AlertEvent.Invoke(Me, s.ToMessage) ' löst ein OnHandyAlert-Ereignis aus,
das vom Server verarbeitet wird
End Sub
' wird aufgerufen, wenn maximale Anzahl von Nachrichten innerhalb der
Zeitspanne empfangen wurde
' Argumente: w - Nachricht
Private Sub MessageAlert(ByVal w As Message)
RaiseEvent OnHandyYellowAlert(Me, w) ' löst ein OnHandyYellowAlertEreignis aus
20
End Sub
End Class
' Klasse, die eine Nachricht darstellt
Public Class Message
' Klasse, die eine Auflistung von Nachrichten darstellt
Public Class MessageList
End Class
' private Variablen
Private p_Pollutant As String ' Name eines Schadstoffs
Private p_MaxMessages As Integer ' maximale Anzahl von eingehenden
Nachrichten
Private p_Duration As TimeSpan ' Zeitintervall
Private StartTime As DateTime ' speichert Anfangszeit
Private MessageCount As Integer ' Nachrichten-Zähler
Private AlertEvent As OnMessageAlert ' Ereignis-Variable (für OnHandyAlertDelegate)
' öffentliche Eigenschaften
Public ReadOnly Property Pollutant() As String
End Property
Public Property Duration() As TimeSpan
End Property
Public Property MaxMessages() As Integer
End Property
' Konstruktor
' Argumente: pollutant - Schadstoff-Name
Public Sub New(ByVal pollutant As String)
p_Pollutant = pollutant
p_Duration = New TimeSpan(0, 0, 10) ' Zeitintervall standardmäßig auf 10
Sekunden setzen
p_MaxMessages = 8 ' maximale Nachrichten-Anzahl auf 8 setzen
StartTime = New DateTime
End Sub
' wird aufgerufen, wenn eine neue Nachricht empfangen wurde
Public Sub OnMessage()
' wenn momentane Nachrichten-Anzahl ungleich 0 ist und wenn die Differenz
von momentane Zeit und Anfangszeit
' kleiner ist als das Zeitintervall, dann
If MessageCount <> 0 And
TimeSpan.op_LessThan(DateTime.op_Subtraction(Date.Now, StartTime),
p_Duration) Then
MessageCount += 1 ' eine Nachricht dazu zählen
' wenn momentane Nachrichten-Anzahl gleich der maximale NachrichtenAnzahl ist, dann
If MessageCount = p_MaxMessages Then
AlertEvent.Invoke(Me) ' löst ein OnMessageAlert-Ereignis aus, das
von einem Handy oder Server verarbeitet wird
MessageCount = 0 ' momentane Nachrichten-Anzahl auf 0 setzen
End If
Else
StartTime = DateTime.Now ' Anfangszeit auf momentane Zeit festlegen
MessageCount = 1 ' momentane Nachrichten-Anzahl auf 1 setzen
End If
End Sub
' erstellt eine Kopie des Objekts
Public Function Clone() As Message
Return New Message(Me.p_Pollutant)
End Function
21
End Class
' Klasse, die einen Server darstellt
Public Class Server
' Klasse, die eine Auflistung von Handys darstellt
Public Class ServerList
End Class
' Ereignis-Deklarationen
' wird ausgelöst, wenn ein Server eine Rot-Nachricht sendet
Public Event OnServerRedAlert(ByVal s As Server, ByVal w As Message)
' wird ausgelöst, wenn ein Server eine Gelb-Nachricht sendet
Public Event OnServerYellowAlert(ByVal s As Server, ByVal w As Message)
' wird ausgelöst, wenn ein Server seine Nachbarn alamiert
Public Event OnServerNeighborAlert(ByVal s As Server, ByVal w As Message)
' private Variablen
Private p_Handys As Handy.HandyList ' Handy-Liste
Private p_Neighbors As Server.ServerList ' Nachbar-Server-List
Private p_YellowAlertPrecentage As Single ' Prozentzahl der zufällig
benachrichtigten Handys
Private p_MaxMessage As Single ' maximale Anzahl von eingehenden GelbNachrichten
Private p_Duration As TimeSpan ' Zeitintervall
Private AlertEvent As OnServerAlert ' Ereignis-Variable (für OnServerAlertDelegate)
Private Messages As Message.MessageList ' Nachrichten-Liste
' öffentlich Eigenschaften
Public Property Handys() As Handy.HandyList
End Property
Public Property YellowAlertPrecentage() As Single
End Property
Public Property Neighbors() As Server.ServerList
End Property
Public Property MaxMessage() As Single
End Property
Public Property Duration() As TimeSpan
End Property
' Konstruktor
Public Sub New()
p_Handys = New Handy.HandyList(AddressOf HandyAlert)
p_Neighbors = New Server.ServerList
Messages = New Message.MessageList(AddressOf MessageAlert)
p_Duration = New TimeSpan(0, 0, 20) ' Zeitspanne auf 20 Sekunden setzen
p_MaxMessage = 0.2
p_YellowAlertPrecentage = 0.2
End Sub
' wird aufgerufen, wenn ein Handy Alarm schlägt
' Argumente: h - Verweis zum Handy, das Alarm geschlagen hat
'
: w - Nachricht
Public Sub HandyAlert(ByVal h As Handy, ByVal w As Message)
Dim i As Integer ' Schleifen-Variable
Dim b As Boolean
Dim s As String = w.Pollutant.ToLower ' Name des Schadstoffs in
Kleinbuchstaben
' Schleife von 0 bis Anzahl aller unterschiedlicher empfangener
Nachrichten minus 1
For i = 0 To Messages.Count - 1
22
' wenn Schadstoff schon vorhanden, dann
If s = Messages(i).Pollutant.ToLower Then
b = True
Exit For ' verlässt die Schleife
End If
Next
' wenn b wahr ist, dann
If b Then
Messages(i).OnMessage()
Else
Dim warn As Message = w.Clone ' Kopie der Nachricht
warn.Duration = p_Duration
warn.MaxMessages = Math.Round(p_Handys.Count * p_MaxMessage)
Messages.Add(warn) ' fügt die Nachricht der Liste hinzu
warn.OnMessage() ' löst ein Nachrichten-Ereignis aus
End If
' Zufallszahlen-Generator mit System-Zeit initialisieren
Dim rnd As New
Random(BitConverter.ToInt32(BitConverter.GetBytes(DateTime.Now.Ticks), 0))
Dim ary As New ArrayList(p_Handys.Count) ' Liste mit Zufallszahlen
Dim num As Integer = rnd.Next(0, p_Handys.Count) ' Zufallszahl zwischen 0
und Anzahl der Handy
For i = 0 To Convert.ToInt32(Math.Round((p_Handys.Count *
p_YellowAlertPrecentage))) - 1
While ary.Contains(num) ' wenn Zufallszahl noch nicht vorhaden ist
num = rnd.Next(0, p_Handys.Count) ' Zufallszahl zwischen 0 und
Anzahl der Handy
End While
ary.Add(num) ' Zufallszahl der Liste hinzufügen
' wenn zufällig ausgewähltes Handy nicht dem
' Handy, der die Nachricht geschickt hat, entspricht
If Not p_Handys(num) Is h Then
p_Handys(num).OnServerYellowAlert(Me, w)
End If
Next
RaiseEvent OnServerYellowAlert(Me, w) ' löst ein OnServerYellowAlertEreignis aus
End Sub
' wird aufgerufen, wenn maximale Anzahl von Nachrichten innerhalb der
Zeitspanne empfangen wurde
' Argumente: w - Nachricht
Private Sub MessageAlert(ByVal w As Message)
Dim i As Integer ' Schleifen-Variable
' sendet allen Handys eine Rot-Nachricht
For i = 0 To p_Handys.Count - 1
p_Handys(i).OnServerRedAlert(Me, w)
Next
' sendet allen Nachbarn eine Nachricht
For i = 0 To p_Neighbors.Count - 1
p_Neighbors(i).NeighborAlert(Me, w)
Next
RaiseEvent OnServerRedAlert(Me, w) ' löst ein OnServerRedAlert-Ereignis
aus
RaiseEvent OnServerNeighborAlert(Me, w) ' löst ein OnServerNeighborAlertEreignis aus
End Sub
23
' wird aufgerufen, wenn ein Nachbar Alarm schlägt
' Argumente: srv - Verweis zum Server, der Alarm geschlagen hat
'
: w - Nachricht
Private Sub NeighborAlert(ByVal srv As Server, ByVal w As Message)
' leer, da keine Funktion laut Alarmstrategie zugewiesen wurde
End Sub
End Class
24
Aufgabe 3
Lösungsidee
Das Finden des kürzesten und somit kostengünstigsten Wegs wurde mit einem Algorithmus realisiert, der im
Folgenden erläutert werden soll:
1. Von jedem Diamantenvorkommen wird der kürzeste
Weg zum Rand des Bergs errechnet. Es ist darauf zu
achten, dass sich zwischen dem Diamantenvorkommen und dem Rand kein anderes Vorkommen
befindet. Sollte nun aber ein Diamantenvorkommen
von anderen Diamantenvorkommen so umschlossen
sein, dass es keinen Weg zum Rand des Bergs gibt,
so sollte sein Abstand zum Rand der Breite beziehungsweise der Höhe der Karte, je nach dem
welcher Wert größer ist, entsprechen. In Karte 1 sind
die kürzesten Abstände zum Rand des Bergs mit der
roten Farbe gekennzeichnet.
2. Als nächstes wird das Umfeld jedes DiamantenKarte 1 - Abstände der Diamantenvorkommen zum
Bergrand
vorkommens auf weitere untersucht. Dazu werden alle
Planquadrate, die sich im gleichen Abstand wie dem
Abstand zum Bergrand befinden auf ein Vorhandensein anderen Vorkommen überprüft. Sollten mehrere
Vorkommen gefunden werden, so muss ein solches
ausgewählt werden, dessen Abstand zum AusgangsDiamantenvorkommen am kürzesten ist. Wenn auch
hier mehrere gefunden werden, muss jedes Vorkommen in einem weiteren Verlauf betrachtet werden.
In Karte 2 wird dies an den beiden unteren Diamantenvorkommen verdeutlicht.
3. Nun muss mit dem zweiten Schritt solange rekursiv
fortgefahren werden, bis kein Diamantenvorkommen
mehr in einem Umfeld gefunden wird. Es ist nötig die
Karte 2 - Ein Weg zu zwei Vorkommen
Wege, die sich bei einem solchen Ablauf ergeben,
darauf hin zu überprüfen, dass kein Planquadrat mehrfach verwendet wird.
4. Zum Schluss werden die verschiedenen Wege so miteinander verknüpft, dass jeweils alle Diamantenvorkommen enthalten sind. Dann werden aus der Menge aller möglichen Wege diejenigen herausgesucht, welche die geringste Anzahl von Planquadraten aufweisen.
Programm-Dokumentation
Das Lösungsprogramm stellt die Karte mit Hilfe eines Arrays dar. Diese Darstellung der Planquadrate als
zwei-dimensionale Koordinaten ist notwendig, um das Problem der Wegfindung zu mathematisieren. Da ein
Array ein rechteckiges Feld, also eine Matrix, darstellt und der Berg nicht unbedingt rechteckig ist, wurde
eine Graslandschaft um den Berg herum erstellt. Weiterhin erhält jedes Koordinatenpaar zusätzlich eine
Information zu welchen der drei Typen Gras, Berg oder Diamantenvorkommen es gehört. Als nächstes
ordnet das Programm jedem Koordinatenpaar, das vom Typ Diamantenvorkommen ist, den kürzesten Weg
zum Rand und die Richtung zu diesem zu. Mit vollständig umschlossenen Diamantenvorkommen wird, wie
oben beschrieben, verfahren. An dieser Stelle durchläuft das Lösungsprogramm das Karten-Array. Wenn
nun die aktuellen Koordinaten zu einem Diamantenvorkommen gehören, ruft das Programm die rekursive
Prozedur auf. In dieser werden dann alle Vorkommen, die sich in der nähe des Ausgangs-Vorkommens
befinden, herausgesucht. Die Vorkommen, die den kürzesten Abstand zum Ausgangs-Vorkommen haben,
werden durch das Lösungsprogramm rekursiv aufgerufen. Dabei werden alle durchlaufenen Koordinatenpaare in Listen gespeichert. Weiterhin überprüft das Programm in der rekursiven Prozedur, ob kein
Koordinatenpaar in den Listen mehrfach verwendet wird. Die Rekursion wird beendet, wenn im Umfeld eines
Diamantenvorkommens kein weiteres vorhanden ist. Nun sucht das Programm den Kürzesten aus allen
Wegen heraus, der die gleichen Diamantenvorkommen verwendet und entfernt alle Längeren aus der
Wegliste. An dieser Stelle kombiniert das Programm alle Wege so miteinander, dass alle möglichen
Lösungen, das heißt es müssen alle Diamantenvorkommen enthalten sein, aufgestellt werden. Die hier
erstandene maximale Anzahl der Permutation berechnet sich aus n ! . Anschließend muss das Programm
aus allen so gefundenen Wegen diejenigen heraussuchen, welche die geringsten Anzahlen von
Koordinatenpaare enthält, also die kürzesten Wege.
25
Nutzungsgrenzen
Eines der größten Probleme des vorgeschlagenen Lösungsalgorithmus ist die stark zunehmende Rechenzeit mit der Zunahme von Diamantenvorkommen. Dies geschieht durch die sehr schnell steigende Anzahl an
möglichen Wegen. Weiterhin besteht die Gefahr eines Stapelspeicherüberlaufs aufgrund des rekursiven
Aufrufs. Ein weiters Problem wurde nach ausgiebigen Tests des Algorithmus festgestellt. Es kommt
gelegentlich vor, dass, besonders bei vielen Diamantenvorkommen, der errechnete Lösungsweg nicht der
kürzeste ist. Dieses nicht Finden des optimalen Wegs kann auf die Größe des zu untersuchenden Felds
eines Diamantenvorkommens zurückgeführt werden. Anstelle des Bergrand-Abstands als ein Maß für die
Größe des Felds zu verwenden, sollten vielmehr Abstände in einem festgelegten Intervall untersucht
werden. Im Lösungsprogramm ist allerdings auf eine solche Rechentiefe verzichtet worden, da sich die
Rechenzeit sonst erheblich erhöhen dürfte.
Programmablauf-Protokoll
Aufbau
Abbildung 5 - Aufbau des Lösungsprogramms
Im Lösungsprogramm wurde, wie in Abbildung 5 zu erkennen, ein Menü implementiert. Dieses besitzt drei
Haupteinträge: Im „Datei“-Eintrag befindet sich eine Funktion zum Speichern der erstellten Karte und eine
weitere zum Einladen von zuvor angefertigten Karten. Als nächstes folgt der „Karte“-Eintrag, über den die
Karte bearbeitet und gelöscht werden kann. Hier findet der Benutzer die Untereinträge „Gras“, „Berg“ und
„Diamant“, mit denen er die Elemente einer Karte erstellen kann. Der Letzte Haupteintrag „Weg“ enthält
Funktionen zur Wegfindung. Im Einzelnen kann der Benutzer dort eine Wegfindung starten und die
möglichen Wege durchlaufen. Unter der Menüleiste befindet sich eine Werkzeugleiste. Mit dieser werden
dieselben Kartenelement wie im „Karten“-Menü erstellt. Der größte Teil des Fensters wird durch die Karte
ausgefüllt.
Funktionsweise
Der Benutzer muss eine Karte einladen oder erstellen bevor er die Verbindungen zwischen den Diamantenvorkommen und den Bergränder einzeichnen lassen kann:
1. Einladen einer Karte
1.1. Der Benutzer ruft, um eine Karte einzuladen, den „Datei-Öffnen“-Dialog über das Untermenü
„Öffnen…“ im Menü „Datei“ auf.
1.2. Sobald der Dialog erscheint, wählt der Anwender eine Karten-Datei aus und betätigt anschließend
die „Öffnen“-Schaltfläche. Hinweis: Im Anwendungsverzeichnis befinden sich die drei Beispielkarten
„Karte 1.map“, „Karte 2.map“ und „Karte 3.map“.
1.3. Nun sollte das Lösungsprogramm die Karten-Datei einlesen und die Karte dann anschließend
graphisch darstellen.
2. Erstellen einer benutzerdefinierten Karte
2.1. Zu Beginn sollte der Benutzer eine rein grüne Kartenfläche vorfinden. Falls dies nicht der Fall ist,
kann die aktuelle Karte über das Untermenü „Löschen“ aus dem Menü „Karte“ entfernt werden.
26
2.2. An dieser Stelle wird empfohlen, den Berg zuerst einzutragen. Dazu betätigt der Anwender die
„Berg“-Schaltfläche aus der Werkzeugleiste. Nun bewegt er die Maus über die Karte und drückt
dann auf die linke Maustaste, um ein beim Mauszeiger befindliches Planquadrat mit einem
Bergelement auszufüllen.
2.3. Nachdem der Benutzer den Berg erstellt hat, sollten die Diamantenvorkommen hinzugefügt werden.
Um dies zu bewerkstelligen, betätigt er die „Diamant“-Schaltfläche aus der Werkzeugleiste.
Anschließend trägt der Anwender die Diamantenvorkommen so ein, wie er es bereits mit den
Bergelementen getan hat.
2.4. Falls Berg- oder Diamantenelemente falsch positioniert worden sind, kann der Benutzer dies mit
Hilfe des Graselements aus der Werkzeugleiste korrigieren.
2.5. Zum Schluss sollte der Anwender die Karte abspeichern, indem er den „Datei-Speichern“-Dialog
über das Untermenü „Speichern unter…“ im Menü „Datei“ aufruft. Im Dialog gibt er dann einen
Dateinamen an und betätigt anschließend die „Speichern“-Schaltfläche.
Nachdem nun eine Karte eingeladen beziehungsweise erstellt wurde, kann der Benutzer die Wegfindung
starten:
1. Finden und Anzeigen von Wegen
1.1. Zu Beginn muss der Anwender die optimalen Wege berechnen lassen. Dies kann über das
Untermenü „Optimalen finden“ im „Weg“-Menü erreicht werden.
1.2. Je nach dem wie komplex die Karten sind, kann das Berechnen der Wege einige Zeit in Anspruch
nehmen. Sollte eine Wegfindung einmal zu viel Zeit in Anspruch nehmen, kann der Benutzer den
Vorgang über die „Abbrechen“-Schaltfläche im sich beim Beginn der Berechnung geöffneten
„Berechnung möglicher Wege...“-Dialog beenden.
1.3. Nachdem nun ein oder mehrere Wege gefunden worden sind, zeigt das Lösungsprogramm den
ersten Lösungsweg in Form von roten Elementen, welchen in entsprechenden Planquadraten
eingezeichnet sind, an.
1.4. Als nächstes kann der Anwender die Wege mit den im „Weg“-Menü befindlichen Untermenüs
„Nächster“ und „Vorheriger“ durchlaufen. Hinweis: Bei beiden Untermenüs befindet sich jeweils eine
Information über aktuelle Lösungswegnummer und Anzahl aller Lösungswege.
Probeläufe des Programms
1. Zu Beginn wird die Karte aus der Datei „Karte 1.map“ eingeladen und die Wegfindung gestartet:
Um möglichst wenige Planquadrate mit Wegelementen zu
belegen, hat das Lösungsprogramm zwei Wege verwendet. Insgesamt wurden zwölf Planquadrate benötigt.
Karte 3 - Karte aus Datei „Karte 1.map"
2. Als nächstes werden die Wege der Karte „Karte 2.map“ gesucht:
In dieser Karte wurden, um alle Diamantenvorkommen zu erreichen, drei
Wege angesetzt. Insgesamt wurden elf Planquadrate benötigt.
Karte 4 - Karte aus Datei „Karte
2.map"
27
3. Zum Schluss wird die Datei „Karte 2.map“ eingeladen:
In dieser Karte werden zehn Diamantenvorkommen verwendet.
Das Lösungsprogramm errechnet zwei Wegansätze und benötigt
23 Wegelemente.
Karte 5 - Karte aus Datei "Karte 3.map"
Programm-Text
Die unten aufgeführten Methoden dienen zur Bestimmung der Weglösungen:
' Feld-Typ - Aufzählung von Kartenelementen (Gras, Berg, Diamantenvorkommen)
Private Enum FieldType
Grass = 0
Mountain = 1
Diamond = 2
End Enum
' Feld-Informationen - Eigenschaften eines Felds
Private Structure FieldInfo
Public BorderDistance As Integer ' Abstand zum kürzesten Rand (nur Diamant)
Public BorderDirection As Integer ' Richtung zum kürzesten Rand (nur Diamant)
Public Type As FieldType ' Feld-Typ
End Structure
Private p_FieldWidth, p_FieldHeight As Integer ' Planquadrat-Anzahl der Breite
und der Höhe
Private p_Field(,) As FieldInfo ' Array, das alle Planquadrat darstellt
Private p_Offset() As Point = New Point() {New Point(1, 0), New Point(-1, 0),
New Point(0, 1), New Point(0, -1)} ' Richtungsvektoren
Private p_BestVariants As New VariantList ' Liste, die die Lösungswege enthält
Private p_Diamonds As PointList ' Liste aller Diamantenvorkommen
Private p_FrmWait As FrmWait ' "Berechnung möglicher Wege..."-Dialog
' findet die kürzesten Wege
Private Sub FindShortestWays()
Dim i, k, j As Integer ' Schleifen-Variablen
Dim PtList As PointList ' ein Weg
Dim CurWay, AllWays As WayList ' aktuelle Wege und Gesamtwege
' die Abstände der Diamantenvorkommen zum Rand ermitteln
AssignBorderDistance()
' neue Wegliste erstellen
AllWays = New WayList
' Diamantenvorkommen aufzählen
For i = 0 To p_Diamonds.Count - 1
' wenn Abstand des aktuellen Diamantenvorkommens kleiner als Breite oder
Höhe der Karte ist
28
If (p_Field(p_Diamonds(i).X, p_Diamonds(i).Y).BorderDistance <
Math.Max(p_FieldWidth, p_FieldHeight)) Then
' neue Wegliste erstellen
CurWay = New WayList
' neuen Weg erstellen
PtList = New PointList
For j = 0 To p_Field(p_Diamonds(i).X, p_Diamonds(i).Y).BorderDistance 1
' alle Punkte die zum Rand führen mit aufnehmen
PtList.Insert(0, New Point(p_Offset(p_Field(p_Diamonds(i).X,
p_Diamonds(i).Y).BorderDirection).X * j + p_Diamonds(i).X,
p_Offset(p_Field(p_Diamonds(i).X,
p_Diamonds(i).Y).BorderDirection).Y * j + p_Diamonds(i).Y))
Next
' Weg der aktuellen Wegliste hinzufügen
CurWay.Add(PtList)
' Rekursions-Prozedur aufrufen
FindNextDiamond(p_Diamonds(i).X, p_Diamonds(i).Y, 0, CurWay)
' aktuelle Wege der Gesamtwege-Liste hinzufügen
For j = 0 To CurWay.Count - 1
AllWays.Add(CurWay(j))
Next
End If
Next
Dim ptD1List As PointList ' Weg 1
Dim ptD2List As PointList ' Weg 2
i = 0
' Gesamtwege aufzählen
While i < AllWays.Count
' Diamantenvorkommen eins Wegs der Gesamtwege herausfinden
ptD1List = GetDiamonds(AllWays(i))
k = i + 1
While k < AllWays.Count
ptD2List = GetDiamonds(AllWays(k))
' wenn Diamantenvorkommen-Anzahlen übereinstimmen und gleiche
Diamantenvorkommen enthalten sind
If (ptD1List.Count = ptD2List.Count) And
ptD1List.ContainsAllOf(ptD2List) Then
' wenn der Weg länger ist
If AllWays(i).Count > AllWays(k).Count Then
AllWays.RemoveAt(i) ' Weg entfernen
i -= 1
Exit While
ElseIf AllWays(i).Count < AllWays(k).Count Then
AllWays.RemoveAt(k)' Weg entfernen
k -= 1
End If
End If
k += 1
End While
i += 1
End While
Dim AllVariants As New VariantList ' alle möglichen Wegvarianten
Dim history As ArrayList ' Verlauf-Liste (indizes)
Dim avariant As WayList ' ein Wegvariante
' Gesamtwege aufzählen
29
For i = 0 To AllWays.Count - 1
' neuen Verlauf erstellen
history = New ArrayList
' aktuellen Index hinzufügen
history.Add(i)
' neue Weg-Liste
avariant = New WayList
' Weg aus Gesamtwege hinzufügen
avariant.Add(AllWays(i))
' sucht mögliche Weglösungen
EnumerateVariants(avariant, AllVariants, AllWays, history)
Next
' neue Varianten-Liste erstellen
p_BestVariants = New VariantList
' kürzeste Weglänge
Dim bestlength As Integer = Integer.MaxValue
Dim len As Integer ' eine Weglänge
' alle möglichen Wegvarianten aufzählen
For i = 0 To AllVariants.Count - 1
len = 0
' Wege aufzählen
For k = 0 To AllVariants(i).Count - 1
' Weglänge errechnen
len += AllVariants(i)(k).Count
Next
' wenn aktuelle Weglänge kleiner gleich kürzeste Weglänge ist
If len <= bestlength Then
If len < bestlength Then p_BestVariants.Clear() ' Lösungswege-Liste
löschen
' aktuelle Weglösung der Lösungswege-Liste hinzufügen
p_BestVariants.Add(AllVariants(i))
' kürzeste Weglänge auf aktuelle festlegen
bestlength = len
End If
Next
p_FrmWait.DialogResult = DialogResult.OK
p_FrmWait.Close() ' "Berechnung möglicher Wege..."-Dialog schließen
End Sub
' sucht aus einer Punkt-Liste alle Diamantenvorkommen heraus
' Argumente: ptList - Punkt-Liste, die untersucht werden soll
Private Function GetDiamonds(ByVal ptList As PointList) As PointList
Dim newPtList As New PointList ' Liste die zurückgegeben wird
' Punkte der Liste aufzählen
For i As Integer = 0 To ptList.Count - 1
' wenn Punkt ein Diamantenvorkommen ist
If p_Field(ptList(i).X, ptList(i).Y).Type = FieldType.Diamond Then
' Punkt in Liste aufnehmen
newPtList.Add(ptList(i))
End If
Next
Return newPtList ' Diamantenvorkommen-Liste zurückgeben
End Function
' sucht alle möglichen Weglösungen (rekursiv)
' Argumente: avariant - eine Lösung, die am Ende der Rekursion hinzugefügt wird
30
'
variants - Liste aller möglichen Weglösungen
'
ways - alle möglichen Wege
'
history - Liste bereits verwendeter Wege (indizes)
Private Sub EnumerateVariants(ByVal avariant As WayList, ByRef variants As
VariantList, ByVal ways As WayList, ByVal history As ArrayList)
Dim newhistory As ArrayList ' Kopie der Verlauf-Liste
Dim newvariant As WayList ' Kopie der Weglösung
' wenn die Weglösung alle Diamantenvorkommen enthält
If avariant.Combine.ContainsAllOf(p_Diamonds) Then
variants.Add(avariant) ' Weglösung hinzufügen
Return ' Prozedur beenden
End If
' alle möglichen Wege aufzählen
For i As Integer = 0 To ways.Count - 1
' wenn aktueller Weg nicht im Verlauf vorhanden ist
If Not history.Contains(i) Then
' wenn Weglösung kein Diamantenvorkommen des aktuellen Wegs enthält
If Not avariant.Combine.ContainsOneOf(ways(i)) Then
' Verlauf kopieren
newhistory = history.Clone()
' aktuellen Wegindex hinzufügen
newhistory.Add(i)
' Weglösung kopieren
newvariant = avariant.Clone()
' aktuellen Weg der Weglösung hinzufügen
newvariant.Add(ways(i))
' rekursiver Aufruf
EnumerateVariants(newvariant, variants, ways, newhistory)
End If
End If
Next
End Sub
' jedem Diamantenvorkommen einen Abstand zum Rand zuweisen
Private Sub AssignBorderDistance()
Dim i, k, j As Integer ' Schleifen-Variablen
Dim dist As Integer ' Abstand
Dim IsNotBlocked As Boolean = True ' ist ein Diamantenvorkommen von anderen
umschlossen
Dim pt As Point ' Punkt-Puffer
' Diamantenvorkommen-Liste neu erstellen
p_Diamonds = New PointList
' jedes Planquadrat aufzählen
For i = 0 To p_FieldWidth
For k = 0 To p_FieldHeight
' wenn aktuelles Planquadrat ein Diamantenvorkommen ist
If p_Field(i, k).Type = FieldType.Diamond Then
' Punkt der Diamantenvorkommen-Liste hinzufügen
p_Diamonds.Add(New Point(i, k))
' Abstand des Diamantenvorkommens zum Rand auf ein Maximum
initialisieren
p_Field(i, k).BorderDistance = Math.Max(p_FieldWidth, p_FieldHeight)
' alle Richtungen aufzählen
For j = 0 To 3
pt.X = p_Offset(j).X
pt.Y = p_Offset(j).Y
' solange in Richtung j weitergehen bis ein Diamantenvorkommen
oder Gras erreicht wird
31
While ((pt.X + i) >= 0) And ((pt.X + i) <= p_FieldWidth) And
((pt.Y + k) >= 0) And ((pt.Y + k) <= p_FieldHeight)
' Feld-Typen unterscheiden
Select Case p_Field(pt.X + i, pt.Y + k).Type
Case FieldType.Grass
Exit While
Case FieldType.Diamond
IsNotBlocked = False
Exit While
End Select
pt.X += p_Offset(j).X
pt.Y += p_Offset(j).Y
End While
' wenn Diamantenvorkommen nicht behindert wird
If IsNotBlocked Then
' Abstand ermitteln
dist = Math.Abs(pt.X) + Math.Abs(pt.Y)
' Abstand vom Rand nur setzen, wenn der gefundene Abstand
kleiner ist
If dist <= p_Field(i, k).BorderDistance Then
With p_Field(i, k)
.BorderDirection = j
.BorderDistance = dist
End With
End If
End If
IsNotBlocked = True
Next
End If
Next
Next
End Sub
' überprüft, ob der Weg zwischen zwei Diamantenvorkommen frei ist (auf dem gibt
es nur Berg-Elemente)
' Argumente: ptFirst - erstes Diamantenvorkommen
'
ptLast - zweites Diamantenvorkommen
'
ptList - vorhandener Weg (darf nicht geschnitten werden)
'
way1 - ist erster Weg möglich (Verweis)
'
way2 - ist zweiter Weg möglich (Verweis)
Private Function IsWayFree(ByVal ptFirst As Point, ByVal ptLast As Point, ByVal
ptList As PointList, ByRef way1 As Boolean, ByRef way2 As Boolean) As Boolean
Dim ptVec, ptStep As Point ' Punkt-Puffer
Dim j, l As Integer ' Schleifen-Variablen
' Richtungsvektor zwischen beiden Diamantenvorkommen
ptVec.X = ptLast.X - ptFirst.X
ptVec.Y = ptLast.Y - ptFirst.Y
' Hilfsvektor
If ptVec.X >=
ptStep.X =
Else
ptStep.X =
End If
für Schleife (offset)
0 Then
1
' Hilfsvektor
If ptVec.Y >=
ptStep.Y =
Else
ptStep.Y =
für Schleife (offset)
0 Then
1
-1
-1
32
End If
' wenn kein schräger Weg
If ptVec.X = 0 Or ptVec.Y = 0 Then
' Punkte auf Schnitt mit vorhandenem Weg überprüfen (horizontal)
For j = ptFirst.X + ptStep.X To ptLast.X Step ptStep.X
If ptList.Contains(New Point(j, ptLast.Y)) Then
Return False
End If
Next
' Punkte auf Schnitt mit vorhandenem Weg überprüfen (vertikal)
For l = ptFirst.Y + ptStep.Y To ptLast.Y Step ptStep.Y
If ptList.Contains(New Point(ptLast.X, l)) Then
Return False
End If
Next
Else ' wenn schräger Weg
' ersten Schrägen Weg auf Schnitt mit vorhandenem Weg überprüfen
horizontal dann vertikal
way1 = True
For j = ptFirst.X + ptStep.X To ptLast.X Step ptStep.X
If ptList.Contains(New Point(j, ptFirst.Y)) Then
way1 = False
Exit For
End If
Next
For l = ptFirst.Y + ptStep.Y To ptLast.Y Step ptStep.Y
If ptList.Contains(New Point(ptLast.X, l)) Then
way1 = False
Exit For
End If
Next
' zweiten Schrägen Weg auf Schnitt mit vorhandenem Weg überprüfen vertikal
dann horizontal
way2 = True
For l = ptFirst.Y + ptStep.Y To ptLast.Y Step ptStep.Y
If ptList.Contains(New Point(ptFirst.X, l)) Then
way2 = False
Exit For
End If
Next
For j = ptFirst.X + ptStep.X To ptLast.X Step ptStep.X
If ptList.Contains(New Point(j, ptLast.Y)) Then
way2 = False
Exit For
End If
Next
Return (way1 Or way2)
End If
Return True
End Function
' sucht ein Diamantenvorkommen im Umfeld eines Diamantenvorkommens
' Argumente: x, y - Diamantenvorkommen-Koordinaten
'
index - Index für aktuellen Weg
'
waylist - alle Wege
Private Sub FindNextDiamond(ByVal x As Integer, ByVal y As Integer, ByVal index
As Integer, ByVal waylist As WayList)
Dim x1, x2, y1, y2 As Integer ' Koordinaten des Umfelds
Dim ptBest As New PointList ' Liste der nähsten Diamantenvorkommen
Dim ptVec, ptStep As Point ' Punkt-Puffer
33
Dim
Dim
Dim
Dim
Dim
i, k, j, l As Integer ' Schleifen-Variablen
len, lenBest As Integer ' aktuelle und kürzeste Länge
way1temp, way2temp As Boolean ' Puffer
way1best As New ArrayList
way2best As New ArrayList
' das Rechteck der zu überprüfenden Koordinaten
x1 = Math.Max(0, x - p_Field(x, y).BorderDistance)
y1 = Math.Max(0, y - p_Field(x, y).BorderDistance)
x2 = Math.Min(p_FieldWidth, x + p_Field(x, y).BorderDistance)
y2 = Math.Min(p_FieldHeight, y + p_Field(x, y).BorderDistance)
' das nähste Diamantvorkommen suchen
For i = x1 To x2
For k = y1 To y2
If p_Field(i, k).Type = FieldType.Diamond Then
' Abstand zwischen aktuellem Diamantvorkommen und AusgangsDiamantvorkommen
len = Math.Abs(i - x) + Math.Abs(k - y)
' wenn Diamantvorkommen nicht die selben sind und Abstand vom
aktuellem Diamantvorkommen kleiner gleich Rand-Abstand des AusgangsDiamantvorkommens ist
If (i <> x Or k <> y) And (len <= p_Field(x, y).BorderDistance) Then
' wenn Abstand zwischen aktuellem Diamantvorkommen und AusgangsDiamantvorkommen kleiner gleich Rand-Abstand des aktuellem
Diamantvorkommens ist
If len <= p_Field(i, k).BorderDistance Then
' wenn Abstand zwischen aktuellem Diamantvorkommen und
Ausgangs-Diamantvorkommen kleiner gleich allen zuvor
gefundenen ist
If ptBest.Count = 0 Or (len <= lenBest) Then
' wenn Weg frei ist
If IsWayFree(New Point(x, y), New Point(i, k),
waylist(index), way1temp, way2temp) Then
If len < lenBest Then
ptBest.Clear()
way1best.Clear()
way2best.Clear()
End If
ptBest.Add(New Point(i, k))
lenBest = len
way1best.Add(way1temp)
way2best.Add(way2temp)
End If
End If
End If
End If
End If
Next
Next
' (siehe IsWayFree)
Dim globalcopy As PointList = waylist(index).Clone()
Dim index2 As Integer = index ' Puffer
For j = 0 To
' diverse
ptVec.X =
ptVec.Y =
ptBest.Count - 1
Koordinaten berechnen
ptBest(j).X - x
ptBest(j).Y - y
If ptVec.X >= 0 Then
ptStep.X = 1
Else
34
ptStep.X = -1
End If
If ptVec.Y >= 0 Then
ptStep.Y = 1
Else
ptStep.Y = -1
End If
' die Wegliste kopieren, wenn nötig
If index2 = -1 Then
index2 = waylist.Add(globalcopy.Clone())
End If
If ptVec.X = 0 Or ptVec.Y = 0 Then
For i = x + ptStep.X To ptBest(j).X Step ptStep.X
waylist(index2).Add(New Point(i, ptBest(j).Y))
Next
For k = y + ptStep.Y To ptBest(j).Y Step ptStep.Y
waylist(index2).Add(New Point(ptBest(j).X, k))
Next
' rekursiver Aufruf
FindNextDiamond(ptBest(j).X, ptBest(j).Y, index2, waylist)
Else
' Schräger Punkt
' Weg 1
If way1best(j) Then
For i = x + ptStep.X To ptBest(j).X Step ptStep.X
waylist(index2).Add(New Point(i, y))
Next
For k = y + ptStep.Y To ptBest(j).Y Step ptStep.Y
waylist(index2).Add(New Point(ptBest(j).X, k))
Next
' rekursiver Aufruf
FindNextDiamond(ptBest(j).X, ptBest(j).Y, index2, waylist)
End If
' Weg 2
If way2best(j) Then
index2 = waylist.Add(globalcopy.Clone())
For k = y + ptStep.Y To ptBest(j).Y Step ptStep.Y
waylist(index2).Add(New Point(x, k))
Next
For i = x + ptStep.X To ptBest(j).X Step ptStep.X
waylist(index2).Add(New Point(i, ptBest(j).Y))
Next
' rekursiver Aufruf
FindNextDiamond(ptBest(j).X, ptBest(j).Y, index2, waylist)
End If
End If
index2 = -1
Next
End Sub
35
Aufgabe 4
Lösungsidee
Um einen Fischteich zu simulieren, wurde das folgende mit Hilfe der Vektorrechnung mathematisierte Modell
entworfen:
Das Modell vereinfacht alle Fische zu Punkten, sodass jeder dieser Fische in einem orthonormierten
Koordinatensystem als Ortsvektor seiner Lage eindeutig bestimmt werden kann. Weiterhin ist es nötig zu
definieren, was das Mögen oder Verabscheuen in dem Modell bedeutet. Hierzu wurde festgelegt, dass ein
Fisch der einen anderen mag einen bestimmten geringen Abstand einhalten sollte. Ähnlich verhält sich ein
Fisch der einen anderen verabscheut, nur das dieser zu dem anderen einen möglichst großen Abstand
einhalten sollte. Um nun die Bewegung der Fische zu errechnen, sind die folgenden Gleichungen aufgestellt
worden:
a1 = OF1 OF0
(1)
a 2 = OF2
OF0
a n = OFn
OF0
b0 = a1 + a2 +
(2)
b1 =
(3)
OF1
OF0
+ an =
1
n
i =1
a1
OB
ai
b1
b0
b0
OF2
OB = OF0 + b1
(4)
x
o
a1
OF0 dargestellt ist, wenn
y
OF1 und OF2 mag. Dabei sind die
er zwei andere Fische,
b0
a2
Die neben stehende Abbildung 6 verdeutlicht das Prinzip der
Bewegung eines Fischs, der durch
a2
Zustände der beiden anderen Fische irrelevant. Da die
Abbildung 6 - Prinzip zur Berechnung der neuen Lage
Abbildung 6 nur ein Beispiel für einen Teich darstellt, soll
eines Fischs
anhand der oben stehenden Gleichungen die Bewegung der
Fische in allgemeinen Teichsystemen erläutert werden:
1. Wenn sich ein Fisch zu n -vielen Fischen hingezogen fühlt, werden zunächst, wie in den Gleichungen
(1) erkennbar, Differenzvektoren zwischen jeden und dem zu bewegenden Fisch erstellt.
2. Nun werden alle Differenzvektoren miteinander addiert. Dies ist in Gleichung (2) dargestellt.
3. Anschließend wird der Summenvektor normiert (3), da der Fisch ja seine Lage nicht sprunghaft, sondern
nur Stück für Stück um die Länge 1 verändern darf.
4. Zum Schluss wird, wie in Gleichung (4) erkennbar, der neue Ortsvektor errechnet, indem der alte
Ortsvektor mit dem normierten Summenvektor addiert wird.
Hinweis: Hat der zu bewegende Fisch einen entsprechend kleinen Abstand erreicht, so wird der gemochte
Fisch im nächsten Rechendurchlauf solange nicht beachtet, bis sein Abstand den kritischen Wert übersteigt.
Nachdem nun die Bewegung für das Mögen eines oder mehrerer Fische erfolgt ist, wird an dieser Stelle die
Bewegung für das Verabscheuen eines oder mehrerer Fische erläutert:
(
= a1
(5)
= 1
=
n
i =1
r
)
1
a1 +
a1
1
a1 +
a1
r
r
ai
x
o
b0 = c1 + … + cn
ai
(
+ an
+ 1
r > ai
r
an
r
)
1
an
an
OF0
OF1
r
an
c1
a1
y
Abbildung 7 - Prinzip des Nichtmögens eines Fischs
36
Im Großen und Ganzen wird wie im oberen Teil verfahren. Da die Bewegung des Fischs aber vom
verabscheuten Fisch weg verlaufen muss, sobald er diesem zu nahe kommt, wird statt Gleichung (2) die
Gleichung (5) verwendet. Nun wird die Länge jedes Differenzvektors
an von r subtrahiert und mit dem
Einheitsvektor des Differenzvektors multipliziert. Da die Länge der Differenzvektoren immer kleiner sein
muss als r , werden die Differenzen in der Klammer immer kleiner als 0 sein und somit wird die Richtung
von
cn der von an genau entgegengesetzt sein. An dieser Stelle wird nun bei Gleichung (3) fortgefahren.
Da jetzt die Bewegung beim Mögen oder Verabscheuen erörtert wurde, stellt sich nun die Frage was
geschehen soll, wenn beide Zustände auftreten. Hierzu werden einige Differenzvektoren
an in Gleichung
(2) je nach dem, ob ein Fisch einen anderen mag oder verabscheut, durch einige Vektoren
cn aus Glei-
chung (5) substituiert.
Programm-Dokumentation
Das Programm, das das Modell verwirklicht, besteht im Kern aus einer Klasse. Diese Fisch-Klasse steuert
jeweils die Bewegung eines Fischs nach dem oben erläuterten Prinzip. Um dabei alle anderen Fische mit
einzubeziehen, wurde in der Klasse eine Auflistung aller dieser eingebunden. Die eigentliche Berechnung
der Bewegung der Fische wurde mit Hilfe der Vektorrechnung erklärt. Da das Programm nun möglichst nah
am Modell arbeiten soll, wurde eine primitive Vektor-Klasse im Lösungsprogramm implementiert. Im weiteren
Verlauf soll nun die Möglichkeiten der Vektor- und Fisch-Klasse, sowie das Darstellen des Modells erläutert
werden:
Vektor-Klasse
Die Vektor-Klasse enthält Eigenschaften wie Koordinaten, Länge und Einheitsvektor eines Vektors.
Sie weist Funktionen wie Addition und Subtraktion von Vektoren auf.
Weiterhin gibt es in ihr eine Methode zur Skalarmultiplikation.
Fisch-Klasse
Die Fisch-Klasse enthält im Wesentlichen eine Methode zum Errechnen der Bewegung eines Fisch-Objekts.
Das Arbeiten der Methode soll nun an dem folgenden Pseudo-Code erklärt werden:
' i – Schleifen-Variable (ganze Zahl)
' Mögen_Abstand – maximaler Abstand für das Mögen eines Fischs (ganze Zahl)
' Verabscheuen_Abstand – maximaler Abstand für das Verabscheuen eines Fischs
(ganze Zahl)
' v2 – Differenzvektor (Vektor-Objekt)
' v1 – neuer Ortsvektor (Vektor-Objekt)
' Fische – Fischliste mit allen Fischen, zu denen der zu bewegende Fisch eine
' Beziehung hat (Array-Objekt)
' Hans – zu bewegender Fisch (Vektor-Objekt)
' jedes Fisch-Objekt (in Fische, Hans) enthält einen Ortsvektor, der hier als
Position bezeichnet wird
Für i = 0 Bis Fische.Anzahl – 1
' Differenzvektor ausrechnen
v2 = Fische(i).Position.Subtrahiere(Hans.Position)
Wenn Fische(i).Gemocht Dann
Wenn v2.Länge > Mögen_Abstand Dann
v1 = v1.Addiere(v2) ' Differenzvektor addieren
Ende
Ansonsten
Wenn v2.Länge < Verabscheuen_Abstand Dann
' Vektor, der angibt in welche Richtung und wie weit sich der Fisch
entfernen soll, ausrechnen
v1 = v1.Addiere(v2.Einheitsvektor.Sklarmultipliziert(v2.Länge Verabscheuen_Abstand))
Ende
Ende
37
Nächster
Wenn nicht(v1.X = 0 Oder v1.Y = 0) Dann
Hans.Position = Hans.Position.Addiere(v1.Einheitsvektor)
Ende
Dieser Pseudo-Code spiegelt nicht die gesamte Methode wieder, da beispielsweise keine Behandlung der
Grenzen des Teichs stattfindet. Diese wurde im richtigen Programm so realisiert, dass alle Fische die
versuchen die Grenze zu überschreiten unter einem Winkel von 45° reflektiert werden.
Darstellung des Modells
Da das ganze Modell visualisiert werden sollte, wurde für die Darstellung auf die relativ moderne
Grafikkarten-Schnittstelle „Graphics Device Interface Plus“, GDI+, zurückgegriffen. Zunächst einmal wurden
alle Fische durch Kreise mit verschiedenen Farben dargestellt. Damit die Grafikqualität entsprechend gut
ist, wurden die Kurven der Kreise mit Hilfe der Antialiasing-Technik geglättet. Weiterhin, um ein Flackern der
Grafiken zu vermeiden, wurde das System des „double buffering“ verwendet.
Nutzungsgrenzen
Das ganze Modell würde hervorragend funktionieren, wenn der Teich keine Grenzen hätte. Denn gerade
durch diese ist es wahrscheinlich, dass ein Fisch der einen anderen verabscheut aber von diesem gemocht
wird, zu einer Grenze gelangt und dann nicht weiter kommt. Um aber ein Nichtbewegen der Fische in
solchen Situationen zu vermeiden, muss sich ein Fisch, auch wenn es den normalen Berechnungen
widerstrebt, von der Grenze entfernen. Dies kann realisiert werden, indem der Fisch nah an den Grenzen
weiter schwimmt oder aber auch durch ein Reflektieren der Fische an diesen. In beiden Fällen würde aber
ein verfolgender Fisch dem Verfolgtem näher kommen.
Programmablauf-Protokoll
Damit das Programm benutzt werden kann, werden im weiteren Verlauf Aufbau und Funktionsweise des
Lösungsprogramms verdeutlicht.
38
Aufbau
Abbildung 8 - Aufbau des Lösungsprogramms
Im oberen Teil des Programms befindet sich, wie in Abbildung 7 erkennbar, eine Werkzeugleite. Mit dieser
kann der Benutzer eine Fischteich-Simulation starten und beenden, sowie Fische mit zufällig verteilten Zuund Abneigungen automatisch erstellen lassen. Der Fischteich mit seinen Fischen, welche alle unterschiedliche Farben und Name haben, ist unter der Leiste als ein schwarzes Rechteck zu erkennen. Rechts
neben dem Teich befindet sich der „Einstellungen“-Bereich für das Erstellen von Fischen. Dort können
Name, Farbe, Ausgangsposition und Beziehungen zu anderen Fischen vereinbart werden.
Mit dem Fisch-Generator in Abbildung 9 ist es
möglich, eine bestimmte Anzahl von Fischen,
deren Zu- und Abneigungen nach einem einstellbaren Verhältnis zufällig verteilt sind, zu erzeugen.
Abbildung 9 - automatische Erstellung von Fischen
Funktionsweise
Um nun eine Simulation starten zu können, muss der Benutzer die kommenden Schritte ausführen:
1. Erstellen der Fische
1.1. Zunächst einmal ist es nötig, die Ausgangsposition des zu erstellenden Fischs in Form eines
Koordinatenpaars anzugeben. Dies geschieht über das Bewegen der beiden im „Einstellungen“-
39
Bereich befindlichen Schieberegler, wobei der Horizontale die X- und der Vertikale die Y-Achse
darstellt.
1.2. Nun sollte der Benutzer die Farbe des Fischs aussuchen. Dazu muss er mit der Maus auf die
farbige Fläche nahe den Schieberegler drücken. Nun öffnet sich ein „Farbauswahl“-Dialog, in
welchem der Benutzer die Farbe des Fisches aussucht.
1.3. Als nächstes gibt der Anwender dem Fisch zur eindeutigen Identifizierung einen Namen, den er im
„Fischname“-Textfeld einträgt.
1.4. Zum Schluss betätigt der Benutzer nur noch die „Fisch erstellen“-Schaltfläche und schon wird der
Fisch auf der linken Seite mit seinem Namen angezeigt.
1.5. Die oben genannten Schritte sind so oft durchzuführen, bis die gewünschte Anzahl an Fischen
erreicht ist. Hinweise: Der Anwender sollte darauf achten, dass er die Fische nicht an gleicher Stelle
positioniert und, dass er jedem Fisch mit einem anderen eindeutigen Name versieht. Ansonsten
weist ihn das Programm auf entsprechende Fehleinstellungen hin.
2. Festlegen der Beziehungen
2.1. Nachdem der Anwender nun einige Fische erstellt hat, ist es an der Zeit die Fische mit Zu- und
Abneigungen zu versehen. Dazu wählt er zu Beginn ein Fisch in dem Kombinationsfeld „Fische“
aus.
2.2. Anschließend füllt sich die unter dem Kombinationsfeld befindliche Liste mit den Namen der
anderen Fische, sowie einer Anordnung von Kontrollkästchen neben diesen Namen. Wenn der
ausgewählte Fisch einen anderen Fisch mögen soll, muss der Benutzer auf das Kontrollkästchen
vor dem Namen des zu mögenden Fischs mit der Maus drücken. Hinweis: Das Kontrollkästchen
bekommt dann einen Haken. Möchte der Anwender diesen und somit auch die Zuneigung zu
diesem Fisch entfernen, muss er nochmals mit der Maus auf das Kontrollkästchen drücken.
2.3. An dieser Stelle sollte der Anwender weitere Beziehungen zwischen den Fischen festlegen.
3. Bedienung der Simulation
3.1. Nach dem Festlegen der Beziehungen, kann der Benutzer die Simulation über die „Starten“Schaltfläche beginnen lassen. Nun werden sich die Fische je nach Einstellungen mehr oder
weniger stark bewegen.
3.2. Wünscht der Anwender das Ende der Simulation herbei, so muss er nur die jetzt aktive „Anhalten“Schaltfläche betätigen.
4. Automatisches Generieren der Fische
4.1. Das Programm unterstützt ein automatisches Erstellen von Fischen, welches der Benutzer über die
„Fisch-Generator“-Schaltfläche erreichen kann.
4.2. Hat der Benutzer diese Schaltfläche betätigt, so wird der „Fisch-Generator“-Dialog geöffnet. Jetzt
sollte er die Anzahl der zu erstellenden Fische mit dem oberen Schieberegler verändern. Hinweis:
Es können zwei bis einhundert Fische erstellt werden.
4.3. Als nächstes wird den Fischen das Verhältnis von Zu- und Abneigung im darunter befindlichen
Schieberegler zugeteilt.
4.4. An dieser Stellen kann der Benutzer optional noch die Farbe aller Fische mit dem drücken der
linken Maustaste auf die farbige Fläche verändern.
4.5. Zum Schluss muss der Anwender nur noch die „Erstellen“-Schaltfläche bedienen und die Simulation, wie oben erklärt, starten. Hinweise: Die Namen der Fische werden in der Simulation,
aufgrund der teilweise großen Anzahl, nicht dargestellt. Weiterhin ist zu beachten, dass alle zuvor
manuell erstellten Fische gelöscht werden.
Probeläufe des Programms
1. Zu Beginn soll demonstriert werden, wie sich das Lösungsprogramm verhält, wenn ein Fisch von allen
anderen gemocht wird, aber alle anderen verabscheut:
Nachdem die Simulation gestartet wurde, bewegen sich die
blauen Fische auf den von allen gemochten orangfarbenen Fisch
zu. Dieser Fisch versucht sich dann anschließend immer von den
verabscheuten blauen Fischen zu entfernen. Diese verfolgen
jedoch den orangen Fisch immer weiter, sodass letztendlich alle
Fische ständig in Bewegung bleiben.
Abbildung 10 - der orange Fisch wird von allen
anderen gemocht, mag selber aber kein Fisch
40
2. Als nächstes wird ein Test durchgeführt, bei dem jedem Fisch zufällig Zu- und Abneigungen im
Verhältnis 70 : 30 zugeteilt werden:
Da die Fische zu Beginn zufällig im Teich verteilt sind, bewegen
sie sich alle in Richtung Mitte des Teichs. Nun bilden sie einen
Kreis der sich als ganzes Objekt scheinbar zufällig im Teich
bewegt. Der Radius des Kreises bleibt dabei relativ konstant.
Abbildung 11 - automatisch generierte Fische
(70:30)
3. Zum Schluss wird noch eine Simulation durchgeführt, bei der jedem Fisch zufällig Zu- und Abneigungen
im Verhältnis 30 : 70 zugeteilt werden:
Die Fische verhalten sich ähnlich, wie in der 2. Simulation,
sodass sie einen Kreis bilden. Er bewegt sich auch ähnlich, hat
aber einen größeren Radius.
Abbildung 12 - automatisch generierte Fische
(30:70)
Programm-Text
Die folgende Prozedur berechnet die Position eines Fischs:
Public Sub Render(ByVal gr As Graphics)
' neuen Richtungsvektor zurücksetzen
v1.X = 0
v1.Y = 0
' wenn Richtung durch andere Fische bestimmt wird
If direction = -1 Then
' Fische aufzählen
For i = 0 To p_Fishes.Count - 1
' Vektor vom Ausgangs-Fisch bis zum aktuellen Fisch (i)
v2 =
FrmMain.Fishes(p_Fishes(i).FishIndex).p_Position.Subtract(p_Position)
' wenn aktueller Fisch gemocht wird
If p_Fishes(i).Likes Then
' wenn der Abstand größer als 25 Pixel ist
If v2.Length > 25 Then
' Vektor vom Ausgangs-Fisch bis zum aktuellen Fisch zum neuen
Richtungsvektor addieren
v1 = v1.Add(v2)
ElseIf v2.Length < 24 Then ' wenn der Abstand kleiner als 24 Pixel
ist
' Gegenvektor vom Ausgangs-Fisch bis zum aktuellen Fisch zum
neuen Richtungsvektor addieren
v1 = v1.Add(v2.UnitVector.ScalarMultiplication(-25 + v2.Length))
End If
Else ' wenn Fisch verabscheut wird
' wenn Abstand kleiner als 100 Pixel
If v2.Length < 100 Then
41
' Gegenvektor vom Ausgangs-Fisch bis zum aktuellen Fisch zum
neuen Richtungsvektor addieren
v1 = v1.Add(v2.UnitVector.ScalarMultiplication(-100 + v2.Length))
End If
End If
Next
' neue Richtung berechnen
v2 = p_Position.Add(v1.UnitVector)
Else
count += 1
v1.X = Directions(direction).X
v1.Y = Directions(direction).Y
If count = 100 Then
count = 0
direction = -1
End If
End If
' Richtung zum entfernen vom Rand berechnen
If p_Position.Y < 5 And direction <> 0 Then
direction = 0
ElseIf p_Position.Y > 394 And direction <> 1 Then
direction = 1
End If
If p_Position.X < 5 And direction <> 2 Then
direction = 2
ElseIf p_Position.X > 444 And direction <> 3 Then
direction = 3
End If
' wenn Richtungsvektor kein Nullvektor
If v1.X <> 0 Or v1.Y <> 0 Then
' neuen Ortsvektor ermitteln
p_Position = p_Position.Add(v1.UnitVector)
End If
' Bitmap zeichnen (Kreis)
gr.DrawImageUnscaled(bmp, p_Position.X - 5, p_Position.Y - 5, bmp.Width,
bmp.Height)
End Sub
42
Aufgabe 5
Schwächen einer Wort-für-Wort-Übersetzung
Aus den vorgegebenen Vokabeln wurden folgende Sätze konstruiert:
Wort-für-WortEnglischer Satz
Deutscher Satz
Übersetzung
Übersetzungsfehler
Big cats eat small
mice.
Groß Katzen essen klein
Mäuse.
Große Katzen essen
kleine Mäuse.
The mouse runs into
the hole.
Der/Die/Das Maus rennt in
der/die/das Loch.
Die Maus rennt in
das Loch.
Later the mouse runs
into the hole.
Später der/die/das Maus
rennt in der/die/das Loch.
Später rennt die
Maus in das Loch.
The mouse runs into
the cat.
Der/Die/Das Maus rennt in
der/die/das Katze.
Die Maus stößt auf
die Katze.
The cat runs after the
mouse.
Der/Die/Das Katze rennt
hinterher/nach der/die/das
Maus.
Die Katze rennt der
Maus hinterher.
Deklinationen der Adjektive
sind fehlerhaft.
Bestimmter Artikel kann
keinem eindeutigen Genus
zugeordnet werden.
Durch temporale Angabe
steht das Verb an falscher
Stelle.
Verknüpfung von Verb und
Präposition im Englischen
als neues deutsches Wort
nicht richtig übersetzt.
Adverb steht an falscher
Stelle.
Leistungsfähigkeit von Sprachübersetzungsprogrammen
Es wurden die drei Sprachübersetzer „http://www.google.de/“, „http://ets.freetranslation.com/“ und „L&H
Power Translator“ auf ihre Funktionalität überprüft. So wurden fünf deutsche und anschließend fünf
englische Sätze auf ihre richtige Übersetzung hin kontrolliert. Diese Sätze mit ihrer Übersetzungen werden
im Folgenden dargelegt:
Deutscher Satz
Englischer Satz
Nutze den Tag!
Wie man einem Computer das Sprechen lehrt.
Es gibt nicht genug Nahrung für alle Menschen.
Sie ist ihm hinterhergelaufen.
Du bist ins Fettnäpfchen getreten
Seize the day.
How to teach a computer the speaking.
There is not enough food for all humans.
She runs after him.
You put your foot in it.
Englischer Satz
Deutscher Satz
There is no space left on hard disk.
People often make crazy things.
Getting the most out of your computer.
Never meant anything else.
Stick together team!
Es ist kein freier Speicher auf der Festplatte übrig.
Die Leute machen oft verrückte Sachen.
Wie man das Meiste aus seinem Computer erhält.
Es war nie etwas anderes gemeint.
Zusammenhalten, Mannschaft!
Im weiteren Verlauf folgen die Ausgaben der jeweiligen Übersetzter und Vermutungen über die Fehlerursache:
Nutze den Tag!
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
Use the day!
Utilize the day!
Use the day!
„Nutze den Tag!“ ist aus dem Lateinischen „carpe diem“, was wörtlich soviel wie „pflücke den Tag“ heißt,
abgeleitet. Dies ist eine feststehende Redewendung, die im Englischen nicht mit einer Form von „use“
übersetzt wird, sondern mit „seize“. Das Wort „use“ ist nicht richtig, da der „Tag“ ja schließlich nicht wie ein
Computer „benutzt“ wird. Die Vokabel „seize“ drückt hingegen vielmehr das „Nutzen/Ergreifen einer
Gelegenheit“ aus. Die Übersetzungsdienste scheinen das Wort „Nutze“ als eine Form von „etwas benutzen“
zu erkennen. Letztendlich bietet keiner der Übersetzer eine brauchbare Lösung an.
Wie man einem Computer das Sprechen lehrt.
http://www.google.de/
http://ets.freetranslation.com/
As one teaches a computer a
How one teaches a computer the
L&H Power Translator
As one teaches a computer of the
43
speaking.
speaking.
speaking.
Das Indefinitpronomen „man“ wird von allen Diensten mit übersetzt, obwohl dies nicht unbedingt notwendig
ist. Das bei „http://www.google.de/“ und „L&H Power Translator“ verwendete Wort „as“ ist nicht richtig, da
„as“ nicht die Art und Weise wie etwas gemacht wird repräsentiert, sondern einen Vergleich. Weiterhin wird
bei den Übersetzungsdiensten der bestimmte Artikel „das“ zum einen als unbestimmter Artikel „a“ und zum
anderen als eine „of the“-Konstruktion falsch übersetzt. Die beste Lösung, die die drei Übersetzer anbieten
können, ist die von „http://ets.freetranslation.com/“.
Es gibt nicht genug Nahrung für alle Menschen.
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
There is not enough food for all
There is not enough nourishment
There is food for all people
humans.
for all persons.
sufficiently not.
„L&H Power Translator“ ist bei diesem Satz als einziger Sprachübersetzer gescheitert. Die Konstruktion
„sufficiently not“ ist falsch und heißt übersetzt soviel wie „ausreichend nicht“. Außerdem impliziert diese
Konstruktion einen fehlerhaften Satzbau. Die beiden anderen Dienste gaben eine richtige Übersetzung aus,
wobei die von „http://www.google.de/“ ausgegebene die beste ist.
Sie ist ihm hinterhergelaufen.
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
It afterwards-ran it.
It ran behind it.
She/it after-ran for him/it.
Abgesehen davon, dass alle Übersetzungen fehlerhaft sind, hat „L&H Power Translator“ zu mindest die
verschiedenen Möglichkeiten der Personalpronomen-Übersetzung angegeben. Das Wort „hinterhergelaufen“
wurde bei allen Diensten nicht wirklich als „run after“ erkannt.
Du bist ins Fettnäpfchen getreten.
http://www.google.de/
http://ets.freetranslation.com/
You stepped into the fat cell.
L&H Power Translator
You stepped into the bowl of fat.
You stepped into the
Fettnäpfchen.
Auch bei diesem Beispiel wird eine Schwäche aller Sprachübersetzer deutlich, da jeder versucht das
Sprichwort wortgetreu zu übersetzen. Scheinbar ist das Sprichwort in keiner Datenbank der Dienste
enthalten. Erstaunlich ist weiterhin, dass „L&H Power Translator“ das Wort „Fettnäpfchen“ überhaupt nicht
kennt.
There is no space left on hard disk.
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
Es gibt keinen Raum nach links
Es gibt keinen Platz links auf
Es ist kein Raum mehr auf
auf Festplatte.
Festplatte.
Festplatte.
Die Sprachübersetzer „http://www.google.de/“ und „http://ets.freetranslation.com/“ haben das Wort „left“ nicht
als „übrig“ erkannt. Auch haben „http://www.google.de/“ und „L&H Power Translator“ das Wort „space“ nicht
im richtigen Zusammenhang erfasst. Die hier auftretenden Fehler ergeben sich wahrscheinlich aus dem
Nichterkennen des Kontexts und der Tatsache, dass dies ein fachlicher Satz aus der Informatik ist.
People often make crazy things.
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
Leute bilden häufig verrückte
Leute machen oft verrückte
Bevölkern Sie oft, machen Sie
Sachen.
Dinge.
verrückte Sachen.
Hier hat sich „http://ets.freetranslation.com/“ als bester Übersetzer erwiesen. Die beiden anderen verwendeten falsche Worte und „L&H Power Translator“ gab sogar eine komplett andere Satzstruktur aus.
Scheinbar hat der Sprachübersetzer das Wort „people“ als Verb erfasst.
Getting the most out of your computer.
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
Herausbekommen die die
Das meiste aus Ihrem Computer
Das Herausholen des Meisten
meisten aus Ihrem Computer.
erhaltend.
aus Ihrem Computer.
Die einzige annehmbare Übersetzung liefert „L&H Power Translator“. Es scheint so, als würden die Dienste
mit der Satzstruktur nicht zu recht kommen, da das Gerundium „getting“ im Satz verwendet wird.
Never meant anything else.
44
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
Nie bedeutet noch etwas.
Nie hat noch etwas bedeutet.
Nie meinte sonst noch etwas.
Bei diesem Ausspruch fehlt ein Subjekt. Die Dienste erkennen dies nicht und bilden darauf hin sehr
merkwürdige Übersetzungen.
Stick together team!
http://www.google.de/
http://ets.freetranslation.com/
L&H Power Translator
Des Stockes Mannschaft
Kleben Sie Mannschaft zuKleben Sie zusammen, tun Sie
zusammen!
sammen!
sich zusammen!
Keiner der Übersetzungsdienste erfasst, dass die Wörter „stick together“ als ein Wort übersetzt werden
müssten. „http://www.google.de/“ nimmt sogar fälschlicher Weise an, dass „stick“ hier als Substantiv gemeint
ist. Die anderen beiden Übersetzer erkennen das Verb irrtümlich als „kleben“.
Fundamentale Schwierigkeiten maschineller Übersetzung
1. Idiomatische Ausdrücke
Idiome sind Redewendung, deren Gesamtbedeutung nicht aus der Bedeutung der Einzelwörter erschlossen
werden kann. Dass heißt, wenn ein Dienst versucht alle Vokabeln wörtlich zu übersetzen, wird der Sinn der
Redewendung in der anderen Sprache nicht mehr nachvollziehbar. Beispiel: Das Idiom „Einer für alle und
alle für ein“ würde wörtlich übersetzt „One for all and all for one“ lauten. Niemand der das lesen würde,
könnte die Bedeutung, „das Zusammenhalten einer Gruppe“, erkennen. Die richtige Übersetzung lautet:
„Where we go, we go all“. Generell muss versucht werden den zu übersetzenden Text so umzuändern, dass
die Idiome durch eine einfache, klare und bedeutungsnahe Struktur ersetzt werden.
2. Umgangssprachliche Ausdrücke
Die Übersetzung eines umgangssprachlichen Texts ist für Übersetzungsdienste besonders schwer, da von
der formellen hochdeutschen durch Regeln aufgestellten Grammatik erheblich abgewichen wird. Die
Übersetzer arbeiten mit diesen festgelegten Regeln. Es kommt häufig vor, dass die Dienste selbst mit diesen
zum Teil viele Ausnahmen enthaltenen und komplexen Regeln enorme Schwierigkeiten aufweisen. Beispiel:
„Er kommt mit dem Geld einfach nicht rüber.“ ist ein solcher umgangssprachlicher Satz für den die Wort-fürWort-Übersetzung folgende ist: „He simply doesn't come across with the money“. Diese Übersetzung
verändert die eigentliche Bedeutung des umgangssprachlichen Satzes, da „come across“ „herüberkommen“
und nicht, wie es richtig wäre, „herausgeben“ heißt.
3. Verschachtelte Ausdrücke
Gerade bei einer besonders tiefen Verschachtelung von Sätzen gelangen die Übersetzungsdienste schnell
an ihre Granzen, da sie ja nicht differenzieren können, welche Gedanken in den einzelnen Nebensätzen
enthalten sind. Beispiel: „Unser Lehrer gab heute, nachdem er die Klausuren, die gestern geschrieben
worden waren, kontrolliert hatte, die Lösungen, welche den Schülern recht einfach erschienen, und die
Zensuren bekannt.“