use std::cmp::max; use std::str::FromStr; use aoc_2022::*; use color_eyre::eyre::{Report, Result}; use itertools::Itertools; use tracing::*; use tracing_subscriber::EnvFilter; #[derive(Debug)] struct Move { count: usize, from: usize, to: usize, } impl FromStr for Move { type Err = Report; fn from_str(s: &str) -> Result { let split_str: Vec<_> = s.split_whitespace().collect(); let ints = vec![split_str[1], split_str[3], split_str[5]] .iter() .map(|x| x.parse::().unwrap()) .collect_vec(); let (count, from, to) = (ints[0], ints[1], ints[2]); Ok(Move { count, from, to }) } } #[derive(Debug)] struct Ship(Vec>, Vec); impl FromStr for Ship { type Err = Report; fn from_str(s: &str) -> Result { let split_s = s.split("\n\n").collect_vec(); let (stacks_str, moves_str) = (split_s[0], split_s[1]); let mut stacks: Vec> = Vec::new(); stacks_str .lines() .rev() .skip(1) .map(|l| l.to_string() + " ") .for_each(|l| { l.chars().skip(1).step_by(4).enumerate().for_each(|(i, c)| { if i >= stacks.len() { stacks.push(Vec::new()); } if c != ' ' { stacks[i].push(c); } }); }); let moves = strings_to_structs(moves_str.lines()); Ok(Ship(stacks, moves)) } } type Input = Ship; type Output = String; fn part_1(input: &Input) -> Output { let Ship(stacks, moves) = input; let mut stacks = stacks.clone(); moves.iter().for_each(|m| { for _ in 0..m.count { if stacks[m.from - 1].is_empty() { break; } let popped = stacks[m.from - 1].pop().unwrap(); stacks[m.to - 1].push(popped); } }); stacks.iter().fold(String::new(), |acc, stack| { if let Some(c) = stack.last() { acc + &c.to_string() } else { acc } }) } fn part_2(input: &Input) -> Output { let Ship(stacks, moves) = input; let mut stacks = stacks.clone(); moves.iter().for_each(|m| { let size = stacks[m.from - 1].len(); let mut s = stacks[m.from - 1].split_off(max(0, size - m.count)); stacks[m.to - 1].append(&mut s); }); stacks.iter().fold(String::new(), |acc, stack| { if let Some(c) = stack.last() { acc + &c.to_string() } else { acc } }) } fn parse_input(pathname: &str) -> Input { file_to_string(pathname).parse::().unwrap() } fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .with_target(false) .with_file(true) .with_line_number(true) .without_time() .compact() .init(); color_eyre::install()?; let input = parse_input("inputs/day05.txt"); info!("Part 1: {}", part_1(&input)); info!("Part 2: {}", part_2(&input)); Ok(()) } #[cfg(test)] mod day_05 { use super::*; #[test] fn test_part_1() { let sample = parse_input("samples/day05.txt"); assert_eq!(part_1(&sample), "CMZ".to_string()); } #[test] fn test_part_2() { let sample = parse_input("samples/day05.txt"); assert_eq!(part_2(&sample), "MCD".to_string()); } }