1use std::fs;
2
3struct Subroutine {
4 should_pop: bool,
5 gate_offset: i64,
6 source_offset: i64,
7}
8
9const RADIX: u32 = 26;
10
11impl Subroutine {
12 fn from_lines(lines: &[&str]) -> Subroutine {
13 Subroutine {
14 should_pop: match lines[4].split_whitespace().nth(2).unwrap().parse().unwrap() {
15 1 => false,
16 RADIX => true,
17 _ => panic!("Unexpected value for b"),
18 },
19 gate_offset: lines[5].split_whitespace().nth(2).unwrap().parse().unwrap(),
20 source_offset: lines[15]
21 .split_whitespace()
22 .nth(2)
23 .unwrap()
24 .parse()
25 .unwrap(),
26 }
27 }
28
29 fn evaluate(&self, w: i64, z: i64) -> (i8, i64) {
30 let radix = RADIX as i64;
31 let should_push = ((z % radix) + self.gate_offset) != w;
32 match (self.should_pop, should_push) {
33 (false, false) => (0, z),
34 (false, true) => (1, z * radix + self.source_offset + w),
35 (true, false) => (-1, z / radix),
36 (true, true) => (0, z / radix * radix + self.source_offset + w),
37 }
38 }
39}
40
41fn _subroutines(text: &str) -> Vec<Subroutine> {
42 text.lines()
43 .collect::<Vec<&str>>()
44 .chunks(18)
45 .map(Subroutine::from_lines)
46 .collect()
47}
48
49fn _fmt_int(mut value: u64, radix: u32) -> String {
50 let mut chars = Vec::new();
51 while value != 0 {
52 chars.push(std::char::from_digit(value as u32 % radix, radix).unwrap());
53 value /= radix as u64;
54 }
55 chars.into_iter().rev().collect()
56}
57
58fn _first_valid(subroutines: &[Subroutine], old_z: i64, path: u64, digits: &[u64]) -> Option<u64> {
59 if subroutines.is_empty() {
60 if old_z == 0 {
61 return Some(path);
62 } else {
63 return None;
64 }
65 }
66
67 for digit in digits {
68 let (delta, new_z) = subroutines[0].evaluate(*digit as i64, old_z);
69 if subroutines[0].should_pop && 0 <= delta {
70 continue;
71 }
72 match _first_valid(&subroutines[1..], new_z, path * 10 + digit, digits) {
73 Some(result) => return Some(result),
74 None => continue,
75 }
76 }
77 None
78}
79
80pub fn part_1(input: &str) -> u64 {
81 let subroutines = _subroutines(input);
82 _first_valid(&subroutines[..], 0, 0, &[9, 8, 7, 6, 5, 4, 3, 2, 1][..]).unwrap()
83}
84
85pub fn part_2(input: &str) -> u64 {
86 let subroutines = _subroutines(input);
87 _first_valid(&subroutines[..], 0, 0, &[1, 2, 3, 4, 5, 6, 7, 8, 9][..]).unwrap()
88}
89
90fn _from_file<F, T>(func: F, stem: &str) -> T
91where
92 F: Fn(&str) -> T,
93{
94 func(&fs::read_to_string(format!("inputs/24/{}.txt", stem)).unwrap())
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn part_1_works_on_input() {
103 assert_eq!(_from_file(part_1, "input"), 41299994879959);
104 }
105
106 #[test]
107 fn part_2_works_on_input() {
108 assert_eq!(_from_file(part_2, "input"), 11189561113216);
109 }
110}