From 2909dfd80332fba363fe4de84297f54a85480fa6 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Thu, 5 Dec 2024 18:46:36 +0200 Subject: [PATCH] day-05 part-2 --- 2024/benchmarks.txt | 35 ++++-- 2024/day-05/bench.txt | 4 + 2024/day-05/src/part2.rs | 241 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 2024/day-05/bench.txt diff --git a/2024/benchmarks.txt b/2024/benchmarks.txt index 6f21035..8343d70 100644 --- a/2024/benchmarks.txt +++ b/2024/benchmarks.txt @@ -14,8 +14,8 @@ running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s day_01_bench fastest │ slowest │ median │ mean │ samples │ iters -├─ part1 54.79 µs │ 195.1 µs │ 56.06 µs │ 70.49 µs │ 100 │ 100 -╰─ part2 156.2 µs │ 202.6 µs │ 157.3 µs │ 160.7 µs │ 100 │ 100 +├─ part1 92.56 µs │ 200.5 µs │ 93.49 µs │ 96.49 µs │ 100 │ 100 +╰─ part2 268.4 µs │ 319.8 µs │ 269.3 µs │ 272.3 µs │ 100 │ 100 running 2 tests @@ -33,8 +33,8 @@ running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s day_02_bench fastest │ slowest │ median │ mean │ samples │ iters -├─ part1 81.18 µs │ 325.1 µs │ 83.12 µs │ 122.7 µs │ 100 │ 100 -╰─ part2 130.1 µs │ 172.9 µs │ 131.2 µs │ 134.1 µs │ 100 │ 100 +├─ part1 169 µs │ 272.7 µs │ 172.5 µs │ 182.2 µs │ 100 │ 100 +╰─ part2 247.1 µs │ 296.2 µs │ 249.6 µs │ 252.1 µs │ 100 │ 100 running 2 tests @@ -52,8 +52,8 @@ running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s day_03_bench fastest │ slowest │ median │ mean │ samples │ iters -├─ part1 253.9 µs │ 796.2 µs │ 279.8 µs │ 305.1 µs │ 100 │ 100 -╰─ part2 363.1 µs │ 1.209 ms │ 417.6 µs │ 536.8 µs │ 100 │ 100 +├─ part1 377.4 µs │ 761.1 µs │ 384.8 µs │ 396.8 µs │ 100 │ 100 +╰─ part2 527 µs │ 583.2 µs │ 538 µs │ 540 µs │ 100 │ 100 running 2 tests @@ -71,6 +71,25 @@ running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s day_04_bench fastest │ slowest │ median │ mean │ samples │ iters -├─ part1 598 µs │ 2.157 ms │ 611 µs │ 800.4 µs │ 100 │ 100 -╰─ part2 632.9 µs │ 886 µs │ 640.8 µs │ 646.7 µs │ 100 │ 100 +├─ part1 903.5 µs │ 985.1 µs │ 918.4 µs │ 920.2 µs │ 100 │ 100 +╰─ part2 939.4 µs │ 1.025 ms │ 950 µs │ 952.5 µs │ 100 │ 100 + + +running 14 tests +iiiiiiiiiiiiii +test result: ok. 0 passed; 0 failed; 14 ignored; 0 measured; 0 filtered out; finished in 0.00s + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +day_05_bench fastest │ slowest │ median │ mean │ samples │ iters +├─ part1 433.7 µs │ 698.6 µs │ 441.2 µs │ 454.8 µs │ 100 │ 100 +╰─ part2 5.355 ms │ 6.07 ms │ 5.42 ms │ 5.45 ms │ 100 │ 100 diff --git a/2024/day-05/bench.txt b/2024/day-05/bench.txt new file mode 100644 index 0000000..7363457 --- /dev/null +++ b/2024/day-05/bench.txt @@ -0,0 +1,4 @@ +day_05_bench fastest │ slowest │ median │ mean │ samples │ iters +├─ part1 431 µs │ 782.6 µs │ 437.5 µs │ 460.8 µs │ 100 │ 100 +╰─ part2 5.358 ms │ 6.38 ms │ 5.434 ms │ 5.468 ms │ 100 │ 100 + diff --git a/2024/day-05/src/part2.rs b/2024/day-05/src/part2.rs index cbcdee4..cf67ae4 100644 --- a/2024/day-05/src/part2.rs +++ b/2024/day-05/src/part2.rs @@ -1,20 +1,249 @@ -use miette::Result; +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; + } + + if self.is_correctly_ordered(&pages) { + return None; + } + + let reordered = self.reorder(pages); + Some(reordered.0[len / 2]) + } + + fn is_correctly_ordered(&self, pages: &Pages) -> bool { + pages + .0 + .windows(2) + .all(|window| self.is_pair(&window[0], &window[1])) + } + + fn reorder(&self, pages: Pages) -> Pages { + let mut result = pages.0; + + for i in 0..result.len() - 1 { + for j in i + 1..result.len() { + if self.is_pair(&result[j], &result[i]) { + result.swap(i, j); + } + } + } + + Pages(result) + } + + 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, Clone)] +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 { - todo!("day xx - part 2"); - Ok(0) + let (rules, pages) = parse(input)?; + let sum = pages + .iter() + .filter_map(|page| rules.check(page.clone())) + .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", None)] + #[case("97,61,53,29,13", None)] + #[case("75,29,13", None)] + #[case("75,97,47,61,53", Some(Page(47)))] + #[case("61,13,29", Some(Page(29)))] + #[case("97,13,75,29,47", Some(Page(47)))] + 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 = ""; - todo!("haven't built test yet"); - let result = 0; + 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 = 123; assert_eq!(process(input)?, result); Ok(()) }