Skip to content

Commit ace0893

Browse files
committed
2022 d5: add input, solutions and walkthrough
1 parent 214ef7e commit ace0893

File tree

4 files changed

+814
-12
lines changed

4 files changed

+814
-12
lines changed

2022/README.md

+194-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Table of Contents
88
- [Day 2 - Rock Paper Scissors][d02]
99
- [Day 3 - Rucksack Reorganization][d03]
1010
- [Day 4 - Camp Cleanup][d04]
11-
11+
- [Day 5 - Supply Stacks][d05]
1212

1313
Day 1 - Calorie Counting
1414
------------------------
@@ -340,6 +340,7 @@ print('Part 2:', group_total)
340340

341341
Easy peasy! Daily puzzle solved once again.
342342

343+
343344
Day 4 - Camp Cleanup
344345
--------------------
345346

@@ -471,6 +472,179 @@ print('Part 2:', overlap)
471472

472473
Et voilà! 8 out of 50 stars.
473474

475+
476+
Day 5 - Supply Stacks
477+
---------------------
478+
479+
[Problem statement][d05-problem][Complete solution][d05-solution][Back to top][top]
480+
481+
### Part 1
482+
483+
Do you like simulations? Today we're gonna need to simulate a crane moving
484+
crates stacked on top of each other. We start with a few stacks of crates, which
485+
is given as input in the following form:
486+
487+
```none
488+
[D]
489+
[N] [C]
490+
[Z] [M] [P]
491+
1 2 3
492+
```
493+
494+
This represents the initial configuration of N stacks of crates (in the above
495+
example we have N = 3, but in the real input N = 10). Following the initial
496+
configuration is an empty line followed by a list of instructions, one per line,
497+
of the form `move <n> from <i> to <j>`, meaning "move the top `n` crates from
498+
stack `i` to stack `j`, one at a time.
499+
500+
After executing all instructions, we need to answer with a string of letters
501+
representing the topmost crate of each stack in order. For example, if the above
502+
configuration was the final one, we would answer `NDP`.
503+
504+
Today's input is particularly annoying to parse: we are given stacks in fancy
505+
ASCII art columns, and we need to somehow turn each one into a string or list in
506+
order to work with it. The easiest way to approach this is probably to just read
507+
the entirety of the first few lines of input, stopping at the first empty line,
508+
and then *transpose* them to obtain a list of columns. In other words, do
509+
something like this:
510+
511+
```none
512+
+----------> ' [[ '
513+
|+---------> ' NZ1'
514+
||+--------> ' ]] '
515+
|||+-------> ' '
516+
||||+------> '[[[ '
517+
|||||+-----> 'DCM2'
518+
||||||+----> ...
519+
|||||||
520+
' [D] '
521+
'[N] [C] '
522+
'[Z] [M] [P]'
523+
' 1 2 3 '
524+
```
525+
526+
After reading the initial ASCII art and stopping at the first empty line:
527+
528+
```python
529+
fin = open(...)
530+
raw = []
531+
532+
for line in fin:
533+
if line == '\n':
534+
break
535+
raw.append(line)
536+
537+
# raw = [' [D] \n',
538+
# '[N] [C] \n',
539+
# '[Z] [M] [P]\n',
540+
# ' 1 2 3 \n']
541+
```
542+
543+
We can transpose the `raw` list with the help of [`zip()`][py-builtin-zip] plus
544+
an [unpacking operator][py-unpacking]:
545+
546+
```python
547+
columns = list(zip(*raw))
548+
# [(' ', '[', '[', ' ', '\n'),
549+
# (' ', 'N', 'Z', '1', '\n'),
550+
# ... ]
551+
```
552+
553+
This seemingly esoteric single-line transposition works because `zip()` yields
554+
tuples consisting of the i-th element of each line in `raw`, i.e. it effectively
555+
yields columns.
556+
557+
We went from strings to tuples, but that's no problem for now. The next thing to
558+
do is skip all the useless columns (those consisting of only spaces and square
559+
brackets) and keep the rest, turning good columns into strings through
560+
[`str.join()`][py-str-join] and discarding leading whitespace with
561+
[`str.lstrip()`][py-str-lstrip].
562+
563+
Fortunately, all we need to do to identify good columns is
564+
[`enumerate()`][py-builtin-enumerate], skip the first column and then only keep
565+
one every 4, which can be achieved using the modulo (`%`) operator.
566+
567+
```python
568+
# Indexes in the instructions are 1-based, fill stacks[0] with some useless
569+
# value so that later we can do stacks[i] instead of stacks[i - 1].
570+
stacks = [None]
571+
572+
for i, column in enumerate(zip(*raw)):
573+
if i % 4 == 1:
574+
# column = (' ', 'N', 'Z', '1', '\n')
575+
column = ''.join(column[:-1]) # -> ' NZ'
576+
column = column.lstrip() # -> 'NZ'
577+
stacks.append(column)
578+
579+
# Make a copy to use for part 2
580+
original = stacks[:]
581+
```
582+
583+
Now that we *finally* have the initial stacks parsed, let's also parse the
584+
instructions. This is quite simple: iterate over input lines, split them and
585+
extract the three numbers at positions `1`, `3` and `5`:
586+
587+
```python
588+
moves = []
589+
590+
for line in fin:
591+
line = line.split()
592+
moves.append((int(line[1]), int(line[3]), int(line[5])))
593+
```
594+
595+
We have the instructions parsed, now let's simply follow them:
596+
597+
```python
598+
for n, i, j in moves:
599+
for _ in range(n):
600+
crate = stacks[i][0] # Extract top of stacks[i]
601+
stacks[i] = stacks[1:] # Remove it from stacks[i]
602+
stacks[j] = crate + stacks[j] # Add it to top of stacks[j]
603+
```
604+
605+
We optimize the above operation by extracting all `n` crates at once and then
606+
reversing their order doing `crate[::-1]`, a common Python trick to reverse an
607+
indexable iterable through [slicing][py-slicing]:
608+
609+
```python
610+
for n, i, j in moves:
611+
chunk = stacks[i][:n][::-1]
612+
stacks[i] = stacks[i][n:]
613+
stacks[j] = chunk + stacks[j]
614+
```
615+
616+
Finally, we can extract the topmost element of each stack using a simple
617+
[generator expression][py-generator-expr] and `.join()` the result into a
618+
string:
619+
620+
```python
621+
top = ''.join(s[0] for s in stacks[1:]) # Skip stacks[0], which is None
622+
print('Part 1:', top)
623+
```
624+
625+
### Part 2
626+
627+
For part two, we need to follow the same list of instructions as part 1, but
628+
this time moving *all* of the topmost `n` crates from a given stack to another
629+
at once, meaning that their final order on top of the second stack will *not* be
630+
reversed.
631+
632+
Well, given the code we already wrote, this is really child's play: we can use
633+
the same code as part 1, removing the reversing operation (`[::-1]`):
634+
635+
```python
636+
# Restore initial state from the copy we made earlier
637+
stacks = original
638+
639+
for n, i, j in moves:
640+
chunk = stacks[i][:n] # <- removed [::-1] from here
641+
stacks[i] = stacks[i][n:]
642+
stacks[j] = chunk + stacks[j]
643+
644+
top = ''.join(s[0] for s in stacks[1:])
645+
print('Part 2:', top)
646+
```
647+
474648
---
475649

476650
*Copyright &copy; 2022 Marco Bonelli. This document is licensed under the [Creative Commons BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) license.*
@@ -482,36 +656,44 @@ Et voilà! 8 out of 50 stars.
482656
[d02]: #day-2---rock-paper-scissors
483657
[d03]: #day-3---rucksack-reorganization
484658
[d04]: #day-4---camp-cleanup
659+
[d05]: #day-5---supply-stacks
485660

486661
[d01-problem]: https://adventofcode.com/2022/day/1
487662
[d02-problem]: https://adventofcode.com/2022/day/2
488663
[d03-problem]: https://adventofcode.com/2022/day/3
489664
[d04-problem]: https://adventofcode.com/2022/day/4
665+
[d05-problem]: https://adventofcode.com/2022/day/5
490666

491667
[d01-solution]: solutions/day01.py
492668
[d02-solution]: solutions/day02.py
493669
[d03-solution]: solutions/day03.py
494670
[d04-solution]: solutions/day04.py
671+
[d05-solution]: solutions/day05.py
495672

496673
[d02-alternative]: misc/day02/mathematical.py
497674

498675
[py-cond-expr]: https://docs.python.org/3/reference/expressions.html#conditional-expressions
676+
[py-generator-expr]: https://www.python.org/dev/peps/pep-0289/
499677
[py-list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions
500678
[py-slicing]: https://docs.python.org/3/glossary.html#term-slice
501679
[py-tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
680+
[py-unpacking]: https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists
502681
[py-with]: https://peps.python.org/pep-0343/
503682

504-
505-
[py-builtin-map]: https://docs.python.org/3/library/functions.html#map
506-
[py-builtin-max]: https://docs.python.org/3/library/functions.html#max
507-
[py-builtin-min]: https://docs.python.org/3/library/functions.html#min
508-
[py-builtin-ord]: https://docs.python.org/3/library/functions.html#ord
509-
[py-list]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists
510-
[py-list-sort]: https://docs.python.org/3/library/stdtypes.html#list.sort
511-
[py-str-maketrans]: https://docs.python.org/3/library/stdtypes.html#str.maketrans
512-
[py-str-split]: https://docs.python.org/3/library/stdtypes.html#str.split
513-
[py-str-rstrip]: https://docs.python.org/3/library/stdtypes.html#str.split
514-
[py-str-translate]: https://docs.python.org/3/library/stdtypes.html#str.translate
683+
[py-builtin-enumerate]: https://docs.python.org/3/library/functions.html#enumerate
684+
[py-builtin-map]: https://docs.python.org/3/library/functions.html#map
685+
[py-builtin-max]: https://docs.python.org/3/library/functions.html#max
686+
[py-builtin-min]: https://docs.python.org/3/library/functions.html#min
687+
[py-builtin-ord]: https://docs.python.org/3/library/functions.html#ord
688+
[py-builtin-zip]: https://docs.python.org/3/library/functions.html#zip
689+
[py-list]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists
690+
[py-list-sort]: https://docs.python.org/3/library/stdtypes.html#list.sort
691+
[py-str-join]: https://docs.python.org/3/library/stdtypes.html#str.join
692+
[py-str-lstrip]: https://docs.python.org/3/library/stdtypes.html#str.lstrip
693+
[py-str-maketrans]: https://docs.python.org/3/library/stdtypes.html#str.maketrans
694+
[py-str-rstrip]: https://docs.python.org/3/library/stdtypes.html#str.rstrip
695+
[py-str-split]: https://docs.python.org/3/library/stdtypes.html#str.split
696+
[py-str-translate]: https://docs.python.org/3/library/stdtypes.html#str.translate
515697

516698
[algo-quickselect]: https://en.wikipedia.org/wiki/Quickselect
517699

0 commit comments

Comments
 (0)