220 lines
6.7 KiB
Rust
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(())
|
|
}
|
|
}
|