@@ -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
116140fn 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