From 093e724fa9a2779db0863cbfb66beae1287c1f18 Mon Sep 17 00:00:00 2001 From: Louis Vallat Date: Sat, 15 Feb 2025 23:38:02 +0100 Subject: [PATCH] feat: added day 24 part 1 and 2 Signed-off-by: Louis Vallat --- .gitea/workflows/build_and_run.yml | 2 +- day24/Cargo.toml | 6 + day24/src/main.rs | 185 +++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 day24/Cargo.toml create mode 100644 day24/src/main.rs diff --git a/.gitea/workflows/build_and_run.yml b/.gitea/workflows/build_and_run.yml index 9981f77..0cf847d 100644 --- a/.gitea/workflows/build_and_run.yml +++ b/.gitea/workflows/build_and_run.yml @@ -11,7 +11,7 @@ jobs: name: Challenge for day strategy: matrix: - day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] + day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] runs-on: rust-bookworm steps: - name: Check out repository code diff --git a/day24/Cargo.toml b/day24/Cargo.toml new file mode 100644 index 0000000..ddaaf58 --- /dev/null +++ b/day24/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day24" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day24/src/main.rs b/day24/src/main.rs new file mode 100644 index 0000000..42104ba --- /dev/null +++ b/day24/src/main.rs @@ -0,0 +1,185 @@ +use std::cmp::PartialEq; +use std::collections::{HashMap, HashSet}; +use std::{env, fs}; + +const MAX_Z: &str = "z45"; + +#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] +enum Operation { + AND, + OR, + XOR, +} + +impl From<&str> for Operation { + fn from(value: &str) -> Self { + match value { + "AND" => Operation::AND, + "OR" => Operation::OR, + "XOR" => Operation::XOR, + &_ => unreachable!(), + } + } +} + +#[derive(Debug)] +struct Instruction { + left: String, + operation: Operation, + right: String, + result: String, + computed: bool, +} + +impl Instruction { + fn compute(&self, registers: &HashMap) -> Option { + let reg_left = registers.get(&self.left); + let reg_right = registers.get(&self.right); + if reg_left.is_some() && reg_right.is_some() { + match self.operation { + Operation::AND => Some(reg_left.unwrap() & reg_right.unwrap()), + Operation::OR => Some(reg_left.unwrap() | reg_right.unwrap()), + Operation::XOR => Some(reg_left.unwrap() ^ reg_right.unwrap()), + } + } else { + None + } + } +} + +fn read_input(path: &str) -> String { + fs::read_to_string(path).expect("Cannot read file.") +} + +fn parse_input( + input: &str, +) -> ( + HashMap, + Vec, + HashMap>, +) { + let mut registers = HashMap::new(); + let mut lines = input.lines(); + while let Some(line) = lines.next() { + if line.is_empty() { + break; + } + + let split = line.split_once(": ").unwrap(); + registers.insert(split.0.to_string(), split.1.parse().unwrap()); + } + + let mut instructions = Vec::new(); + let mut operations = HashMap::new(); + while let Some(line) = lines.next() { + let mut split = line.split(" "); + let left = split.next().unwrap().to_string(); + let operation = Operation::from(split.next().unwrap()); + let right = split.next().unwrap().to_string(); + let result = split.skip(1).next().unwrap().to_string(); + instructions.push(Instruction { + left: left.clone(), + operation, + right: right.clone(), + result, + computed: false, + }); + operations + .entry(left) + .or_insert(HashSet::new()) + .insert(operation); + operations + .entry(right) + .or_insert(HashSet::new()) + .insert(operation); + } + + (registers, instructions, operations) +} + +fn part_1(registers: &mut HashMap, instructions: &mut Vec) -> u128 { + while instructions.iter().any(|instruction| !instruction.computed) { + for instruction in instructions.iter_mut() { + if instruction.computed { + continue; + } + let computation = instruction.compute(registers); + if computation.is_some() { + instruction.computed = true; + registers.insert(instruction.result.clone(), computation.unwrap()); + } + } + } + + let mut result = 0; + for (key, value) in registers.iter().filter(|&(key, _)| key.starts_with("z")) { + result += (*value as u128) << key[1..].parse::().unwrap(); + } + + result +} + +fn is_x_y_pair(instruction: &Instruction) -> bool { + (instruction.left.starts_with("x") && instruction.right.starts_with("y")) + || (instruction.left.starts_with("y") && instruction.right.starts_with("x")) +} + +fn part_2( + instructions: &Vec, + operations: &HashMap>, +) -> String { + let xa: HashSet = HashSet::from_iter(vec![Operation::XOR, Operation::AND]); + let o: HashSet = HashSet::from_iter(vec![Operation::OR]); + let mut anomalies = Vec::new(); + for instruction in instructions { + if instruction.operation == Operation::OR { + if instruction.result != MAX_Z && operations.get(&instruction.result) != Some(&xa) { + anomalies.push(instruction.result.clone()); + } + } else if instruction.operation == Operation::XOR { + if is_x_y_pair(instruction) { + if instruction.result != "z00" && operations.get(&instruction.result) != Some(&xa) { + anomalies.push(instruction.result.clone()); + } + } else { + if !instruction.result.starts_with("z") { + anomalies.push(instruction.result.clone()); + } + } + } else { + if is_x_y_pair(instruction) { + if ["x00", "y00"].contains(&instruction.left.as_str()) { + if operations.get(&instruction.result) != Some(&xa) { + anomalies.push(instruction.result.clone()); + } + } else if operations.get(&instruction.result) != Some(&o) { + anomalies.push(instruction.result.clone()); + } + } else { + if !is_x_y_pair(instruction) && operations.get(&instruction.result) != Some(&o) { + anomalies.push(instruction.result.clone()); + } + } + } + } + + anomalies.sort(); + anomalies.join(",") +} + +fn main() { + let args: Vec = env::args().collect(); + for arg in args.iter().skip(1) { + let input = read_input(&arg); + let (mut registers, mut instructions, operations) = parse_input(&input); + println!("[{}]", &arg); + println!( + "\t[Part 1] => Answer is '{}'.", + part_1(&mut registers, &mut instructions) + ); + println!( + "\t[Part 2] => Answer is '{}'.", + part_2(&instructions, &operations) + ); + } +}