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.“