Skip to main content

advent_of_code/year2020/
day13.rs

1use crate::input::Input;
2
3#[derive(Copy, Clone)]
4struct ExtendedEuclidResult {
5    gcd: i128,
6    x: i128,
7    y: i128,
8}
9
10/// Given the integers `a` and `b`, compute:
11///
12/// - `gcd`, the greatest common divisor of `a` and `b`.
13/// - Two integers `x` and `y` such that `gcd = a * x + b * y`, called the "Bézout coefficients".
14///
15/// See <https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm/>
16/// and <https://cp-algorithms.com/algebra/extended-euclid-algorithm.html#toc-tgt-0/>
17fn extended_euclid(a: i128, b: i128) -> ExtendedEuclidResult {
18    if b == 0 {
19        ExtendedEuclidResult { gcd: a, x: 1, y: 0 }
20    } else {
21        let next = extended_euclid(b, a % b);
22        // We have found the coefficients (x', y') for (b, a % b):
23        //     b * x' + (a % b) * y' = gcd             [1]
24        // We want to find values for (x, y) in:
25        //     a * x + b * y = gcd                     [2]
26        // Since `a % b` can be written as:
27        //     a % b = a - (a / b) * b                 [3]
28        // we can rewrite [1] as:
29        //     b * x' + (a - (a / b) * b) * y' = gcd   [4]
30        // which means that for [2] and [4] to be equal:
31        //     x = y'
32        //     y = x' - (a / b) y'
33        ExtendedEuclidResult {
34            gcd: next.gcd,
35            x: next.y,
36            y: next.x - (a / b) * next.y,
37        }
38    }
39}
40
41/// Find the modular multiplicative inverse of the integer `a` with respect to modulo `m`
42/// if and only if `a` and `m` are coprime (the only positive integer dividing both are 1).
43///
44/// That is, the value `x` returned makes `(a * x) % m` equal `1`.
45fn modular_multiplicative_inverse(a: i128, m: i128) -> Option<i128> {
46    // See https://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Extended_Euclidean_algorithm
47    // The extended Euclidean algorithm gives us `x` and `y` such that:
48    //     a * x + m * y = gcd(a, m)
49    // Since gcd(a, m) is 1 when a and m are coprime, this can be rewritten as:
50    //     a * x + m * y = 1
51    // Dividing both sides with m:
52    //     (a * x) / m = 1 / m
53    // Which means that
54    //     (a * x) % m = 1
55    // So that x is the searched after modular multiplicative inverse, which
56    // we finally need to make positive if necessary.
57    let ExtendedEuclidResult { gcd, x, .. } = extended_euclid(a, m);
58    if gcd == 1 {
59        if x > 0 { Some(x) } else { Some(x + m) }
60    } else {
61        None
62    }
63}
64
65/// Compute X where X fulfill:
66///   remainders[i] == T % divisors[i]
67/// for all i.
68///
69/// See <https://en.wikipedia.org/wiki/Chinese_remainder_theorem/>
70/// and <https://www.youtube.com/watch?v=ru7mWZJlRQg/>.
71fn chinese_remainder(remainders: &[i128], divisors: &[i128]) -> Option<i128> {
72    // Start by multiplying all divisors together, to facilitate obtaining
73    // other_divisors_multiplied in the loop below:
74    let all_divisors_multiplied = divisors.iter().product::<i128>();
75    if all_divisors_multiplied == 0 {
76        return None;
77    }
78
79    // Consider T split into a sum:
80    //   T = value[0] + value[1] + ...
81    // We want to calculate each term, value[i] to satify
82    //   remainders[i] = value[i] % divisors[i]
83    // locally without having to care about other indices.
84    let mut sum = 0;
85    for (&remainder, &divisor) in remainders.iter().zip(divisors) {
86        // Start with all other divisors multiplied together
87        let other_divisors_multiplied = all_divisors_multiplied / divisor;
88
89        // That is evenly divided by all other divisors, so we can ignore those and focus on
90        // finding a multiple of this value that has the sought after remainder value:
91        let value_with_one_as_remainder = other_divisors_multiplied
92            * modular_multiplicative_inverse(other_divisors_multiplied, divisor)?;
93        let fulfilling_value = remainder * value_with_one_as_remainder;
94
95        sum += fulfilling_value;
96    }
97
98    Some(sum % all_divisors_multiplied)
99}
100
101pub fn solve(input: &Input) -> Result<i128, String> {
102    let mut lines = input.text.lines();
103
104    let not_until = lines
105        .next()
106        .ok_or("Not two lines")?
107        .parse::<u32>()
108        .map_err(|error| format!("Line 1: Cannot parse number - {error}"))?;
109
110    let bus_ids = lines
111        .next()
112        .ok_or("Not two lines")?
113        .split(',')
114        .enumerate()
115        .filter_map(|(offset, entry)| {
116            if entry == "x" {
117                None
118            } else {
119                entry.parse::<u32>().map_or_else(
120                    |_| Some(Err("Line 2: Invalid entry".to_string())),
121                    |value| Some(Ok((offset, value))),
122                )
123            }
124        })
125        .collect::<Result<Vec<_>, _>>()?;
126
127    if input.is_part_one() {
128        // For a bus with id bus_id, the we want to find the one with minimal waiting
129        // time, which is given by `bus_id - not_until % bus_id`, since we need to wait
130        // a full bus_id cycle, but can subtract the time that has passed,
131        // `not_until % bus_id`, for that cycle.
132        let (bus_id, wait_time) = bus_ids
133            .iter()
134            .map(|(_offset, bus_id)| (bus_id, (bus_id - not_until % bus_id) % bus_id))
135            .min_by(|&a, &b| a.1.cmp(&b.1))
136            .ok_or("No bus ID:s")?;
137        Ok(i128::from(*bus_id) * i128::from(wait_time))
138    } else {
139        // Searching for a time T such that, for every bus_ids[i]:
140        //
141        //    bus_ids[i] - T % bus_ids[i] == i.
142        //
143        // Which is the same as:
144        //
145        //    bus_ids[i] - i == T % bus_ids[i]
146        //
147        // Formulated differently:
148        //
149        //    remainders[i] = T % divisors[i]
150        //
151        // where
152        //
153        //    remainders[i] := bus_ids[i] - i
154        //    divisors[i] := bus_ids[i]
155        //
156        // Which is what the chinese reminder theorem is about.
157        let remainders = bus_ids
158            .iter()
159            .map(|&(offset, bus_id)| i128::from(bus_id) - offset as i128)
160            .collect::<Vec<_>>();
161
162        let divisors = bus_ids
163            .iter()
164            .map(|&(_offset, bus_id)| i128::from(bus_id))
165            .collect::<Vec<_>>();
166
167        chinese_remainder(&remainders, &divisors)
168            .ok_or_else(|| "Bus id:s not pairwise coprime".to_string())
169    }
170}
171
172#[test]
173pub fn tests() {
174    let example = "939\n7,13,x,x,59,x,31,19";
175    test_part_one!(example => 295);
176    test_part_two!(example => 1_068_781);
177
178    test_part_one!("100\n10" => 0);
179
180    let real_input = include_str!("day13_input.txt");
181    test_part_one!(real_input => 4722);
182    test_part_two!(real_input => 825_305_207_525_452);
183}