Wir waren ja im Bereich funktionales Programmieren angekommen, bei der Frage,
wie können wir Funktionen höherer Ordnung verwenden, um Ereignisroutinen zu modellieren,
im Rahmen von grafischen Bedienoberflächen im Rahmen von Animationen spielen.
Das heißt eben, in all den Arten von Anwendungen,
wo wir einen Kontrollrahmen parametrisieren wollen mit Dingen,
die geschehen sollen, wenn bestimmte Ereignisse auftreten.
Wir haben zum Abschluss der letzten Vorlesung schon ganz kurz angeschaut,
so die Grundidee, wie wir grafische Bedienen, Oberflächen ausstatten können.
Wir hatten das allersimpelste Beispiel gesehen, ein Programm,
das nichts weiter kann, als ich freundlich zu verabschieden.
Dabei in das Titzpeck-Gui reingeguckt. Was ich heute gern mit ihm besprechen möchte,
ist, wie wir diese Methoden verwenden können,
um Animationen, Spiele, Simulationen zu programmieren,
der bietet Record wirklich einfach zu benutzende Methoden,
wie wir
es tun können
Und zwar sollte das Beispiel einmal sein,
dass wir eine ganz simple Simulation programmieren,
um zu sehen, wie wir es benutzen können.
Und als weiteres Beispiel auch einfach unsere private Uhr,
wo die Zeiger und Sekundenzeiger auf dem Bildschirm dann weiter bewegt werden,
abhängig von der Uhrzeit.
Was brauchen wir für so was? Wir brauchen einmal die Möglichkeit,
wenn wir etwas simulieren wollen,
wie zum Beispiel so eine Uhr,
die tickt,
dass wir
eine Aktion auslösen können
Eine simulierte Welt, die wir irgendwo repräsentiert haben oder irgendwie repräsentiert haben, aktualisieren.
Passend zu dem Zustand, in dem wir jetzt sind.
Gibt es ausgefeilte Methoden, sowas zu machen? Wir schauen
uns hier die simpelste Möglichkeit, eine Simulation zu machen,
an die man immer wieder in den Lehrbüchern findet, einfach ereignisorientiert.
Und zwar diskret. Also keine prozessorientierte Simulation,
wo kontinuierlich etwas geschieht, sondern wir stellen uns einfach vor,
unsere Welt ist diskret, sie besteht aus bestimmten Momenten,
wo wir einfach einen Schnappschuss machen und von Schnappschuss gucken,
immer auf den vorherigen Zustand der Welt und überlegen dann,
wie die nächste Welt aussehen soll.
Stellen Sie da und zwischendrin können eben Spieler,
Benutzer, was auch immer, Ereignisse auslösen,
worauf reagiert werden muss in dieser Welt.
Dafür gibt es in Regic das schöne Rahmenwerk Universe Regic,
wo wir ein Simulationsuniversum darstellen können und definieren können.
Und dieses Universum ist so ausgelegt, dass Teile des Universums
auf verschiedenen Rechnern laufen können.
Und wir haben auch die Möglichkeiten zwischen diesen Rechnern
Uns zu synchronisieren, sodass wir, sagen wir mal,
mehr Personenspiele machen können,
wo jedes Team für sich von einem eigenen Rechner sitzt,
sieht, was der offizielle sichtbare Teil der Welt ist.
Und wir können eben ihre Spielzüge machen und die anderen im Gegenzug.
Und das Universe-Paket vermittelt zwischen all diesen Teilnehmern.
Wie auch immer für die simpelste Art,
vergessen wir erstmal das Mehrprozesssystem im Hintergrund,
wollen wir einfach jeweils auf die Uhr schauen,
wie weit sind wir in unserer Welt, wie weit ist sie fortgeschritten.
Und dann soll etwas geschehen, um die aktuelle Welt darzustellen
Wir sind muskrafisch angezeigt werden. Für jede Zustandsänderung, neu.
Und wenn irgendetwas geschieht, Sprachangabe, Maus, Tastatur,
muss darauf reagiert werden können. Und genau dieses darauf reagieren,
und was geschieht das, werden wir uns eben anschauen,
parametrisieren wir, indem wir Ereignisroutinen übergeben.
Callback Functions. So, für den aller, aller einfachsten Fall,
für eine Animation, fragen wir einfach den Rechner, wie spät ist es?
Und dann wird zu diesem Zeitpunkt ein Bild gezeichnet.
Der erste Parameter, den wir haben für das System,
ist dann eine Funktion, die sagt,
wie abhängig vom Zustand des Bilder nur aussehen muss.
Und das rübers Paket hat einen Parameter Create Image,
wo wir die Funktionen übergeben, die für den augenblicklichen Zustand
ein Bild erzeugt.
Und Animate erzeugt einen, kennen wir es,
startet eine Simulationsuhr.
Es ist das Aufsetzen des Ganzen
Sorgt etwas, worauf wir zeichnen können,
startet eine Uhr und die wird 28 mal pro Sekunde sagen,
nächstes Bild, bitte nächstes Bild, bitte nächstes Bild, bitte.
Bei jedem Tick, jede 28. Sekunde wird ein Zähler um eins erhöht und diese Call-Back-Function-Create-Image,
die kriegt nicht viel, die kriegt einfach nur die Zahl dieses Ticks.
Also wir sind bei Tick 1, 2, 3 bis 28 und das Programm,
das läuft und läuft und läuft und läuft, so lange,
bis wir etwas dagegen tun, entweder mit der Stopptaste abbrechen oder einfach das Fenster zumachen, indem wir unsere Simulationen sehen.
Das Beispielprogramm aus Rected, was einige vielleicht schon gesehen haben,
wenn sie sich die Dokumentationen angesehen haben,
ist jetzt wirklich die aller, aller simpelste Simulation,
aber die sagt einem so im Prinzip, wie es geht,
also zum einen, damit wir Bilder überhaupt anzeigen können,
brauchen wir wieder aus Pow-to-Design-Programms zweite Auflage,
das Image-Paket und für den,
damit wir überhaupt Bilder erzeugen können und damit wir eine Animation laufen lassen können,
brauchen wir eben das Modul Universe.
So, und jetzt wollen wir also ein UFO fliegen lassen.
Da soll er praktisch nichts weiter tun, als einfach so vom Himmel fallen.
Und der Hintergrund für unsere UFO-Welt ist einfach ein weißes Rechteck.
Um jetzt ein UFO-Bild auf ein, dieses Rechteck zu bringen,
verwenden wir aus dem Image-Modul die Funktion Underley Exvite,
das heißt lege Ein, das, was ich jetzt angebe,
unter das, was als nächstes kommt,
und zwar bezogen auf die Position XY.
Und nach unten kommt hier also ein Recht erkundert,
mal 100 groß, gefüllt.
Das heißt, diese Solid String und White soll weiß aussehen.
Farbe weiß. Und das ist immer an der Ex-Position 50.
Und der zweite Parameter ist die Höhe, wo es zurzeit
gerade sein soll, dass also ein Parameter aus unserer Welt.
Und dahin kommt als anderes Bild,
unser Ufer-Bild,
Ufer ist eine vordefinierte
Eine von uns definierte Variable, die das Bild unseres UFOs anzeigt.
So, also wenn wir das machen,
das Erzeugen der UFO-Szene als erstes mal bei der gegebenen Höhe hier,
wo die Szene erzeugt wird, also ganz oben,
da geht's los.
Damit startet das Ganze. Und das UFO definieren wir hier
. Das wird auch mit Andalé allein erzeugt.
Und zwar mittig in X, mittig in Y ausgerichtet.
Das sind die beiden Center
Beiträge hier. Ein Kreis und der ist grün und ein
Rechteck ist auch grün und das Ganze wird übereinandergelegt, ja.
Und damit haben wir also dieses UFO-Bild definiert und jetzt geht
das Ganze los hier mit Animate. Wir sagen,
rufen Any mit auf, kreiert UFO 10,
damit wird das erste Bild erzeugt und dann bei jedem Tick
wird es neu gezeichnet und bei jedem Tick, wenn die Simulationsuhr tickt,
ändert sich diese einzige Parameter von der Callback-Funktion create uphosy.
Das heißt, wir zum Anfang haben wir einen ersten Parameter Hate von null,
weil Animate kreiert UFO ziehen mit dem Tick null Aufruf,
dann eins, dann zwei, dann drei, dann vier.
Und wir sehen also im Laufe der Animation,
wird diese Funktion jeweils mit einem anderen Parameter Hate aufgerufen.
Und das ist das, was Animate für uns macht,
einfach mitzählen, bei welchem Ticks sind wir.
Und damit ändert sich bei dem ganzen Code hier eigentlich
nur die Höhe, in der das UFO gezeichnet wird
So, das, was hier passiert. Also simpel,
Animate startet unsere Animation. Der eine Parameter, den wir
haben, ändert sich. Und im Wesentlichen wird das UFO
einfach dem Horizont die hoch und runter steigen, ja?
Kann man das beeinflussen, in welchem Faktor sozusagen der Held steigt?
Also das, das muss dann das Programm hier drin machen, ne?
Also das, das kreiert UFO 10 Macht ist,
guckt sich den Parameter an und überlegt, wie zeichne ich mein Bild.
Und natürlich kann ich hier bei Rektengel sagen,
wo soll es sein, ob es beschleunigt oder nicht.
Also hier muss ich dann echt rechnen, also hier ist
das allersimpelste Geschehen, wir geben einfach den Zähler weiter.
Aber dieses, es diskreiert UFO-Sinn,
das ist unser Händler und da dürfen wir all unser Gehirnschmalz reinstecken,
wie ein schönes Bild gezeichnet wird.
Also hier ist es eben einfach nur ein Übereinanderwegen von einem
weißen Rechteck und dem UFO-Bild in der gegebenen Höhe.
Wir könnten das UFO jeweils da tanzen lassen oder was immer
wir wollen.
Wenn wir es denn wollen
Und das ist, was dieser Animationsfilm hier macht.
Ich habe den einfach nur rausschreiben lassen, also die grünen Männchen kommen. Gut,
war jetzt als nächstes, nach dieser kleinen Vorübung,
wo wir gesehen haben, im Wesentlichen schreiben wir diese Funktion,
die wir übergeben an Animate, zeichnen wir jetzt meine Uhr.
Um das ein bisschen aufwendiger zu machen. Eine Uhr mit Zeigern,
für jede Sekunde die Uhr mit der aktuellen Zeit und der Position der Zeiger.
Also die Teilaufgaben, die wir lösen müssen,
einmal die Uhrzeit feststellen
Das können wir, indem wir unser System fragen,
wie spät es ist, mit Karin Seconds, eine Funktion,
die abfragt, die uns den Stand der Rechneruhr weitergibt,
ja in Sekunden, seit definierten Nullpunkten.
Und da ist für uns ein bisschen mühsam wäre,
jetzt natürlich aus den Sekunden gezielt ab 1970 oder so auszurechnen,
wie spät ist es jetzt wirklich?
Gibt es dafür noch eine Funktion, die einfach diese Systemsekunden,
die gezielten Systemsekunden in Eine Structure vom Typ tät überführt.
Und deshalb, wenn wir die Uhrzeit, also mit einer Dead-Struct-Chat dann abfragen.
Karin Seconds gibt uns die Sekunden seit Ursprung, ich glaube,
das war 1970. Ich bin mir nicht ganz sicher,
plus Seconds to Date gibt uns Destructure,
die jetzt das genaue Datum mit Uhrzeit gibt.
Ja, und dann zweite Teilaufgabe zeichnen wir mal ein Ziffernblatt.
Ich werde den Teil ein klein wenig ausführlicher machen,
weil sie auf dem augenblicklichen Aufgabenblatt ja auch eine Aufgabe haben,
in der verlangt wird, dass sie ein Bild zeichnen.
Deshalb gucken wir uns jetzt nochmal genauer an
Wie wir denn Bilder machen können. Gut, ja,
unser Händler, den wir an Animate übergeben wollen, als Callback-Funktion,
das soll sein. Next Clockwort. Und das ist dieser Händler,
bekommt von Animate einfach immer die aktuelle Welt.
Das ist eben entweder dieses, nur diese Zahl,
eins, zwei, drei, ein Zielen der Clockticks,
aber wir können auch etwas ausgefeiltere Versionen haben,
wo wir selbst sagen, was der nächste Wert sein soll.
Also wenn wir die allersimpelste Methode mit Animate nehmen,
dann haben wir genau das, was wir beim UFO gesehen haben.
Wir kriegen einfach nur null an 2, 3, 1,
gezählten Ticks zurück. Wenn wir stattdessen selbst noch sagen,
wir wollen etwas reichhaltigere Informationen haben, wobei der alte System
Zustand genommen wird und ich möchte den neuen Systemzustand haben.
Dann nehmen wir die Funktion Next Clockworld,
das ist ein Händler, dem wird immer die Welt gegeben,
Was immer die Datenstruktur für die Welt ist, das ist generisch.
Wir können da reinstöpseln, was wir wollen.
Wir schaffen eine Welt von einem bestimmten Datentyp. Und beim
nächsten Aufruf von Next Clockworth, also beim nächsten Simulationstext,
wird genau unsere Funktion Next Clockworth hier aufgerufen.
Und die guckt sich die alte Welt an,
denn was immer dieses Structure oder was es ist,
war unser Datentyp, rechnet den neuen Zustand der Welt aus.
So, und in diesem Fall ist die ganze Welt,
die eine Uhr zu interessiert hat,
es reduziert auf eins,
wie spät ist es
Also mehr weiß man nicht über die Welt. Deshalb wird Next Glockworld
sich gar nicht anschauen, wie war die Welt vorher,
sondern fragt einfach den Rechner, wie spät ist es.
Und gibt als neue Struktur für die Welt, wie sie jetzt ist,
eben dieses Seconds to Date für die aktuelle Uhrzeit zurück.
Und das muss, dann brauchen wir als zweites den Händler,
der die Welt, die wir jetzt haben, zeichnet.
Und dazu müssen wir uns jetzt üben, überlegen,
bezeichnen wir eine Uhr.
Ich nehme mal das ganz klassische Modell,
Analoguhr mit Zeigern
Einfach, weil es auf dem Mac fehlt. Früher hatte der Mac
immer eine hübsche Uhr, genau mit so einem Zippernblatt.
Und da das inzwischen fehlt, habe ich gedacht,
machen wir uns mal wieder eine eigene.
Genau mit dem alten Ziffernblatt. Das hatte ich mir
noch vom Backup runtergeholt. So, also die Uhr braucht
ein Ziffernblatt und dafür definieren wir eine globale Variable Blockface.
Und dem PNG-Fall, der dieses Ziffernblatt zeichnet,
holen wir über das Men Special Insert-Menü in Third-Image und da lassen wir uns das Bild hier einsetzen.
Jetzt haben wir unser Bild hier drin
Müssen wir jetzt für die Uhr uns überlegen,
wie groß soll diese Welt gezeichnet werden?
Mindestens muss das Ziffernblatt draufpassen. Deshalb fragen wir,
wie groß ist denn dieses Ziffernblatt eigentlich?
Kriegen wir mit der Funktion Imagehate? Und damit haben wir
die Höhe unseres Scannenwess und die für die Breite, fragen wir,
dieses Bild hier ab nach der Breite mit Imagewitz und kriegen die Breite.
Und damit wird jetzt unsere Welt gezeichnet auf einem Canvas,
auf einem Hintergrund von eben den Abmessungen
genau des Ziffernblattes
So, wir können jetzt zehn zeichnen,
indem wir in dem ausgefeilteren System einfach sagen,
wir erzeugen erst mal mit Empty-Scene das leere Chaos.
Zu Anfang war nichts da außer der Lehre
und wir müssen das jetzt füllen mit irgendwas.
Und dann, wenn wir eine solche leere Szene haben,
können wir mit Place-Image Dinge auf diesem leeren Hintergrund drauf tun.
So, und für, um jetzt die Uhr zu zeichnen,
nehmen wir unser Ziffernblatt als erstes Bild
Tun es mit Place-Image in die MTScene rein.
Und dann müssen wir ein klein wenig rechnen aus den Stunden
und Minuten ausrechnen, wie die Zeiger stehen sollen.
Und dann zeichnen wir die Zeiger und die Zeiger zeichnen wir
einfach als Graden. Gut. Also den Stundenzeiger,
den zeichnen wir mit einer Funktion, die nennen wir Our Hand,
übergeben wir Stunde, Minute und wo es raufgezeichnet werden soll.
Und genauso und Sekundenzeiger, nur alle drei Zeiger werden im Wesentlichen in derselben Art gezeichnet.
Deshalb bin ich natürlich eine Funktion Zeiderzeichnen, definiert auf Englisch,
Play Cent, also einen der Zeiger zeichnen.
Und der kriegt als Parameter einfach als erstes den Winkel,
unter dem er gezeichnet werden soll,
eine Farbe und wo er hin soll und die Länge.
Und den Winkel rechnen wir für den Stundenzeiger aus aus der Stunde
Und dann natürlich noch den Minutenanteil,
damit es ein bisschen genauer ist, Minuten durch 60.
Für den Minutenzweiger nehmen wir die übergebenen Minuten und addieren eben den Sekundenbruchteil drauf.
Und beim Sekundenbruchteil müssen wir gar nichts mehr groß machen.
Den Sekundenzeiger machen wir am längsten. Und den Stundenzeiger am
kürzesten, okay, ja. Und gegeben diese Funktionen
können wir jetzt unsere Uhr zeichnen
Also, Trock, Glockwirt, das ist jetzt unser Händler,
den wir auch an Simulationssystemen übergeben,
wenn immer wir eine neue Welt haben, soll sie gezeichnet werden.
Und zwar genau so. Der Parameter ist dann natürlich die Welt.
In unserem Fall die Uhrzeit. Und wir gehen hier davon aus,
dass ein Date-Record übergeben wird, was genau die Zutaten hat.
Also können wir jetzt hier fragen, als erstes unsere neue Welt,
ist Seconds to Date, Carren Seconds, der Dollhändler.
Denn die Höhe des Kennwess-Imageshild, Breite,
den kennen wir es, erzeugen wir mit MDC.
Und dann brauchen wir noch eben Stunden, Minuten, Sekunden.
Das kriegen wir mit den Akzessorfunktionen auf diese Structure, die zurückgegeben wird.
Date Hour von unserer Welt, Date Minutes. Denn Date Seconds,
schauen wir uns da nochmal die Syntax an. Wenn Sie sich zurückerinnern,
als wir besprochen haben, wie Structures in Recket aussehen,
wenn wir mit Defined Struct einen neuen Rekord-Typ definieren,
dann erzeugt das für uns, die automatisch ja die Akzessor-Funktion.
Wenn wir jetzt einen Datentyp-Date erzeugt hätten,
der ist vordefiniert, aber wir verwenden ihn.
Also wenn wir einen Datentyp erzeugen, wird automatisch,
werden die Akzessor-Funktionen definiert und die beginnen einfach mit dem Namen
des Typs und als nächstes mit dem Namen der Felder in diese Struktur.
Und weil diese Struktur eben die Felder ein Hour-Minute-Second hat,
so ein Date-Rekord, ja, heißen die Akzessor-Funktionen,
eben Date, Hour,
Date, Minute-Dezecken,
die sind automatisch übergeben worden
Ich habe hier jetzt nicht mehr abgefragt, ob tatsächlich das,
was wir zurückbekommen haben, ein Date ist,
weil wir es aber erzeugen, mit Second to Date.
Klar, können wir davon ausgehen, dass es von dem Typ ist.
So als erstes Anzeigen können wir es uns in Klartext anzeigen lassen,
zum Debatten, deshalb mache ich hier mit Display und mache einfach
eine Liste aus all den Dingen, die gebraucht werden.
Das ist also das, was wir zurückbekommen. Ja,
und jetzt müssen auf diese leere Welt die Teilbilder draufgetan werden
und Wir hatten ja diese Hour to Hand, Minute to Hand,
Second-to-Hand-Funktionen definiert. Also ganz im Innersten beginnen wir damit,
dass wir das Ziffernblatt hier auf den Lehren,
kennen wir es tun mit Place-Image, mittig.
Das ist genau das hier, da wird ausgerechnet,
wo ist denn die Mitte von unserem Kennenmess?
Und die Referenzpunkte beziehen sich auf die Mitte des Bildes und darauf
kommt der Sekundenzeiger, der darauf der Minuten zeige,
der darauf der Stundenzeiger und was dann zurückkommt,
ist gebunden worden, hier an unsere variable Welt.
So, jetzt rufen wir in dem Met
auf und dem übergeben wir jetzt unser Droher Klock wird.
Das wird eben im ersten Schritt hingehen und eine leere Welt erzeugen.
Und dann, Moment, falsche Richtung. Und also hier,
das ist unsere leere Welt, die wir am Anfang haben,
Beim ersten Druck, Klockwold verwendet.
Und Animate wird bei jedem weiteren Aufruf diesen Händlerdrohr-Klockwort aufrufen,
der ja hier als Parameter die Welt bekommt.
Und dann wird immer die gerade aktuelle Welt genommen und die wird aktualisiert.
Das ist also das, was hier passiert.
Und jetzt können wir uns mal angucken,
was sind unsere schöne Uhr hier macht.
Ich habe nicht jedes Bild der Folge gezeichnet.
Sparsamkeitsgründen hier. Also bei jedem Aufruf sehen wir
einen neuen Stand der Zeiger
Das ist also das, was wir tun können,
wenn wir einfach einen Prozess haben, den wir nicht beeinflussen wollen.
Das soll was beobachten, fragt Werte ab und fertig.
Denn die nächste Eskalationsstufe bei solchen animierten Abläufen ist,
wenn wir selbst eingreifen können wollen, in einem Spiel,
Spielkarten auf den Tisch legen oder sowas.
Und dafür haben wir einen noch feineren Kontrollrahmen.
Das ist der Big Bang, mit dem können wir ein
Universum kreieren und das noch viel Feindkörniger strukturieren, ja.
Also zunächst mal, die Funktion Big Bang würde uns als erstes eine leere Welt erzeugen,
mit der wir jetzt anfangen, was anfangen können.
Dann ein Fenster, in dem wir dem Zustand der Weltgrafik grafisch anzeigen können.
Simulationsuhrstaaten, kennen wir schon. Und wir können dort Ereignisroutinen
anmelden. Also wirklich, statt sie einmal so aufzurufen,
wie wir es eben zu Fuß gemacht haben bei Animate,
mit nur einer einzigen,
können wir verschiedene Ereignisroutinen im Universum registrieren lassen und damit dann besser eingreifen.
Gucken wir uns mal ein Beispiele an. Also einmal können wir sagen,
was geschehen soll, wenn die Simulationsuhr, die läuft,
tickt. Dafür können wir auf jeden Fall sorgen,
dass das Universum nicht stehen bleibt, sondern sie fortentwickelt.
Bei On-Chick wird immer, wie unsere Routine,
wie wir sie eben definiert hatten, aufgerufen.
Dann können wir aber auch sagen, was geschehen soll,
wenn die Welt neu gezeichnet werden soll.
Warum braucht man denn das? Warum Ist das Ding
so dämlich, dass sie nicht selbst tätig zeichnet?
Na, es wird schon immer bei jedem Tick neu zeichnen.
Aber so ein typischer Rechnerschirm sieht ja so aus,
dass man dem mit Fenstern zukleistert, die sich alle überlappen.
Und wir müssen ab und zu, sagt das System,
jetzt würde ich neu zeichnen. Und wenn man dann die Situation hat,
dass irgendwer sagt, es muss neu gezeichnet werden,
dann können wir dafür Vorsorge tragen und sagen,
wenn es gezeichnet werden soll, dann nehmen bitte diese Zeichenroutine.
Und wir können auch sagen, wenn die Zeit des Universums abgelaufen ist,
stoppt, wenn, da übergeben wir ein Prädikat,
das testet, ist diese Welt noch lebensfähig.
Und da soll sie beendet werden. Und für Tastaturereignisse können wir auch Routinen schreiben,
also wenn immer jemand eine Taste drückt, dann kommt eben die Routine,
den Code für die gedrückte Taste und dazu ein Tastaturevent und das
können wir abfragen und dann überlegen, was machen wir denn,
wenn jetzt jemand Befehl Kuh drückt oder so.
Okay, gibt noch mehr,
aber wir können jetzt uns zwar ein bisschen anschauen,
wie man so mit Big Bang arbeitet,
also wenn wir ein Universum erzeugen wollen, unsere eigene Simulationswelt,
würden wir sagen, Big Bang, sagen,
wie groß soll der Kennen was sein?
Und Moment, was war noch N und W?
Ich muss noch mal scharf nachdenken. Die Ereignisroutine, die fragt,
bei jedem Uhr, ach so, jetzt bei der Uhr,
genau, da erzeugen wir also den Kennen was und wir übergeben hier jetzt für uns zwei Parameter einfach die Zinnspezipisch sind.
Die Ereignisroutinen und Tick, was wir da übergeben,
wenn wir jetzt an unsere Uhr denken,
da würden wir Eben unser Abfragen der Systemzeit reinkodieren,
was wir schon haben, weil um die Uhr zu zeichnen,
würden wir bei Tutrain die Zeiger der Urneubare zeichnen,
wie wir es auch schon gemacht haben.
Und bei On Key, da könnten wir jetzt uns überlegen,
was tun wir, wenn wir,
wenn das Ganze beendet werden soll oder so.
So. Also, der Big Bang,
der sieht jetzt in diesem Beispiel so aus,
also wie immer,
wir verwenden das Universe-Modul
Haben für unsere eigene Funktion SIM-Clock, die so ein Universum erzeugen soll.
Hier noch gesagt, wie schnell soll die Uhr eigentlich ticken?
Ja, da übergeben wir einen weiteren Parameter. So,
wir haben eine lokale Variable, die erste Uhr
und da rufen wir unseren Next-Clock-Wall schon mal auf.
Die Funktion hatten wir ja schon mal definiert. Und die gibt was,
ihren kleinen Helptext aus, ja, für die Tasten,
die das Programm verstehen soll,
also dass man entweder einen Kuh einen geben soll oder
die Escape-Taste
Ja. Dann sagen wir hier Big Bang. So. Und
dem Aufruf von Big Bang bekommt jetzt die ganzen Händler übergeben mit Keywords.
Ja? Also, das erste, was der Big Bang kriegt,
ist die erste Uhr, mit der das Universum erschaffen wird.
Das ist unsere First-Clock, die wir hier oben definiert haben.
Und dann bekommt das Big Bang die eine lange Liste von
Händlern und zwar immer durch einen Aufruf der Funktion,
die diesen Händler registriert und einträgt
Im Händler-Verzeichnis. Also wir rufen hier auf Parameterposition und schick
auf und übergeben das Next-Clockwold. Wir übergeben hier einen Aufruf von Two-Draw,
das registriert dann den Dreuerhändler mit diesen Parametern, den Stop-Wenn-Händler, den On-Key-Händler.
Dieses Sandal Key haben wir uns noch nicht angeschaut,
müssen wir uns gleich nochmal überlegen, wann das programmieren würde.
Und Rekord, das ist dafür da,
dass man ein Protokoll anlegt von der Simulation.
Manchmal macht man die Simulation ja nicht nur so zum Vergnügen,
sondern man möchte gerne ein Protokoll all der Ereignisse bekommen können.
Und das hier ist der Name für den Kennenwiss, der gezeichnet wird.
Ja? Gut. Damit wir aufhören können,
definieren wir uns eine kleine Hilfsfunktion, Glock, stoppt.
Und da fragen wir einfach, wenn die Welt gleich der letzten Glockwold ist,
also wir wollen aufhören in diesem Jahr hier,
seconds to date, was immer das ist. Ja,
unser Händler für Key, also einer der weiteren Ereignisroutinen,
die wir hier übergeben haben, Da fragen wir einfach,
ob die, das Tastatur-Ereignis, was wir bekommen,
das wird simpel ausgedrückt durch einen dringend, der das Zeichen enthält.
Und da fragen wir, ob das eben eskepe ist oder die Guttaste.
Und wenn das gegeben ist, dann werden wir aufhören und das machen wir,
indem wir dem Universum teilnehmen, mitteilen,
halt an und Der Zustand, in dem du anhalten sollst,
soll die letzte Glockwelt sein, ja?
Habt ihr das im Sinn? Also ich meine,
das ist ja so, dass die Silma ist zum Ende
der Uhrzeit und nicht bei der Uhrzeit, die momentan umgekehrt ist.
Ja, also ich habe es nur als ein Beispiel gemacht,
um zu zeigen, wie wir eine definierte Welt am Ende übergeben können,
aber Sie haben völlig recht.
Das ist genau die Frage, wie man sie als Programmierer immer stellen muss,
macht es Sinn, weil die Benutzer nicht lieber was anderes haben.
Aber in manchen Simulationen sieht es ganz gut aus,
wenn der Endzustand der ist,
dass da der große Explosionskrater ist oder irgendeine Welt,
die wir gern genau als Ziel erreicht hätten
Gut, also sie können bei dem Stop-West können sich überlegen,
was denn der letzte Zustand sein soll. Ja,
benutzen wir das jetzt mal noch für eine andere Art von Simulation und versuchen mal dieses Beispiel,
was ich vor zwei Vorlesungen hier vorgelesen habe von Theodor Storm,
die Vermehrung seiner Mai-Katzen,
die die Köchin so geärgert haben, zu simulieren.
Wir beginnen hier jetzt mal mit einer Anfangspopulation und wollen einfach
einen Bevölkerungswachstum simulieren. Also bei jedem Tick der Uhr
wird eine neue Population in Abhängigkeit der Alten
neu berechnet und gezeichnet
So, in dem Fall, wenn wir das jetzt mit
einem Big Bang machen wollen, wir starten die Mai-Katzen,
also mit einem Tick, den wir hier übergeben.
Unser Big Bang ruft ein erzeuge Katzenuniversum auf,
mit unseren beiden Parametern eine Katze und im Jahr eins.
Und fragen Sie mich,
wie eine einzelne Katze in der Welt sich vermehren kann,
ist das Problem, muss der Schöpfer lösen.
Irgendjemand hat das mal der Vorlesung, einen katerfreien Prozess genannt.
Wie auch immer. Wir haben jetzt Onzig, die nächste Generation.
Und dann bei Tutor muss meine Funktion zeige Katzenwelt übergeben.
Dann haben wir, die wird gezeigt, und die zeichnet
eben auf unserem Katzenkännen, was die wir noch erzeugen wollen.
Und die hatten bestimmte Höhe. So,
wir hören auf, wenn es die letzte Katzenwelt ist,
und das soll davon abhängig werden sein,
wie viel Platz ist da eigentlich auf der Welt?
Ein Händel,
Kiy übergeben wir noch und das Ganze heißt Mike Katzen
Das Katzenuniversum, das ist auch wieder sehr abstrakt,
ist nichts niedlich über diese Welt,
ist einfach die Anzahl von Katzen, die wir zurzeit haben.
Und wie sollen sie sich vermehren? Wir sind hier mal etwas vorsichtig,
nicht so schlimm wie Tier oder Sturmes beschrieben hatte.
Auch noch in jedem Jahr eine andere Katzenrate,
sondern wir haben ja jetzt nur vier Katzen vor Jahr.
Und die nächste Generation, also unsere neue Welt,
ergibt sich dann daraus, dass wir hier mal jetzt den Zustand,
den wir gerade haben,
Cat Universier von der Welt, die wir gerade haben,
da fragen wir eben, wie viel Katzen haben wir.
Und dazu addieren wir eins. Und dann,
also das hier ist die Zahl, die wir gerade haben,
also das Jahr, in welchem Jahr sind wir?
Und dazu addieren wir eins. Und das,
der zweite Parameter ist die Zahl der Katzen,
die in diesem Jahr im Universum sind. Und dazu,
das machen wir einfach, indem wir Katzen pro Jahr hier um eins erhöhen
Und das Multiplizieren mit der Zahl der Katzen, die wir gerade haben,
und dann haben wir so neue Bevölkerung, ja.
Und dann geben wir als Ergebnis diese neue Welt zurück.
Ja. Gut. Und das Anzeigen zum einen Textuell,
in dem wir einfach mit einem Display immer rausschreiben,
was sind die Zahlen, die wir gerade haben.
Und dann rufen wir die Hilfsfunktion Drohr Caps auf,
die winzige Katze eben so oft zeichnet,
wie für unsere Struktur Caps Universe Number Cats,
für den Weltparameter, den wir bekommen haben, fragt,
wie viele Katzen sind es?
Ja. Setzen wir es mal auf, das Bild Katze winzig,
habe ich irgendwo aus dem Internet geborgt, okay.
Und der Katzen kennen wir 700 mal 500.
Ich hoffe, das passt hier an den Beamer.
Und wir lassen es laufen. Und wir sehen hier
das Wachstum der Katzen von Jahr zu Jahr, angezeigt.
Und dann schlägt unser Ende-Kriterium zu,
Bildschirm ist voll
Also wächst ziemlich schnell. Ja, okay. Gibt es zu diesem Universe und Simulationsrahmen noch Fragen? Gut,
dann muss ich einfach in dem Fall nur klar machen,
wenn wir irgendwie einen Ablaufrahmen haben und der soll sich verändern und wir möchten steuern können,
was geschieht immer zu bestimmten Ereignissen,
dann haben wir es hier eben so gelöst in dieser Universe-Architektur,
Dieser Kontrollrahmen für einen Ablauf eines Simulationsuniversums,
Funktionen bietet, denen wir sagen können,
in dieser Situation für diesen Zweck nehme, diesen Händler.
Ja, das war genau, gehen wir nochmal zurück zum
Big Bang. Ja. Hier wird der Rahmen aufgesetzt und
auf Parameterpositionen können wir für diesen Kontrollrahmen jetzt eben sagen,
was für die bestimmten Zwecke verwendet werden soll
Und das ist sehr flexibel, weil das Standarduniversum hat Default-Dinge,
die verwendet werden, wenn wir gar nichts sagen.
Aber für da, wo wir speziell werden wollen,
sagen wir jetzt, nimm nicht da in die Vorzeichnen,
sondern wenn wir es gezeichnet werden sollen,
dann nimm bitte euer Glockwold und die Abmessung,
auf die gezeichnet werden soll, sind diese.
Wenn du entscheiden willst, ob du aufhören willst,
dann verwenden wir diese Funktions-Stop, wenn und übergeben der unseren eigenen Händler,
der aufgerufen werden soll, entschieden werden soll, was geschieht.
Also das ist die Art, wie wir im Funktionalen Programmierstil arbeiten,
wir übergeben keine Actionhändler, Objekte wie im Jahr war,
sondern übergeben, schlichtweg unsere Funktionen. Die anderen Arten,
das haben wir jetzt in den Übungen ja schon intensiv besprochen,
wie wir Funktionen höhere Ordnung verwenden, Kontrollabläufe steuern können,
sind eben diese allgemeinen Rahmen,
die für uns Iterationen der Wiederholung ersetzen
Und das Apply als Standardhilfsmittel, wenn wir Argumente flach bekommen wollen,
alle Argumente für eine Funktion stecken in der Liste und wir brauchen die Apparatmeterposition,
haben wir gesehen, da ist Apply genial.
Wenn wir eine Liste von Werten abbilden wollen auf eine Liste gleicher Struktur entlängern,
nur jedes Element soll abgebildet werden auf was anderes,
nehmen wir Map und übergeben die Funktionen, die abbildet.
Beim Filtern reduzieren wir Listen auf die Teilmenge von Elementen,
die Prädikat erfüllen, übergeben das Prädikat,
Falten,
Und die Varianten, er fult, er fult,
er unterscheiden sich danach, wenn wir nicht kommunitative Funktionen haben,
in welcher Reihenfolge die Argumente gebunden werden.
Und ob das Ganze entrekursiv ist oder nicht. It dirate,
das waren unsere handgestrickten Iteratoren, verwenden wir,
wenn wir eine bestimmte Aktion immer wieder machen wollen.
Und wir haben auch gesehen, bei den Verträgen,
wenn wir Zusicherungen machen und für bestimmte Variablen in der Schnittstelle Zusicherungen machen wollen,
dass wir die Prädikate, die geprüft werden sollen,
um zu sehen, ob alles korrekt ist, über Verträge beschreiben.
Und diese Verträge werden parametrisiert mit Funktionen. Also das sind
so all die Anwendungen, die was gesehen haben.
Letztes Mal haben wir gesehen, wie wir uns Hilfsfunktionen machen können.
Auch das nochmal ganz wichtig, weil wir nicht immer die Funktionen von Hand aus formulieren wollen.
Manchmal ist es einfacher, man nimmt bestehende Funktionen,
verknüpft sie, um zu den Hilfsfunktionen zu kommen.
Und da sind die allerwichtigsten einmal die Curry-Funktionen,
mit denen wir Argumente von Funktionen schon mit default werten belegen können und die anderen bleiben frei.
Also zum Beispiel Curry plus eins würde uns eine Inkrementfunktion bauen,
die das zweite Argument um eins erhöht.
Das Korry R bindet von rechts, statt von links,
Funktionskompositionen, viele Funktionen nehmen und die Hintereinanderausführung all dieser Funktionen als neue Funktionen bekommen.
Oder Konjunktionen und Disjunktionen, Conjoine Distroy, ein neues Prädikat bauen,
das aus dem Großen und aller Funktionen besteht, das ist wahr,
wenn jede der übergeblichen Funktionen wahr ist oder disjoin, das große Oder,
das Prädikat, das wahr ist, wenn jede der Basisfunktionen.
Nein, wenn mindestens eine der Basisfunktionen sagt, ja,
dann haben wir das Ganze mal verwendet, eigene Rahmen geschrieben,
wie zum Beispiel eine Funktion höhere Ordnung, die eine andere differenziert,
das war die Funktion der Riff und die dann gleichgenommen,
um im Newton-Verfahren ganz allgemein einen Rahmen zu haben,
ganz Nullstellen für Funktionen sucht,
wo man die beliebigen Funktionen als Parameter übergeben können.
Gut, wir hatten gesehen, wir Funktionen höherer Ordnung als Callback.
Wenn wir das grafische User-Interface aufbauen, so ein Gui bauen,
hatten wir Funktionen wie Make-Button. Den konnten wir dann sagen,
was geschehen soll, wenn der Knopf gedrückt wird,
über die Funktionen übergeben, die aufgerufen werden soll.
Any made, animiert, eine Folge von Funktionsaufrufen,
um einen Ablauf darzustellen und den Big Bang frei kombinierbar,
indem wir diese schöne Schnittstelle haben
Überall Händlerfunktionen zu definieren für bestimmte Zwecke.
Und im Big Bang brauchten wir nur zu sagen,
wo wir eigene Händler haben wollen, die anderen bleiben default.
Ja, wir kommen jetzt zu was ganz anderem. Ich möchte,
nachdem wir so jetzt das Werkzeug kennen,
mit dem man im funktionalen Programmieren überhaupt arbeitet,
in eine ganze Reihe von Fallstudien zeigen, wo wir das einsetzen,
um jetzt unsere eigenen Programmentwürfe zu machen.
Dass man den Ersten sieht, für welche Zwecke bietet sich
funktionales Programmieren an
Was sind die typischen Entwurfsmuster für diese Zwecke? Und dann kommen zunehmend komplexere Beispiele.
Ich werde ab hier nicht mehr jedes Programm Zeile für Zeile erklären,
weil ich schlichtweg davon ausgehe,
sind taktisch und Semantik wissen, sie mantisch wissen sie,
was Drecke tut und wir brauchen das nicht mehr einzeln vorzulesen.
Denn wir werden jetzt eigentlich nur noch über die Entwürfe sprechen.
Also eine Stärke funktionaler Algorithmen sind die Stelle,
wo wir kombinatorische Probleme haben. Wenn es darum geht,
Ein kompliziertes Problem zu beschreiben und zu suchen,
gibt es für all die Randbedingungen, die ich habe.
Gibt es eine Lösung oder nicht? Das ist deshalb im
funktionalen Programmierstil sehr schön, weil meistens die Randbedingungen,
die wir haben, ohnehin schon rekursiv formuliert sind.
Und wenn wir dann ein Programmierstil wählen, der Rekursion gut unterstützt,
dann haben wir da auch eine Chance.
Also ich möchte jetzt in der zweiten Hälfte der Vorlesung heute eben genau erklären,
wie wir solche Dinge, wo wir in einem großen Raum von Möglichkeiten nach Lösungen suchen,
Konsistenz von Randbedingungen prüfen über das im Funktionalen Programmierstil mit Rekursionen gut machen können.
Diejenigen von Ihnen, die bei Herrn Menzel des internationalen Probierens schon programmieren,
schon gelernt haben, die wissen, dass das eigentlich die Dinge sind,
wo man im relationalen Programmierstil einfach die Randbedingungen hinschreibt,
als Prädikate und dann eine Anfrage stellt, an Po-Log,
wer ist der Mörder von?
Wir haben alles aufgeschrieben,
was wir an Fakten gesammelt haben und dann geben wir Kaffee trinken,
wenn wir wiederkommen und sagt uns, Unser Pro-Look.
Joa, der Norder ist der Gärtner. Oder die,
oder die Männer sind alle Verbrecher, glaube ich, auch in seiner Beispiele.
Okay, was Polock uns abnimmt in dem Fall,
dass es Unifikation herausfinden, welche Regeln überhaupt passen.
Und für all die Regeln, die passen,
dann durch systematisches Programmieren Backtracking zu sagen, ja, das habe ich jetzt gefunden.
Wir werden hier uns jetzt erstmal anschauen,
tun wir in einer Welt, wo ein Backtracking nicht gleich eingebaut ist,
wie programmieren wir uns das als allgemeine Kontrollstruktur zu Fuß,
um es einmal und nie wieder programmieren zu müssen.
Genauso wie wir eben vorher gesagt haben, okay,
wir definieren uns ein für alle Mal,
wie wir eine Funktion ableiten und brauchen es dann wieder oder das Newton-Verfahren ein für alle Mal.
Und dann möchte ich noch einen kleinen Ausflug machen,
nicht Determinismus, solche Suchtprobleme kann man sich ja auch gut vorstellen,
dass man die dadurch löst,
dass man einfach Sehr viele Rechner gleichzeitig loslässt und jeder probiert vor sich hin und einer wird schon eine Lösung finden.
Und wir schauen uns dann nur an, das Ergebnis von dem,
der gesagt hat, ich habe was gefunden.
Das Nicht-Deterministische ist in der Automatentheorie sehr beliebt. Diejenigen,
die sich mit formalen Grundlagen der Informatik schon ein bisschen mehr befasst haben müssen,
dass Beweise und so immer richtig einfach sind, wenn wir einfach statt deterministische Automaten nicht deterministische nimmen.
Und funktionale Programme sehen auch richtig gut aus,
wenn man sie nicht deterministisch schreibt.
Und das passt sehr gut in den funktionalen Programmierstil und deshalb
schauen wir uns auch das mal an
Okay, erst mal in den Bereich der kombinatorischen Probleme,
fangen wir mit einem bisschen einfacheren an.
Als Kern stellt man fest,
dass wir meistens im Wesentlichen sowas machen müssen,
wie alle Permutationen durchprobieren oder alle möglichen Unterlisten einer Liste und gucken,
passt eine davon oder nicht.
Und davon gibt es berühmte Ausprägungen,
eins davon ist das Rücksackproblem,
gucken wir uns auch nochmal an
Jedenfalls lassen sich so kombinatorische Sachen oft gut rekursiv lösen,
weil wir uns dann immer nur auf einen kleinen Teilaspekt
konzentrieren müssen und den Rest stecken wir in die Rekursion.
Und die Fälle, die wir uns mal genauer anschauen wollen,
also alle Unterlisten einer Liste, alle Permutationen in der Anordnung,
alle Möglichkeiten aus einer gegebenen Menge, welche zu kombinieren,
sodass die Kombination eine Bedingung erfüllt.
Und das sind eben die Zusammensetzungen.
Wie viele Möglichkeiten gibt es einen Euro in Kleingeld rauszugeben
Oder das Problem hat seinen Namen, Rucksackproblemen von der Frage,
ich gehe auf eine Wanderschaft für 14 Tage,
habe meinen Rucksack fast 20 Kilogramm, ein Gewicht,
mehr will ich auch nicht schleppen, was von den vielen Lebensmitteln,
die ich jetzt einkaufen könnte, hat die meisten Kalorien,
dass ich meinen Rucksack so fülle,
dass ich erstens nicht an Vitaminenmangel sterbe und zweitens nicht für Hungere,
sondern genügend Kalorien mit habe.
So, wenn man diese Randbedingungen hat,
ist es eben eine Frage, kombinieren, sich angucken,
aha, Spaghetti hätten so viele Kalorien,
die Spaghetti-Soße so viel,
fehlen aber die Vitamine,
vielleicht sollten wir doch
eine Zitrone mitnehmen und so weiter
Gut, beginnen wir mal mit der Liste aller Unterlisten,
weil das ist die Basis praktisch für alle ein Probieralgorithmen ist, ja?
Also stellen wir uns einfach vor,
wir haben eine Liste und wollen jetzt die Menge aller Listen bilden,
die wir bilden können, indem wir Elemente aus dieser Liste nehmen,
in Mengensprache, solange die Listen keine doppelten Elemente enthalten können,
wäre es praktisch die Frage,
wie bekomme ich die Potenzmenge zu einer Menge?
Das können wir rekursiv sehr schön so formulieren. Die Menge aller Unterlisten,
die wir aus der Liste ABC machen können, die enthält,
Auf jeden Fall alle Unterlisten, die mit das erste Element enthalten.
Und dann, wo der Rest eine der Unterlisten aus dem Rest von der Menge ist.
Also wenn ich sage, ich will die Menge aller Unterlisten bauen,
dann kann ich zuerst ein Element nehmen.
Das allererste ist, heißt vielleicht A. Und dann nehme ich
den Rest der Liste und betrachte alle Unterlisten,
die ich aus dem Rest der Liste bilden kann.
Und die Listen, die mit A beginnen und dann
ein Element aus dem Rest haben,
gehören auf jeden Fall in die Menge aller Unterlisten
Aber auch alle, die das gerade nicht enthalten,
das ist der zweite Fall. Alle Unterlisten, die das
erste Element nicht haben und deren Rest eine Unterliste von Kötter ist.
Und diese beiden Mengen sind es jung, eben weil nämlich die eine enthält Karfan
Exist und die anderen nicht. Gut, und das können
wir rekursiv wunderbar schreiben, indem wir uns das mal kurz angucken.
Potenzmelmengen bilden zum systematischen Probieren. Nehmen wir mal einfach nur die Liste 1,
2, 3. Jetzt die Menge aller Unterlisten,
hat also im linken Zweig alle Unterlisten,
in denen die eins vorkommt und im zweiten alle wo die eins nicht vorkommt,
die eins haben jetzt verbraucht,
deshalb liegt sie da am Rand.
In der nächsten Stufe müssen wir jetzt diese Unterlisten bilden.
Rekursiv müssen wir uns jetzt fragen, aha, was sind denn die Unterlisten?
Eine, die zwei enthalten und alle, die zwei nicht enthalten.
Und hier in dem Bereich auch wieder alle,
die zwei enthalten, die zwei nicht enthalten.
Und dann hier noch alle, die drei enthalten,
die drei nicht enthalten und so weiter.
Was sie schon sehen, wenn wir irgendwo ein
kombinatorisches Problem haben, wo wir Kombinationen ausprobieren müssen
Denn wird es meistens drauf hinauslaufen,
dass wir irgendwo bei der Frage landen, Menge aller Unterlisten.
Und da haben wir eine ganz typische Baumrekursion. Und die
haben wir jetzt, also für diese kleine, hier alle sechs,
nee, acht Unterlisten, die wir bilden können.
Die leere Menge gehört ja auch noch dazu. Gut,
was wir also haben, Menge aller Unterlisten bilden,
ist eine ganz typische Baumrekursion.
Und dazu können wir es rekursiv so schreiben.
Subsets of Excess, ja, auch aus dem Buch von
Word und Wordler
jetzt übernommen
Also wenn die Liste leer ist,
sind wir wie immer fertig und geben dann aber nicht die Lehrenliste zurück,
sondern die Menge aller Unterlisten enthält eben nur die leere Liste,
deshalb hier gleich doppelt geklammert.
So und dann nehmen wir einmal das erste Element,
Head und das zweite, den Rest der Liste und sagen,
unsere Lösung besteht da drin,
alle Unterlisten des Restes und dann alle Unterlisten des Restes,
aber wir setzen das erste Element davor
Und sie sehen schon, wenn man ein bisschen mit Funktionen
höherer Ordnung spielt, dann kann man Baumrekursionen verstecken.
Also die Map-Funktionen hilft einem meistens Baumrekursionen so zu verstecken,
dass das Ganze ein bisschen lesbarer wird. Noch,
wir brauchen jetzt ja nur aus der Menge aller Unterlisten des Restes,
da das erste Element jeweils davor setzen,
für diesen linken Zweig des Baumes.
Den müssen wir jetzt basteln. Also wir haben alle Unterlisten
des Restes gebaut. Und jetzt wollen wir noch diesen Baum
hier bauen und dazu müssen wir jetzt vor jedes Element,
was wir haben,
die eins davor setzen
Und das machen wir natürlich. Inzwischen sind wir ja geübt mit Map und Curry
. Wir nehmen die Map-Funktion, bauen uns die Funktionen,
die vor jedes Element einer Liste den Kopf unserer Ursprungsliste setzt.
Die Funktion ist Curry, Konzfunktion mit diesem ersten Element.
Und das schicken wir über all die Unterlisten,
die wir hier schon gebaut haben.
Und über das geht das Mapping und setzt jetzt überall noch
die Eins davor.
Und schwupp,
die Wupp haben wir
Diese Lösung hier hinbekommen. Also dieses Muster ist ganz typisch,
im Funktionalen Programmierstil wird man selten, außer man ist dazu gezwungen,
Baumrepresionen so ausformulieren, dass wir tatsächlich den zweiten rekursiven Aufruf schreiben.
Das wird ja jetzt ziemlich, ich sag mal,
müllintensive Variante erkennen. In dem Fall geht es nicht,
nein, das ist gut beobachtet,
also hier machen wir, das pennt auf dieser Ebene,
auf der Ebene sieht es zaumlos aus, Problem ist,
dass dieser Aufruf natürlich rückwärts immer wieder in das Apent reingeht.
Problem ist bei Baumrekursionen, lässt sich für die äußere Stufe
meistens das Apent nicht vermeiden. Das ist,
weil wir eben aus jedem, es bleibt uns gar nichts anderes übrig,
als in jedem Schritt hier die Teilbäume zu verbinden.
Und das geht nur mit einem Apent. Wenn wir von
dieser Rekursion Definition ausgehen, die Baumstruktur nutzt, haben wir ein Problem.
Könnte der Müll vermeiden, indem man Systematisch probiert Elemente entfernt und guckt,
ist das, was sich jetzt nach Entfernen des Elementes,
deshalb schon in meiner Lösung drin.
Aber dann haben wir eine Zeitkomplexität. Also in manchen Fällen
muss man einfach in den sauren Apfel beißen und tatsächlich hier diesen Müll in Kauf nehmen.
Es war gut, dass man, wenn man weiß,
dass man ihn erzeugt. Gut, euch freut aber,
Sie drum bitten, sich dieses Muster einzuprägen, dass wir,
wenn wir über alle Zweige eines Baumes gehen wollen,
dass in der Regel so machen können,
dass wir einen Map machen und die Stützfunktion,
die irgendwie mit Curry zusammenbauen und dann das Ganze über den Rest schicken.
Und was wir hier jetzt gemacht haben, ist uns erspart haben,
dass wir die Rekursion über die Unterlisten des Restes hier zweimal ausführen.
Die können wir an dieser Stelle, wenn wir
sie einmal hier erzeugt haben, eine lokale Variable binden.
Das sollte man noch tun oder war die Folie voll.
Also das Subs von Till könnte man sich hier jetzt
nochmal als weiteren Hilfsgröße hinschreiben und dann die einmal berechnete Hilfsgröße
nehmen und das hier nur einmal machen
Das Zweite, was wir oft brauchen, wenn wir in kombinatorischen Problemen,
also einmal dieses Muster, wir brauchen irgendwie alle Unterlisten.
Das zweite Muster, was man auch immer wieder braucht,
ist, dass es auf die Anordnung der Elemente ankommt.
Und eine nette Stützfunktion dafür ist das Verweben oder auf Englisch
Inter lief. Also die Liste aller Möglichkeiten,
gegeben eine Liste von Elementen, ein anderes dazwischen zu schieben.
Ich kann es hinter das erste Element schieben,
hinter das zweite, hinter das dritte,
hinter vierte und die Menge aller Möglichkeiten in eine solche Liste eins einzufügen,
als neue Liste zu verwenden.
Ja, das braucht man immer als Basis für all die Algorithmen,
wo es darum geht,
Elemente in einer Liste zu vertauschen und die Anordnung zu überprüfen.
Ja, also interlief selbst ist noch nicht Baum rekursiv oder in irgendeiner Weise ziemlich kompliziert.
Wenn wir irgendwer ein Element haben und wollen wissen,
welche Möglichkeiten habe ich die Ys da reinzuschieben,
die erste Möglichkeit, wenn das Y nicht leer ist,
dann setzen wir es an den Anfang und das Y
Und dann betrachten wir alle Möglichkeiten, das Element mit dem Rest der Liste zu vergeben.
Also der Kopf wird jeweils vor dem verwebten Rest gesetzt.
Und das erst mal ein Beispiel Aufbruch, was wir zurückkriegen wollen.
Also wir wollen irgendwie ein großes E-Symbol hier verweben mit der Liste ABC.
Also die erste Möglichkeit ist am Anfang, dann an die zweite Stelle,
dann an die dritte und dann ganz ans Ende.
Und wenn die Liste überhaupt leer ist,
Dann ist das einzige, was wir zurückkriegen könnten,
die Liste an der Liste mit diesem Element eh drin.
Ja, das haben wir hier. Wenn XYs,
das wo es eingefügt werden soll, leer ist,
dann ist nur die Liste mit dem einzelnen Element das einzige, was geht.
Und die Liste davon geben wir zurück. Andernfalls merken wir
uns wieder das erste Element und den Rest der Liste.
Und wieder ein Appent zum Zusammenfügen. Die erste Liste hier,
kons XS Ys, die wir da haben, muss vorgesetzt werden,
das, was wir beim Verweben des Elementes mit dem Rest kriegen.
Und dazu fürs Verweben, des ersten Elementes mit dem Rest,
machen wir einfach wieder ein Mapping über den verwebten Restziel.
Und die Funktion, die wir drüber schicken müssen,
die bekommt einfach schon den Head, den wir hier haben,
an die Konstfunktion gebunden und setzt das immer davor.
Also wir wollen ja für jedes Element des Restes wieder das Element der Vorsetzen.
Und das machen wir eben, indem wir einen Kunst mit dem Kopf machen.
Und um die Funktion beim Map übergeben zu können,
na klar, winden wir es mit Curry fest ans Argument.
Also ich vermute mal, bei jeder der weiteren Folien,
die ich zeigen werde,
werden sie mindestens einen Curry-Kompost oder Map oder so finden,
weil ab jetzt werden wir das mit Funktionen höherer Ordnung machen wollen.
Gut. Dieses Inter lief sollte jetzt nur eine kleine Hilfe sein.
Wir nehmen es mal an, das Ding funktioniert.
Und wir wollen damit das eigentliche Problem lösen
Alle Permutationen einer Liste finden. So, Permitationen,
also alle Möglichkeiten, wiege ich die Elemente anders arrangieren kann.
Eben, wo es auf die Anordnung ankommt und nicht nur aufs Enthaltensein.
Und auch das können wir rekursiv ganz simpel so beschreiben.
Wir nehmen das erste Element der Liste.
Und wir bekommen alle Permutationen, indem wir das erste Element
der Liste mit allen Permutationen des Restes verweben
Dieses eine Element, das kann ja überall reingefügt werden,
wenn wir es umordnen wollen. Und um alle Möglichkeiten
zu haben, wie der Rest aufgemischt werden kann.
Berechnen wir regursiv eben die Permostationen des Restes. Und das
erste Element schieben wir dann überall dazwischen und haben dann die Lösung gefunden.
Und dann kann man es auch das nochmal am Beispiel anschauen.
Wir haben hier wieder unsere Liste 1, 2,
3. Eine Permutationen davon.
Wir berechnen jetzt alle Permutationen des Restes 2,
Das ist einmal hier bis 2, 3 selbst oder das Umgedrehte.
Und dann verweben wir das erste Element mit allen diesen Parmutationen.
Das erste Verweben gibt uns die eins am Anfang,
in der Mitte und am Ende.
Und dann dasselbe nochmal hierfür. Das heißt also,
alle Permutationen hier von sechs verschiedene Listen, die wir erzeugen können.
Und diese Permutationen berechnen, nehmen wir die Funktionen mal einfach
helms an. Wenn die Liste leer ist,
das einzige, die Liste mit der leeren Liste drin.
Und andernfalls eben verweben mit dem Rest,
mit allen paar Mutationen des Restes,
alle paar Mutationen des Restes kriegen wir in diesem rekursiven Aufruf hier.
Palms von Kötter van Exis. So, und das soll jetzt verwirbt werden.
Jedes davon wollen wir alle ein paar Mutationen haben.
Was wir von diesen Aufrüfen zurückkriegen, sind Listen von Listen.
Und deshalb natürlich müssen wir das Ganze mit Appent zusammenbauen,
weil wir Listen und Listen miteinander vergleichst sein müssen.
Also, deshalb hier ein großes Apply-Appent draußen herum,
dass die alle zusammensetzt
Machen wir ein Mapping über alle paar Mutationen des Restes.
Und zwar, indem wir ein Interlief machen, verweben mit
dem ersten Element der Liste. Und damit das merkt,
dass immer mit dem ersten Element der Liste macht,
müssen wir das Ganze mit Curry zusammenbauen, den ersten Parameter schon binden.
Und wunderbar, unser Haar kriegen wir hier in alle Listen und reingesetzt.
Okay, also auch hier wieder das Schema, es ist eine verkappte Baumrekursion.
Man sieht sie nicht mehr so ohne weiteres. Also immer,
wenn wir ein Map stehen haben und hier unten einen rekursiven Aufruf,
dann heißt das, Im Wesentlichen,
dass wir tatsächlich da eine Baumrekursion abgekürzt haben.
Gut, jetzt mal ein paar Beispiele fürs Rucksackproblem.
Menge von Objekten und wir wollen es eben so kombinieren,
dass wir genügend Kalorien für den Marsch über die Alpen oder so dabei haben.
Und ich zeige es mal in einem simpleren Beispiel.
Auch jetzt aus dem Buch von Ebels und Sessman übernommen.
Der hat es am Beispiel,
wechsle einen Dollar in kleinen Geld
Wir haben einen Dollarschein. So, und wann wir jetzt
wissen, wie viele verschiedene Möglichkeiten haben wir denn da?
Das als Wechselgeld rauszugeben. Wir können Half-Dollars nehmen, Quarters,
Dimes, Nichols, Pennys. Und schätzen Sie mal,
wie viel werden wir Möglichkeiten, werden wir so haben?
15, 20. Schon mal gar nicht schlecht.
Gut. Lassen wir es einfach mal Racket ausrechnen.
Dann sehen wir, wer am Dichtesten dran war.
Ja, wir machen eine Funktion, die das eben ausprobiert.
Und per Kombinatorik dieses Geldproblem löst
Variable A ist der Betrag am Mount, ja? So,
und auch hier wieder, wir haben alle Möglichkeiten,
den Betrag zur Art zu wechseln,
indem wir von der Liste der verschiedenen Münzen,
die wir haben, auf jeden Fall die erste einmal verwenden.
Oder eben, indem wir, und dann den Rest wechseln ohne diese,
oder aber den Betrag ohne die Münze A zu wechseln und D-Betrag der Einmünze,
die wir jetzt betrachten, D soll für Dinomination,
also Wert stehen, ja.
Anzahl der Möglichkeiten, A-D zu wechseln und D ist der Wert der ersten Münzsorte.
Also das haben wir. Also alle Münzsorten außer der ersten
verwenden und einmal die erste verwenden und dann alle Möglichkeiten,
den Rest noch zu wechseln,
aber ohne die erste verwendet zu haben.
Ist ein ähnliches Problem wie bei den ein paar Mutationen.
Und auch da haben wir eine nette Baumrekursion,
die habe ich sie mal nicht verkleidet mit Map,
damit man sie direkt sieht
Also wir führen hier unsere Münzsorten ein, die Nominations,
einfach als Assoziationsliste. Halber Dollar, 50, ein Quarter 25,
die Däumen sind 10, Cent Nicle 5 und ein Cent sind eins.
Und dann füllen wir das Ganze ein, weil wir hier
mit einem Akkumulator arbeiten wollen, Count Change, Amount.
Wir übergeben hier als Parameter auch die Liste unserer Werte,
die wir verwenden wollen und die soll ich bei jedem rekursiven Aufbruch wieder neu übergeben werden.
Gut, also kurz CC wird hier mal, ah,
und die Liste, die Nominations haben.
Ja, und wenn der Betrag gleich null ist,
haben wir eine einzige Möglichkeit, was zurückzugeben, nämlich gar nichts.
Ja, wenn unsere Währung überhaupt keine Münzen hat,
dann können wir nicht mal das gar nicht zurückgeben und sagen,
tut uns leid, schlecht gestelltes Problem, haben wir null Möglichkeiten.
Und wenn der Betrag kleiner null ist, dann geben wir auch
nur null zurück, also für Schulden kriegt niemand was.
Andernfalls nehmen wir mal die erste Münze und das
ist jetzt Cutter vom ersten Element der Listen hier
Das wäre also, das wird 50. Und die anderen Listen,
das ist einfach der Kötter unserer Liste hier,
Quarter Daimon und so weiter.
Ja, und dann die rekursive Definition von eben,
die Menge aller Möglichkeiten ist Count Change,
einmal den Betrag ohne den Wert von 1 gewechselt mit der Liste aller Möglichkeiten.
Oder aber wir verwenden gezielt die erste Münze schon mal
und wechseln dann den Rest.
Aber nur mit den anderen
Wenn wir das machen, einen Betrag hier von 100 Cent
wechseln, sind ja bei Sage und schreibe 292 lösen.
Also die 200 waren nicht schlecht. Das ist sehr viel
, ja? Okay. Was wir jetzt gesehen haben,
ist, dass wir in vielen Fällen so systematisch probieren müssen.
Und das kann man eigentlich am besten dadurch lösen,
dass man sich einmal überlegt, was ist es eigentlich das,
was wir probieren müssen und in welcher Weise müssen wir es
systematisch machen
Es hat sich gezeigt in der Algorithmik,
dass es dafür eine Klasse von Problemen gibt. Backtracking-Verfahren,
die sich so parametrisieren lassen, dass man einmal diesen Rahmen schreibt.
Und dann macht man das Parametrisieren mit Funktionen,
die man übergibt und die diese Randbedingungen prüfen.
Und das geht im funktionalen Programmierstil eben besonders gut.
Es geht auch im Objektorientierten recht gut. Nur nicht ganz so frei,
solange man nicht generische Klassen hat. Aber das Erkennen,
dass man dieses Probieren im Wesentlichen auf so ein Backtracking-Schema zurückführen kann,
das steckt im Herzen der Implementation von Prolog
Die Programmiersprache, die das einfach so macht. Und wir wollen jetzt
mal nach Lösungen suchen. Also wenn wir ein Problem haben,
wo wir systematisch etwas probieren müssen,
dass wir die Systematik für die Teilprobleme in Funktionen fassen und
nur noch einen allgemeinen Rahmen haben, wo wir die Funktionen reinstecken.
Wichtig ist dafür, dass wir ein Problem haben,
wo jeder Probierschritt strukturell den vorherigen ähnlich ist,
sodass man es durch Rekursionen lösen kann.
Und dass wir bei jedem Schritt,
den wir machen, nur eine kleine,
endliche Zahl von Möglichkeiten haben
Also typische Probleme dafür. Beispiel einen Weg aus dem Labyrinth
zu suchen. Wir stehen im Irrgarten und machen einen Schritt
bis zur nächsten Ecke. Und die sieht wieder genauso aus
wie vorher. Das ist ganz typisch für Backtracking. In
jedem Schritt haben wir eine endliche Möglichkeit, Wege auszuprobieren.
Und wir müssen uns merken, welche Schritte Möglichkeiten wir
im Schritt davor hatten, wenn wir jetzt weitergehen.
Oder kürzeste Verbindung zwischen zwei Orten finden.
Auch da probieren wir verschiedene Wege. Und bei jedem probieren,
wenn wir es so formulieren können,
dass wir immer nur die Etappe bis zum nächsten Ort anschauen,
haben wir jedes Mal strukturell wieder
dasselbe Problem
Färbende Landkarte mit vier Farben oder diese schönen Puzzle,
einen Wolf und Kohlkopf und ihr Ziege wollen gemeinsam über den Fluss setzen,
haben nur ein Boot mit,
wo zwei Objekte auf einmal rein können und sie wollen so fahren,
dass keiner den anderen auffrisst.
Und am Ende alle da sind. Dass Acht-Dame-Problem,
gucken wir uns gleich nochmal genauer an oder magische Quadrate,
Krypto-Arythmetik, finde die Belegung für den Buchstaben F,
sodass diese Summe hier aufgeht,
zum Beispiel jeder Buchstabe für eine eigene Ziffer.
Schauen wir es uns am Beispiel des Bekannten auch damit Problems an.
Ich hoffe, Sie kennen es, da müssen wir nicht
so viel drüber reden, sondern uns nur anschauen,
wie machen wir es denn in der funktionalen Programmiersprachenwelt.
Das ist auch damit Problemen besteht ja darin,
man hat ein Schachtbrett oder Damebrett und möchte die Damen so aufstellen,
entsprechend in Regeln, dass keine der Damen eine andere bedroht.
Frage ist, gibt es überhaupt eine Möglichkeit,
das Feld komplett abzudecken,
wenn man es dann geschafft hat,
Acht Damen zu bekommen und sie
möglichst sinnvoll einzusetzen
So, an der Dame bedroht eine andere Dame,
wenn sie in derselben Zeile, wie die andere Dame steht,
oder in derselben Spalte oder in derselben Diagonalen.
Also eine Lösung, die tatsächlich gefunden wurde für dieses Problem
, wäre dies hier. Also jede Dame hat ihre
eigene Diagonale, ihre eigene Spalte, ihre eigene Zeile.
Nur wie finden wir denn solche Lösungen und wie probieren wir
ein geschicktesten und was hat das mit Backtracking zu tun?
Für den rekursiven Ansatz sagt man glaube ich, ach Gott,
weshalb soll man es auf acht mal acht Schachbretter beschränken,
machen wir es für ein N-N-N-Großes Schachbrett
Und wir haben rekursiv die Lösung gefunden,
wenn wir in der ersten Zeile eine Dame untergebracht haben und dann in den anderen Zeilen,
in jeder Zeile eine weitere Dame,
sodass die Regeln erfüllt sind und sie sich nicht bedrohen.
Weshalb in jeder Zeile nur eine? Ja, weil die Vorschrift
ja sagt, in jeder Zeile darf nur eine stehen.
Es macht gar keinen Sinn zu probieren, eine zweite da reinzusetzen.
Das wissen wir schon vorher, dass das durch die Regeln verboten ist. Deshalb,
wir führen, machen eine Rekursion über die Zeilen der Schachbrettes versuchen,
in jeder Zeile eine Dame so unterzubringen, dass die anderen sie nicht bedrohen.
Ja, und rekorsiv immer eine Verzeile weiter, eine Dame. So,
wenn wir jetzt feststellen, wir haben Zeile 1 und Dame,
Zeile 2, Zeile 3,
Zeile 4 und in Zeile 5 finden wir keine Möglichkeit,
die Dame unterzubringen, weil egal, wo wir sie hintun,
kriegen wir ein Problem.
Sie wird bedroht, dann bleibt uns nichts anderes übrig, als zurückzugehen. Und das,
was wir im Schritt vorher getan haben, zu korrigieren,
und das ist genau das, was dem Backtracking den Namen gegeben hat,
Man geht zurück und versucht im Schritt davor, die anderen Möglichkeiten auszuprobieren.
Also was ein Backtrack in Kontrollrahmen vor allem leisten muss,
ist, zu verwalten, was in jedem Probierschritt noch an weiteren Möglichkeiten da war.
Und wenn wir jetzt im Schritt der Form auch nicht weitergekommen
sind und in der ersten schließlich so weit zurückgehen mussten,
dass wir im ersten Schritt die Dame über die ganze Zeile
schieben und wir können den Rest nicht füllen,
dann existiert keine Lösung und wir waren erfolglos.
Gut, machen wir das mal. Ich habe Ihnen
hier mal ein edles, machen wir mal
Lachit-Schachbrett gebaut
So, also in jede Zeile, in jeder Dame,
in jeder Spalte darf nach unserer Regel überhaupt nur eine Dame stehen.
Und wenn wir da jetzt noch Lösungen suchen, gut,
so ist mein Brett. Hier,
dann können wir uns jetzt überlegen, wer bedroht was,
wenn wir hier angefangen haben zu füllen.
Zuerst stellen wir mal unsere Dame einfach hier in die linke Ecke.
So, dann in der nächsten Zeile sehen wir schon,
hier darf sie nicht stehen, beschieben sie weiter,
weil schieben sie weiter.
Aha, hier ist nichts bedroht, also lassen wir sie
erstmal dastehen. Der nächste Aufruf wird dazu führen,
jetzt in der nächsten Zeile
Eine Dame unterbringen zu wollen. Und da müssen wir schon bis hierhin,
bevor wir feststellen, ist nichts mehr bedroht,
denn in der vierten Zeile hat man auch Glück,
da können wir hier hingehen, aber jetzt in der fünften Zeile,
da setzt eben das Backtracking ein,
weil wenn wir es hier hintun, dann stellen wir fest,
wir können überhaupt nicht mehr etwas weiteres unterbringen,
weil eben in der Zeile danach alles ausgekreuzt ist.
Ja, die blauen Linien überstreichen hier jedes Feld,
also spätestens hier müssen wir anfangen,
in dieser Zeile mir zu verschieben, nur wohin?
Wenn wir sie hier hinschieben, geht nicht, hierhin,
geht nicht, hierhin geht nicht. Hier können wir noch einmal hin,
weil dann ist Schluss und wir kriegen dann hier wieder nichts.
Also langsam rüppelt es sich hier zurück. Das ist also,
dass die Backtracking-Verwaltung machen muss. Und jetzt gucken wir mal,
wie wir das in Recket programmiert kriegen.
Wir führen als erstes mal eine Datenstruktur ein,
wo wir sagen, wo steht denn in welcher Zeile,
welche Dame, weil in jeder Zeile dafür nur eine stehen.
Also können wir die aktuelle Belegung Zum Beispiel der Histracture beschreiben,
in der wir Zeile und Spalte haben für die Dame,
wo sie steht. Und das Schachbrett als Liste für jede Zeile
wurde in so Damen stehen. Dann, um es anzuzeigen,
kleine Hilfsfunktionen, also sowas hört man sich immer bauen müssen,
dass man mal einen Print Queen-Co-Ort einführt oder so.
Ob das ist jetzt nicht so wichtig.
Wir führen auch eine Datenstruktur fürs Resultat ein,
wo wir Accessor-Funktionen haben, die uns sagen,
was ist denn da rausgekommen
Zur Spiellogik oder Suchlogik. Um schnell zu testen,
ob auf so einem Damenbrett diagonalen besetzt sind,
dann hilft es sich zu überlegen,
dass eine Dame in derselben Diagonale steht,
wenn die Differenz der Zeilen- und Spaltenkoordinaten gleich ist,
dass wir hier bei diesen Diagonalen.
Also wenn wir eine Zeile weitergehen, wird er für die Spalte um
eins kleiner. Das heißt, entlang dieser Diagonalen bleibt immer
Die Differenz gleich und entlang dieser Diagonalen ist entlang der Diagonale
die Summe der Zeilen- und Spaltenkoordinaten gleich,
wenn wir auf einer einzelnen diagonalen sind. Das heißt,
wenn wir jetzt testen wollen, ob eine Dame sicher steht,
gegeben, alle anderen Damen auf dem Schachbrett,
dann müssen wir die Position unserer aktuellen Dame nehmen.
Vergleichen ist die Spalte gleich. Die Zeile müssen wir nicht
vergleichen. Wir wissen, dass wir sie nur in unterschiedliche
Zeilen gestellt haben. Und dann prüfen, ob die diagonalen
gleich sind
Und dann kommen wir jetzt, unsere Funktion steht die Dame sicher,
gegeben, eine Liste von bisherigen Positionen,
unser Schachbrett und die neue Position, die wir haben,
indem wir ein Endmap machen, also über alle Positionen,
die belegt sind auf dem Schachbrett, prüfen,
ob die Dame bezüglich dieser anderen sicher ist.
Also das macht das Endmap,
geht über alle bekannten Positionen und da muss sie jetzt prüfen,
steht sie sicher
Ja, und jetzt, wir gehen davon aus,
wir haben eine Funktion definiert, die prüft,
ob die sich dann sich bedrohen im Schach.
Wir brauchen das Gegenteil davon von der Funktion,
also nehmen wir die Funktion höherer Ordnung.
Mir geht dieses Gegenteil verkehrt. Und die soll jetzt vergleichen,
eine Position aus dieser Liste mit unserer aktuellen Position und für
alle Vergleiche ist die neue Position dieselbe.
Also machen wir es mit Currying, das Kompliment von Check,
finden wir mit Curry an die neue Position
und schicken es mit Endmap über alle Positionen.
Dann haben wir es gefunden.
Und das Check, so gut, die müssen wir einfach in die Zeilen spalten vergleichen.
Brauchen wir uns jetzt nicht mit aufzuhalten, das ist trivial. Der Kontrollrahmen,
dem Wempen für dieses Backtracking machen wollen,
das wird im ersten Ansatz eine Baumrekursion, nämlich einen Versuch rekursiv,
immer in der nächsten Zeile eine Dame zu platzieren,
gegeben Schachbrettröße, gegeben der Positionen, die wir gerade haben,
gegeben der Zeile, wo wir es die Dame hinstellen wollen,
der Spalte, wo sie hingestellt werden soll und nur um mitzuzählen,
ob wir schon versucht haben, zählen wir noch mit,
die Zahl der Züge.
So, dann stellen wir hier die Liste der Positionen auf,
die man noch ausprobieren kann, Positions to 3 erzeugen,
at to 3 erzeugen hier einfach die möglichen neuen Positionen.
So, und jetzt gucken wir, sind wir denn schon fertig?
Also wenn die Zeile, wo wir es hinstellen sollen,
größer ist, als dieses Schachbrett groß, das sind wir fertig,
dann zeigen wir einfach das Resultat an und alles ordentlich untergebracht.
Wenn die Spalte größer ist, als die größte Schachtbretts.
Dann haben wir festgestellt, Toppler, unsere Dame ist am rechten Rand runtergepurzelt.
Und wir müssen Backtracking machen. Also in dem Fall geben wir
einfach einen Fehler zurück und sagen, wir haben nichts gefunden.
Wenn die Dame, darüber sie hier hingestellt haben,
Zeilespalte nicht sicher ist, dann müssen wir jetzt was unternehmen.
Wir schieben sie einfach eine Spalte weiter. Das steht hier.
Funktion Showboard zeigt an, wo sie jetzt steht für die Simulation.
Und wir schieben sie einfach eine Spalte weiter und versuchen es
erneut und sagen, wir haben einen Versuch mehr probiert
Und in allen anderen Fällen, versuchen wir rekursiv,
die in der nächsten Zeile unterzubringen.
Hier ist der Rekursive Aufruf für die nächste Zeile.
Und wenn wir von dir zurückkommen und feststellen, das hat geklappt.
Dann geben wir das Resultat zurück und andernfalls versuchen wir,
dass eine Spalte zurückzugehen für den vorherigen Aufruf und dass der Backtracking-Schritt,
den wir hier machen, wir vergessen es,
wo wir waren und geben zurück,
hat nicht geklappt,
versuch es mal mit der Spalte davor
So, das Versuchen, um diese Lösung zu finden,
finden 8 mal 8 Schachbrett. Man sieht schon,
es lohnt sich dafür, einen Computer zu nehmen,
die Zahl der Versuche, schon etwas größer. Und man braucht,
findet dann immer die Lösung, die ich gezeigt habe,
wenn wir um kleinere Schachbrett nehmen, 4 mal 4,
man glaubt, es kaum sind es immer noch,
53 Versuche und wir haben verschiedene Lösungen.
Wenn wir das mal laufen lassen wollen,
ich habe es ja in unserer SE3-Bib,
das Queens-Modul Modul reingebaut, wo man das ausprobieren kann,
Da kann man das anzeigen, machen mit der eingebauten Funktion Show Queen,
How to Design Programs, Show Queen,
hat diese nette kleine Funktion.
Da sagt man einfach, wo auf dem Schachbrett Damen stehen,
für jede Zeile eine Liste, das TU ist eine Position einer Dame.
Und so könnte man es anzeigen. Das Schokfinen ist
ganz pfiffig gemacht, wenn man auf irgendein Feld geht,
dann wird einem sofort angezeigt,
welcher dann dieses Feld bedrohen.
Was wir das nächste Mal machen müssen, Dezember heute,
jetzt leider nicht mehr die Zeit, ist uns zu überlegen,
wie man aus von diesem Backtracking-Schimmer, was tatsächlich explizit die Baumrekursion macht,
wegkommt und damit zu einer Lösung kommt, wo man alle Damen findet.
Alle Lösungen. Es hat sich gezeigt,
dass bei kombinatorischen Problemen es sehr viel einfacher ist,
das so zu programmieren, dass man ein Erfolgslistenverfahren macht.
Lieber sagt, gib mir alle Lösungen, anstatt aufzuhören und man eine gefunden hat. Also der zusätzliche Test,
aufhören, ich bin fertig,
der macht das Programm meistens viel und strukturierter,
wenn man stattdessen einfach weitersucht und nach allen Lösungen sucht und sich dann nur die erste von allen Lösungen anschaut,
dann hat man meistens das bessere Verfahren und das passt besonders gut in die Lesi-Programmiersprachen,
weil mit der Methode,
ich programmiere die Liste aller denkbaren Lösungen,
wenn man damit Lesi-Evaluation reingeht und sich dann nur den ersten Wert geben lässt,
hat man es kostenlosten.
Okay, aber davon das nächste Mal mehr.
Danke für die Aufmerksamkeit