Blog

Advent of Code, Tag 23: Das Netzwerk ist zuverlässig

Adé Mochtar

Aktualisiert Oktober 21, 2025
8 Minuten

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 ASCII-Zeichen als Ein- und Ausgabe akzeptiert und ausgibt. Zwei Tage später erhalten wir ein IntCode-Programm zur Steuerung von Drohnen, mit denen wir überprüfen können, ob wir uns in Reichweite eines Traktorstrahls befinden. Und an Tag 21 gibt es ein weiteres ASCII-basiertes Programm, das die Lücken anzeigt, über die ein Springdroid springen muss. All die Jahre, in denen ich Super Mario gespielt habe, zahlen sich endlich aus.

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 Schlüsselwort, aber jetzt auf der rechten Seite eines Ausdrucks. Wenn eine Coroutine auf eine dieser 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

Verfasst von

Adé Mochtar

Contact

Let’s discuss how we can support your journey.