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` involved 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 lines: Vec<_> = prefix.lines().collect();
30 let width = (lines[0].len() + 1) / 4;
31
32 let mut stack: Stack = vec![Vec::new(); width];
33 for row in lines.iter().rev().skip(1) {
34 for (i, c) in row.chars().skip(1).step_by(4).enumerate() {
35 if c.is_ascii_alphabetic() {
36 stack[i].push(c);
37 }
38 }
39 }
40
41 let moves: Vec<_> = suffix
42 .iter_unsigned()
43 .chunk::<3>()
44 .map(|[amount, from, to]| [amount, from - 1, to - 1])
45 .collect();
46
47 (stack, moves)
48}
49
50/// Move elements from stack to stack, reversing each time.
51pub fn part1(input: &Input) -> String {
52 play(input, true)
53}
54
55/// Move elements from stack to stack without reversing.
56pub fn part2(input: &Input) -> String {
57 play(input, false)
58}
59
60/// `get_disjoint_mut` allows us to acquire two simultaneous mutable references to disjoint indices.
61/// A nice standard library feature is that we can collect an iterator of `char`s into a `String`
62/// for the final answer.
63fn play(input: &Input, reverse: bool) -> String {
64 let (initial, moves) = input;
65 let mut stack = initial.clone();
66
67 for &[amount, from, to] in moves {
68 let [from, to] = stack.get_disjoint_mut([from, to]).unwrap();
69 let start = from.len() - amount;
70 let iter = from.drain(start..);
71
72 if reverse {
73 to.extend(iter.rev());
74 } else {
75 to.extend(iter);
76 }
77 }
78
79 stack.iter().map(|v| v.last().unwrap()).collect()
80}