Skip to main content

aoc/year2022/
day05.rs

1//! # Supply Stacks
2use crate::util::iter::*;
3use crate::util::parse::*;
4
5type Stack = Vec<Vec<char>>;
6type Move = [usize; 3];
7type Input = (Stack, Vec<Move>);
8
9/// Parses the input in 2 stages.
10///
11/// First, the input is split into a prefix and suffix, using a blank line (or 2 newline characters
12/// one after another) as the delimiter.
13///
14/// The suffix consisting of triplets of (amount, from, to) can be parsed using our utility
15/// [`iter_unsigned`] and [`chunk`] methods to tokenize the string into numbers, then group it into
16/// triples. One minor nuance is that the `from` and `to` field are *1* based indexing, so we
17/// convert them to 0 based for convenience.
18///
19/// The prefix is a little more complex. The number of columns is the width in characters plus 1
20/// divided by 4 (the last column has no trailing space). Then we build the vectors from the bottom
21/// up by iterating through the rows in reverse. This places the elements at the top of each stack
22/// at the end of the `vec` which is a more natural location for mutation (as removing elements from
23/// the start of a `vec` involves moving all remaining elements).
24///
25/// [`iter_unsigned`]: ParseOps::iter_unsigned
26/// [`chunk`]: ChunkOps::chunk
27pub fn parse(input: &str) -> Input {
28    let (prefix, suffix) = input.split_once("\n\n").unwrap();
29    let width = prefix.lines().next().unwrap().len().div_ceil(4);
30
31    let mut stack: Stack = vec![Vec::new(); width];
32    for row in prefix.lines().rev().skip(1) {
33        for (i, c) in row.chars().skip(1).step_by(4).enumerate() {
34            if c.is_ascii_alphabetic() {
35                stack[i].push(c);
36            }
37        }
38    }
39
40    let moves: Vec<_> = suffix
41        .iter_unsigned()
42        .chunk::<3>()
43        .map(|[amount, from, to]| [amount, from - 1, to - 1])
44        .collect();
45
46    (stack, moves)
47}
48
49/// Move elements from stack to stack, reversing each time.
50pub fn part1(input: &Input) -> String {
51    play(input, true)
52}
53
54/// Move elements from stack to stack without reversing.
55pub fn part2(input: &Input) -> String {
56    play(input, false)
57}
58
59/// `get_disjoint_mut` allows us to acquire two simultaneous mutable references to disjoint indices.
60/// A nice standard library feature is that we can collect an iterator of `char`s into a `String`
61/// for the final answer.
62fn play(input: &Input, reverse: bool) -> String {
63    let (initial, moves) = input;
64    let mut stack = initial.clone();
65
66    for &[amount, from, to] in moves {
67        let [from, to] = stack.get_disjoint_mut([from, to]).unwrap();
68        let start = from.len() - amount;
69        let iter = from.drain(start..);
70
71        if reverse {
72            to.extend(iter.rev());
73        } else {
74            to.extend(iter);
75        }
76    }
77
78    stack.iter().filter_map(|v| v.last()).collect()
79}