Advent of code 2022 python walk through.
Day 1: Calorie Counting.
Part One.
Read the full puzzle prose here: https://adventofcode.com/2022/day/1
The TL;DR of the sample puzzle input.
- Our sample puzzle input represents the number of calories that each of the jungle-trotting elves has brought with them.
- An empty line means the end of one elf’s caloric notes, and the start of another’s.
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
In the sample input above, 5 elves have noted down their calorie counts for various food items.
The task.
Our task is to find the elf that is carrying the most calories, and to find the total calories that that elf is carrying.
The thought process, with code.
- Store that puzzle input as a data-structure of some sort.
A python list is sufficient for this problem.
sample_input = """
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
""".strip().splitlines()
sample_input
now represents a list of calories (stored as strings), along with the empty spaces(stored as empty strings) between each elf’s input.
['1000', '2000', '3000', '', '4000', '', '5000', '6000', '', '7000', '8000', '90
00', '', '10000']
.strip()
exists solely to remove the first empty string that would’ve been present in the calories list. Without .strip()
, the list would’ve looked like this:
# The list of calories, had .strip() not been applied.
['', '1000', '2000', '3000', '', '4000', '', '5000', '6000', '', '7000', '8000',
'9000', '', '10000']
.splitlines()
is what does the heavy lifting of turning our sample puzzle input from the triple-quoted string into a list.
Had .splitlines()
not been applied, the data-structure wouldn’t even be a list . We would’ve been dealing with a string instead…
# The string of calories, had just .strip() but not
# .splitlines() been applied.
'1000\n2000\n3000\n\n4000\n\n5000\n6000\n\n7000\n8000\n9000\n\n10000'
- Visit every element in the calories list, making sure to add the calorie we find to the current elf’s total.
- Aside from this per-elf calorie total, let’s also create a global total, that represents the highest total calories of any elf we have seen so far. At the end of our program, this highest total will be our puzzle solution.
- If, while adding calories, we encounter an empty string, we must take note of the current elf’s total and compare it with the highest total so far.
If the current elf’s total is greater than our highest total, then we update the highest total. Otherwise, we do nothing.
highest_elf_total = 0
current_elf_total = 0
for calorie in sample_input:
if calorie == '': # denotes next elf's input
highest_elf_total = max(highest_elf_total, current_elf_total)
current_elf_total = 0 # reset before reading the next elf's calories
else:
current_elf_total += int(calorie)
print(highest_elf_total)
Of some note, is this line:
highest_elf_total = max(highest_elf_total, current_elf_total)
The highest_elf_total
is whichever is bigger between the current_elf_total
and whatever the highest_elf_total
was before.
The above line is a more terse, and yet still readable, version of:
if current_elf_total > highest_elf_total:
highest_elf_total = current_elf_total
The final program looks like this:
sample_input = """
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
""".strip().splitlines()
highest_elf_total = 0
current_elf_total = 0
for calorie in sample_input:
if calorie == '': # denotes next elf's input
highest_elf_total = max(highest_elf_total, current_elf_total)
current_elf_total = 0 # reset before reading the next elf's calories
else:
current_elf_total += int(calorie)
print(highest_elf_total)
Though this program works for both our sample input, and the much longer puzzle input that you get from advent of code, there is a subtle bug in it.
You may have noticed that if our input only had one elf’s notes (if there are no empty lines), then highest_elf_total
would be 0
.
This is because the conditional on line 22 would remain False
for the entirety of the program.
One way to amend this is to change line 28 such that we are printing not just highest_elf_total
, but either current_elf_total
or highest_elf_total
, whichever is bigger.
We can use max
like before, thereby transforming line 28 into:
print(max(highest_elf_total, current_elf_total))
Part Two.
Recalling the sample puzzle input:
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
The new task.
Our new task is to find the top 3 elves (not just the top 1) that are carrying the most calories, and to find the total calories that those top 3 elves are carrying.
- We will still store this input like we did previously, with
sample_input
representing this list:
['1000', '2000', '3000', '', '4000', '', '5000', '6000', '', '7000', '8000', '90
00', '', '10000']
The thought process, with code.
- Visit every element in the calories list, making sure to add the calorie we find to the current elf’s total.
- If, while adding calories, we encounter an empty string, we must take note of the current elf’s total, and store that total inside a list of totals. This totals list, named
total_calories_per_elf
, will hold all the totals of calories for every elf.
total_calories_per_elf = []
current_elf_total = 0
for calorie in sample_input:
if calorie == '': # denotes next elf's input
total_calories_per_elf.append(current_elf_total)
current_elf_total = 0 # reset before the next batch of calories
else:
current_elf_total += int(calorie)
Did you spot the subtle bug there?
There are still some calories missing.
Specifically, the last elf’s calories were not added to the total_calories_per_elf
list, due to the nature of our data structure and how the data is stored in it.
Looking at the contents of sample_input
again:
['1000', '2000', '3000', '', '4000', '', '5000', '6000', '', '7000', '8000', '90
00', '', '10000']
The current iteration of our code only adds calorie totals for the current elf, if we encounter an empty string, ''
after the current elf’s last calorie.
The last 10000
does not have an empty string after it, and so it will not be added to the total_calories_per_elf
list.
To fix this, we can — after safely assuming that there are still left-over calories stored in current_elf_total
— add those left-over calories to total_calories_per_elf
before going any further.
# add any left-over calories to `total_calories_per_elf`
total_calories_per_elf.append(current_elf_total)
- Sort the totals list in ascending order. The first 3 elements of the sorted list are the top 3 elves that are carrying the most calories.
We will use the builtin sorted()
method which, by default, sorts a list of integers from smallest to biggest. We will need to remember to reverse that default behavior, to get the ascending order we want.
# sort in ascending order
sorted_calories = sorted(total_calories_per_elf, reverse=True)
We can then extract the first three elements of this sorted list using python’s list slicing feature, recalling that the second list index in the slice is excluded from the new list that is returned.
top_three_calories = sorted_calories[0:3]
- The sum of these top three calories is the solution to our task.
print(sum(top_three_calories))
Altogether, the final program looks like this:
sample_input = """
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
""".strip().splitlines()
total_calories_per_elf = []
current_elf_total = 0
for calorie in sample_input:
if calorie == '': # denotes next elf's input
total_calories_per_elf.append(current_elf_total)
current_elf_total = 0 # reset before the next batch of calories
else:
current_elf_total += int(calorie)
# add any left-over calories to `total_calories_per_elf`
total_calories_per_elf.append(current_elf_total)
# sort in ascending order
sorted_calories = sorted(total_calories_per_elf, reverse=True)
top_three_calories = sorted_calories[0:3]
print(sum(top_three_calories))
Congrats! That wraps up Day 1. 🎉🎉🎉