From 86d9af647f661fd25eb3c2b081c0fc1b305417bd Mon Sep 17 00:00:00 2001 From: Dylan Thies Date: Fri, 8 Dec 2023 23:45:56 -0500 Subject: [PATCH] day 8 done had to cheat for part 2 because brute force was not good even though algorithm was --- 2023/day-8/Cargo.toml | 13 ++++ 2023/day-8/src/lib.rs | 4 + 2023/day-8/src/main.rs | 12 +++ 2023/day-8/src/part1.rs | 134 +++++++++++++++++++++++++++++++++ 2023/day-8/src/part2.rs | 160 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 323 insertions(+) create mode 100644 2023/day-8/Cargo.toml create mode 100644 2023/day-8/src/lib.rs create mode 100644 2023/day-8/src/main.rs create mode 100644 2023/day-8/src/part1.rs create mode 100644 2023/day-8/src/part2.rs diff --git a/2023/day-8/Cargo.toml b/2023/day-8/Cargo.toml new file mode 100644 index 0000000..c4544a9 --- /dev/null +++ b/2023/day-8/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day-8" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true +rstest = {workspace = true} diff --git a/2023/day-8/src/lib.rs b/2023/day-8/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-8/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-8/src/main.rs b/2023/day-8/src/main.rs new file mode 100644 index 0000000..8fb6f62 --- /dev/null +++ b/2023/day-8/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_8::part1; +use day_8::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-8/src/part1.rs b/2023/day-8/src/part1.rs new file mode 100644 index 0000000..eaedcdf --- /dev/null +++ b/2023/day-8/src/part1.rs @@ -0,0 +1,134 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete, + multi::{many1, separated_list1}, + sequence::{delimited, pair, separated_pair, tuple}, + IResult, Parser, +}; +use std::collections::BTreeMap; + +#[derive(Debug, Copy, Clone)] +enum Direction { + Left, + Right, +} + +#[derive(Debug, Clone)] +struct Branches { + pub left: String, + pub right: String, +} + +impl Branches { + fn choose(&self, direction: Direction) -> &str { + match direction { + Direction::Left => &self.left, + Direction::Right => &self.right, + } + } +} + +/// day 4 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part1(input: &str) -> String { + let (_, (steps, branches)) = parse_input(input).expect("aoc expects valid input"); + + let mut current = "AAA"; + let mut count = 0_usize; + for x in steps.iter().cycle() { + if current == "ZZZ" { + break; + } + current = branches.get(current).expect("aoc").choose(*x); + count += 1; + } + count.to_string() +} + +fn parse_directions(input: &str) -> IResult<&str, Vec> { + let (input, directions) = many1(alt(( + tag("L").map(|_| Direction::Left), + tag("R").map(|_| Direction::Right), + )))(input)?; + let (input, _) = complete::line_ending(input)?; + Ok((input, directions)) +} + +fn parse_branches(input: &str) -> IResult<&str, Branches> { + let (input, (left, right)) = delimited( + pair(tag("("), complete::space0), + separated_pair( + complete::alpha1, + pair(tag(","), complete::space1), + complete::alpha1, + ), + pair(complete::space0, tag(")")), + )(input)?; + let left = left.to_string(); + let right = right.to_string(); + Ok((input, Branches { left, right })) +} + +fn parse_nodes(input: &str) -> IResult<&str, (String, Branches)> { + let (input, (node, branches)) = separated_pair( + complete::alpha1, + tuple((complete::space1, tag("="), complete::space1)), + parse_branches, + )(input)?; + Ok((input, (node.to_string(), branches))) +} + +fn parse_node_tree(input: &str) -> IResult<&str, BTreeMap> { + let (input, map) = separated_list1(complete::line_ending, parse_nodes)(input)?; + let map = map.into_iter().collect(); + Ok((input, map)) +} + +fn parse_input(input: &str) -> IResult<&str, (Vec, BTreeMap)> { + let (input, x) = + separated_pair(parse_directions, complete::line_ending, parse_node_tree)(input)?; + Ok((input, x)) +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + + #[rstest] + #[case( + "RL + +AAA = (BBB, CCC) +BBB = (DDD, EEE) +CCC = (ZZZ, GGG) +DDD = (DDD, DDD) +EEE = (EEE, EEE) +GGG = (GGG, GGG) +ZZZ = (ZZZ, ZZZ)", + "2" + )] + #[case( + "LLR + +AAA = (BBB, BBB) +BBB = (AAA, ZZZ) +ZZZ = (ZZZ, ZZZ)", + "6" + )] + + fn part1_works(#[case] input: &str, #[case] expected: &str) { + let result = part1(input); + assert_eq!(result, expected); + } +} diff --git a/2023/day-8/src/part2.rs b/2023/day-8/src/part2.rs new file mode 100644 index 0000000..6df2a30 --- /dev/null +++ b/2023/day-8/src/part2.rs @@ -0,0 +1,160 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete, + multi::{many1, separated_list1}, + sequence::{delimited, pair, separated_pair, tuple}, + IResult, Parser, +}; +use std::collections::BTreeMap; + +#[derive(Debug, Copy, Clone)] +enum Direction { + Left, + Right, +} + +struct Branches { + pub left: String, + pub right: String, +} + +impl Branches { + fn choose(&self, direction: Direction) -> &str { + match direction { + Direction::Left => &self.left, + Direction::Right => &self.right, + } + } +} + +/// day 4 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part2(input: &str) -> String { + let (_, (steps, branches)) = parse_input(input).expect("aoc expects valid input"); + + let starting_node: Vec<&str> = branches + .keys() + .map(String::as_str) + .filter(|x| x.ends_with('A')) + .collect(); + + let cycles = starting_node + .iter() + .map(|node| { + let mut visited_nodes = vec![*node]; + let mut current = *node; + steps + .iter() + .cycle() + .enumerate() + .find_map(|(i, direction)| { + let new = branches.get(current).expect("aoc1").choose(*direction); + if new.ends_with('Z') { + return Some(i + 1); + } + visited_nodes.push(new); + dbg!(current = new); + None + }) + .expect("aoc4") + }) + .collect::>(); + lcm(&cycles).to_string() +} + +fn lcm(nums: &[usize]) -> usize { + if nums.len() == 1 { + return nums[0]; + } + let a = nums[0]; + let b = lcm(&nums[1..]); + a * b / gcd(a, b) +} + +fn gcd(a: usize, b: usize) -> usize { + if b == 0 { + return a; + } + gcd(b, a % b) +} + +fn parse_directions(input: &str) -> IResult<&str, Vec> { + let (input, directions) = many1(alt(( + tag("L").map(|_| Direction::Left), + tag("R").map(|_| Direction::Right), + )))(input)?; + let (input, _) = complete::line_ending(input)?; + Ok((input, directions)) +} + +fn parse_branches(input: &str) -> IResult<&str, Branches> { + let (input, (left, right)) = delimited( + pair(tag("("), complete::space0), + separated_pair( + complete::alphanumeric1, + pair(tag(","), complete::space1), + complete::alphanumeric1, + ), + pair(complete::space0, tag(")")), + )(input)?; + let left = left.to_string(); + let right = right.to_string(); + Ok((input, Branches { left, right })) +} + +fn parse_nodes(input: &str) -> IResult<&str, (String, Branches)> { + let (input, (node, branches)) = separated_pair( + complete::alphanumeric1, + tuple((complete::space1, tag("="), complete::space1)), + parse_branches, + )(input)?; + Ok((input, (node.to_string(), branches))) +} + +fn parse_node_tree(input: &str) -> IResult<&str, BTreeMap> { + let (input, map) = separated_list1(complete::line_ending, parse_nodes)(input)?; + let map = map.into_iter().collect(); + Ok((input, map)) +} + +fn parse_input(input: &str) -> IResult<&str, (Vec, BTreeMap)> { + let (input, x) = + separated_pair(parse_directions, complete::line_ending, parse_node_tree)(input)?; + Ok((input, x)) +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + + #[rstest] + #[case( + "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)", + "6" + )] + + fn part2_works(#[case] input: &str, #[case] expected: &str) { + let result = part2(input); + assert_eq!(result, expected); + } +}