Advent of Code 2022: Day 1

by Michael Welborn on 2022-12-18

Alright, time for the first puzzle. If you haven't already, take a look at the prep work we did on day 0, and view the puzzle description on AoC.

They're starting us off with an easy problem. Our puzzle input is a list of caloric values for food items each elf is carrying, separated by a newline. Elves are separated by two newlines.

1000
2000
3000

4000

5000
6000

7000
8000
9000

10000

Part One

The desired output for part one is the total number of calories being carried by the elf who's carrying the most calories. When solving problems, developing APIs, and otherwise engaging in exploratory programming, I like to start from the end result and work backwards. That's to say, if all the right abstractions were already in place, how would I like to interact with them to implement a solution? Let's do that here.

I'd like to define the solution to part one as a simple max operation over the sum of the calories that each elf is carrying. And Python already gives us those two functions as built-ins.

from solver import Solver


class CalorieCounting(Solver):
    calories_by_elf: list[list[int]]

    def solve_part_one(self) -> int:
        return max(
            sum(calories)
            for calories in self.calories_by_elf
        )

To support this solution, we'll need to parse the input text into a list where each element is itself a list of caloric values for an elf.

[
    [1000, 2000, 3000],
    [4000],
    [5000, 6000],
    [7000, 8000, 9000],
    [10000]
]

Which is straightforward to implement with str.split() and a nested list comprehension.

class CalorieCounting(Solver):
    ...

    def parse_input(self) -> None:
        self.calories_by_elf = [
            [int(calories) for calories in elf.split("\n")]
            for elf in self.input_text.strip().split("\n\n")
        ]

Testing it with the sample input produces the correct answer 24000.

Part Two

Part two shares part one's input, but this time the desired output is the sum of the top 3 calorie-carrying elves.

This is a simple transformation of our previous solution. Let's rephrase the max function to be sorting the totals and taking the last one. Then this solution is the same, except we sort the totals and sum the last three.

The transformation of summing each elf's calorie total is shared between the two parts, so let's factor it out while we implement part two's solution.

class CalorieCounting(Solver):
    ...

    calorie_totals: list[int]

    def transform_input(self) -> None:
        self.calorie_totals = [
            sum(calories)
            for calories in self.calories_by_elf
        ]

    def solve_part_one(self) -> int:
        return max(self.calorie_totals)

    def solve_part_two(self) -> int:
        return sum(sorted(self.calorie_totals)[-3:])

We could refactor further and define both solutions in terms of a sum of the highest N calorie totals, but in this case I think max(self.calorie_totals) has better readability. So let's leave it as is.

Testing it with the sample input produces the correct answer 45000.

You can try this solution in your browser with Pyodide.