use std::cmp::Ordering; use super::template::{DayTrait, ResultType}; use itertools::Itertools; use thiserror::Error; const DAY_NUMBER: usize = 13; pub struct Day; impl DayTrait for Day { fn get_day_number(&self) -> usize { DAY_NUMBER } fn part1(&self, lines: &[String]) -> anyhow::Result { let packets = Packets::parse_all(lines)?; let result: i64 = packets .iter() .tuples() .enumerate() .filter_map(|(pos, (first, second))| { if first < second { Some(pos as i64 + 1) } else { None } }) .sum(); Ok(ResultType::Integer(result)) } fn part2(&self, lines: &[String]) -> anyhow::Result { let small = Packets::parse("[[2]]")?; let large = Packets::parse("[[6]]")?; let mut pos_small = 1; let mut pos_large = 2; let packets = Packets::parse_all(lines)?; for packet in packets { if packet < small { pos_small += 1; pos_large += 1; } else if packet < large { pos_large += 1; } } Ok(ResultType::Integer(pos_small * pos_large)) } } #[derive(Debug, Error)] enum PacketError { #[error("Empty Packet not allowed")] EmptyPacketNotAllowed, #[error("Packtet must start with a [ was: {0}")] ExpectedSquare(char), #[error("Unknown char: {0}")] UnknownChar(char), #[error("End of input reached to early")] PrematureEndOfInput, #[error("Number was not terminated correctly")] UnfinishedNumber, } #[derive(Debug, PartialEq, Eq, Clone)] enum Packets { Number(u32), List(Vec), } impl PartialOrd for Packets { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Packets::Number(first), Packets::Number(second)) => first.partial_cmp(second), (Packets::List(first), Packets::List(second)) => { for pair in first.iter().zip_longest(second.iter()) { let result = match pair { itertools::EitherOrBoth::Left(_) => Some(Ordering::Greater), itertools::EitherOrBoth::Right(_) => Some(Ordering::Less), itertools::EitherOrBoth::Both(first, second) => first.partial_cmp(second), }; if !matches!(result, Some(Ordering::Equal)) { return result; } } Some(Ordering::Equal) } (Packets::Number(_), Packets::List(_)) => self.as_list().partial_cmp(other), (Packets::List(_), Packets::Number(_)) => self.partial_cmp(&other.as_list()), } } } impl Packets { pub fn as_list(&self) -> Packets { match self { Packets::Number(_) => Packets::List(vec![self.clone()]), Packets::List(_) => self.clone(), } } pub fn parse(line: &str) -> Result { let mut chars = line.chars(); let Some(start) = chars.next() else { return Err(PacketError::EmptyPacketNotAllowed); }; if start != '[' { return Err(PacketError::ExpectedSquare(start)); } let packet = Packets::parse_one(&mut chars)?; Ok(packet) } fn parse_one(chars: &mut dyn Iterator) -> Result { let mut list = Vec::new(); let mut number = None; while let Some(next) = chars.next() { match next { '[' => { if number.is_some() { return Err(PacketError::UnfinishedNumber); } list.push(Packets::parse_one(chars)?); } ']' => { if let Some(number) = number { list.push(Packets::Number(number)); } return Ok(Packets::List(list)); } ',' => { if let Some(number) = number { list.push(Packets::Number(number)); } number = None; } '0'..='9' => match number { None => number = Some(next.to_digit(10).unwrap()), Some(prev) => number = Some(prev * 10 + next.to_digit(10).unwrap()), }, _ => return Err(PacketError::UnknownChar(next)), } } Err(PacketError::PrematureEndOfInput) } pub fn parse_all(lines: &[String]) -> Result, PacketError> { lines .iter() .filter(|line| !line.is_empty()) .map(|line| Packets::parse(line)) .collect::, _>>() } } #[cfg(test)] mod test { use super::*; use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_parse() -> Result<()> { let input = "[[1],[2,3]]"; let expected = Packets::List(vec![ Packets::List(vec![Packets::Number(1)]), Packets::List(vec![Packets::Number(2), Packets::Number(3)]), ]); let result = Packets::parse(input)?; assert_eq!(result, expected); Ok(()) } #[test] fn test_parse_all() -> Result<()> { let day = Day {}; let lines = read_lines(day.get_day_number(), "example01.txt")?; let result = Packets::parse_all(&lines)?; assert_eq!(result.len(), 16); Ok(()) } #[test] fn test_compare_all() -> Result<()> { let day = Day {}; let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = vec![true, true, false, true, false, true, false, false]; let result = Packets::parse_all(&lines)?; let compare = result .iter() .tuples() .map(|(first, second)| first < second) .collect::>(); assert_eq!(compare, expected); Ok(()) } #[test] fn test_part1() -> Result<()> { let day = Day {}; let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(13); let result = day.part1(&lines)?; assert_eq!(result, expected); Ok(()) } #[test] fn test_part2() -> Result<()> { let day = Day {}; let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(140); let result = day.part2(&lines)?; assert_eq!(result, expected); Ok(()) } }