From 25dedc74fa3263e250d16aab4fce4e7cff874aa5 Mon Sep 17 00:00:00 2001 From: Dylan Thies Date: Thu, 5 Dec 2024 11:47:14 -0500 Subject: [PATCH] 2024 day-5 clippy and done --- 2024/day-5/Cargo.toml | 23 +++++++ 2024/day-5/src/lib.rs | 4 ++ 2024/day-5/src/main.rs | 31 +++++++++ 2024/day-5/src/part1.rs | 119 +++++++++++++++++++++++++++++++++ 2024/day-5/src/part2.rs | 143 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 320 insertions(+) create mode 100644 2024/day-5/Cargo.toml create mode 100644 2024/day-5/src/lib.rs create mode 100644 2024/day-5/src/main.rs create mode 100644 2024/day-5/src/part1.rs create mode 100644 2024/day-5/src/part2.rs diff --git a/2024/day-5/Cargo.toml b/2024/day-5/Cargo.toml new file mode 100644 index 0000000..743e534 --- /dev/null +++ b/2024/day-5/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "day-5" +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 +log.workspace = true +error-stack.workspace = true +thiserror.workspace = true +dhat.workspace = true + +[dev-dependencies] +test-log.workspace = true + +[features] +dhat-heap = [] diff --git a/2024/day-5/src/lib.rs b/2024/day-5/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2024/day-5/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2024/day-5/src/main.rs b/2024/day-5/src/main.rs new file mode 100644 index 0000000..789efc4 --- /dev/null +++ b/2024/day-5/src/main.rs @@ -0,0 +1,31 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_5::part1; +use day_5::part2; + +use error_stack::{Result, ResultExt}; +use thiserror::Error; + +#[cfg(feature = "dhat-heap")] +#[global_allocator] +static ALLOC: dhat::Alloc = dhat::Alloc; + +#[derive(Debug, Error)] +enum Day5Error { + #[error("Part 1 failed")] + Part1Error, + #[error("Part 2 failed")] + Part2Error, +} + +fn main() -> Result<(), Day5Error> { + #[cfg(feature = "dhat-heap")] + let _profiler = dhat::Profiler::new_heap(); + + let input = include_str!("./input.txt"); + let part1_result = part1(input).change_context(Day5Error::Part1Error)?; + println!("part 1: {part1_result}"); + let part2_result = part2(input).change_context(Day5Error::Part2Error)?; + println!("part 2: {part2_result}"); + Ok(()) +} diff --git a/2024/day-5/src/part1.rs b/2024/day-5/src/part1.rs new file mode 100644 index 0000000..aad82f5 --- /dev/null +++ b/2024/day-5/src/part1.rs @@ -0,0 +1,119 @@ +#![warn(clippy::all, clippy::pedantic)] + +use std::collections::HashMap; + +use error_stack::{Report, Result, ResultExt}; +use nom::{ + bytes::complete::tag, character::complete, multi::separated_list1, sequence::separated_pair, + IResult, +}; +use thiserror::Error; + +// day-5 +#[derive(Debug, Error)] +pub enum Day5Part1Error { + #[error("Problem parsing Day 5")] + ParseError, +} + +type Orderings = HashMap>; + +/// Day-5 Part 1 for 2024 advent of code +/// Problem can be found here: +/// +/// # Errors +/// - `ParseError` there was an issue with the parser +pub fn part1(input: &str) -> Result { + //parse into "bad list where X|Y + let (_, (ordering, updates)) = parse_input(input) + .map_err(|x| Report::from(x.to_owned())) + .change_context(Day5Part1Error::ParseError)?; + let middles: u32 = updates + .iter() + .filter_map(|update| { + let update_len = update.len(); + for i in 0..update_len { + let before = &update[..i]; + if let Some(a) = update.get(i) { + if let Some(rules) = ordering.get(a) { + if rules.iter().any(|b| before.contains(b)) { + return None; + } + } + } + } + Some(update[update_len / 2]) + }) + .sum(); + Ok(middles.to_string()) +} + +fn parse_ordering(input: &str) -> IResult<&str, Orderings> { + let (input, rules) = separated_list1( + complete::line_ending, + separated_pair(complete::u32, tag("|"), complete::u32), + )(input)?; + let ordering = rules.iter().fold(HashMap::new(), |mut acc: Orderings, (a, b)| { + acc.entry(*a).or_default().push(*b); + acc + }); + Ok((input, ordering)) +} + +fn parse_update(input: &str) -> IResult<&str, Vec> { + separated_list1(tag(","), complete::u32)(input) +} + +fn parse_updates(input: &str) -> IResult<&str, Vec>> { + separated_list1(complete::line_ending, parse_update)(input) +} + +fn parse_input(input: &str) -> IResult<&str, (Orderings, Vec>)> { + let (input, ordering) = parse_ordering(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, updates) = parse_updates(input)?; + Ok((input, (ordering, updates))) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "47|53 +97|13 +97|61 +97|47 +75|29 +61|13 +75|53 +29|13 +97|29 +53|29 +61|53 +97|53 +61|29 +47|13 +75|47 +97|75 +47|61 +75|61 +47|29 +75|13 +53|13 + +75,47,61,53,29 +97,61,53,29,13 +75,29,13 +75,97,47,61,53 +61,13,29 +97,13,75,29,47"; + + #[test_log::test] + #[test_log(default_log_filter = "trace")] + fn part1_works() { + let result = part1(INPUT).unwrap(); + assert_eq!(result, "143".to_string()); + } +} + diff --git a/2024/day-5/src/part2.rs b/2024/day-5/src/part2.rs new file mode 100644 index 0000000..1a35dd7 --- /dev/null +++ b/2024/day-5/src/part2.rs @@ -0,0 +1,143 @@ +#![warn(clippy::all, clippy::pedantic)] + +use std::{cmp::Ordering, collections::HashMap}; + +use error_stack::{Report, Result, ResultExt}; +use nom::{bytes::complete::tag, character::complete, multi::separated_list1, sequence::separated_pair, IResult}; +use thiserror::Error; + +// day-5 +#[derive(Debug, Error)] +pub enum Day5Part2Error{ + #[error("Problem parsing Day 5")] + ParseError, +} + +type Orderings = HashMap>; + +/// Day-5 Part 2 for 2024 advent of code +/// Problem can be found here: +/// +/// # Errors +/// - `ParseError` there was an issue with the parser +pub fn part2 (input: &str) -> Result { + let (_, (ordering, mut updates)) = parse_input(input) + .map_err(|x| Report::from(x.to_owned())) + .change_context(Day5Part2Error::ParseError)?; + let middles: u32 = updates + .iter_mut() + .filter_map(|update| { + let update_len = update.len(); + for i in 0..update_len { + let before = &update[..i]; + if let Some(a) = update.get(i) { + if let Some(rules) = ordering.get(a) { + if rules.iter().any(|b| before.contains(b)) { + return Some(update); + } + } + } + } + None + }) + .map(|update| { + update.sort_by(|a,b| { + let Some(rule_a) = ordering.get(a) else { return Ordering::Equal;} ; + //let Some(rule_b) = ordering.get(b) else { return Ordering::Equal;} ; + if rule_a.contains(b) { + return Ordering::Less; + } + Ordering::Equal + }); + update[update.len()/2] + }) + .sum(); + Ok(middles.to_string()) +} + +/* + * --- Part Two --- + +While the Elves get to work printing the correctly-ordered updates, you have a little time to fix the rest of them. + +For each of the incorrectly-ordered updates, use the page ordering rules to put the page numbers in the right order. For the above example, here are the three incorrectly-ordered updates and their correct orderings: + + 75,97,47,61,53 becomes 97,75,47,61,53. + 61,13,29 becomes 61,29,13. + 97,13,75,29,47 becomes 97,75,47,29,13. + +After taking only the incorrectly-ordered updates and ordering them correctly, their middle page numbers are 47, 29, and 47. Adding these together produces 123. + +Find the updates which are not in the correct order. What do you get if you add up the middle page numbers after correctly ordering just those updates? +*/ + +fn parse_ordering(input: &str) -> IResult<&str, Orderings> { + let (input, rules) = separated_list1( + complete::line_ending, + separated_pair(complete::u32, tag("|"), complete::u32), + )(input)?; + let ordering = rules.iter().fold(HashMap::new(), |mut acc: Orderings, (a, b)| { + acc.entry(*a).or_default().push(*b); + acc + }); + Ok((input, ordering)) +} + +fn parse_update(input: &str) -> IResult<&str, Vec> { + separated_list1(tag(","), complete::u32)(input) +} + +fn parse_updates(input: &str) -> IResult<&str, Vec>> { + separated_list1(complete::line_ending, parse_update)(input) +} + +fn parse_input(input: &str) -> IResult<&str, (Orderings, Vec>)> { + let (input, ordering) = parse_ordering(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, updates) = parse_updates(input)?; + Ok((input, (ordering, updates))) +} + + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "47|53 +97|13 +97|61 +97|47 +75|29 +61|13 +75|53 +29|13 +97|29 +53|29 +61|53 +97|53 +61|29 +47|13 +75|47 +97|75 +47|61 +75|61 +47|29 +75|13 +53|13 + +75,47,61,53,29 +97,61,53,29,13 +75,29,13 +75,97,47,61,53 +61,13,29 +97,13,75,29,47"; + + #[test_log::test] + #[test_log(default_log_filter = "trace")] + fn part2_works() { + let result = part2(INPUT).unwrap(); + assert_eq!(result, "123".to_string()); + } +} +