advent-2022-rust/src/days/day13/mod.rs
2023-02-09 07:15:30 +01:00

220 lines
6.7 KiB
Rust

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<ResultType> {
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<ResultType> {
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<Packets>),
}
impl PartialOrd for Packets {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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<Packets, PacketError> {
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<Item = char>) -> Result<Packets, PacketError> {
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<Vec<Packets>, PacketError> {
lines
.iter()
.filter(|line| !line.is_empty())
.map(|line| Packets::parse(line))
.collect::<Result<Vec<_>, _>>()
}
}
#[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::<Vec<_>>();
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(())
}
}