/// --- Day 5: Supply Stacks --- /// /// The expedition can depart as soon as the final supplies have been unloaded from the /// ships. Supplies are stored in stacks of marked crates, but because the needed supplies are /// buried under many other crates, the crates need to be rearranged. /// /// The ship has a giant cargo crane capable of moving crates between stacks. To ensure none of /// the crates get crushed or fall over, the crane operator will rearrange them in a series of /// carefully-planned steps. After the crates are rearranged, the desired crates will be at the /// top of each stack. /// /// The Elves don't want to interrupt the crane operator during this delicate procedure, but they /// forgot to ask her which crate will end up where, and they want to be ready to unload them as /// soon as possible so they can embark. /// /// They do, however, have a drawing of the starting stacks of crates and the rearrangement /// procedure (your puzzle input). For example: /// /// [D] /// [N] [C] /// [Z] [M] [P] /// 1 2 3 /// /// move 1 from 2 to 1 /// move 3 from 1 to 3 /// move 2 from 2 to 1 /// move 1 from 1 to 2 /// /// In this example, there are three stacks of crates. Stack 1 contains two crates: crate Z is on /// the bottom, and crate N is on top. Stack 2 contains three crates; from bottom to top, they are /// crates M, C, and D. Finally, stack 3 contains a single crate, P. /// /// Then, the rearrangement procedure is given. In each step of the procedure, a quantity of /// crates is moved from one stack to a different stack. In the first step of the above /// rearrangement procedure, one crate is moved from stack 2 to stack 1, resulting in this /// configuration: /// /// [D] /// [N] [C] /// [Z] [M] [P] /// 1 2 3 /// /// In the second step, three crates are moved from stack 1 to stack 3. Crates are moved one at a /// time, so the first crate to be moved (D) ends up below the second and third crates: /// /// [Z] /// [N] /// [C] [D] /// [M] [P] /// 1 2 3 /// /// Then, both crates are moved from stack 2 to stack 1. Again, because crates are moved one at a /// time, crate C ends up below crate M: /// /// [Z] /// [N] /// [M] [D] /// [C] [P] /// 1 2 3 /// /// Finally, one crate is moved from stack 1 to stack 2: /// /// [Z] /// [N] /// [D] /// [C] [M] [P] /// 1 2 3 /// /// The Elves just need to know which crate will end up on top of each stack; in this example, the /// top crates are C in stack 1, M in stack 2, and Z in stack 3, so you should combine these /// together and give the Elves the message CMZ. /// /// 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) { for _ in 0..order.amount { let v = self.pop(order.from); self.push(order.to, v); } } } 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()); }