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}