day05 finished

This commit is contained in:
Ruediger Ludwig 2023-02-01 21:48:01 +01:00
parent 01f6afed9e
commit 8e4face21e
4 changed files with 780 additions and 1 deletions

253
src/days/day05/mod.rs Normal file
View file

@ -0,0 +1,253 @@
use super::template::{DayTrait, ResultType};
use std::num::ParseIntError;
use thiserror::Error;
const DAY_NUMBER: usize = 5;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let (mut stacks, commands) = parse(lines)?;
for command in commands {
command.with_turn(&mut stacks)?;
}
Ok(ResultType::String(get_word(&stacks)))
}
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let (mut stacks, commands) = parse(lines)?;
for command in commands {
command.without_turn(&mut stacks)?;
}
Ok(ResultType::String(get_word(&stacks)))
}
}
#[derive(Debug, Error)]
enum CrateError {
#[error("Illegal move: {0}")]
IllegalMove(String),
#[error("Cant Parse Integer in Move")]
NotAnInteger(#[from] ParseIntError),
#[error("No crate stacks given")]
NoCargoStacksGiven,
#[error("Illegal move: Too few stacks have {0} needed {1}")]
TooFewStacks(usize, usize),
#[error("Illegal move: Too few crates have {0} needed {1}")]
TooFewCrates(String, usize),
#[error("Stack number must not be 0")]
NoZeroStack,
}
fn parse_crate(line: &str) -> Vec<Option<char>> {
line.chars()
.skip(1)
.step_by(4)
.map(|c| if c == ' ' { None } else { Some(c) })
.collect()
}
fn parse_all_crates(line_it: &mut dyn Iterator<Item = &String>) -> Result<Vec<String>, CrateError> {
let rows: Vec<_> = line_it
.take_while(|line| !line.is_empty())
.map(|row| parse_crate(row))
.collect();
let Some(stacks_count) = rows.iter().map(|row| row.len()).max() else {
return Err(CrateError::NoCargoStacksGiven);
};
let mut stacks = Vec::with_capacity(stacks_count);
for stack_num in 0..stacks_count {
let mut stack = String::from("");
for row in &rows {
if let Some(Some(one_crate)) = row.get(stack_num) {
stack.push(*one_crate);
}
}
stacks.push(stack);
}
Ok(stacks)
}
fn parse(line: &[String]) -> Result<(Vec<String>, Vec<Move>), CrateError> {
let mut iter = line.iter();
let stacks = parse_all_crates(&mut iter)?;
let moves: Vec<_> = iter
.map(|command| Move::try_from(command.as_str()))
.collect::<Result<_, _>>()?;
Ok((stacks, moves))
}
fn get_word(stacks: &[String]) -> String {
stacks
.iter()
.map(|stack| stack.chars().next().unwrap())
.collect::<String>()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Move {
from_stack: usize,
to_stack: usize,
amount: usize,
}
impl Move {
pub fn new(amount: usize, from_stack: usize, to_stack: usize) -> Move {
Move {
amount,
from_stack,
to_stack,
}
}
fn apply_single(
&self,
stacks: &mut Vec<String>,
func: fn(&str) -> String,
) -> Result<(), CrateError> {
if self.to_stack >= stacks.len() {
return Err(CrateError::TooFewStacks(stacks.len(), self.to_stack));
}
let Some(from_stack) = stacks.get(self.from_stack ) else {
return Err(CrateError::TooFewStacks(stacks.len(), self.from_stack));
};
if from_stack.len() < self.amount {
return Err(CrateError::TooFewCrates(from_stack.to_owned(), self.amount));
}
let from_stack = from_stack.to_owned();
let (start, end) = from_stack.split_at(self.amount);
stacks[self.from_stack] = end.to_owned();
stacks[self.to_stack] = format!("{}{}", func(start), stacks[self.to_stack]);
Ok(())
}
pub fn with_turn(&self, stacks: &mut Vec<String>) -> Result<(), CrateError> {
self.apply_single(stacks, |crates| crates.chars().rev().collect())
}
pub fn without_turn(&self, stacks: &mut Vec<String>) -> Result<(), CrateError> {
self.apply_single(stacks, |crates| crates.to_owned())
}
}
impl TryFrom<&str> for Move {
type Error = CrateError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let parts: Vec<_> = value.split_whitespace().collect();
if parts.len() != 6 {
return Err(CrateError::IllegalMove(value.to_owned()));
}
let amount: usize = parts[1].parse()?;
let from_stack: usize = parts[3].parse()?;
let to_stack: usize = parts[5].parse()?;
if from_stack == 0 || to_stack == 0 {
return Err(CrateError::NoZeroStack);
}
Ok(Move::new(amount, from_stack - 1, to_stack - 1))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_lines;
use anyhow::Result;
#[test]
fn test_parse_crates() -> Result<()> {
let line = " [D] ";
let expected = vec![None, Some('D'), None];
let result = parse_crate(line);
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_parse_move() -> Result<()> {
let line = "move 3 from 2 to 1";
let expected = Move::new(3, 1, 0);
let result = Move::try_from(line)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_parse_all_crates() -> Result<()> {
let lines = vec![
" [D] ".to_owned(),
"[N] [C] ".to_owned(),
"[Z] [M] [P]".to_owned(),
" 1 2 3 ".to_owned(),
"".to_owned(),
"move 1 from to 2 1".to_owned(),
];
let mut input = lines.iter();
let expected = vec!["NZ1".to_owned(), "DCM2".to_owned(), "P3".to_owned()];
let result = parse_all_crates(&mut input)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_with_turn() -> Result<()> {
let mut stacks = vec!["NZ1".to_owned(), "DCM2".to_owned(), "P3".to_owned()];
let command = Move::new(2, 1, 0);
let expected = vec!["CDNZ1".to_owned(), "M2".to_owned(), "P3".to_owned()];
command.with_turn(&mut stacks)?;
assert_eq!(stacks, expected);
Ok(())
}
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let expected = ResultType::String("CMZ".to_owned());
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_without_turn() -> Result<()> {
let mut stacks = vec!["NZ1".to_owned(), "DCM2".to_owned(), "P3".to_owned()];
let command = Move::new(2, 1, 0);
let expected = vec!["DCNZ1".to_owned(), "M2".to_owned(), "P3".to_owned()];
command.without_turn(&mut stacks)?;
assert_eq!(stacks, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let expected = ResultType::String("MCD".to_owned());
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
}

View file

@ -2,6 +2,7 @@ mod day01;
mod day02;
mod day03;
mod day04;
mod day05;
mod template;
pub use template::DayTrait;
@ -11,7 +12,7 @@ pub mod day_provider {
use super::*;
use thiserror::Error;
const MAX_DAY: usize = 4;
const MAX_DAY: usize = 5;
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
match day_num {
@ -19,6 +20,7 @@ pub mod day_provider {
2 => Ok(Box::new(day02::Day)),
3 => Ok(Box::new(day03::Day)),
4 => Ok(Box::new(day04::Day)),
5 => Ok(Box::new(day05::Day)),
_ => Err(ProviderError::InvalidNumber(day_num)),
}
}