2023 AoC Day 20 – Pulse Propagation

This is a solution to Advent of Code 2023 day 20, written in Raku.

https://adventofcode.com/2023/day/20

Part One

Consult your module configuration; determine the number of low pulses and high pulses that would be sent after pushing the button 1000 times, waiting for all pulses to be fully handled after each push of the button. What do you get if you multiply the total number of low pulses sent by the total number of high pulses sent?

enum State <Low High>;

class Tx {
    has $.name;
    has @.dests;
    method send(State $s, $from, @q, $i) {
        for @!dests -> $d {
            @q.push($d => ($!name, $s));
        }
    }
    method gist { "  Tx {$!name} -> {@!dests}" }
}

class FlipFlop is Tx {
    has $.state is rw = Low;
    method send(State $s, $from, @q, $i) {
        return if $s == High;
        $!state = $!state == Low ?? High !! Low;
        nextwith($!state, $from, @q, $i);
    }
    method gist { "Flip {self.name} -> {self.dests}" }
}

class Con is Tx {
    has %.inputs;
    method send(State $s, $from, @q, $i) {
        # for part 2
        take $i if self.name eq 'qt' and $s == High;

        %!inputs{$from} = $s;
        my $inv = all(%!inputs.values) == High ?? Low !! High;
        nextwith($inv, $from, @q, $i);
    }
    method gist { " Con {self.name} -> {self.dests} [{%!inputs.keys}]" }
}

my %modules = '20-input.txt'.IO.lines.map(
    -> $line {
        my $m = $line.match(
            / $<kind>=(<[%&]>)? $<name>=(\w+) ' -> ' $<dests>=(\w+)+ % ', ' $/);
        my $name = $<name>.Str;
        my @dests = $m<dests>>>.Str;

        $name => do given ($m<kind> // '').Str {
            when '%' { FlipFlop.new(:$name, :@dests) }
            when '&' { Con.new(:$name, :@dests) }
            default { Tx.new(:$name, :@dests) }
        }
    });

for %modules.values -> $m {
    for $m.dests -> $d {
        if %modules{$d}:exists {
            my $dm = %modules{$d};
            if $dm.isa(Con) {
                $dm.inputs{$m.name} = Low;
            }
        }
    }
}

my $total-low = 0;
my $total-high = 0;

sub press(@q is copy, $i) {
    while @q {
        my @next;
        for @q -> $message {
            if $message.value[1] == Low {
                $total-low += 1;
            } else {
                $total-high += 1;
            }

            next if %modules{$message.key}:!exists;
            my $m = %modules{$message.key};

            $m.send($message.value[1], $message.value[0], @next, $i);
        }
        @q = @next;
    }
}

my @q = 'broadcaster' => ('button', Low);
my @presses = gather press(@q, $_) for 1..1000;
say "Part 1 - {$total-low} * {$total-high} = {$total-low * $total-high}";

@presses.append: gather press(@q, $_) for 1000^..^5000;
say "Part 2 - " ~ [lcm] @presses;

say "Took " ~ (now - ENTER now).base(10,2) ~ " seconds";
Part 1 - 18771 * 46872 = 879834312
Part 2 - 243037165713371
Took 1.99 seconds
raku