diff --git a/Cargo.lock b/Cargo.lock index 4c9466e..6aab387 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ version = 3 name = "advent_of_code" version = "0.9.3" dependencies = [ + "gcd", "itertools", "pico-args", ] @@ -16,6 +17,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + [[package]] name = "itertools" version = "0.12.0" diff --git a/Cargo.toml b/Cargo.toml index f203230..46b113d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,6 @@ doctest = false test_lib = [] [dependencies] +gcd = "2.3.0" itertools = "0.12.0" pico-args = "0.5.0" diff --git a/README.md b/README.md index 8cb6b48..e17570c 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,16 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | Day | Part 1 | Part 2 | | :---: | :---: | :---: | -| [Day 1](./src/bin/01.rs) | `86.2µs` | `1.0ms` | -| [Day 2](./src/bin/02.rs) | `48.9µs` | `63.0µs` | -| [Day 3](./src/bin/03.rs) | `615.7µs` | `605.0µs` | -| [Day 4](./src/bin/04.rs) | `277.2µs` | `281.7µs` | -| [Day 5](./src/bin/05.rs) | `23.1µs` | `93.9µs` | -| [Day 6](./src/bin/06.rs) | `392.0ns` | `333.0ns` | -| [Day 7](./src/bin/07.rs) | `360.4µs` | `372.4µs` | - -**Total: 3.83ms** +| [Day 1](./src/bin/01.rs) | `70.9µs` | `848.4µs` | +| [Day 2](./src/bin/02.rs) | `37.7µs` | `48.4µs` | +| [Day 3](./src/bin/03.rs) | `500.7µs` | `487.2µs` | +| [Day 4](./src/bin/04.rs) | `238.1µs` | `239.4µs` | +| [Day 5](./src/bin/05.rs) | `20.5µs` | `74.7µs` | +| [Day 6](./src/bin/06.rs) | `456.0ns` | `385.0ns` | +| [Day 7](./src/bin/07.rs) | `289.8µs` | `291.3µs` | +| [Day 8](./src/bin/08.rs) | `506.5µs` | `3.3ms` | + +**Total: 6.95ms** --- diff --git a/data/examples/08-1.txt b/data/examples/08-1.txt new file mode 100644 index 0000000..59e2d47 --- /dev/null +++ b/data/examples/08-1.txt @@ -0,0 +1,9 @@ +RL + +AAA = (BBB, CCC) +BBB = (DDD, EEE) +CCC = (ZZZ, GGG) +DDD = (DDD, DDD) +EEE = (EEE, EEE) +GGG = (GGG, GGG) +ZZZ = (ZZZ, ZZZ) \ No newline at end of file diff --git a/data/examples/08-2.txt b/data/examples/08-2.txt new file mode 100644 index 0000000..34ffa8a --- /dev/null +++ b/data/examples/08-2.txt @@ -0,0 +1,5 @@ +LLR + +AAA = (BBB, BBB) +BBB = (AAA, ZZZ) +ZZZ = (ZZZ, ZZZ) \ No newline at end of file diff --git a/data/examples/08-3.txt b/data/examples/08-3.txt new file mode 100644 index 0000000..a8e2c98 --- /dev/null +++ b/data/examples/08-3.txt @@ -0,0 +1,10 @@ +LR + +11A = (11B, XXX) +11B = (XXX, 11Z) +11Z = (11B, XXX) +22A = (22B, XXX) +22B = (22C, 22C) +22C = (22Z, 22Z) +22Z = (22B, 22B) +XXX = (XXX, XXX) \ No newline at end of file diff --git a/src/bin/08.rs b/src/bin/08.rs new file mode 100644 index 0000000..e2e1cee --- /dev/null +++ b/src/bin/08.rs @@ -0,0 +1,160 @@ +use gcd::Gcd; +use std::collections::HashMap; +advent_of_code::solution!(8); + +#[derive(Copy, Clone, Debug)] +enum Direction { + Left, + Right, +} + +impl From for Direction { + fn from(value: char) -> Self { + match value { + 'L' => Self::Left, + 'R' => Self::Right, + _ => panic!(), + } + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialOrd, PartialEq, Ord)] +struct Node { + inner: [char; 3], +} + +impl Node { + pub fn new(values: [char; 3]) -> Self { + Self { inner: values } + } + + pub fn start() -> Self { + Self::new(['A', 'A', 'A']) + } +} + +impl From<&str> for Node { + fn from(value: &str) -> Self { + Self::new( + value + .chars() + .filter(|c| *c != '(' && *c != ')') + .collect::>() + .try_into() + .unwrap(), + ) + } +} + +fn solve( + from: Node, + map: &HashMap, + sequence: &[Direction], + target_evaluation: F, +) -> (usize, Node) +where + F: Fn(Node) -> bool, +{ + let mut current = from; + let mut steps = 0; + + for direction in sequence.iter().cycle() { + steps += 1; + + let (left, right) = map[¤t]; + + current = match direction { + Direction::Left => left, + Direction::Right => right, + }; + + if target_evaluation(current) { + break; + } + } + + (steps, current) +} + +pub fn part_one(input: &str) -> Option { + let mut lines = input.lines(); + let sequence: Vec = lines.next().unwrap().chars().map(Direction::from).collect(); + + let nodes_to_indices: HashMap = lines + .skip(1) + .map(|line| { + let (departure, targets) = line.split_once(" = ").unwrap(); + let (left, right) = targets.split_once(", ").unwrap(); + + (Node::from(departure), (Node::from(left), Node::from(right))) + }) + .collect(); + + Some( + solve(Node::start(), &nodes_to_indices, &sequence, |node| { + node.inner == ['Z', 'Z', 'Z'] + }) + .0, + ) +} + +pub fn part_two(input: &str) -> Option { + let mut lines = input.lines(); + let sequence: Vec = lines.next().unwrap().chars().map(Direction::from).collect(); + + let nodes_to_indices: HashMap = lines + .skip(1) + .map(|line| { + let (departure, targets) = line.split_once(" = ").unwrap(); + let (left, right) = targets.split_once(", ").unwrap(); + + (Node::from(departure), (Node::from(left), Node::from(right))) + }) + .collect(); + + let starting_positions: Vec = nodes_to_indices + .keys() + .filter(|node| node.inner[2] == 'A') + .cloned() + .collect(); + + Some( + starting_positions + .iter() + .map(|starting_position| { + let (steps_to_first_target, _) = + solve(*starting_position, &nodes_to_indices, &sequence, |node| { + node.inner[2] == 'Z' + }); + + steps_to_first_target + }) + .fold(1, |acc, next| (acc * next) / acc.gcd(next)), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file_part( + "examples", DAY, 1, + )); + assert_eq!(result, Some(2)); + + let result = part_one(&advent_of_code::template::read_file_part( + "examples", DAY, 2, + )); + assert_eq!(result, Some(6)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file_part( + "examples", DAY, 3, + )); + assert_eq!(result, Some(6)); + } +}