/// --- Part Two --- /// /// As you watch the crane operator expertly rearrange the crates, you notice the process isn't /// following your prediction. /// /// Some mud was covering the writing on the side of the crane, and you quickly wipe it away. The /// crane isn't a CrateMover 9000 - it's a CrateMover 9001. /// /// The CrateMover 9001 is notable for many new and exciting features: air conditioning, leather /// seats, an extra cup holder, and the ability to pick up and move multiple crates at once. /// /// Again considering the example above, the crates begin in the same configuration: /// /// ``` /// [D] /// [N] [C] /// [Z] [M] [P] /// 1 2 3 /// ``` /// /// Moving a single crate from stack 2 to stack 1 behaves the same as before: /// /// ``` /// [D] /// [N] [C] /// [Z] [M] [P] /// 1 2 3 /// ``` /// /// However, the action of moving three crates from stack 1 to stack 3 means that those three moved /// crates stay in the same order, resulting in this new configuration: /// /// ``` /// [D] /// [N] /// [C] [Z] /// [M] [P] /// 1 2 3 /// ``` /// /// Next, as both crates are moved from stack 2 to stack 1, they retain their order as well: /// /// ``` /// [D] /// [N] /// [C] [Z] /// [M] [P] /// 1 2 3 /// ``` /// /// Finally, a single crate is still moved from stack 1 to stack 2, but now it's crate C that gets moved: /// /// ``` /// [D] /// [N] /// [Z] /// [M] [C] [P] /// 1 2 3 /// ``` /// /// In this example, the CrateMover 9001 has put the crates in a totally different order: MCD. /// /// Before the rearrangement process finishes, update your simulation so that the Elves know where /// they should stand to be ready to unload the final supplies. After the rearrangement procedure /// completes, what crate ends up on top of each stack? use clap::Parser; use itertools::Itertools; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::{alpha1, digit1, multispace1}; use nom::combinator::{map, opt, peek}; use nom::error::{ErrorKind as NomErrorKind, ParseError}; use nom::multi::{many1, many1_count}; use nom::sequence::{delimited, preceded, terminated}; use nom::IResult; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; use std::iter::*; use std::path::PathBuf; pub type Input<'a> = &'a str; pub type Result<'a, T> = IResult, T, Error>>; #[derive(Clone, Debug, PartialEq, Eq)] pub enum ErrorKind { Nom(NomErrorKind), Context(&'static str), Custom(String), } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Error { pub errors: Vec<(I, ErrorKind)>, } impl ParseError for Error { fn from_error_kind(input: I, kind: NomErrorKind) -> Self { let errors = vec![(input, ErrorKind::Nom(kind))]; Self { errors } } fn append(input: I, kind: NomErrorKind, mut other: Self) -> Self { other.errors.push((input, ErrorKind::Nom(kind))); other } } const FILEPATH: &'static str = "examples/input.txt"; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Cli { #[clap(short, long, default_value = FILEPATH)] file: PathBuf, } #[derive(Debug, Clone)] struct Supplies(Vec>); #[derive(Debug, Clone)] struct Order { from: usize, to: usize, amount: usize, } #[derive(Debug, Clone)] enum LineKind { Axis(usize), Crates(Vec), } fn parse_digit(input: &str) -> Result { map(preceded(multispace1, digit1), |s: &str| { s.chars().next().unwrap() })(input) } fn parse_crate(input: &str) -> Result { map(delimited(tag("["), alpha1, tag("]")), |s: &str| { s.chars().next().unwrap() })(input) } fn parse_crate_or_empty(input: &str) -> Result { terminated(alt((map(tag(" "), |_| ' '), parse_crate)), opt(tag(" ")))(input) } fn parse_line(input: &str) -> Result { let res = peek(parse_digit)(input); if res.is_ok() { map(many1_count(parse_digit), |len| LineKind::Axis(len))(input) } else { map(many1(parse_crate_or_empty), |v| LineKind::Crates(v))(input) } } impl Supplies { fn new() -> Self { Self(Vec::>::new()) } fn idx_to_internal(idx: usize) -> usize { idx - 1 } fn initialize(&mut self, input: Vec) { let axis = &input[input.len() - 1]; let LineKind::Axis(len) = axis else { panic!(); }; self.resize(*len); let _ = input .into_iter() .rev() .skip(1) .map(|x| match x { LineKind::Crates(v) => v, _ => panic!(), }) .map(|vc| { vc.into_iter() .zip(1usize..) .filter(|(c, _)| *c != ' ') .collect_vec() }) .flatten() .scan(self, |state, (c, idx)| { state.push(idx, c); Some(()) }) .last(); } fn pop(&mut self, idx: usize) -> char { self.0[Self::idx_to_internal(idx)].pop().unwrap() } fn peek(&self, idx: usize) -> char { self.0[Self::idx_to_internal(idx)][self.0[Self::idx_to_internal(idx)].len() - 1] } fn push(&mut self, idx: usize, letter: char) { self.0[Self::idx_to_internal(idx)].push(letter) } fn resize(&mut self, len: usize) { self.0.resize(len, Vec::::new()) } fn top_chars(&self) -> String { (0..self.0.len()) .map(|idx| self.peek(idx + 1).to_string()) .join("") } fn transfer(&mut self, order: Order) { let popped = (0..order.amount) .into_iter() .map(|_| self.pop(order.from)) .collect_vec(); for c in popped.into_iter().rev() { self.push(order.to, c); } } } fn main() { let args = Cli::parse(); let file = File::open(&args.file).unwrap(); let reader = BufReader::new(file); let init = reader .lines() .map(|l| l.unwrap()) .take_while(|l| !l.is_empty()) .map(|l| parse_line(l.as_str()).unwrap().1) .collect_vec(); let mut supplies = Supplies::new(); supplies.initialize(init); let file = File::open(&args.file).unwrap(); let reader = BufReader::new(file); let _ = reader .lines() .map(|l| l.unwrap()) .skip_while(|l| !l.contains("move")) .map(|l| { let sp = l.split_whitespace().collect_vec(); Order { from: sp[3].parse::().unwrap(), to: sp[5].parse::().unwrap(), amount: sp[1].parse::().unwrap(), } }) .scan(&mut supplies, |state, order| { state.transfer(order); Some(()) }) .last(); println!("{}", supplies.top_chars()); }