diff --git a/.aoc_tiles/tiles/2025/10.png b/.aoc_tiles/tiles/2025/10.png new file mode 100644 index 0000000..60bcc90 Binary files /dev/null and b/.aoc_tiles/tiles/2025/10.png differ diff --git a/README.md b/README.md index b97cb02..8897295 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- 2025 - 17 ⭐ - Rust + 2025 - 18 ⭐ - Rust

@@ -29,4 +29,7 @@ + + + diff --git a/src/day10.rs b/src/day10.rs new file mode 100644 index 0000000..0d33b39 --- /dev/null +++ b/src/day10.rs @@ -0,0 +1,185 @@ +use std::{collections::BinaryHeap, iter::repeat_n}; + +use aoc_runner_derive::{aoc, aoc_generator}; +use itertools::Itertools; +use regex::Regex; + +#[derive(Clone, Debug, Default)] +struct MachineDefinition { + desired: Vec, + buttons: Vec>, + joltages: Vec, +} + +impl MachineDefinition { + fn create<'a>(&'a self) -> Machine<'a> { + Machine { + d: self, + lights: Vec::from_iter(repeat_n(false, self.desired.len())), + } + } +} + +impl From<&str> for MachineDefinition { + fn from(value: &str) -> Self { + let parse_re = Regex::new( + r##"\[(?[.#]+)\] (?(\([0-9,]+\) ?)+) \{(?[0-9,]+)\}"##, + ) + .unwrap(); + + let parts = parse_re.captures(value).unwrap(); + + MachineDefinition { + desired: parts["desired"].chars().map(|c| c == '#').collect(), + buttons: parts["buttons"] + .split_ascii_whitespace() + .map(|s| { + s[1..s.len() - 1] + .split(',') + .map(|n| n.parse().unwrap()) + .collect() + }) + .collect(), + joltages: parts["joltages"] + .split(',') + .map(|n| n.parse().unwrap()) + .collect(), + } + } +} + +#[derive(Clone, Debug)] +struct Machine<'a> { + d: &'a MachineDefinition, + lights: Vec, +} + +impl<'a> Machine<'a> { + /// Get the state after pressing `button`, returns None if the state is as desired + fn press(&self, button: usize) -> Option { + let mut new_state = self.lights.clone(); + for light in &self.d.buttons[button] { + new_state[*light] = !new_state[*light] + } + if new_state == self.d.desired { + None + } else { + Some(Machine { + d: self.d, + lights: new_state, + }) + } + } + + /// Get the possible states from the current position + fn next_states(&self) -> Vec<(usize, Option>)> { + self.d + .buttons + .iter() + .enumerate() + .map(|(i, _but)| (i, self.press(i))) + .collect() + } +} + +#[derive(Debug, Clone)] +struct PressSet<'a> { + machine: Machine<'a>, + presses: Vec, +} + +// NOTE: All compares are reversed so our max heap becomes a min heap +impl<'a> Eq for PressSet<'a> {} + +impl<'a> PartialEq for PressSet<'a> { + fn eq(&self, other: &Self) -> bool { + other.presses.len().eq(&self.presses.len()) + } +} + +impl<'a> PartialOrd for PressSet<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + other.presses.len().partial_cmp(&self.presses.len()) + } +} + +impl<'a> Ord for PressSet<'a> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + other.presses.len().cmp(&self.presses.len()) + } +} + +fn find_best<'a>(md: &'a MachineDefinition) -> Vec { + let m = md.create(); + let mut to_check = BinaryHeap::new(); + for next in m.next_states() { + if let Some(new_m) = next.1 { + to_check.push(PressSet { + presses: vec![next.0], + machine: new_m.clone(), + }) + } else { + // what we found a solution on the first move? + return vec![next.0]; + } + } + while let Some(candidate) = to_check.pop() { + let cm = candidate.machine.clone(); + for next in cm.next_states() { + let mut presses = candidate.presses.clone(); + presses.push(next.0); + if let Some(new_m) = next.1 { + to_check.push(PressSet { + presses, + machine: new_m.clone(), + }) + } else { + return presses; + } + } + } + panic!() +} + +#[aoc_generator(day10)] +fn parse(input: &str) -> Vec { + input.lines().map(|l| l.into()).collect() +} + +#[aoc(day10, part1)] +fn part1(input: &Vec) -> u64 { + input + .into_iter() + .map(|md| find_best(md)) + // .inspect(|sol| println!(" [{sol:?}]")) + .map(|sol| sol.len() as u64) + .sum() +} + +#[aoc(day10, part2)] +fn part2(input: &Vec) -> u64 { + 0 +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::*; + + const EXAMPLE: &str = "[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7} +[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2} +[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}"; + + #[rstest] + #[case(EXAMPLE, 7)] + fn part1_example(#[case] input: &str, #[case] expected: u64) { + assert_eq!(part1(&parse(input)), expected); + } + + #[rstest] + #[case(EXAMPLE, 0)] + fn part2_example(#[case] input: &str, #[case] expected: u64) { + assert_eq!(part2(&parse(input)), expected); + } +} diff --git a/src/lib.rs b/src/lib.rs index b38dc0b..4831a4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod day1; +mod day10; mod day2; mod day3; mod day4;