1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! # Pulse Propagation
//!
//! Similar to [`Day 8`] the input has a very specific structure. The flip-flips form 4 rows
//! of 12 columns, following by 2 conjunctions (in square brackets):
//!
//! ```none
//!            / aa ab ac ad ae af ag ah ai aj aj al [ax] [ay] \
//!           /  ba bb bc bd be bf bg bh bi bj bj bl [bx] [by]  \
//!     () - ()                                                 [zz] -> [rx]
//!           \  ca cb dc cd ce cf cg ch ci cj cj cl [cx] [cy]  /
//!            \ da db dc dd de df dg dh di dj dj dl [dx] [dy] /
//! ```
//!
//! The penultimate conjuction in each row, for example `ax` both takes input and delivers output
//! to the flips flops. This follows a pattern, for example using `v` to indicate input from the
//! conjunction and `v` to indicate output:
//!
//! ```none
//!     v     v        v              v
//!     aa ab ac ad ae af ag ah ai aj aj al
//!     v  v     v  v     v  v  v   v     v
//! ```
//!
//! The flip flops form a binary counter. When the counter reaches a specific value the conjunction
//! will pulse low and reset the counter to zero. When all 4 counters hit their limit at the
//! same time then a low pulse will be sent to `rx`. The answer is the
//! [LCM](https://en.wikipedia.org/wiki/Least_common_multiple) of the 4 limit values.
//!  For my input the numbers were co-prime so the LCM simplified to a product.
//!
//! For part one, as long as all numbers are greater than 1000, then the counting pulses follow
//! a predictable pattern that we can calculate with some bitwise logic.
//!
//! [`Day 8`]: crate::year2023::day08
use crate::util::hash::*;

type Input = [u32; 4];

pub fn parse(input: &str) -> Input {
    // Build the graph
    let mut node = FastMap::with_capacity(100);
    let mut kind = FastMap::with_capacity(100);

    for line in input.lines() {
        let mut tokens = line.split(|c: char| !c.is_ascii_lowercase()).filter(|s| !s.is_empty());

        let key = tokens.next().unwrap();
        let children: Vec<_> = tokens.collect();

        node.insert(key, children);
        kind.insert(key, !line.starts_with('&'));
    }

    // Follow the nodes from the broadcaster node building each binary number.
    let mut todo = Vec::new();
    let mut numbers = Vec::new();

    for &start in &node["broadcaster"] {
        todo.push((start, 0, 1));
    }

    while let Some((key, mut value, bit)) = todo.pop() {
        let children = &node[key];

        if let Some(next) = children.iter().find(|&&k| kind[k]) {
            if children.len() == 2 {
                value |= bit;
            }
            todo.push((next, value, bit << 1));
        } else {
            numbers.push(value | bit);
        }
    }

    numbers.try_into().unwrap()
}

/// Use bitwise logic to count pulses.
pub fn part1(input: &Input) -> u32 {
    // Counting only works correctly if there are no resets from 1 to 1000
    // so that we can assume all rows increment exactly the same.
    assert!(input.iter().all(|&n| n > 1000));

    // Each conjunction feeds back into the chained flip-flops in the inverse pattern
    // to the flip-flops feeding into the conjunction, except for the least significant
    // flip-flop which is always set. Thus the total is 12 - count_ones + 1.
    let pairs: Vec<_> = input.iter().map(|n| (n, 13 - n.count_ones())).collect();

    // The button and broadcaster contribute 5 low pulses each press.
    let mut low = 5000;
    let mut high = 0;

    for n in 0..1000 {
        // Flip flop changing from off to on emits a high pulse.
        let rising: u32 = !n & (n + 1);
        high += 4 * rising.count_ones();

        // Flip flop changing from on to off emits a low pulse.
        let falling: u32 = n & !(n + 1);
        low += 4 * falling.count_ones();

        for &(number, feedback) in &pairs {
            // Factor is the number of high pulses sent to the conjunction.
            // For each pulse the conjunction feeds a high pulse back to "feedback" flip flops.
            // In addition the penultimate conjunction in each row receives "factor" high pulses,
            // resulting in "factor" low pulses the final conjunction and finally "factor" high
            // pulses to "rx".
            let factor = (rising & number).count_ones();
            high += factor * (feedback + 3);
            low += factor;

            // Factor is the number of low pulses sent to the conjunction.
            // For each pulse the conjunction feeds a high pulse back to "feedback" flip flops.
            // In addition the penultimate conjunction in each row receives "factor" high pulses,
            // resulting in "factor" low pulses the final conjunction and finally "factor" high
            // pulses to "rx".
            let factor = (falling & number).count_ones();
            high += factor * (feedback + 2);
            low += 2 * factor;
        }
    }

    low * high
}

/// Assume all numbers are prime (or co-prime) so that the LCM is equal to the product.
pub fn part2(input: &Input) -> u64 {
    input.iter().map(|&n| n as u64).product()
}