Skip to content

Commit edd5e1f

Browse files
committed
day 5 cleanup & docs
1 parent c160e00 commit edd5e1f

1 file changed

Lines changed: 65 additions & 33 deletions

File tree

day_05/src/main.rs

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,37 @@ impl FromStr for Problem {
3939
fn from_str(s: &str) -> Result<Self, Self::Err> {
4040
let (stacks_raw, instructions_raw) = s
4141
.split_once("\n\n")
42-
.ok_or_else(|| anyhow!("expected input divided by single empty line"))?;
42+
.ok_or_else(|| anyhow!("expected input separated by single empty line"))?;
4343

44+
// Process stacks from the bottom-up
4445
let mut stacks_iter = stacks_raw.lines().rev();
4546

47+
// Use first (bottom-most) stacks description line (labels) to determine
48+
// their count
4649
let stacks_count = stacks_iter
4750
.next()
4851
.ok_or_else(|| anyhow!("empty stacks description"))?
4952
.split_whitespace()
5053
.count();
5154

52-
let mut stacks = vec![vec![]; stacks_count + 1]; // NOTE: 0-th stack
53-
55+
// Allocate `stacks_count + 1` vectors to accommodate `stacks_count`
56+
// 1-indexed stacks and a dummy 0-th stack, to not bother with
57+
// re-indexing stacks. 0-th stack will simply never be touched when
58+
// executing instructions, and is an issue only when reading tops of
59+
// stacks to retrieve solution - a nuisance we can live with
60+
let mut stacks = vec![vec![]; stacks_count + 1];
61+
62+
// Consider rest of stacks description input in (at most) 4-character
63+
// long chunks. We will then encounter three kinds of input: (1) `[X] `
64+
// - an item on stack, (2) ` ` - no item, (3) `[X]` - an item on
65+
// stack in last column of input (note no space at the end), same as (1)
66+
// for our purposes.
5467
for stack_line in stacks_iter {
55-
for (stack_number, chunk) in
56-
stack_line.chars().collect::<Vec<_>>().chunks(4).enumerate()
57-
{
58-
if let Some(c) = chunk.get(1) {
59-
if *c != ' ' {
60-
stacks[stack_number + 1].push(*c);
68+
let chars = stack_line.chars().collect::<Vec<_>>();
69+
for (stack_number, chunk) in chars.chunks(4).enumerate() {
70+
if let Some(&c) = chunk.get(1) {
71+
if c != ' ' {
72+
stacks[stack_number + 1].push(c);
6173
}
6274
}
6375
}
@@ -66,8 +78,8 @@ impl FromStr for Problem {
6678
let instructions = instructions_raw
6779
.lines()
6880
.map(|l| {
69-
l.parse::<Instruction>()
70-
.with_context(|| anyhow!("trying to parse Instruction from '{}'", l))
81+
l.parse()
82+
.with_context(|| format!("parsing Instruction from '{}'", l))
7183
})
7284
.collect::<Result<_, _>>()?;
7385

@@ -78,39 +90,51 @@ impl FromStr for Problem {
7890
}
7991
}
8092

81-
fn run_instructions_on_stacks(
93+
/// Executes instructions on stacks using "single-item pick up" interpretation
94+
fn run_instructions_with_single_pick_up(
8295
stacks: &Vec<Vec<char>>,
8396
instructions: &Vec<Instruction>,
84-
) -> Vec<Vec<char>> {
97+
) -> Result<Vec<Vec<char>>, anyhow::Error> {
8598
let mut stacks = stacks.clone();
8699

87100
for instruction in instructions {
88-
let Instruction { num, from, to } = instruction;
89-
90-
for _ in 0..*num {
91-
let v = stacks[*from].pop().unwrap();
92-
stacks[*to].push(v);
101+
let Instruction { num, from, to } = *instruction;
102+
103+
// Move items one by one. Once could re-use loop-less solution from
104+
// [`run_instructions_with_multi_pick_up`], by just reversing the order
105+
// of items returned by [`std::vec::Vec::drain`], but this way the
106+
// intention is much clearer
107+
for _ in 0..num {
108+
let v = stacks[from]
109+
.pop()
110+
.ok_or_else(|| anyhow!("no items left on stack {}", from))?;
111+
stacks[to].push(v);
93112
}
94113
}
95114

96-
stacks
115+
Ok(stacks)
97116
}
98117

99-
fn run_instructions_on_stacks_2(
118+
/// Executes instructions on stacks using "multi-item pick up" interpretation
119+
fn run_instructions_with_multi_pick_up(
100120
stacks: &Vec<Vec<char>>,
101121
instructions: &Vec<Instruction>,
102-
) -> Vec<Vec<char>> {
122+
) -> Result<Vec<Vec<char>>, anyhow::Error> {
103123
let mut stacks = stacks.clone();
104124

105125
for instruction in instructions {
106-
let Instruction { num, from, to } = instruction;
107-
108-
let from_stack_len = stacks[*from].len();
109-
let mut v = stacks[*from].drain((from_stack_len - num)..).collect();
110-
stacks[*to].append(&mut v);
126+
let Instruction { num, from, to } = *instruction;
127+
128+
// move `num` items from the end of stack at once
129+
let idx_to_pick_up_from = stacks[from]
130+
.len()
131+
.checked_sub(num)
132+
.ok_or_else(|| anyhow!("stack {} has less than {} items left on it", from, num))?;
133+
let mut v = stacks[from].drain(idx_to_pick_up_from..).collect();
134+
stacks[to].append(&mut v);
111135
}
112136

113-
stacks
137+
Ok(stacks)
114138
}
115139

116140
fn read_tops_of_stacks(stacks: &Vec<Vec<char>>) -> String {
@@ -130,11 +154,19 @@ fn main() -> Result<(), anyhow::Error> {
130154

131155
println!(
132156
"Part 1 solution: {}",
133-
read_tops_of_stacks(&run_instructions_on_stacks(&stacks, &instructions)).trim()
157+
read_tops_of_stacks(&run_instructions_with_single_pick_up(
158+
&stacks,
159+
&instructions
160+
)?)
161+
.trim()
134162
);
135163
println!(
136164
"Part 2 solution: {}",
137-
read_tops_of_stacks(&run_instructions_on_stacks_2(&stacks, &instructions)).trim()
165+
read_tops_of_stacks(&run_instructions_with_multi_pick_up(
166+
&stacks,
167+
&instructions
168+
)?)
169+
.trim()
138170
);
139171

140172
Ok(())
@@ -193,13 +225,13 @@ move 1 from 1 to 2";
193225
}
194226

195227
#[test]
196-
fn test_run_instructions_on_stacks() {
228+
fn test_run_instructions_with_single_pick_up() {
197229
let Problem {
198230
stacks,
199231
instructions,
200232
} = TEST_INPUT.parse().unwrap();
201233

202-
let stacks = run_instructions_on_stacks(&stacks, &instructions);
234+
let stacks = run_instructions_with_single_pick_up(&stacks, &instructions).unwrap();
203235

204236
assert_eq!(
205237
stacks,
@@ -208,13 +240,13 @@ move 1 from 1 to 2";
208240
}
209241

210242
#[test]
211-
fn test_run_instructions_on_stacks_2() {
243+
fn test_run_instructions_with_multi_pick_up() {
212244
let Problem {
213245
stacks,
214246
instructions,
215247
} = TEST_INPUT.parse().unwrap();
216248

217-
let stacks = run_instructions_on_stacks_2(&stacks, &instructions);
249+
let stacks = run_instructions_with_multi_pick_up(&stacks, &instructions).unwrap();
218250

219251
assert_eq!(
220252
stacks,

0 commit comments

Comments
 (0)