Wir haben es fast geschafft, es fehlen nur noch zwei Tage. Das gestrige Rätsel war ein schwieriges. Es ist sogar das erste, das ich an dem Tag, an dem ich es begonnen habe, nicht beendet habe. Noch schlimmer ist, dass ich meinen zweiten Stern noch nicht bekommen habe. Ich schätze, meine modulare Arithmetik muss noch etwas aufgefrischt werden... Deshalb war ich froh, heute ein einfacheres Rätsel zu sehen. Und es war ein weiteres IntCode-Rätsel, bereits das 11. in diesem Jahr.
Nachdem wir unser Schiff repariert haben, müssen wir nun das Netzwerk von Grund auf neu aufbauen. Es gibt 50 Computer, die alle das IntCode-Programm ausführen. Sie senden sich gegenseitig Pakete mit den Output- und Input-Anweisungen. Das kommt Ihnen doch bekannt vor, oder? Wir haben bereits 5 Ampere miteinander verbunden. Der Clou ist jedoch, dass jeder Computer an jeden anderen Computer senden kann und, was noch wichtiger ist, die Ein- und Ausgänge nicht blockiert sind. Wie sieht es also mit meiner IntCode-Implementierung aus?
Es ist alles miteinander verbunden
Wie jedes Jahr habe ich Python gewählt, um meine Lösungen zu implementieren. Für mich besteht der Spaß an Advent of Code darin, herauszufinden, wie man die Rätsel lösen kann, und nicht darin, eine effiziente Schnittmenge von Mengen zu implementieren oder eine weitere BFS-Suche durch ein Labyrinth durchzuführen. Während ich also bei meiner täglichen Arbeit viel mit Go und Bash arbeite, bevorzuge ich für AoC etwas Ausdrucksstärkeres. Und es ist sehr praktisch, dass Bibliotheken wie networkx, numpy und itertools zur Verfügung stehen.
Meine anfängliche IntCode-Implementierung für Tag 2 war schnell und schmutzig, indem ich über die Ganzzahlen iterierte und die Opcodes nach und nach verarbeitete. Das hat die Aufgabe gut gelöst. Natürlich hätte ich nie gedacht, dass ich das Ding noch mindestens 10 Mal brauchen würde.
Der nächste Schritt: Tag 5. Damit wurden einige weitere Opcodes, Eingänge, Ausgänge und Parametermodi (Positions- vs. Sofortmodus) hinzugefügt, wobei ausdrücklich darauf hingewiesen wurde, dass Schreibvorgänge niemals im Sofortmodus erfolgen würden. Natürlich hat mein Verstand das in "Schreibvorgänge werden immer im Positionsmodus sein" übersetzt. Eingaben sind statisch, sie können also fest kodiert werden. Und Ausgaben können einfach in einer Liste gesammelt und nach Abschluss des Programms gedruckt werden. So weit, so gut.
Generatoren als Retter in der Not
Dann kam Tag 7. Jetzt haben wir fünf Verstärker, auf denen IntCode-Programme laufen. Der Ausgang von Verstärker 1 muss mit dem Eingang von Verstärker 2 verbunden werden, 2 mit 3, usw. Für Teil 1 war das kein Problem, da jeder Verstärker auf die Fertigstellung des vorherigen wartet. Teil 2 hatte jedoch eine Rückkopplungsschleife. Die Berechnung erfolgte immer noch sequentiell, aber der Ausgang von Verstärker 5 musste mit Verstärker 1 verbunden werden. Das bedeutet, dass wir die Berechnung von 4 der 5 Verstärker unterbrechen müssen, während wir den Ausgang des fünften berechnen. Meine erste Reaktion war, ich hätte das in Go machen sollen, dann hätte ich einfach Kanäle und Goroutinen verwenden können. Dann erinnerte ich mich daran, dass Python auch einen netten Trick im Ärmel hat: Generatoren.
Generatoren, die in PEP 255 eingeführt wurden, sind eine besondere Art von Funktionen, die einen Lazy Iterator zurückgeben. Das heißt, Sie können über die Rückgabewerte iterieren, wie Sie es bei einer Liste tun. Aber im Gegensatz zu Listen speichern sie nicht den gesamten Inhalt im Speicher, sondern nur den Zustand des Generators. Außerdem machen sie eine Pause, nachdem sie den Wert next() erhalten haben, so dass sie ideal für unseren Anwendungsfall der Verstärker sind.
Das Schreiben von Generatoren ist erstaunlich einfach. Definieren Sie einfach eine Funktion, wie Sie es normalerweise tun würden, aber anstatt returneinen Wert zu geben, schreiben Sie yield. Um ein einfaches Beispiel zu geben, ist dies eine Implementierung der Fibonacci-Folge:
>>> def fib():
a, b = 0, 1
while True:
yield b
a, b = b, a+b
>>> f = fib()
>>> next(f)
1
>>> next(f)
1
>>> next(f)
2
Diese Funktion erzeugt jedes Mal, wenn next() aufgerufen wird, eine neue Fibonacci-Zahl.
Wir können also Generatoren für die Ausgabe des Amps-Signals verwenden. Die Ausgänge werden also mit yield Anweisungen bereitgestellt, während wir für die Eingänge weiterhin eine Liste verwenden können , die von Amps gemeinsam genutzt wird. Problem gelöst!
6:00 AM
An Tag 9 wurde die letzte Funktion unseres IntCode-Computers vorgestellt: der relative Modus. Im relativen Modus verhalten sich die Parameter ähnlich wie im Positionsmodus, mit dem Unterschied, dass die Position, die sie verwenden, um eine relative Basis versetzt ist. Kinderleicht, nicht wahr? Fügen Sie einfach eine Variable für die relative Basis hinzu und verwenden Sie diese beim Lesen der Parameter. Kein Problem, die mitgelieferten Beispiele funktionieren alle. Jetzt muss ich sie nur noch mit meiner Eingabe ausführen und die Ausgabe übermitteln: [203, 0].
Hmm... das scheint nicht richtig zu sein, es sollte ein einzelner Wert sein. Spulen Sie 3 Stunden vor. Nach einer Diskussion auf Slack mit mehreren Kollegen - die alle dasselbe Problem zu haben scheinen - habe ich das Problem gefunden. Erinnern Sie sich noch an meinen Gedankenspeicher "Schreibvorgänge werden immer im Positionsmodus durchgeführt"? Es hat sich herausgestellt, dass der relative Modus auch für Schreibvorgänge gilt... Genau wie es im Text des Rätsels deutlich steht. Lesen ist schwer, besonders um 6:00 Uhr morgens. Noch dazu, weil im Text auch steht, dass das IntCode-Programm Opcodes ausgibt, die nicht richtig zu funktionieren scheinen. Auch das habe ich völlig übersehen.
IntCode all die Dinge
Nach Tag 9 ist der IntCode Computer nun vollständig. Das heißt aber nicht, dass er in den folgenden Tagen nicht benutzt wird. Im Gegenteil, jeden zweiten Tag wird uns ein anderes IntCode-Programm präsentiert. An Tag 11 gibt es einen Malroboter, der das Rufzeichen unseres Schiffes druckt. An Tag 13 gibt es ein voll funktionsfähiges Arkanoid-Spiel (oder Breakout, je nachdem, wie alt Sie sind) mit etwas mehr als 2000 Anweisungen.
Tag 15 enthält die Eingabe für einen Reparaturdroiden, der schließlich ein Labyrinth drucken wird, das wir durchqueren müssen (juhu networkx). Gefolgt von einem Staubsaugerroboter, der an Tag 17
Nicht aufhören
Zurück zu Tag 23. Wir haben 50 IntCode-Computer, die parallel laufen und sich gegenseitig Pakete mit nicht-blockierender E/A senden. Es hat sich herausgestellt, dass die 50 Computer nicht wirklich parallel laufen müssen, die Iteration über sie funktioniert einwandfrei. Die Generator-basierte Implementierung für die Ausgaben funktioniert immer noch gut. Aber für die Eingaben brauchten wir etwas anderes. Glücklicherweise hat Python ein Gegenstück zu Generatoren: Coroutines.
Einfache Koroutinen werden in PEP 342 eingeführt. Sie verwenden das gleiche yield Anweisungen stößt, wartet sie, bis sie einen Wert über einen send(value) Aufruf erhält. Um ein kleines Beispiel zu geben:
>>> def grep(pattern):
print("Looking for", pattern)
while True:
line = (yield)
if pattern in line:
print(line)
>>> g = grep("python")
>>> g.send(None) # Start he coroutine
Looking for python
>>> g.send("Yeah, but no, but yeah, but no")
>>> g.send("A series of tubes")
>>> g.send("python generators rock!")
python generators rock!
Wenn Sie mehr über Generatoren und Coroutines erfahren möchten, lesen Sie mehr in diesem ausgezeichneten Blogbeitrag.
Meine derzeitige (endgültige?) IntCode-Implementierung verwendet nun also sowohl Generatoren als auch Coroutines, um Ausgaben bereitzustellen und Eingaben zu lesen. Es bleibt also nur noch abzuwarten, was Eric Wastl in den Tagen 24 und 25 für uns geplant hat. Vielleicht eine letzte Ergänzung zu unserem IntCode-Computer? Halten Sie mich jetzt nicht auf, denn ich amüsiere mich prächtig!
Advent des Codes 2019 Serie
- Advent of Code 2019 hat begonnen - Machen Sie mit!
- Advent of Code 2019 - Tag 2
- Advent des Codes: Wie Excel mir den Tag rettete und auch meinem Sohn den Tag rettete
- Advent des Codes - Tag 4: Visualisieren
- Advent des Codes - Tag 5: Saintaerkla2s
- Advent of Code - Tag 6: Wie ich von AoC begeistert wurde
- Advent of Code - Tag 7: Teilen Sie Ihren Arbeitsablauf
- Advent of Code - Tag 8: Wie einfache Dinge für manche Menschen sehr schwierig sein können
- Advent of Code - Tag 9: Wie ich anfing, Spaß am Lösen von Programmierrätseln zu haben
- Advent of Code - Tag 10: Weltraum-Cowboys schießen Pixel in den Himmel
- Advent des Codes - Tag 11: Sein oder nicht sein
- Advent of Code - Tag 12: Nach dem Mond schießen
- Advent des Codes - Tag 14: Kettenreaktion
- Advent des Codes - Tag 16: Flüche
- Advent of Code - Tag 17: Vakuumieren eines Gerüsts mit dem Intcode-Programm
- Wie kleine grüne Männchen mir geholfen haben, ein Rätsel zu lösen
- Advent of Code - Tag 20: Ein bisschen Überarbeitung
- Advent of Code - Tag 21: Es ist ein Marathon, kein Sprint
- Advent of Code - Tag 22: Mischen von Karten bis in alle Ewigkeit
- Advent of Code - Tag 23: Das Netzwerk ist zuverlässig
- Advent of Code - Tag 24 & 25: Unkonventionell denken
Verfasst von
Adé Mochtar
Contact



