[Advent Of Code 2022] Day 05: Supply Stacks [SPOILERS]

Oh boy… parsing is not my favorite pasttime.
This time part 2 was a simpler version of part 1. That’s new.

Python soluton
def read_file(file: TextIO):
    """Read lines from the `file`."""
    lines = (line.rstrip() for line in file)

    stacks = [[] for _ in range(9)]
    for line in lines:
        if not line:
            break
        for i in range(0, len(line), 4):
            chunk = line[i:i + 4]
            if re.match(r'\[[A-Z]\]', chunk):
                stacks[i // 4].append(chunk[1])
    for stack in stacks:
        stack.reverse()

    moves = []
    for line in lines:
        mat = re.match(r'move (\d+) from (\d) to (\d)', line)
        assert mat
        count, src, dst = (int(s) for s in mat.groups())
        moves.append((count, src, dst))

    return [stacks, moves]


def part1(file: TextIO) -> str:
    """Solve the first part of the puzzle."""
    stacks, moves = read_file(file)
    for count, src_idx1, dst_idx1 in moves:
        assert src_idx1 != dst_idx1
        src = stacks[src_idx1 - 1]
        dst = stacks[dst_idx1 - 1]
        dst.extend(src[-count:][::-1])
        del src[-count:]
    return ''.join(stack[-1] for stack in stacks if stack)


def test_part1() -> None:
    """Use the example from the instructions as a test for the first half."""
    file = io.StringIO(test_data)
    assert part1(file) == 'CMZ'


def part2(file: TextIO) -> str:
    """Solve the second part of the puzzle."""
    stacks, moves = read_file(file)
    for count, src, dst in moves:
        assert src != dst
        src = stacks[src - 1]
        dst = stacks[dst - 1]
        crates = src[-count:]
        dst.extend(crates)
        del src[-count:]
    return ''.join(stack[-1] for stack in stacks if stack)


def test_part2() -> None:
    """Use the example from the instructions as a test for the second half."""
    file = io.StringIO(test_data)
    assert part2(file) == 'MCD'
1 Like

Seemed straightforward but took me about 45 minutes in Java, one-handed. This may be it until I get my cast off.

Python. Initial working solution and Cleaned Code

I briefly considered regex … then opted to not bother. I was surprised how similar parts 1 and 2 were; I was expecting to need to change more than just that.

Python Solution
    def build_stacks(self, setup: list[str]) -> list[list[str]]:
        """Build the initial stacks from input."""
        stack_count = len(setup[-1].split())
        width = stack_count * 4
        stack = [[] for _ in range(stack_count)]
        for line in setup[-2::-1]:
            line = line.ljust(width)
            for i in range(stack_count):
                crate = line[1 + i * 4]
                if crate != " ":
                    stack[i].append(crate)
        return stack

    def part1(self, parsed_input: InputType) -> str:
        """Return crates after moving them with a CrateMover 9000."""
        setup_block, moves = parsed_input
        stack = self.build_stacks(setup_block)

        for move_count, src, dst in moves:
            for _ in range(move_count):
                stack[dst - 1].append(stack[src - 1].pop())
        return "".join(s.pop() for s in stack)

    def part2(self, parsed_input: InputType) -> str:
        """Return crates after moving them with a CrateMover 9001."""
        setup_block, moves = parsed_input
        stack = self.build_stacks(setup_block)

        for move_count, src, dst in moves:
            stack[dst - 1].extend(stack[src - 1][-move_count:])
            del stack[src - 1][-move_count:]

        return "".join(s.pop() for s in stack)

    def input_parser(self, puzzle_input: str) -> InputType:
        """Parse the input data."""
        setup, moves = puzzle_input.split("\n\n")
        return (
            setup.splitlines(),
            [
                tuple(int(i) for i in line.split() if i.isdigit())
                for line in moves.splitlines()
            ]
        )

I also feel the real challenge on this one was the parsing of the stacks :smiley:

I considered using regexes for the stack parsing part, but I realized I probably was trying to make my solution parse a generic version of the input format with no need. The regex was becoming too big and too complicated and it was literally the meme “when you try to solve a problem with regexes, now you have 2 problems”. The input only had <10 stacks, so stack numbers only took 1 digit to represent and items only had 1 letter, so all items were in a predictable position. Ultimately I decided to take advantage of that and just looked for specific indexes directly in the matrix of characters.

Other than that, I enjoyed the problem itself.

Typescript solution: Advent-Of-Code/2022/typescript/day05.ts at master · andrerfcsantos/Advent-Of-Code · GitHub

If I have there is time, there are some simplifications I want to try, but will leave those for later.

Right? I was also expecting a big twist on this one. Still enjoyed the problem, and I imagine that depending on the solution, part 2 can still require significant changes for some people, although I imagine for most it won’t.

Almost used a 2D slice to represent the stack, until I realized I could use a string instead (assuming all the crate names are just one character).

My day 5 solution

Note, strings in Go (and Python and other languages) are immutable.
In languages were strings are immutable, string concatenations are relatively expensive and should generally not be done in loops. Arrays/lists are much more efficient.

The suspense is driving me crazy. When are they going to drop the insidious problems? Last year this time, it felt like we were already to problems where part 2 would take forever if using brute force.

The really hard problems tend to be on the weekends and after at least 4 days.

Ruby Solution
def parse_stack_and_moves
  File.read('inputs/05.txt').then do |str|
    str.split("\n\n").then do |top, bottom|
      stacks = top.lines[0..-2]
                  .map { |line| line.scan(/\[(.)\](?: |$)| ( ) (?: |$)/).map(&:first) }
                  .transpose
                  .map(&:compact)
                  .map(&:reverse)
      moves = bottom.scan(/^move (\d+) from (\d+) to (\d+)$/).map { |move| move.map(&:to_i) }
      [stacks, moves]
    end
  end
end

def a_strategy(stacks, count, from, to) = stacks[to].push(*stacks[from].slice!(-count..).reverse)
def b_strategy(stacks, count, from, to) = stacks[to].push(*stacks[from].slice!(-count..))

def solve(strategy)
  stacks, moves = parse_stack_and_moves()
  moves.each { |move| strategy.call(stacks, move[0], move[1] - 1, move[2] - 1) }
  stacks.map(&:last).join
end

a = solve(method(:a_strategy))
b = solve(method(:b_strategy))

Parsing was definitely the tricky bit. I did go with a regex, but it’s not something particularly pretty :man_shrugging: