Dem bin ich hoffentlich gut vorbereitet. Einige kennen mich auch aus
der Übungsgruppe. Ich bin eigentlich immer ganz gut vorbereitet.
Also werden wir das heute ganz gut hinbekommen. Im Rahmen der Vorlesung
wurde mir gesagt, dass bisher schon relativ viel dran war.
Ja, mit relativ viel meine ich zum einen Funktion höherer Ordnung
und das letzte größere Thema, was dran war, waren Closures.
Also die sogenannten funktionalen Abschlüsse und auch schon, was man
damit alles machen kann. Und die Anwendung von diesen Closures
Die schauen wir uns heute mal im Kontext der Animation und Spiele an.
Also ich gehe davon aus, dass diese ersten beiden Teile
grafische Bedienoberflächen und Gui möglicherweise schon behandelt worden sind.
Und wir gucken mal heute, was uns die Closures in
Bezug auf Animation und Spiele bieten.
Und da habe ich auch direkt eine kleine Bitte.
Wer jetzt schon merkt, dass Recket eigentlich viel spannender ist,
als er damals oder als sie damals gedacht hatte,
ja, den lade ich herzlich ein.
Im nächsten Sommersemester haben wir ein Praktikum,
das heißt zwar Bildverarbeitungspraktikum
Ist aber im Prinzip ein Racket-Praktikum. Und da werden wir
genau in diesem Kontext, über den ich heute vortrage,
uns ein Semester lang mit Racket und möglichen Arten der ganz anderen Entwicklung von Programmen beschäftigen.
Und diese ganz anderen Programme sind zum Beispiel Spiele. Also das,
was ich heute vorstelle, werden wir hoffentlich am Ende sehen,
eignet sich auch ganz hervorragend zur Spieleprogrammierung.
Generell haben wir mit den Closures ja die Möglichkeit, Funktion zu definieren,
die dann natürlich gegebenenfalls durch irgendeinen Aufrufer wieder aufgerufen werden.
Und das ist eine Möglichkeit hier, die er beschrieben ist,
die wir benötigen, um eben so etwas wie Programme,
die simulieren oder Simulationen ablaufen, zu modellieren.
Ja, das heißt, wenn wir Spiele oder Animationen definieren möchten,
dann benötigen wir zwangsläufig die Möglichkeit, auf Dinge zu reagieren.
Für Animationen müssen wir auf die Zeit reagieren. Für Spiele oder
interaktive Anwendungen müssen wir möglicherweise gegebenenfalls dazu noch auf dem Ausklicks,
auf Tastatur-Events und auf irgendwelche anderen Arten von Ereignisse von außen reagieren.
Wir müssen natürlich, und das ist auch ein Konzept,
was in Record ganz gut durchgezogen wird, was wir als Welt bezeichnen.
Also den Zustand, den unsere Welt hat,
die wir zeichnen möchten, den müssen wir irgendwie darstellen.
Und eine gute Art der Darstellung ist,
dass getrennt von der grafischen Darstellung zu machen.
Denn man überlegt sich mal, in der typischen Software-Schmiede,
wir sind dabei, ein tolles Produkt zu entwerfen.
Und es wäre nun schade, wenn jemand eine neue grafische Benutzungsoberfläche dafür haben muss.
Und man muss das bis auf die allertiefste Ebene runterbrechen.
Also da ist es viel schöner, wenn man einen Zustand hat,
der die Welt repräsentiert und dann eben eine Funktion,
die gegeben dieser Zustand eine Animation darstellt.
Also im Prinzip das Ganze in eine Zeichenbare Routine verwandelt
Und eben auch auf interaktive Aufgaben reagieren.
In Recket ist das Ganze zum Glück schon schön vorgefertigt.
Mit schön meine ich in diesem Fall tatsächlich schön.
Es gibt ein Modul, das heißt Universe, RKT.
Und in dem werden all diese Dinge schon vordefiniert,
die wir brauchen. Das heißt, man hat im Prinzip
gar nicht mehr den Aufwand, das Rahmenwerk anzupassen oder anzufassen.
Man muss es nur noch mit den eigenen Funktionen ausstatten.
Also zum Beispiel mit einer Funktion,
die einen Weltzustand in einen anderen überführt, gegeben,
ein Maus-Event oder eine Funktion, die einen Weltzustand zeichnet.
Wenn man diese Funktionen beisammen hat,
Aber es ist völlig ausreichend, um sowas wie ein Spiel,
wie eine interaktive, grafische Benutzungsoberfläche zu definieren.
Wir starten aber noch gar nicht so tief,
sondern gehen erstmal einen Schritt zurück und machen das Ganze etwas einfacher.
Nehmen wir mal an, wir möchten etwas animieren, ohne Interaktion.
Das heißt, die einzige Interaktion, in Anführungsstrichen,
die dann zutrifft, ist das Ticken der Maus.
Das ist das Ticken der Uhr. Das ist das Einzige,
was in dem Fall unseren Zustand, unsere Zeichnung beeinflussen kann.
Ist vielleicht auch ganz interessant,
wer sich jetzt am Wochenende erst mit dem kommenden Aufgabenblatt beschäftigen möchte,
das ist auch ein Teil der Zusatzaufgabe.
Verwenden Sie Animate, um eine Animation dieser Christbaum-Szene,
die da gefordert ist,
Diese Animate-Funktion ist selber eine Funktion höherer Ordnung. Das heißt,
das erwartet von Ihnen als Entwickler, dass Sie dem eine
Funktion übergeben und was es dann tut mit dieser Funktion ist,
es ruft die garantiert 28 Mal in der Sekunde auf.
Und mit dem einen Argument, den sogenannten Ticks.
Und was könnte das wohl sein? Hier steht es ja
auch, die werden einfach erhöht. Also es hat nichts
mit der Uhr zu tun, die im Rechner ist,
auch nichts mit dem Datum oder der Zeit.
Sobald man auf Start klickt, fängt es bei Null an zu zählen.
Und dann wird das einfach 28 Mal pro Sekunde hochgezählt.
Immer weiter,
immer weiter
Und das Ganze läuft auch ein bisschen alle Ewigkeit,
oder wenn man Fehler hat, bis der Speicher voll ist,
ja, aber es läuft auf jeden Fall immer weiter, immer weiter.
Und wenn man Stopp drückt, dann ist die einzige Ausgabe,
dieses Animate selber liefert tatsächlich die Anzahl der vergangenen Ticks.
Was aber interessant ist, ist,
dass während dieses Animate läuft, etwas passiert, ja,
und das, was passiert, ist,
dass über die grafische Benutzungsoberfläche von Recket ein sogenannter Canvas erzeugt wird.
Das ist nichts anderes als eine Leinwand, auf der
man zeichnen kann. Jetzt fragen sich einige wahrscheinlich,
wie kann ich darauf zeichnen, ist das sehr kompliziert?
Nein,
ganz im Gegenteil
Eine Gesundheit. Denn diese Animate-Funktion, die erwartet,
noch etwas Besonderes von der übergebenen Funktion.
Wir hatten ja schon gesagt, es muss eine Funktion sein,
sie muss ein Argument Tee haben, was mit den Ticks hochzählt.
Und jetzt kommt der Clou, diese Funktion muss ein Bild zurückgeben.
Ein Bild, so wie ihr es schon bei den Schmetterlingen
kennt, so wie ihr es für die Weihnachtsszene definiert.
Ja, wenn diese Gegebenheiten erfüllt sind, dann kann man
diese Funktion einfach Animate übergeben und es wird simuliert.
Mehr muss man dann nicht tun. Das einzige,
was man missen muss, ist, was das Tee ist und wie das hochzählt.
Jedes Mal ein einfaches Beispiel
Dazu sage ich gleich auch noch ein bisschen mehr. Ja,
hier sieht man schon, in dem Fall wurden zwei Pakete eingebaut, eben das Image,
damit man noch die erweiterten Funktionen der Bildmanipulation zur Verfügung hat.
Und dann dieses neue Paket, das sogenannte Universe, was wir für die Animationen brauchen.
Jetzt sieht man hier so ein relativ einfache,
eine relativ einfache Funktion, die eine UFO-Szene erstellt und diese UFO-Szene,
die tut jetzt nichts anderes als ein Rechteck,
der Größe 100 mal 100, dann könnte auch sagen,
das ist im Prinzip der äußere Rahmen von unserem UFO.
Und auf diesem platziert es in X,
also horizontal in der Mitte und in offensichtlich einem Y,
was man mit dem Parameter Höhe beeinflussen kann, ein UFO.
Wie dieses UFO aussieht, ist es nicht besonders schön,
wir werden es gleich sehen. Ja, das besteht einfach
nur aus einem Rechteck und dann noch einem Kreis. Und diese Funktion,
wir können das mal kurz prüfen, hat ein Argument,
das scheint zu passen, was hier noch nicht abgebildet ist,
ist, dass das Argument jetzt eine sinnvolle Belegung bekommt durch die Ticks,
aber wir können uns das ja mal überlegen,
das fängt jetzt an hochzuzählen und nach einer Sekunde ist halt auf 28.
Ich verrate jetzt mal
die Bedeutung,
Mal einen Tick erzählt, dass 28 hoch und fängt dann,
der war bei 00. Nein, 28 mal pro Sekunde und immer fortlaufend.
Ja, genau. Und ich verrate jetzt mal kurz,
was diese Argumente hier bedeuten für alle, die es nicht auf Anhieb sehen.
Das sind Angaben in Pixeln. Das heißt,
dieses Rechteck ist 100 mal 100 Bildpunkte hoch und in
einer Sekunde verschieben wir das UFO jetzt um 28 Bildpunkte.
In dem Fall nach unten. Die Animation auf der nächsten Seite
funktioniert bei mir leider nicht, aber was bei mir funktioniert,
ist die SE3-Bib und da auch nochmal an dieser Stelle
der Hinweis
Wir benötigen die zwar für die Aufgabenblätter und benötigen Module daraus.
Aber wer interessiert, ist an den Dingen,
die hier in der Vorlesung dran sind,
der darf da auch gerne mal reinschauen.
Also zum Beispiel gibt es hier einen Unterordner Examples,
wo wir doch eine Menge Dinge ausgeschrieben finden, die im Vorlesungsskript auftauchen.
Also wenn man da mal kurz Dinge nachvollziehen möchte oder so
wie bei mir jetzt für diese Vorlesung keinen Zugriff auf das Video hat.
Ich habe mir einfach das UFO hier geöffnet, das ist zufällig da drin.
Dann kann man hier auf Start drücken und jetzt gut aufpassen,
denn das UFO ist leider relativ schnell weg.
Da oben kommt es rein, dann sieht man,
dann fährt das runter. Und ab viereinhalb Sekunden sind wir
halt leider außerhalb dieses Rechtecks, was 100 mal 100 groß ist.
Ja, wenn ich jetzt auf Stopp drücke,
dann sieht man auch, das Fenster wird geschlossen.
Ja, so. Das hat nicht geklappt. Wir machen es
nochmal. So, wenn ich jetzt auf Stopp drücke,
hat es auch nicht geklappt. Eben hat es noch funktioniert
. Ja, dann sollte da eigentlich angezeigt werden,
wie viel Ticks da passiert sind.
Genau, hier nochmal das Bild, was leider nicht funktioniert
Also ein ganz einfaches Beispiel,
wo über die Zeit jetzt nur eine Koordinate eines Objektes verändert wird,
könnte sich natürlich überlegen, dass noch irgendwie zufallsbasiert,
ein bisschen hüpfen zu lassen, das UFO, ja,
oder das halt so Sand landen zu lassen.
Das heißt, dass man zum Beispiel mit einer geeigneten Funktion diese Bewegung steuert,
ja, aber das ist für dieses einfache Beispiel gar nicht notwendig.
Dass etwas kompliziertere Beispiel, das zweite Beispiel,
was ich heute vorstellen darf, ist eine analoge Uhr.
Wir kennen einige vielleicht gar nicht mehr, ja,
aber manche kennen das doch noch, das war das mit den Zeigern.
Wir haben drei Zeiger auf der Uhr, Stunden,
Minuten und Sekunden
Und wenn man jetzt das Ganze als ein Aufgabenblatt betrachten würde,
dann müsste man jetzt hier erstmal eine Analyse machen.
Das heißt, wenn wir so etwas jetzt darstellen wollen würden,
schauen wir uns erstmal die Teilaufgaben an,
relativ klar definiert für dieses einfache Beispiel.
Wir müssen natürlich erstmal irgendwie an die Uhrzeit rankommen.
Wenn wir keine Uhrzeit haben, ist es schwer,
sie darzustellen. Und das zweite ist dann das Zeichnen.
Ausgehend von der Uhrzeit müssen wir das Ziffernblatt ja irgendwie darstellen.
Da helfen einem jetzt zwei Funktionen. Die eine Funktion ist
Current to Seconds, vielleicht auch schon aus anderen Programmiersprachen bekannt
Ja, die liefert die Systemzeit in Sekunden ab 1970,
wenn ich mich nicht täusche.
Und aus diesen Sekunden kann man dann über ein mühevolles Umwandeln,
was einem glücklicherweise diese Funktion her abnimmt, das aktuelle Datum herauslesen.
Das Datum selber, das kommt in einem sogenannten Strukt,
also einem Verbund oder einer Struktur, auf die man unterschiedliche Zugriffe hat.
Das werden wir gleich auch sehen. Das heißt,
wir haben jetzt hier irgendwie eine World und wir möchten
die Next Clock World haben, also wie sieht
Im Prinzip die aktuelle Repräsentation unseres Zustands aus, den wir zeichnen möchten.
Und der besteht in dem Fall tatsächlich aus nichts anderem,
als eben genau diesem Strukt, diesem Datenverband, der die Uhrzeit enthält.
Das heißt, auf der Modellebene unserer Simulierung haben wir nichts anderes als die Uhrzeit.
In einer speziellen Verpackung. Das nächste ist dann natürlich die Zeichnung.
Wie gesagt, das Modell ist hier sehr einfach.
Aber diese Uhr soll ja auch schön gezeichnet werden.
Dafür, wenn man sowas irgendwo findet,
kann man sich zum Beispiel ein schönes vorgefertigtes Bildchen nehmen,
wie hier die Clockface
Ja, da muss man natürlich wissen,
wie hoch und wie breit die ist, wenn man mal annimmt,
dass die gleich hoch wie breit ist,
dann muss man theoretisch nur eins messen.
Hier wird aber nur um sicher zu gehen, beides gemacht.
Das heißt, mit diesen Hilfsfunktionen wird eben ermittelt,
wie breit das Bild ist mit der unteren,
wie hoch das Bild ist. Und auch hier,
Bilder können in Reckett tatsächlich einfach hereingezogen werden mit Plug-in-Play,
in dem Fall quasi, oder Prey mit den meisten Bildern funktioniert das aber.
Sollte das nicht funktionieren, kann man immer noch versuchen,
im Menü mit Insight Image das zu probieren,
das sollte eigentlich auch funktionieren
Denn wie ist das jetzt tatsächlich gesprächert mit dem Mordbrinker auf
die Dateien, wo der Herr zu ungefährlich gefüllt wurde?
Sobald man diese Datei, in der so ein Bild wie hier,
also nehmen wir mal an,
wir hätten das Ganze als eine Recket-Datei gespeichert,
ist das Bild da drin verwoben.
Und es führt leider auch zu dem Effekt,
dass man ab sofort, sobald so ein Bild darin enthalten ist,
diese Recket-Datei nicht mehr mit einem Texteditor öffnen kann.
Also man kann sie noch öffnen,
man kann sie dann nur nicht mehr lesen,
weil die Binärdaten des Bildes werden hier tatsächlich mit hineingespeichert
und das ganze Textformat ändert sich da nicht
Jetzt schauen wir mal, wie man sowas modellieren kann.
Wie gesagt, das Modell haben wir schon.
Wir haben auch schon einen Hintergrund für unsere schöne Uhr.
Und was auch immer ganz nützlich ist, ist so ein
neutrales Element. Wer sich erinnert für die Faltungsoperation Fold,
L zum Beispiel, wenn man damit eine Addition,
also eine Summe einer Liste bilden will,
dann hatten wir so ein neutrales Element als Null definiert,
weil das ganz sinnvoll ist, wenn man nicht weiß,
was reinkommt, einfach erstmal mit Null anzufangen in der Addition.
Für das Zeichnen von 10 gibt es so ein neutrales Element auch.
Das heißt einfach MTC. Das heißt,
das ist eine Szene,
die ist leer
Was man braucht. Ja,
hier ist der Hinweis auf dieses neutrale Element der Zeichnung.
Und, was man noch gut gebrauchen kann, hier ist Place-Image.
Damit kann man verschiedene Bilder, in dem Fall sind es
vor allen Dingen grafische Darstellungen von den Zeigern, zusammen komponieren.
Das heißt, können wir dem Place-Image ein anderes Bild auf ein anderes legen.
Und das Resultat ist dann das zusammengesetzte Bild. Das heißt,
auch hier, wir sind immer noch in der funktionalen Programmierung
durch das Zusammenlegen von Objekten, entsteht wieder ein neues Objekt.
Place-Image verändert nichts auf dem ursprünglichen Bild,
sondern es nimmt dann eben die Bilder und erzeugt ein neues
Was hier übrigens nicht beschrieben ist, wer sich da vielleicht gleich wundert,
ist die eine Funktion, die oben drüber steht.
Das ist die Funktion Placehand. Das ist auch nicht wichtig,
ob man die hier sieht oder nicht.
Das Einzige, was diese Funktion tun soll, ist,
dass sie ein Zeiger im englischen Hand auf unsere Hintergrunduhr platziert.
Wie sie das tut, ist, glaube ich,
nicht so wichtig. Was man nur wissen muss,
ist, dass man dafür natürlich sowas wie einen Winkel braucht,
in dem der Zeiger stehen soll
Möglicherweise noch eine Farbe. Denn ansonsten ist es schwer,
die Zeiger zu unterscheiden oder und noch eine Länge,
weil wir das auch so gewohnt sind von Uhren,
dass die Sekundenzeiger die längsten oder zumindest die dünnsten Zeiger sind.
Und alle Fortlaufenden dann eben kürzer werden, bis eben zum Stundenzeiger,
der dann nur noch so ein kleiner Knubbel ist manchmal.
Das heißt, wenn wir uns das jetzt im Hinterkopf behalten,
die Funktion Plays Hand, die tut eben nichts anderes,
als so ein Zeiger zu zeichnen auf unserem Bild,
dann sehen wir hier,
wir haben eigentlich das Problem unterteilt in drei Funktionen
Einen für die Minuten ein für die Stunden und dann noch den für die Sekunden
. All diese Zeiger müssen ja jetzt komponiert werden.
Und wir können das jetzt derart definieren als das. Wir gehen mal davon aus,
dass diese Funktion Plays Hand, jetzt den Bruchteil eines Zyklus,
in dem Fall ist der Zyklus einmal rum um die Uhr, annimmt.
Tatsächlich eine Länge, die dieser Zeiger haben soll.
Eine Farbe und ein Canvas. So, und jetzt sieht man schon,
dass mir doch ein kleiner Fehler unterlaufen ist,
denn die Funktion Plays Image tut tatsächlich Keine funktionale Bildkomposition.
Da hätte ich ein bisschen besser aufpassen müssen,
aber deswegen sind wir heute alle zusammen hier.
Denn was diese Funktion jetzt auch noch benötigt, ist dieser Canvas.
Und dieser Canvas erinnern wir uns kurz, das war die Zeichenfläche. Das heißt,
wann immer ihn solche Funktionen über den Weg laufen,
die die Zeichenfläche als Argument benötigen, ist es relativ wahrscheinlich,
dass diese Funktionen etwas auf der Zeichenfläche manipulieren.
Das heißt, in dem Fall wird tatsächlich diese Grafik
auf dieser Zeichenfläche platziert und es wird kein Bild zurückgegeben,
Das Einzige, was man sieht, ist als Seiteneffekt,
dass dieser Kenvist diese Zeichenfläche sich verändert hat.
Das Ganze wird hier dreimal gemacht, die Berechnung für die,
wir können ja bei den Sekunden anfangen, wenn ein Rundlauf-Einzyklus eine Eins ist,
dann haben wir natürlich Sekunden durch 60. Okay, gut,
es ist auch für mich neu,
ich war noch nie bei Lecture to Go bisher,
aber es ist doch spannend.
Es ist von weiter oben, ah, okay, auch gut. Ja,
kommen wir noch kurz zurück zu den Sekunden, ja,
das ist eine triviale Rechnung, eins ist voll,
null ist leer und dann sind wir im 60. Takt mit den Sekunden,
einmal um den Kreis
Bei den Minuten ist das ein bisschen komplizierter. Man könnte jetzt ja meinen,
das sind einfach nur die Minuten wiederum durch 60. Aber tatsächlich ist das ja so,
dass die Minute, je nachdem,
wie weit der Sekundenzeiger zeigt, auch fortgeschritten ist.
Also wenn man zwischen Minute eins und Minute zwei ist,
der Sekundenzeiger zeigt das zum Beispiel an,
weil er bei der 30 steht, also ganz unten,
dann ist der Minutenzeiger auch schon halb vorgerückt.
Das wird hier durchmodelliert und das Gleiche müssen wir auch noch
für die Stunden machen.
Denn auch der Stundenzeiger zählt mit dem Minutenzeiger weiter
Wenn man das Ganze jetzt komponieren möchte, ja, ist relativ einfach.
Wir haben hier ein langen, aber übersichtlichen Block aus Let-Anweisung.
Und diese Let-Anweisung gehen wir jetzt einfach mal der Reihe durch. Ja,
die World hier, hier ist sie noch übergeben,
da wird einfach das gemacht, was wir schon besprochen haben,
aus der Sekundenrepräsentation wird eine Datumsrepräsentation gewonnen.
Wir haben Höhe, Breite. Und das Erste,
was wir mal machen, ist,
wir erzeugen eine leere Szene
Aber nicht ganz leer, also sie ist leer im Sinne von
keine Elemente enthalten, aber sie hat schon eine Größe.
Und sie ist genau so groß, wie diese Uhr,
die wir haben und auf dieser Fläche müssen wir ja jetzt zeichnen.
Und damit wir zeichnen können, brauchen wir ja einen strukturellen Zugriff auf den Datumstyp.
Und das geschieht relativ einfach mit Date Hour,
Date Minute und Date Seconds auf das, was wir hier oben haben.
Das heißt, in dem Fall ziemlich einfacher Zugriff,
ja,
da passiert gar nichts
Im Übrigen, das hier mit der World, das ist sehr irritierend.
Da könnte man auch gut ein anderes Argument nehmen,
weil wir die World hier eigentlich gar nicht übergeben,
sondern am Ende dann die Ticks.
Erkennt man auch daran, dass die World direkt in diesem
Letblock hier wieder überschrieben wird und das heißt, alles,
was ab hier jetzt folgt, diese Definition von World benutzt.
Was ja auch ganz sinnvoll ist,
weil wir mit jedem Aufruf direkt die absoluten vergangenen Sekunden berechnen
können und uns daraus die Datumstruktur ziehen können und das nicht
irgendwo extern nochmal speichern müssen
Was man dann sieht, ist, dass wir hier jetzt die Komposition machen,
so wie es jetzt doch wieder aussieht, macht das Place-Image,
gibt es doch ein Bild zurück, also in dem Fall sind wir hin zurück und wieder hin.
Das heißt, wir bekommen, da ist es immer ganz sinnvoll,
vorne anzufangen, vorne für Recket ist,
wie wir auch hoffentlich mittlerweile alle wissen,
außer bei Special Forms von innen, das heißt,
wir schauen uns den innersten Ausdruck an, merken,
aha, wir tun etwas auf diesen Canvas,
in dem Fall ist es das Clockface,
Er sich nicht mehr erinnert, dass Clockface,
war in dem Fall einfach diese leere Uhr im Hintergrund.
Die wird mittig auf den Canvas aufgebracht. Das ist ganz
gut, denn sonst würden wir nicht alles davon sehen.
Und dann wird auf die sogenannten, verwendete oder generierte Hintergrundabbildung,
werden erst der Sekundenzeiger aufgebracht, dann der Minutenzeiger und am Ende der Stundenzeiger.
Das Ganze, ob man da noch eine Höhefunktion drumherum schreibt oder nicht,
ist jedem selbst überlassen. Kann man dann mit Animate Draw Clockworld animieren und deswegen,
wie gesagt, hier nochmal der Hinweis,
wer vielleicht die Folien dabei hat und sich da eine Anmerkung ranschreiben möchte,
ja, das hier ist wirklich verwirrend, ja,
da einfach vorstellen, dass da Text drinsteht.
Ja, und dann sieht man direkt,
dass in der Animation dieser World, ja,
die ja 28 mal aufgerufen wird, dieses Ticks,
was da oben steht, kein einziges Mal verwendet wird,
weil es eben völlig unerheblich ist, wann wir unsere Animation starten,
wie lange sie gelaufen ist, ja,
denn wir möchten in jedem Animationsschritt ja die aktuelle Zeit darstellen.
Und das ist einfach etwas, was wir in jedem prozeduralen Aufruf hier neu ermitteln können.
Dafür brauchen wir Kein Wissen, wie lange unser Programm läuft.
Hier mal so ein paar Schnappschüsse, ja,
also so sieht das dann aus, wenn man das implementiert hat.
Fast die gesamte Implementation steht hier schon, das heißt,
wer das gerne nachvollziehen möchte, das Einzige,
was noch dazukommen müsste, wäre tatsächlich die Funktion Play Cent,
die eben gegeben einen Winkel,
der in einem recht merkwürdigen Format vorliegt, zugegeben,
statt zwischen 0 und P,
liegt er zwischen 0 und 1, ja,
auf diese Uhr aufbringt.
Dann sehen wir mal
ein paar Beispiele,
Und da sieht man auch schon ganz gut,
wie sich tatsächlich der Minutenzeiger auch bewegt in Abhängigkeit des Sekundenzeigers.
Und wenn wir das noch weitergehen würden,
dann würden wir das auch für den Stundenzeiger sehen.
Aber wer das nicht glaubt, ich schaue gerade mal nach.
Im Moment haben wir auch eine Uhrzeit, wo wirklich nichts aufeinandersteht.
Ja, also wer noch eine analoge Uhr um hat,
der glaubt das auch, dass die Zeiger sich in Abhängigkeit bewegen.
Das war eine sehr einfache Art der Animation von der Komplexität
hier möglicherweise sogar noch einfacher als das UFO-Beispiel,
weil wir nirgendwo im Animationsaufruf eigentlich in Bezug auf den Animationsparameter
haben,
nämlich die Zeit
Die nächste Erweiterung oder die nächste Komplexitätsstufe ist,
dass wir eine Interaktion mit hineinnehmen möchten.
Das heißt, wir haben jetzt nicht nur die Möglichkeit,
irgendetwas zu zeichnen und einfach fortlaufend darzustellen, sondern wir möchten möglicherweise eingreifen.
Also man könnte sich ja vorstellen,
dass diese UFO-Szene einem zwar gefallen hat,
aber man zum Beispiel irgendwo mit der Maus hinklicken möchte oder
irgendeine Taste drücken will auf der Tastatur und daraufhin passiert wieder irgendwas.
Zum Beispiel, dass das UFO wieder zurückkommt.
Dafür braucht man jetzt ein bisschen mehr,
das kann das Animate nicht liefern
Was das aber liefern kann, ist die Big Bang-Funktion,
die eine komplett simulierte Welt startet. Und das ist jetzt
tatsächlich ein bisschen mehr, beziehungsweise andersrum könnte man sagen,
das Animate ist eine Untermenge von dem Big Bang.
Ähnlich wie das Animate erzeugt es auch ein Fenster,
in dem irgendein, da ist man relativ frei,
Zustand einer Welt angezeigt werden kann.
Das heißt, solange man eine Funktion hat,
die einem das zeichnet, kann man da alles anzeigen.
Das startet auch eine Simulationsuhr, genauso wie Animate,
die einfach mit den Ticks weiterläuft
Aber, und jetzt kommt das, was wir brauchen,
das verwaltet auch Ereignisroutinen für Interaktion und für weitere Simulationsereignisse.
Was ich für dieses schon angesprochene Praktikum im Sommersemester interessiert,
ja, wir programmieren da auch verteilte Mehrspielerspiele. Das heißt,
eine Ereignisroutine ist zum Beispiel nicht nur on tick,
wo eine Uhr kommt oder To-Draw,
wo man selber zeichnet oder irgendwas, wo abgebrochen wird oder irgendwas,
wo Tastaturereignisse kommen, sondern es gibt auch ein On-Message.
Das heißt, die Macher, dieses Big Bang,
Simulationsmodells, ja, haben sogar eine Netzwerkkommunikation eingebaut,
mit der man sich untereinander Nachrichten schicken kann und auch aufgrund
dieser Nachrichten die Welt verändern kann.
Ja, also so Client-Server-Spiele sind damit sogar auch möglich.
Und natürlich gibt es noch viel mehr. Also es gibt auch
noch On-Maus, ja, wer das jetzt hier vermisst,
das ist wirklich nur ein Auszug. Jetzt schauen wir mal,
wie wir so eine analoge Uhr mit Eventhändlern machen.
Denn das Ganze sieht dann etwas anders aus.
Wir hatten ja im Animate die Vereinfachung, dass einfach in
jedem Tick immer direkt gezeichnet wird, das Ergebnis wird direkt angezeigt.
Für die Ereignisroutine, die wir jetzt hier haben, müssen wir das Ganze aufteilen.
Das heißt, wir haben einen Weltzustand und ein,
den wir in Ontic möglicherweise ändern müssen.
Wir haben eine Ereignisroutine to Draw, die das Urereignis
immer neu zeichnen muss. Und wir haben eine Sonne Key,
was wir zum Beispiel nutzen können, um die Ohren zu halten,
Ja, was könnte man noch machen?
Irgendwie den Hintergrund vielleicht auszutauschen, klassische gegen eine moderne Uhr oder so.
Und man könnte auch sagen, dass man mit Stop-Wen
zum Beispiel abbricht, wenn das Jahr 2000 naht.
Also da gab es ja früher mal Bedenken. Wenn man sich den Ablaufrahmen so anguckt,
ja, dann braucht man für diese Simulation Folgendes,
ja, man braucht einen Startzustand,
wir nennen den einfach mal First Clock, ja,
Next Clock World erzeugte einfach diese Datenstruktur mit den verschiedenen Einträgen für Stunden,
Minuten und ich glaube,
da sind auch noch Tage und
so weiter drin
Jetzt sieht man schon, hier wird auch angezeigt,
das ist ganz nett für den Benutzer, dann weiß der,
was er machen soll oder was er machen kann.
Ja, zum Beispiel, wenn man Quit,
Q oder Escape drückt, dann wird das Ganze angehalten.
Können wir ja gleich in der Implementation mal kontrollieren, ob das auch wirklich stimmt.
Und dann erfolgt hier die Initialzündung des Urknalls.
Das heißt, wir erzeugen jetzt unseren Big Bang.
Und was Big Bang auf jeden Fall braucht, ist ein Startzustand.
Die Idee von diesem Simulationsmuster, was mit Big Bang erzeugt wird,
ist, dass wir hier einen Zustand haben.
In dem Fall starten wir mit First Clock der aktuellen Uhrzeit.
Und all diese Eventhändler, die bekommen jetzt bei jedem Aufruf Argumente,
die halt vom Händler abhängen,
der Maushändler bekommt Ereignisinformationen über die Mausklicks,
der Tastaturhändler bekommt Ereignisinformationen über die Tastatur.
Und den aktuellen Weltzustand. Und was all diese Funktionen zurückliefern
müssen, ist ein neuer Weltzustand. Das heißt,
diese Funktionen wie On-Tick on Key haben die Möglichkeit,
die aktuelle Repräsentation,
die unsere Welt hat,
zu verändern
Wenn man sich das mal vorstellt,
zum Beispiel für ein Computerspiel,
wo man mit der linken und der rechten Maustaste irgendwelche Aktionen ausführt und mit linker und rechter Pfeiltaste die Figur bewegen kann,
dann wäre das hier bei dem On-Key zum Beispiel,
dass wir einen Weltzustand bekommen,
in dem möglicherweise die Position der Spielfigur enthalten ist und wir würden dann je nachdem,
welche Taste gedrückt wird,
diese Weltzustands,
diesen Weltzustand so modifizieren,
dass wir die eben um einen nach rechts oder um einen
nach links versetzen und ändern so in diesen On-Key,
On-Tick und Maus
immer den Weltzustand
Wenn man das jetzt tut, dann ist natürlich die Frage, ja, wunderschön.
Das heißt, die ganze Logik arbeitet, der Weltzustand ändert sich.
Wie bekomme ich das angezeigt? Und für diese Anzeige
gibt es ein extra Eventhändler und das ist der To-do-Draw.
Das heißt, im Gegensatz zu den On-Tick,
On-Key und On so weiter, die alle den Weltzustand verändern,
ist jetzt das To-draw genau diese Funktion,
die uns den Weltzustand zeichnet.
Das heißt, im Prinzip interne Repräsentation unseres Weltzustandes
ist im Moment so
eine Struktur
Muss ich diese Struktur zeichnen? Das ist die Funktion,
die hier bei Toddraw angegeben werden muss. Was man auch machen kann,
wenn man das denn möchte, ist,
dass man zum Beispiel so eine letzte Welt definiert,
ja, wenn wir jetzt beim Spiel sind,
könnte man sagen, wenn der Spieler das durchgeschafft hat,
dann wäre das zum Beispiel der letzte Zustand,
wenn er den erreicht hat, bricht die Simulation ab.
Das ist natürlich auch nicht so fein. Eigentlich werden dann ja
immer noch irgendwelche schönen Sachen angezeigt, aber so könnte man das machen.
Man kann das aber auch benutzen, um zum Beispiel einen Abbruch zu signalisieren.
Ja, zum Beispiel Wenn jemand auf Kuh gedrückt hat oder Escape,
dann setzt man einfach die Welt auf dieses merkwürdige Spezialdatum hier.
Und somit bekommt man mit diesem Prädikat dann jederzeit eine Aussage darüber,
hat der Benutzer die Welt jetzt angehalten oder nicht?
Wofür man das benutzen kann, sieht man hier,
das ist der Eventhändler, der auf Tastaturereignisse reagiert.
Und wir haben jetzt gesagt, wir dürfen sowohl mit Escape
als auch mit Kuh unserer Simulation beenden. Das heißt,
es reicht hier tatsächlich aus, einen einfachen Vergleich zu machen,
ja, ist der gedrückte Tustin Escape oder ist das ein Kuh?
Und wenn da Dann machen wir eben zwei Dinge.
Wir geben das einmal an, das ist so eine sogenannte D-Bug-Ausgabe,
die würde man wahrscheinlich dann, kurz bevor man das Spiel verkauft, weglassen.
Und wir sagen dann einfach,
und jetzt stoppe mit der Last Clock World, ja,
das war eben diese Welt oder diese Anzahl an Sekunden,
die uns sagt, nein, jetzt ist Schluss,
jetzt machen wir nicht mehr weiter.
Immer, wann das nicht der Fall ist,
ja, also wenn irgendjemand auf eine Taste drückt,
aber das weder Q noch Escape ist, dann machen wir Folgendes,
wir verändern den Weltzustand einfach nicht.
Wir lassen den einfach so,
wie er ist
Es ist vielleicht von der Lesbarkeit her jetzt ein bisschen komplizierter,
als wie man das zum Beispiel aus imperativen Programmiersprachen kennt,
wo man in jedem Ausdruck oder Aufruf von den Händlern irgendwelche Objekte manipuliert.
Ja, aber hier ist es tatsächlich so, ihr werden keine Objekte manipuliert.
Hier wird entweder die Welt angehalten oder aus dem Weltzustand,
den wir vorher hatten, kommt dann wieder der Gleiche raus.
Also es wird einfach nichts verändert. Im Übrigen ist dieser Ablaufrahmen hier,
was wirklich, was man für diese Aufgabenstellung noch gar nicht unbedingt braucht.
Denn wie wir alle gesehen haben, ist die Uhr,
bis auf das Escapen, also in dem Fall das Ausbrechen,
wo wir wirklich den Status gezielt setzen müssen,
auf eine andere Anzahl von Sekunden, völlig unabhängig von irgendwelchen Argumenten.
Das heißt, wenn wir jetzt die Uhr 1 zu 1 hätten simulieren wollen,
dann hätten wir dieses Ontic hier zum Beispiel weglassen können,
weil wir ja wissen, dass In diesem Toddraw,
ja, das wird ja sowieso dann aufgerufen.
Da könnten wir dann auch einfach sagen, bei jedem Aufruf
zeichnen die Welt. Mit dem On-Tick ist hier aber sichergestellt,
dass tatsächlich in jedem Tick sich der Weltzustand ändert.
Ja, das ist was, was relativ wichtig zu sehen ist.
Im Gegensatz zu dem Animate wird nämlich bei dem Big Bang
nur dann gezeichnet, wenn sich was verändert hat.
Oder wenn man mit einem Fenster das Fenster verdeckt und das
dann wieder wegzieht. Also wenn die Gui sagt,
sie muss neu gezeichnet werden
Das heißt, wenn man möchte, dass die Welt neu gezeichnet wird,
das möchten wir ja mindestens jede Sekunde,
dann müssen wir dafür sorgen, dass sich der Weltzustand verändert.
Und genau das tut dieses On-Tick hier. Genau,
das ist die Notwendigkeit, warum wir hier auf Ticks reagieren müssen,
wohingegen das Animate sowieso 28 Mal in der Sekunde die Zeichnerfunktion
aufruft und wir diesen Umweg nicht braucht.
Also das vielleicht noch, wer sich jetzt fragt,
warum muss ich denn hier auf Ticks reagieren?
Das ist genau deshalb,
weil wir ja jede neue Sekundenrepräsentation gezeichnet haben möchten
Ein anderes Beispiel für eine recht einfache Simulation ist,
sind Theodor Straumsmike-Katzen. Ich bin mir nicht ganz sicher,
ich glaube, das entspricht irgendwie einem Gedicht. Ja,
ich muss das zum Glück nicht lernen,
ich musste dafür viele andere Gedichte lernen,
jetzt gerade zur Weihnachtszeit fühlt man sich dann manchmal erinnert.
Aber ohne Beachtung aller biologischen Regeln wird hier davon ausgegangen,
dass jede Katze pro Jahr eine feste Zeit von Nachkommen hat.
Und das trifft rekursiv auch auf die Nachkommen zu.
Das heißt, das Ganze geht ziemlich durch die Decke und
zwar exponentiell und das heißt ganz schön schnell.
Das heißt, mit einer Anfangspopulation beginnt. Und idealerweise sind das
zwei Katzen. Wir wissen alle, warum. Wenn man
nur eine Katze hat, dann außer die ist schon schwanger.
Aber ansonsten wird es schwierig. Und bei jedem Tick muss
man jetzt also gemäß dieser Regeln eine neue Population errechnen.
Das heißt, wenn wir jetzt diese Mai-Katzen-Simulation haben,
dann müssen wir im On-Tick, also jedes Mal,
wenn das passiert, müssen wir irgendwie eine neue Generation berechnen.
Das heißt, wie viele Katzen haben wir dann?
Wir starten das Katzen-Universum im Jahr eins mit einer Katze.
Ich habe schon gesagt, das hat biologisch so so
ein paar Feinheiten, die es schwierig machen eigentlich.
Aber die Definition ist ja auch, im Mittel hat jede
Katze pro Jahr und deswegen kann man halt auch statistisch mit einer Katze anfangen.
Wir müssen natürlich auch wieder eine Show-Catworld-Funktion haben, die uns das Ganze dann zeichnet.
Und Andle Key, schauen wir mal, ob wir das brauchen.
Wie man so ein Universum oder so einen Zustand der Welt
repräsentiert, ist relativ egal. Eben hatten wir Datumsstruktur,
mit der wir die Sekunden und Minuten extrahieren konnten.
Das war zum Zeichnen. Gut, hier haben wir jetzt auch eine Struktur,
die einfach das aktuelle Jahr und die Anzahl der Katzen hält.
Genauso gut hätte man eine Liste nehmen können oder einen Vektor
oder für den Fall hätte ja auch einfach ein Paar gereicht.
Das heißt, nicht verunsichern lassen, hier werden immer Struktur verwendet,
es funktionieren aber auch genauso gut Listen als Weltrepräsentation.
Dann führt man hier so eine Konstante ein, ja,
wie viele Kätzchen macht jedes Kätzchen pro Jahr?
Das sind in dem Fall vier. Und wenn man dann
ein Weltzustand hat, Ja. Kann man relativ leicht die nächste Generation feststellen. Das heißt,
man erzeugt dann eine neue Welt, ja,
Make Cat Universe ist der Konstruktor für diese Struktur, erinnern uns kurz,
wenn wir Strukturen anlegen, bekommen wir Zugriffsfunktionen auf diese Strukturen und Konstruktorfunktionen.
Die Zugriffsfunktion heißt immer Make Bindestrich und dann der Name,
deswegen Make Bindestrich Cat Universe. Das erste Argument ist der
erste Parameter der Struktur, in dem Fall das Jahr.
Gut, und da addieren wir einfach auf das aktuelle Jahr,
was die Welt vorher hatte,
einen drauf
Und ähnliches machen wir dann auch um die Anzahl der Kätzchen
zu erhöhen. Wenn man sich das dann hier schön erzeugt hat
in dem Let, dann gibt man das einfach zurück.
In dem Fall wäre das Let sogar verzichtbar. Man hätte
auch direkt diesen Ausdruck, der hier steht von Make Cat bis hier zurückgeben können.
Die Anzeige sich jetzt mal anschaut, dann ist die auch relativ einfach gehalten.
In dem Fall haben wir uns zunutze gemacht,
dass wir in List auch Bilder reintun können,
nicht auch Bilder reintun können, sondern wir können in List verschiedene Argumente hineintun.
Das heißt, wir können statt mit String Apent,
können wir auch einfach eine Liste erstellen,
in der mehrere Strings und Zahlen enthalten sind.
In dem Fall ist hier das Jahr, dann die aktuelle Jahreszahl,
dann die Anzahl der Kätzchen und dann eben die tatsächliche Anzahl der Kätzchen.
Das Ganze stellen wir einfach erstmal auf der Konsole
dar und dann erfolgt die eigentliche Berechnung des Bildes.
Für das Bild haben wir eine Funktion,
ich glaube die kommt auch gleich noch, die braucht ein Argument,
nämlich ein Bild von den Prototypen eines Kätzchens.
Und dann auch, wie oft es die zeichnen soll.
Und das ist genau das, was wir hier bekommen. Das heißt,
in dieser World erinnern uns, das ist unsere Struktur,
da gibt Cat Universe num Cats auf World,
aufgerufen genau die Anzahl der Kätzchen zurück.
Und das geht dann in die Funktion Draw Cap
rein oder Draw Cats, sei ja mehrere zeichnen.
Und hier sieht man es auch schon, ja,
Katze winzig ist auf dem Beamer jetzt ganz schön groß,
aber man sieht schon, die ist eigentlich relativ klein,
die Pixel deutlich zu erkennen.
Und Wenn man das jetzt simulieren lässt,
dann fängt man zum Beispiel mit Jahr eins an.
Wir haben hier ein, dann fünf, dann,
man sieht schon ganz gut den exponentiellen Anstieg hier.
Und irgendwann wird dann relativ schnell ein Abbruchkriterium erfüllt,
was man sich auch wieder selber definieren kann,
end of time, to many cats, Planet ist voll,
ja, das ist dann eben Pech.
In der grafischen Anzeige sieht das dann hingegen eher so aus,
da ist die eine Katze,
die sicherlich schon mit den vier anderen Kätzchen schwanger war,
dann kommen die dazu,
ja,
dann geht es nochmal weiter
Und da sieht man schon, dass das Katzenuniversum doch recht schnell gefüllt ist,
zumindest ohne Ableben von Kätzchen auf natürlichem oder nicht natürlichem Weg. Genau,
jetzt schließt dieses Kapitel, was wir ja relativ anschaulich hatten,
was uns gezeigt hat, wie wir Simulationen machen können,
was uns aber auch gezeigt hat, wie wir Simulation so vereinfachen können,
dass es einfach nur noch Animationen sind,
die auf einen Tick reagieren, der vom Timer kommt, das Kapitel,
mit dem wir jetzt diesen Kreis der Funktion höherer Ordnung erst mal abschließen.
Zumindest von der Seite der Motivation und der Definition.
Das heißt, nach diesem Kapitel, ich glaube,
es ist Kapitel 20, nehmen wir jetzt an,
dass so die wichtigsten Einsatzzwecke von Funktionen höherer Ordnung und von Closures bekannt sind.
Wie gesagt, Closures haben wir uns jetzt ja heute und
das letzte Mal, glaube ich, auch beschäftigt.
Und deswegen hier nochmal die kurze Zusammenfassung. Was man jetzt
kennen und vor allen Dingen auch im richtigen Kontext anwenden können sollte.
Dazu gehören unter anderem diese Funktionen, die hier genannt werden
Ja, Apply,
eben um eine Funktion auf eine Liste von Argumenten anzuwenden,
Map, wenn man eine Struktur erhaltene Abbildung von Listen hat,
Filter, wenn man eine Nicht-Struktur erhaltene Abbildung hat,
also zum Beispiel aus einer Liste nur gewisse Dinge extrahieren möchte,
falten, einmal mit dem Argument links und einmal mit dem Argument rechts,
um Listen, ähnlichen Elementtyps zu einmal dem Elementtyp zusammenzuziehen,
dann gab es noch Funktionen,
die iterieren können und es gab auch für Verträge Dinge,
die Funktionen als Argument enthalten können, ja,
die einfach Funktionen auf bestimmte In dem Fall heißen die Contracts in Rackett prüfen können.
Also sowas ähnliches für Unit-Tests. Außerdem hatten wir ja noch Funktionen höherer Ordnung so definiert,
dass dazu auch Funktionen gehören, die Selbstfunktionen wieder als Resultate haben.
Und das ist zum Beispiel das beliebte Curry,
mit dem man sich jetzt eigentlich auch schon gut auskennen sollte,
auch wenn es einer der schwereren Fälle ist.
Curry Air macht das Gleiche nur von rechts.
Und dann gibt es noch die etwas weniger,
oft verwendeten, aber nicht weniger interessanten Funktionen, Kompos,
Funktionen, Kompositionen, Conjoin,
Disjoin und wir haben hier in der Vorlesung noch die Rive und Newton-Methode kennengelernt,
die selber auf Funktions zurückliefern.
Außerdem hatten wir eben noch Funktionen als Callbacks gesehen,
bei Make-Button im Gummi-Paket war das relativ einfach.
Da muss sich eine Callback-Funktion angeben. Ansonsten, wie soll ich wissen,
was passiert, wenn ich auf einen Button drücke?
Das wird über eine Funktion definiert, die dann aufgerufen wird,
wenn der Benutzer auf den Knopf drückt.
Bei Animate hatten wir das gesehen, benötigen wir eine Funktion übergeben,
die in Abhängigkeit der Ticks des Timers etwas tut
Oder eben auch nicht. Und bei Big Bang hatten wir gesehen,
dass wir die Möglichkeit haben, ganz viele Callback-Funktionen anzuführen.
Ja, eins zum Zeichnen. Eine, die zum Beispiel
auf Tastaturbefehle, eine, die auf Mausbefehle auf Netzwerkbefehle.
Für all diese Reaktionen haben wir Callback-Funktionen verwendet.
Ich bin ganz gut in der Zeit.
Das ist schon mal schön für uns alle.
Das heißt nämlich, dass wir heute auch noch zu
diesem Teil kommen
Ab jetzt wird es ein bisschen knackiger in der Vorlesung.
Das heißt, diese Funktion höherer Ordnung, die haben wir
nicht als funktionale Spielwiese eingeführt. Und auch nicht,
um zu zeigen, was man da so toll alles irgendwie definieren kann,
sondern diese folgenden Folien und auch sicherlich wichtig für die nächsten paar Termine und auch für die nächsten Aufgabenzettel,
ist jetzt nicht nur, dass man verstanden hat,
was diese Funktion höherer Ordnung tun, sondern auch,
wie man sie einsetzen kann.
Denn wir sehen das gleich bei dem ersten Gebiet,
bei den kombinatorischen Problemen. Es ist enorm kompliziert und komplex
. Diese Funktion ohne Funktion höherer Ordnung
Das heißt aber im Umkehrschluss auch,
dass die Definitionen der Funktionen, die wir gleich sehen werden,
zum Beispiel für die Menge aller Teillisten, sogenannte Potenzmenge,
wenn man das verstehen möchte, dann muss man auch verstanden haben,
wie Funktionen höhere Ordnung funktionieren und was so ein Map-Curry-Conscar bedeutet.
Ja, das ist am Anfang etwas schwierig,
aber wenn man sich da rein gedacht hat,
dann fällt es einem auch leicht,
wirklich kompakte, präzise Funktionen zu schreiben,
die zumindest von all denjenigen verstanden werden können,
die Funktionen
höhere Ordnung verstanden haben
Und für alle, die das nicht verstanden haben,
bei denen ist die Wahrscheinlichkeit relativ gering,
dass sie die ausmodellierte, also wirklich,
wo alle Rekursionen selber geschrieben worden sind, die Version verstehen.
Wir sind jetzt halt an dem Punkt, wo die Probleme
so komplex werden, dass wir uns eben dieser Methoden,
die Funktionen eurer Ordnung bereitstellen, bedienen müssen.
Schauen wir mal an, es gibt natürlich eine Vielzahl an
kombinatorischen Problemen oder auch an kombinatorischen Definitionen. Schon angesprochen,
wir schauen uns heute mal so ein paar Dinge zum Einstieg an,
Liste aller Unterlisten, Permutation,
es gibt auch sowas wie Kombination,
wer das noch aus der Statistik kennt
Und so typische Probleme, Rucksackprobleme, Geldwechselproblem. Vielleicht kommen wir
dazu heute noch. Schauen wir mal. Was schon mal interessant ist,
ist, dass sich die meisten dieser kombinatorischen Probleme elegant,
rekursiv definieren, beziehungsweise lösen lassen.
Ja, das heißt, bei den Unterlisten müssen wir irgendwie
die Unterlisten von der Liste aufstellen. Das heißt,
man kann sich leicht überlegen,
wenn ich eine Teilliste von dieser Liste nur habe,
dann brauche ich auf jeden Fall auch von denen alle Unterlisten.
Die müssen auch mit in die Menge der Unterlisten der Oberlisten
rein. Und bei den Permutationen läuft das so ähnlich
Wir schauen uns mal kurz so das Anwendungsgebiet an.
Bei den Unterlisten, wie gesagt, da braucht man nicht
viel zu sagen, ist einfach die Menge der Unterlisten.
Bei den Permutationen ist es alle Möglichkeiten der Vertauschung von Elementen.
Und bei Zusammensetzungen sind wir in so einer Problemklasse,
wo man zum Beispiel einen Geldbetrag hat,
welche Möglichkeiten gibt es den in Münzen zu tauschen.
Jeder, der hier schon mal in der Mensa war oder
auch bei uns in Stelling auf dem Campus weiß,
dass die Kassierer da möglichst viele Wege rausfinden, während man wartet.
Genauso geht es auch bei Rucksäcken. Also womit kann ich
meinen Rucksack maximal gut füllen?
Gegeben verschiedene kleinere Einheiten
Als erstes Beispiel, schauen wir uns heute mal die Liste aller Unterlisten an.
Wie gesagt, vom mathematischen Standpunkt her auch als sogenannte Potenzmenge bezeichnet.
Und wir schauen uns das jetzt mal direkt aus einem Racket-Standpunkt an.
Das heißt, wir haben eine Liste von Elementen.
Und wie wir alle wissen,
haben wir auf diese Liste erst mal in aller einfachster Ausprägung
einen Zugriff auf das Kopfelement mit K von XS
und auf den Schwanz der Liste oder den Rest der Liste
mit CDR von XS
Und wie er eben schon erwähnt wurde, haben wir viele
kombinatorische Probleme in einer rekursiven Definition vorliegen. Das heißt,
wir versuchen das Ganze mal rekursiv aufzuschreiben und mal zu bestimmen,
was enthält denn eigentlich die Liste aller Unterlisten oder was muss sie enthalten,
damit wirklich alle Unterlisten dabei sind.
Und eine typische Definition sehen wir dann hier in dem Kasten.
Das heißt, die Menge aller Unterlisten,
die enthält zum einen alle Unterlisten,
indem der Kopf der aktuellen Liste drin ist und kombiniert mit allen Unterlisten vom Rest.
Mit anderen Worten, die Menge aller Unterlisten enthält entweder den Kopf
der Liste und kombiniert mit allen Unterlisten oder sie enthält den Kopf einfach nicht.
Ja, es ist vielleicht im ersten Blick etwas verwirrend,
aber es ist tatsächlich so einfach in der Definition.
Man muss sich das nur mal überlegen. Entweder ist der Kopf
eben in dieser Unterliste in jedem Element davor oder eben nicht.
Und wenn ich beides kombiniere,
mehr Möglichkeiten habe ich nicht für dieses eine Kopfelement
Schauen uns das mal so an.
Ein ganz einfaches Beispiel. Ja, eine Liste,
wo nur 1, 2 und 3 als Elemente drin sind.
Jetzt haben wir eben gerade in der rekursiven Definition gesehen,
es gibt nur diese beiden Fälle. Also entweder ist die
1 jetzt in der Unterliste mit drin oder eben nicht.
Das heißt, wir haben eine baumartige Verzweigung,
wo wir jetzt in der ersten Unterteilungsstufe das hier sagen.
Ja, wir unterteilen das in alle die, die die eins enthalten.
Und wenn die, die die 1 nicht enthalten.
Für jede dieser Fälle kommt natürlich jetzt die Rekursion ins Spiel.
Das heißt, wir müssen alle Unterlisten von diesen Fällen hier finden.
Das heißt, bei den Mid-1, da gibt es jetzt natürlich welche,
die sind mit zwei und welche, die sind ohne zwei.
Und genauso auch bei dem, wo die 1 nicht drin vorkam.
Und dann gehen wir noch eine Ebene tiefer und machen
genau wieder diesen rekursiven Aufruf. Also im Prinzip,
wie immer hier bei allen Unterlisten, wird das hier fortgepflanzt.
Wenn man hier angekommen ist, dann ist die Liste einfach leer.
Das heißt, da kann man dann tatsächlich nicht mehr weitermachen.
Aber man sieht jetzt schon,
dass wenn man sich hier durchschlängelt,
die einzelnen Teillisten entstehen,
die es gibt
In dem Fall ist es schwer zu lesen,
aber man kann es sich ja leicht auch veranschaulichen.
Hier ist die 1, die 2 und die 3 drin.
Hier ist nur die 1 und die 2 drin,
weil ihr ohne 3 steht. Mit 1 ohne 2, ohne 3,
dann bleibt nur die 1 übrig. Und genauso hier,
als letzter Fall nochmal, da ist weder die 1 drin,
weder die 2 drin, noch die 3 drin.
Das bleibt dann tatsächlich nur die leere Liste. Ja,
und durch diese rekursive Definition der Potenzmenge erkennt man jetzt eigentlich gleich,
dass es tatsächlich auch all das ist, was man hat.
Also hier kann nicht noch irgendwo etwas auftauchen,
wo ran wir jetzt nicht gedacht haben bei den drei Elementen.
Und je mehr Elemente man hat, desto tiefer oder höher,
wenn man es andersrum aufmalt, wächst dann eben dieser Baumann.
So, jetzt hatte ich ja schon angedroht,
dass wir ein bisschen mehr in Funktionen höherer Ordnung reingehen.
Es ist eigentlich gar nicht so viel schlimmer,
aber wir gucken uns mal an, was hier eigentlich passiert.
Das heißt, damit fange ich in meiner Übungsgruppe immer
auch ausgesprochen gerne an, die Abbruchbedingung.
Das ist was, da hängt man noch keinen ab
Das heißt, wenn wir die leere Liste haben,
die Menge aller Teillisten der Lehrenliste, ist die leere Liste.
Mehr gibt es da drin nicht. Da kann ich
einen Kopf wegnehmen, da kann ich hinten was wegnehmen,
da ist nichts drin. Was soll da passieren? Gut,
leider ist das hier jetzt in dem Fall nicht die Lehreliste geworden,
sondern die Liste, die die leere Liste enthält.
Das ist relativ wichtig, weil die Potenzmenge,
die besteht ja aus einer Liste, die alle möglichen Teillisten enthält.
Und wenn wir diese Struktur einbehalten wollen und hier auf die Lehrenliste treffen,
dann wäre in dem Fall ja die Lehrenliste zurückgegeben,
würde korrespondieren, es würde überhaupt keine Einträge in der Potenzmenge geben.
In dem Moment, wo wir das zurückliefern, haben wir
aber eine andere Bedeutung da drin. Das heißt nämlich,
es gibt sehr wohl einen Eintrag in der Menge aller Unterlisten.
Und das ist die Liste wieder selber, nämlich die Lehre.
Aber das war ja der einfache Fall,
deswegen schauen wir jetzt mal zum Komplizierteren rüber.
Wir haben hier jetzt erstmal die Einführung von Head und Tale,
Kopf der Liste,
Rest der Liste
Und wir machen jetzt den rekursiven Aufruf tatsächlich genauso,
wie wir ihn eigentlich auch da geschrieben haben.
Und das ist für den einfachen Fall noch relativ leicht.
Der einfache Fall steht hier vorne. Das heißt,
auf jeden Fall müssen auch die Potenzmengen der Restliste mit enthalten sein.
Also da, wo das erste Element nicht stattfindet,
das ist ganz einfach, was jetzt natürlich ein bisschen komplizierter wird,
ist, die zu finden.
Ja, also im Moment haben wir diesen Pfad hier betreten.
Wir haben das eine Element einfach weggelassen und rufen die Funktionen
dann ganz normal rekursiv wieder auf.
Dann geht es wieder los
Komplizierter ist dieser Fall. Man sieht es vielleicht hier schon an der Grafik,
denn wir müssen uns ja jetzt die Eins merken und müssen
die ja vor alles setzen, was hier am Ende rauskommt.
Das heißt, wir merken uns mal, diese Eins,
die hier kommt, mit der soll jetzt hier was passieren.
Und das Ergebnis, was da passieren soll, das wissen wir eigentlich erst,
wenn wir den Baum ganz bis zum Ende durchgelaufen sind.
Was wir jetzt aber machen können, wenn wir in so einer Situation sind,
ist, wir wissen ja, über die Rekursive Definition,
dass wir mit dem rekursiven Aufruf des Restes alle Kombinationen bekommen,
die eben für, also nicht alle Kombinationen, Entschuldigung,
alle Unterlisten bekommen, die für den Rest der Liste gelten.
Wenn wir vor diese Unterlisten, ja,
in diesem Bild hier wäre das 2, 3,
2, 3 und weil hier nichts mehr kommt,
ohne 2 unter 3, wäre das die Lehreliste.
Für den rekursiven Aufruf, wenn wir uns die jetzt
so vorstellen, als wären die schon da
Dann wäre die Lösung doch relativ einfach. Vor jedes Element
dieser Liste müssten wir jetzt nur noch das Kopfelement hängen.
Wir prüfen das mal, wenn wir die zwei,
drei als ein Listeneintrag hätten, die zwei, die drei und die Lehreliste.
Und wir könnten jetzt das Element,
was wir gerade haben, in dem Fall die Eins,
einfach davor hängen, dann hätten wir das Ziel auch erreicht.
Und genau das ist das, was dieser Ausdruck hier tut. Das heißt,
Cons erinnern wir uns noch kurz, erzeugt ein paar,
wenn wir damit Curry das erste Element auf den Kopf vorbelegen,
dann bekommen wir eine neue Funktion,
die überall den Kopf davor hängt
Ja, in dem Fall resultiert aus diesem Aufruf hier also eine Funktion,
die nur noch ein Argument hat,
die sich fast so verhält wie Cons,
aber die einfach immer das feste Element hätte,
was im Moment unser Listenkopf ist, davor hängt.
Und dieses Davorhängen wird mit Map auf alle Teillisten gemacht.
Damit das vernünftig funktioniert, muss natürlich sichergestellt sein,
dass wir am Ende auch irgendwas haben,
wo wir mit dem Cons arbeiten können.
Denn das Cons braucht er ja als Abbruchbedingung, die Lehreliste.
Erinnern uns, Listen waren so wie Cons, Cons,
Cons, Cons, Cons.
Ganz innen drin stand die Lehreliste
Ja, genau das bekommen wir aber,
weil wir irgendwann auf diese Liste hier stoßen. Das heißt,
wir mappen irgendwann den Kopf auf eine Liste,
in der nichts drin ist, und erhalten so quasi die Liste,
wo nur noch das Kopfelement drin ist. So,
das heißt, wenn man sich das jetzt mal anschaut,
bedeutet das tatsächlich nichts anderes,
als dass die Menge oder die Liste aller Unterlisten die Liste aller Unterlisten des Restes ist,
plus die Menge,
wo das erste Element noch enthalten ist,
kombiniert mit den Unterlisten,
ohne das Element
Nichts anderes steht hier. Und da sieht man schon,
wenn man dieses Map hier, ist ja auch nochmal eine lineare Rekursion,
auch nochmal selbst geschrieben hätte,
wenn man das mit dem Curry hier auch nochmal selbst geschrieben hätte.
Das ganze Programm wäre doch schon etwas explodiert.
Und was man hier auch sieht, fehlen schon mal so
ein bisschen auf die nächsten Aufgabenblätterformen der Rekursion.
Ja, ist hier auch eindeutig ein baumrekursiver Aufruf,
weil wir die Menge der Teillisten hier im Aufruf
zweimal wiederfinden
Und das erkennt man eigentlich auch schon ganz gut an dieser Darstellung.
Das ist zur Lösung wohl eine Baumrekursion benötigt wird. Die nächste,
das nächste Ziel, auf das wir hinarbeiten wollen, sind Permutation.
Und Permutation sind jetzt etwas komplexer, als die Liste aller Unterlisten,
wo man einfach Elemente wegnehmen konnte oder wieder davorstellen.
Für die Menge der Permutation müssen wir jetzt
Elemente untereinander vertauschen können
Das heißt, was wir erstmal brauchen,
ist so eine Hilfsfunktion, die die Liste aller Möglichkeiten ermittelt,
wie ich ein Element in eine andere Liste einfügen kann.
Das heißt, wie kann ich das da hineinweben?
Und da gibt es noch verschiedene Möglichkeiten.
Wir definieren das auch wieder rekursiv. Das heißt,
wenn diese Liste, in die ich das Element hineinleben möchte,
wenn die nicht leer ist,
dann kann ich mein Element natürlich einfach nach vorne setzen.
Und außerdem gibt es ja noch alle Möglichkeiten,
das Element mit dem Rest
davon zu verweben
Und dann wird der Kopf wieder mit dem verwebten Rest gesetzt.
Wir schauen uns das mal an. Wie das dann in der Implementierung aussieht,
wie gesagt, interlief für hinein, neben, relativ einfach,
aber trotzdem erkennen wir hier auch wieder etwas Komplexität der Funktionen höherer Ordnung.
Wenn die zweite Liste leer ist,
dann möchten wir einfach das Element in eine Liste tun.
Und da wir eine Liste drumherum tun,
denn dann bekommen wir genau eine Liste zurück
Die hat nur eine Teilliste und das ist das Element selber.
Ja, in dem Fall gibt es gar keine Möglichkeiten,
das Element irgendwo einzuweben, sondern die einzige Möglichkeit,
das Element einzuweben, ist eine Liste zu erstellen,
wo nur das Element selber drin ist.
Andere Möglichkeiten gibt es in dem Fall nicht. Das ist aber nur der Spezialfall,
wenn man entweder mit einer Lernliste dieses Programm aufruft oder wenn
man im rekursiven Abstieg so weit hinuntergekommen ist,
dass man die Liste YS abgearbeitet hat.
Für die anderen Fälle müssen wir den rekursiven Abstieg machen.
Auch hier ist wieder die Unterzeichteilung in Listenkopf und Listenschwanz
Wir fangen mal wieder hier an. Das heißt,
eine Möglichkeit, die einfache Möglichkeit wieder vorweg, ist,
dass wir dieses Element X einfach davor schreiben.
Das Einfachste, was man machen kann, wenn man was
in eine Liste einweben möchte. Wir schreiben es einfach davor.
Das führt dann zu diesem Ausdruck hier. Und falls das nicht der Fall ist,
erzeugen wir uns wieder eine Hilfsfunktion, die das aktuelle Kopfelement,
jetzt müssen wir ein bisschen aufpassen,
weil das nicht das Element ist, was übergeben wird,
sondern das aktuelle Kopfelement der Liste über Curry mit dem Cons verbindet.
In dem Fall,
wenn wir uns den Aufruf hier unten einfach direkt dazu nehmen,
dann wäre der erste Fall, das Erzeugen dieser Liste,
der zweite Fall würde uns eine Funktion erzeugen,
die sich das A mit dem Cons merkt,
um eben all die Teillisten zu erstellen,
die mit dem A jetzt anfangen.
Und die werden auf den rekursiven Aufruf Von dem Element,
in dem Fall eh, mit der Restliste gemacht.
Wenn man sich das jetzt anschaut,
dann ist es tatsächlich nur eine lineare Rekursion.
Und das Problem ist auch viel einfacher nachzuvollziehen in seiner Lösung.
Denn was wir hier machen, ist, wir merken uns
das A. Und dann gibt es im nächsten Fall wieder zwei Möglichkeiten.
Wir können entweder wieder das E direkt hinschreiben.
Oder wir warten mit dem E noch. Dann haben wir
uns schon das A und das B gemerkt.
Dann sind wir im nächsten Schritt, können das eh
entweder hinschreiben
Oder wir warten wieder. Und in dem Fall,
wo wir hier hinter dem C sind, landen wir tatsächlich bei dem Fall,
wo wir bei der leeren Liste sind, ja,
das heißt, hier steht dann nur noch das E,
es gibt keine andere Möglichkeit,
das wird dann über die Concess mit dem E verbunden.
Ja, das heißt,
so kann man relativ einfach und auch ziemlich kompakt so eine Verwebungsfunktion definieren,
die eben genau da immer einfügt und keine Angst, ja,
wer jetzt bei dem Map-Curry-Concet das erste Mal ein bisschen schluckt,
Als ich die Vorlesung gehört habe, ging es mir nicht anders.
Das muss man sich ein bisschen reindenken,
vielleicht auch ruhig mal ein bisschen experimentieren und schauen,
was man da so machen kann.
Wenn man das aber verstanden hat, dann hilft das einem wirklich sehr schön,
eine sehr kompakte Lösung für wirklich recht komplexe, kombinatorische Probleme zu definieren.
Genau, er hat es ja schon angedeutet,
dieses Verweben von Elementen ist in dem Fall tatsächlich nur eine Hilfe,
damit wir jetzt die Permutation einer Liste beschreiben können,
die wie gesagt alle Elemente der Liste enthält,
aber in allen möglichen Anordnungen
Und das können wir jetzt, wenn wir so eine Verwebefunktion haben,
können wir das relativ einfach wieder rekursiv definieren. Das heißt,
in dem Moment, wo wir verweben können,
können wir sagen, okay,
wenn ich alle per Mutation von dieser Liste haben möchte,
dann verwebe ich einfach den Kopf der Liste mit allen Permutationen der Restliste.
Würde ich nur den Kopf mit der Restliste verweben,
dann hätte ich schon mal einen Teil der Permutation,
wo der Kopf quasi wie bei unserem einen Beispiel
hier durch die Liste einmal durchfahren würde
Nun kann ich ja aber alle anderen Elemente auch noch beliebig vertauschen.
Das heißt, wenn ich wirklich alle Permutationen haben will,
dann ist das hier tatsächlich wichtig,
dass ich den Kopf der Liste auch mit allen Permutationen der Restliste mache.
Ansonsten fehlen da halt welche. Wenn wir uns das jetzt mal anschauen,
ja, in der Unterteilung, hier ist wieder die altbekannte Liste,
1, 2, 3 und wir gucken uns jeweils die Permutation der Restliste an.
Das geht in dem Fall ganz gut, weil die Restliste hiervon nur zwei Elemente enthält
Und zwei Elemente kann man nicht beliebig oft permutieren.
Da gibt es halt nur diese beiden Möglichkeiten.
Die Restliste kann entweder zwei, drei sein,
dann ist sie nicht permutiert, was aber auch eine gültige Permutation ist.
Oder sie ist eben vertauscht, das sehen wir hier.
Und für diese beiden Fälle müssen wir dann,
das sind die Permutationen der Restliste, müssen wir dann die Verwebung durchführen.
Das heißt, wir müssen die Eins mit diesen Permutationen verweben.
Das geht davor in der Mitte oder dahinter.
Und wir müssen die Eins mit diesen Permutationen der Restliste verweben.
Das geht genauso davor in der Mitte
Ist dahinter. Wer das nicht glaubt,
kann sich das ruhig in Ruhe nochmal angucken.
Es gibt für diese drei Elemente tatsächlich nur diese sechs Möglichkeiten.
Werner und der siebte findet, ja, ist interessant,
aber wenig hilfreich. Jetzt schauen wir mal, wie sowas
aussehen könnte in der Anordnung hier. Wir haben die Perms,
das ist jetzt einfach die Funktion, die die Permutation bezeichnen.
Und jetzt sieht man schon mit dieser Hilfsfunktion,
dieser Verwebelhilfsfunktion, wird das Ganze jetzt
unglaublich kompakt
Ja, am Anfang fangen wir wieder an,
wenn wir die Permutation von der leeren Liste machen möchten,
ja, da gibt es nur eine Permutation,
also die resultierende Liste hat nur einen Eintrag und das ist wieder die leere Liste.
Die kann man einfach nicht permutieren. Ansonsten,
hier komme ich gleich noch zu, ansonsten,
mappt man einfach, genauso wie es eigentlich in der Definition steht,
verwebe das Kopfelement mit allen Permutationen der Restliste.
Also wenn man sich jetzt diese Lesart angewöhnt,
dass das Curry hinterlief, K,
eigentlich nichts anderes bedeutet,
erstelle eine Funktion,
die das Kopfelement verwebt
Nichts anderes steht hier. Und das Map anwendet auf alle
Permutationen der Restliste. Ja, mehr ist es nicht.
Mehr ist es fast nicht, denn dadurch,
dass wir hier diese Doppellisten bekommen, bekommen wir ein strukturelles Problem.
Wenn wir diese Listen von Listen,
von Listen bekommen und die einfach alle mit Appent zusammenhängen würden,
dann hätten wir etwas,
wo wir am Ende nicht das Map drauf anwenden könnten,
weil wir eine verschachtelte Listenstruktur hätten
Wann immer man vor sowas steht und merkt, dass das Pent nicht ausreicht,
ist ein sehr guter Hinweis, mal Apply Appent zu probieren.
Weil das genau das tut, was man dann meistens möchte.
Das löst eine Listen-Hierarchie-Ebene auf. Das heißt,
all diese Einträge, die dann in den Listen stehen,
da wird im Prinzip das Apply da vorgeschrieben und das Ganze wird ausgewertet.
Das heißt, in dem Fall ist das hier essentiell notwendig,
damit uns das die Struktur garantiert,
die wir in unserer Liste brauchen,
damit das Map angewendet auf die baumartige,
also auf die rekursiven Aufruf der Restlistenpermutation
erfolgreich ist
Jetzt kommen wir schon zur ersten Anwendung von so Permutationen und solchen Dingen.
Das sogenannte Rucksack-Problem oder auch Knappsack-Problem. Da haben wir
eine Menge von Objekten vorliegen. Und diese Objekte,
die haben zum Beispiel eine unterschiedliche Größe, ein unterschiedliches Gewicht.
Jeder, der diese kleinen Holzboxen kennt,
mit so Steinchen daneben und einem kleinen roten Stein,
der fragt sich am Anfang, wie soll dieser rote Stein da reingehen?
Und manche fragen sich das am Ende auch noch.
Aber manche andere finden da einen Weg, diesen Stein da reinzusetzen.
Das ist in dem Fall ein geometrisches Problem
Also wie kriege ich eine Menge von Objekten, die genau reinpasst in etwas rein?
Was wir hier jetzt aber suchen, ist,
wir haben viel mehr Objekte zur Verfügung, als eigentlich reinpassen.
Und wie können wir die kombinieren, dass eine bestimmte Bedingung erfüllt wird?
Also zum Beispiel, wie können wir in den Rucksack packen,
dass das Grenzgewicht nicht überschritten wird?
Da müssen vielleicht ein paar Sachen draußen bleiben.
Aber wenn man eine Sache doch drin haben will,
welche anderen Sachen müssen dann draus bleiben, draußen bleiben.
Oder ein Geldschein oder irgendeinen Betrag in Kleingeld zu wechseln.
Auch da gibt es
verschiedene Möglichkeiten
Hier ist als Beispiel, jetzt mal nicht der Rucksack genommen,
wobei das im Prinzip eine analoge Problembetrachtung ist,
sondern das Geld wechseln, in dem Fall in dem englischen,
amerikanischen System vermutlich, also nicht unsere Cent,
sondern Half-Dollars, Quarters, Dimes,
Nickels und Pennys.
Und auch da streben wir jetzt wieder,
wie bei allen kombinatorischen Problemen,
eine rekursive Lösung an
Und für alle, die noch keine Weihnachtsgeschenke bekommen,
nein, ich bekomme kein Geld vom Springer-Verlag,
aber dieses Buch ist wirklich empfehlenswert, dass Ablesson in Sussman-Buch,
ja, man kann es sich mal überlegen,
es gibt auch eine aktuellere Fassung als die von 1985 mittlerweile.
Struktur und Interpretation von Computerprogrammen heißt es auf Deutsch.
Da kommt dieses Rätsel und auch diese Lösung her.
Ist eigentlich ganz nett, wenn man mehr so
ein bisschen in die algorithmische Richtung schauen möchte.
Und ich glaube auch in unserer Bibliothek vorhanden.
Also wer es sich mal anschauen möchte,
ist da
herzlich willkommen
Das Problem, so ein Geldbetrag A zu wechseln, können wir jetzt definieren,
als die Anzahl aller Möglichkeiten, den Betrag zu wechseln.
Wobei alle Münzsorten außer einer verwendet werden. Oder wir wechseln den Betrag,
der Auftritt, wenn wir die eine Münze einmal abziehen.
Das heißt, wir nehmen an, wir haben eine Menge
von Münzen, jedenfalls sind so nahezu unendlich viele,
die reichen uns auf jeden Fall aus. Und entweder,
nehmen wir mal an, wir sollen 50 Dollar wechseln,
entweder Nehmen wir jetzt den Half-Dollar und geben den raus
und rufen das Problem dann rekursiv auf mit 49 Dollar 50.
Oder wir verwenden den erstmal nicht. Und wechseln dann den Restbetrag,
die 50 mit denen hier. Was hier noch so ein bisschen eingeführt wird,
sind so ein paar Extrafunktionen, die für uns nicht so wichtig sind,
die zum Beispiel noch Namen definieren für Half-Dollar, Quarter,
Daim, Nickel und Cent.
Für uns relativ wichtig ist die Anzahl, ja,
die wir ja rausbekommen möchten. Und wir schauen mal,
wie dieses CC funktioniert, denn das ist ja das,
was am Ende aufgerufen wird, wenn wir Count-Change von einem Amount machen.
Und damit diese Funktion jetzt ein bisschen genereller ist, ja,
man muss nicht übergeneralisieren, aber bei dieser Funktion ist es ganz schön,
dass man nicht angewiesen ist auf eine spezielle Menge Geld von einer von einem Land,
sondern was man direkt übergeben kann, ist hier diese Denominations,
also was habe ich für Werte?
Ja, und die sind immer so aufgebaut,
dass wir eine Liste haben, in dem jeder Wert definiert ist,
als wieder eine Liste, in der nichts anderes drinsteht,
als ein Name und ein Betrag.
Jetzt schauen wir uns mal an, wie das Ganze funktioniert.
Wie gesagt, wann immer hier Teenominations oder DS steht,
bezieht sich das auf die obere Liste.
Und es ist relativ einfach, da die Möglichkeiten auszuzählen.
Wenn ich, wenn der Betrag null ist,
dann kann ich den genau einmal exceln.
Das heißt jetzt, Wieso das? Wenn der Betrag null ist,
wie kann ich den wechseln? Dafür muss man sich das
rekursive Schema eigentlich erstmal zu guter tun. Denn was wir ja wissen,
was hier noch gar nicht mit drinsteht in der rekursiven Definition,
ist, dass ein Geldbetrag, wenn er gewechselt wird,
also zumindest für vernünftige Kunden, ja nur dann zulässig gewechselt wird,
wenn das auch aufgeht am Ende.
Also wenn man da irgendwie dann nur so 50 Cent für
seine 50 Dollar bekommt und der andere kann ja nicht mehr,
das wäre ein bisschen schade. Deswegen wäre es gut,
wenn der aufgeht und deswegen heißt das hier im rekursiven Abschluss nicht,
dass man das Ganze irgendwie wechseln kann, ja,
sondern man kann die Null natürlich wechseln, ja,
da kriegt man auch nichts zurück.
Aber dieses hier ist im Prinzip die Überprüfung,
dass der rekursive Aufruf, denn wir sehen ja,
hier wird zum Beispiel der Wert von einer Münze abgezogen in einem rekursiven Aufruf,
dass der am Ende bei Null landet, ja.
Und nur wenn das passiert, dann dürfen wir auch einen hochzählen,
ja, nur wenn wir so eine gültige Umtauschaktion rekursiv gefunden haben,
nur dann zählen wir das.
Wenn ich gar nichts habe, was ich wechseln kann,
dann kann ich leider auch gar nichts wechseln.
Und wenn ich am Ende bei irgendetwas lande,
wo der Betrag kleiner Null ist, Auch sowas funktioniert.
Man überlege sich nur, ich habe noch einen Cent übrig,
den ich tauschen kann. Und ich möchte dem dann im rekursiven Aufruf,
ja, da gibt es ja alle Möglichkeiten, mit 50 Cent aushelfen.
Dann lande ich hier bei minus 49 Cent.
Das ist irgendwie auch kein gutes Geschäft. Ja,
deswegen soll das auch nicht als gültig erachtet werden.
Also all das hier sind rekursive Abbrüche, wobei insbesondere diese
und diese hier die Kontrollbedingungen eines gültigen Wechsels darstellen.
Jetzt muss man natürlich nur noch an die Münzen und deren
Werte kommen
Und dann das rekursive Schema vervollständigen. Das heißt,
das, was man zum ersten Mal tut, ist,
dass man sich den ersten Coin dieser Liste raussucht,
das ist in dem Fall K von DS,
wer hat der erste Eintrag, wenn wir das mit der vollständigen Wechselmenge hätten,
wäre das dieses hier.
Und da möchte man gerne an den zweiten Antrag,
da jetzt keine Paare drin stehen, sondern wieder Teillisten,
bleibt uns nichts anderes übrig, als CADR zu nehmen,
tatsächlich an das zweite Element zu kommen.
Wenn das hier Dotted Pairs gewesen wären,
hätte man auch CDR nehmen können,
aber so stimmt es schon
Und dann gibt es natürlich noch die anderen Münzen,
die man jetzt für diesen Wechselschritt nicht berücksichtigen möchte,
die ergeben sich einfach aus dem Rest,
also all das, wo der Half-Dollar nicht mit auftaucht.
Wenn man jetzt die Gesamtzahl der Möglichkeiten errechnen möchte,
dann gibt es genau diesen Aufruf,
den wir ja vorher schon beschrieben hatten in der Analyse
und in der Struktur der rekursiven Unterteilung.
Also entweder tauscht man jetzt schon ein bisschen zurück,
im allerersten Aufruf zum Beispiel zieht man schon die 50 ab.
Die sind übrigens alle
auf Cent normiert
Wer sich da wundert, das sind keine Dollar,
sondern es ist alles hier im Cent-Bereich normiert.
Das heißt, wenn man 10 Dollar da reintut,
müsste man auch 1000 eintragen. Ja, zieht den ab
und wechselt dann wieder fröhlich mit allem, was man hat.
Oder, und das war ja die Alternative,
man nimmt den Betrag so, wie er ist, und versucht,
ihn unter zu hilfenahme all dieser Verbleibenden Restmünzen zu wechseln.
Wenn man sich das überlegt, dann sieht man natürlich,
dass es von der Möglichkeit einen Betrag mit allen Münzen zu wechseln,
auch alle anderen Kombinationen gibt. Ich kann jedes Mal wieder versuchen,
mit dem AfDollar zu wechseln, ich irgendeinen Dollar-Betrag habe,
der auf einen halben Dollar genau aufgeht, kann das auch funktionieren.
Und trotzdem kann ich auch diesen rekursiven Abstieg mit diesen Othercoins
ewig weitertun und alles als Cent zurückgeben. Ja,
das ist dann nicht so schön, zumindest nicht fürs Portemonnaie,
aber auch das sind ja durchaus gültige Lösungen.
Und deswegen sieht man hier schon, wenn man das Ganze jetzt zusammenzählt,
nichts anderes tut das hier, über das Plus,
was die mögliche oder wie viele Möglichkeiten es schon gibt mit diesen Paarmünzen,
ein Dollar zu wechseln, weil das eben alles ein Cent angegeben wird,
dann sieht man, das sind schon
2092. Aber wir wissen ja auch,
das ist ja schon wirklich enorm viele Lösungen geben muss,
weil er mit diesem einen Cent ja hier mit drin haben,
der ja wirklich was sehr feinstrukturiertes ist.
Und insofern da nicht wundern, dass solche hohen Werte rauskommen,
auch was typisches, was man bei Permutation und ähnlichen kombinatorischen Problemen oft bekommt. Das heißt,
schon bei dieser vermeintlich geringen Menge von 1, 2,
3, 4, 5 Dingen, die ich habe,
und einer vermeintlich kleinen Menge Geld von einem Dollar,
lande ich in der Komplexität schon bei etwas,
was über 200, 209, also in dem Fall genau,
292 Möglichkeiten erreicht.
Und je größer diese Menge und auch je größer das Geld,
was man tauschen möchte, desto mehr explodieren diese Zahlen.
Und dieses Explodieren der Zahlen, das ist was,
wo wir, beziehungsweise hoffentlich Leonie,
wenn sie nächste Woche wieder da ist, ansetzen werden.
Denn für ganz allgemeine Probleme oder allgemeine Rätsel
ist es irgendwann nicht mehr praktikabel,
wirklich erstmal alle Möglichkeiten aufzustellen und dann in den Möglichkeiten nach
den Richtigen
zu suchen
Ja, weil das einfach zu viele werden. Das nennt man auch so eine exponentielle,
also so eine, ja, Explosion des Problemraums.
Das heißt, wenn dieser Suchraum einfach zu groß wird,
um ihn einmal komplett aufzubauen, dann muss man sich andere Strategien überlegen.
Und eine dieser Strategien, die das nächste Mal vorgestellt werden wird,
ist das sogenannte Rückzugsverfahren oder auch Backtracking.
Da versucht man erstmal so weit zu kommen mit einer Lösung, wie es geht.
Und immer, wenn man merkt, dass man nicht mehr weiterkommt,
dann tritt man einen Schritt zurück und probiert es wieder.
Es hat natürlich den Vorteil,
dass man sich quasi mit so einer Art Tiefensuche durch den Problembaum bewegt,
hat aber auch den Nachteil,
dass man natürlich irgendwo die Zustände speichern muss,
zu dem man wieder zurückkommt.
Und genau das wird das nächste Mal dran kommen.
Da bedanke ich mich für heute fürs Zuhören und nächste Woche
hoffentlich wieder mit Leonie hier