From 355a685e42117c989a18b139404e0a37c6108397 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Thu, 5 Dec 2024 18:24:31 +0200 Subject: [PATCH] day-05 part-1 --- 2024/Cargo.lock | 15 ++ 2024/day-05/Cargo.toml | 24 ++++ 2024/day-05/benches/benchmarks.rs | 21 +++ 2024/day-05/src/bin/part1.rs | 12 ++ 2024/day-05/src/bin/part2.rs | 12 ++ 2024/day-05/src/lib.rs | 2 + 2024/day-05/src/part1.rs | 228 ++++++++++++++++++++++++++++++ 2024/day-05/src/part2.rs | 21 +++ 8 files changed, 335 insertions(+) create mode 100644 2024/day-05/Cargo.toml create mode 100644 2024/day-05/benches/benchmarks.rs create mode 100644 2024/day-05/src/bin/part1.rs create mode 100644 2024/day-05/src/bin/part2.rs create mode 100644 2024/day-05/src/lib.rs create mode 100644 2024/day-05/src/part1.rs create mode 100644 2024/day-05/src/part2.rs diff --git a/2024/Cargo.lock b/2024/Cargo.lock index 0f09589..eae5e9f 100644 --- a/2024/Cargo.lock +++ b/2024/Cargo.lock @@ -174,6 +174,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "day-05" +version = "0.1.0" +dependencies = [ + "divan", + "itertools", + "miette", + "nom", + "rstest", + "test-log", + "thiserror 2.0.3", + "tracing", + "tracing-subscriber", +] + [[package]] name = "divan" version = "0.1.16" diff --git a/2024/day-05/Cargo.toml b/2024/day-05/Cargo.toml new file mode 100644 index 0000000..b54a0ea --- /dev/null +++ b/2024/day-05/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "day-05" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +itertools.workspace = true +nom.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +miette.workspace = true +thiserror.workspace = true + +[dev-dependencies] +divan.workspace = true +rstest.workspace = true +test-log.workspace = true + +[[bench]] +name = "day-05-bench" +path = "benches/benchmarks.rs" +harness = false diff --git a/2024/day-05/benches/benchmarks.rs b/2024/day-05/benches/benchmarks.rs new file mode 100644 index 0000000..02cdd0e --- /dev/null +++ b/2024/day-05/benches/benchmarks.rs @@ -0,0 +1,21 @@ +use day_05::*; + +fn main() { + divan::main(); +} + +#[divan::bench] +fn part1() { + part1::process(divan::black_box(include_str!( + "../input1.txt", + ))) + .unwrap(); +} + +#[divan::bench] +fn part2() { + part2::process(divan::black_box(include_str!( + "../input2.txt", + ))) + .unwrap(); +} diff --git a/2024/day-05/src/bin/part1.rs b/2024/day-05/src/bin/part1.rs new file mode 100644 index 0000000..10c9851 --- /dev/null +++ b/2024/day-05/src/bin/part1.rs @@ -0,0 +1,12 @@ +use day_05::part1::process; +use miette::{Context, Result}; + +#[tracing::instrument] +fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let file = include_str!("../../input1.txt"); + let result = process(file).context("process part 1")?; + println!("{}", result); + Ok(()) +} diff --git a/2024/day-05/src/bin/part2.rs b/2024/day-05/src/bin/part2.rs new file mode 100644 index 0000000..398ea89 --- /dev/null +++ b/2024/day-05/src/bin/part2.rs @@ -0,0 +1,12 @@ +use day_05::part2::process; +use miette::{Context, Result}; + +#[tracing::instrument] +fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let file = include_str!("../../input2.txt"); + let result = process(file).context("process part 2")?; + println!("{}", result); + Ok(()) +} diff --git a/2024/day-05/src/lib.rs b/2024/day-05/src/lib.rs new file mode 100644 index 0000000..faaf542 --- /dev/null +++ b/2024/day-05/src/lib.rs @@ -0,0 +1,2 @@ +pub mod part1; +pub mod part2; diff --git a/2024/day-05/src/part1.rs b/2024/day-05/src/part1.rs new file mode 100644 index 0000000..3f54907 --- /dev/null +++ b/2024/day-05/src/part1.rs @@ -0,0 +1,228 @@ +use std::{ + fmt::{Debug, Display}, + str::FromStr, +}; + +use miette::{Diagnostic, Result}; +use thiserror::Error; + +#[derive(Debug, Error, Diagnostic)] +enum OrderingError { + #[error("No separator '{0}' was found")] + NoSeparator(char), + #[error("Too many values found ({0}) expected 2")] + TooManyValues(usize), + #[error("Not enough values found ({0}) expected 2")] + NotEnoughtValues(usize), + #[error("Failed to parse number")] + ParseError, +} + +#[derive(Debug)] +struct Ordering(usize, usize); + +impl FromStr for Ordering { + type Err = OrderingError; + fn from_str(s: &str) -> std::result::Result { + let splitter = '|'; + if !s.contains(splitter) { + return Err(OrderingError::NoSeparator(splitter)); + } + + let pages = s.split(splitter).collect::>(); + + if pages.len() > 2 { + return Err(OrderingError::TooManyValues(pages.len())); + } + if pages.len() < 2 { + return Err(OrderingError::NotEnoughtValues(pages.len())); + } + + let x = pages[0] + .trim() + .parse::() + .map_err(|_| OrderingError::ParseError)?; + let y = pages[1] + .trim() + .parse::() + .map_err(|_| OrderingError::ParseError)?; + + Ok(Ordering(x, y)) + } +} + +impl Display for Ordering { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}|{}", self.0, self.1) + } +} + +#[derive(Debug)] +struct Rules(Vec); + +impl Rules { + fn check(&self, pages: &Pages) -> Option { + let len = pages.0.len(); + if len < 2 { + return None; + } + + pages + .0 + .windows(2) + .all(|window| self.is_pair(&window[0], &window[1])) + .then(|| pages.0[len / 2]) + } + + fn is_pair(&self, a: &Page, b: &Page) -> bool { + self.0.iter().any(|Ordering(x, y)| *x == a.0 && *y == b.0) + } +} + +impl FromStr for Rules { + type Err = OrderingError; + fn from_str(s: &str) -> std::result::Result { + let rules = s + .lines() + .map(Ordering::from_str) + .collect::, _>>()?; + Ok(Rules(rules)) + } +} + +#[derive(Debug, Error, Diagnostic)] +enum PageError { + #[error("Failed to parse number")] + ParseError, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct Page(usize); + +impl FromStr for Page { + type Err = PageError; + fn from_str(s: &str) -> std::result::Result { + let page = s.parse().map_err(|_| PageError::ParseError); + Ok(Page(page?)) + } +} + +#[derive(Debug)] +struct Pages(Vec); + +impl FromStr for Pages { + type Err = PageError; + fn from_str(s: &str) -> std::result::Result { + let pages = s + .split(',') + .map(Page::from_str) + .collect::, _>>(); + Ok(Pages(pages?)) + } +} + +#[tracing::instrument] +pub fn process(input: &str) -> Result { + let (rules, pages) = parse(input)?; + let sum = pages + .iter() + .filter_map(|page| rules.check(page)) + .map(|page| page.0) + .sum(); + Ok(sum) +} + +fn parse(input: &str) -> Result<(Rules, Vec)> { + let sections = input.split("\n\n").collect::>(); + + let rules = Rules::from_str(sections.first().unwrap())?; + let pages = get_pages(sections.last().unwrap())?; + + Ok((rules, pages)) +} + +fn get_pages(section: &str) -> Result> { + Ok(section + .lines() + .map(Pages::from_str) + .collect::, _>>()?) +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::*; + + const TEST_RULES: &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"; + + #[rstest] + #[case("75,47,61,53,29", Some(Page(61)))] + #[case("97,61,53,29,13", Some(Page(53)))] + #[case("75,29,13", Some(Page(29)))] + #[case("75,97,47,61,53", None)] + #[case("61,13,29", None)] + #[case("97,13,75,29,47", None)] + fn test_pages(#[case] input: &str, #[case] expected: Option) -> Result<()> { + let rules = Rules::from_str(TEST_RULES)?; + let pages = Pages::from_str(input)?; + + assert_eq!(rules.check(&pages), expected); + Ok(()) + } + + #[test] + fn test_process() -> Result<()> { + let input = "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"; + let result = 143; + assert_eq!(process(input)?, result); + Ok(()) + } +} diff --git a/2024/day-05/src/part2.rs b/2024/day-05/src/part2.rs new file mode 100644 index 0000000..cbcdee4 --- /dev/null +++ b/2024/day-05/src/part2.rs @@ -0,0 +1,21 @@ +use miette::Result; + +#[tracing::instrument] +pub fn process(input: &str) -> Result { + todo!("day xx - part 2"); + Ok(0) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process() -> Result<()> { + let input = ""; + todo!("haven't built test yet"); + let result = 0; + assert_eq!(process(input)?, result); + Ok(()) + } +}