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 { 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 = "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(()) } }