day13 finished
This commit is contained in:
parent
0b388cbd13
commit
2ad5f62785
4 changed files with 695 additions and 1 deletions
220
src/days/day13/mod.rs
Normal file
220
src/days/day13/mod.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
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(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue