01020304050607080910111213141516171819202122232425

Advent of Code

2023/20

Pulse Propagation

in C#

by encse

The task description is copyrighted, but it's available here.

I modeled Part 1 following the description closely. I didn't want to introduce separate classes for the gate types, instead created just one Gate type with a function parameter that defines the inner logic. It basically tells what should be emitted when a signal comes in the gate's input.

Building on this, I defined factory functions for each gate type (Nand, FlipFlop and Repeater). I know that this is Elf logic, but it's 🎄, what did you expect?

I added a function that triggers the button and executes all the logic until things settle down. It returns all signals that were emitted, so that I can work with them in both parts.

I think Part 1 doesn't need more explanation. Part 2 however, is a different beast. It's a reverse engineering problem. We need to tell how many times the button is to be pressed until a single high value is emitted to the rx gate. The catch is that we need to understand a bit what's happening, because just blindly pressing the button will not terminate in a reasonable time.

I layed out the graph using Graphviz to see what's going on. This immediately showed that broadcaster feeds four different subgraphs. These work in isolation and a Nand gate connects their output into rx. Further investigation shows that each subgraph runs in a loop that has prime length (at least for my input). We just need to multiply them to solve the second half of the problem.

namespace AdventOfCode.Y2023.Day20;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Signal = (string sender, string receiver, bool value);

record Gate(string[] inputs, Func<Signal, IEnumerable<Signal>> handle);

[ProblemName("Pulse Propagation")]
class Solution : Solver {

    public object PartOne(string input) {
        var gates = ParseGates(input);
        var values = (
            from _ in Enumerable.Range(0, 1000)
            from signal in Trigger(gates)
            select signal.value
        ).ToArray();
        return values.Count(v => v) * values.Count(v => !v);
    }

    public object PartTwo(string input) {
        // The input has a special structure. Broadcaster feeds 4 disconnected 
        // substructures which are channeled into a single nand gate at the end. 
        // The nand gate is connected into rx. I checked that the substructures 
        // work in a loop, that has prime length. Just need to multiply them all.
        var gates = ParseGates(input);
        var nand = gates["rx"].inputs.Single();
        var branches = gates[nand].inputs;
        return branches.Aggregate(1L, (m, branch) => m * LoopLength(input, branch));
    }

    int LoopLength(string input, string output) {
        var gates = ParseGates(input);
        for (var i = 1; ; i++) {
            var signals = Trigger(gates);
            if (signals.Any(s => s.sender == output && s.value)) {
                return i;
            }
        }
    }

    // emits a button press, executes until things settle down and returns 
    // all signals for investigation.
    IEnumerable<Signal> Trigger(Dictionary<string, Gate> gates) {
        var q = new Queue<Signal>();
        q.Enqueue(new Signal("button", "broadcaster", false));

        while (q.TryDequeue(out var signal)) {
            yield return signal;

            var handler = gates[signal.receiver];
            foreach (var signalT in handler.handle(signal)) {
                q.Enqueue(signalT);
            }
        }
    }

    Dictionary<string, Gate> ParseGates(string input) {
        input += "\nrx ->"; // an extra rule for rx with no output

        var descriptions =
            from line in input.Split('\n')
            let words = Regex.Matches(line, "\\w+").Select(m => m.Value).ToArray()
            select (kind: line[0], name: words.First(), outputs: words[1..]);

        var inputs = (string name) => (
            from d in descriptions where d.outputs.Contains(name) select d.name
        ).ToArray();
        
        return descriptions.ToDictionary(
            d => d.name,
            d => d.kind switch {
                '&' => NandGate(d.name, inputs(d.name), d.outputs),
                '%' => FlipFlop(d.name, inputs(d.name), d.outputs),
                _ => Repeater(d.name, inputs(d.name), d.outputs)
            }
        );
    }

    Gate NandGate(string name, string[] inputs, string[] outputs) {
        // initially assign low value for each input:
        var state = inputs.ToDictionary(input => input, _ => false);

        return new Gate(inputs, (Signal signal) => {
            state[signal.sender] = signal.value;
            var value = !state.Values.All(b => b);
            return outputs.Select(o => new Signal(name, o, value));
        });
    }

    Gate FlipFlop(string name, string[] inputs, string[] outputs) {
        var state = false;

        return new Gate(inputs, (Signal signal) => {
            if (!signal.value) {
                state = !state;
                return outputs.Select(o => new Signal(name, o, state));
            } else {
                return [];
            }
        });
    }

    Gate Repeater(string name, string[] inputs, string[] outputs) {
        return new Gate(inputs, (Signal s) =>
            from o in outputs select new Signal(name, o, s.value)
        );
    }
}

Please ☆ my repo if you like it!

© 2025 Advent of Code is a registered trademark in the US Images provided by Bing image creator