/// --- 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());
}