If you’re unfamiliar with Advent of Code, you can learn more about it on the official site.

I thoroughly enjoyed the challenges presented by Advent of Code 2023, and I would like to share my reflections in this post. This was my most successful season yet, I managed to collect 40 out of 50 stars. In this post, I’ll delve into my problem-solving approach and highlight why it played a pivotal role in making this season stand out for me. Additionally, I’ll share some minor Python tricks and conveniences I learned along the way.

For those interested in exploring my solutions, they are available on my GitHub.

My Approach

This year, I decided to adopt a different approach to tackle the puzzles. In previous seasons, I often found myself demotivated by around day 13, but for Advent of Code 2023, I was determined to persist longer. The result was exactly what I had hoped for—I stayed engaged throughout the entire month of December.

The key shift in my strategy was to approach each day as a fresh start. Regardless of whether I had completed the previous day’s puzzle, I focused solely on solving the current day’s challenge. This mindset allowed me to distance myself from any perceived “failures” or unsolved puzzles. If I finished a puzzle early, I had the flexibility to revisit a previous unfinished puzzle, but that was considered a bonus rather than a mandatory task.

I set specific time constraints for myself—1.5 hours before work in the morning and an additional 1-2 hours during the evening. This intentional timeboxing served to maximize both enjoyment and learning.

To illustrate my approach more vividly, I’ve created a flow chart

image

As with most participants, I began by handling the example input. However, as many of you are aware, successfully solving for the example input does not always guarantee a solution for the real input. Unfortunately, this discrepancy occurs surprisingly often, and it’s disheartening when it happens.

What I introduced to my approach this season was the allowance for a bit of “cheating.” This is illustrated by the “Lookup (similar) solution online and use it for debugging!” box in the flowchart. To enhance my learning experience and maximize enjoyment, I permitted myself to seek assistance from others’ solutions for debugging purposes. This proved to be a valuable addition to my approach. I navigated to r/adventofcode, filtered the solutions megathread for Python solutions, and looked for approaches similar to mine. This not only earned me a few stars but also provided significant insights.

Here are the key takeaways from incorporating this “cheating” aspect into my approach:

  • I maintained motivation throughout the challenges.
  • I thoroughly read and analyzed other people’s code and solutions.
  • I was exposed to different ways of thinking and attacking the problems.

For me, this approach was a clear win-win. However, it’s important to note that I restricted myself to employing this tactic only after successfully solving for the example input.

Python Learnings

Advent of Code provides me with the perfect opportunity to refresh my Python skills, especially since I don’t use it daily anymore. Throughout the challenges, I picked up several Python tricks and conveniences that I’d like to share.

No Regex

Parsing input is a common task in most puzzles, and while regex is often suitable for this, I surprisingly didn’t use it once this year. In Python, you can go a long way with simple string methods like:

str.replace(old, new)
str.split(sep)

Sequences and Enumerations

  • Define the number of steps in a range with range(0, 10, 2) to get [0, 2, 4, 6, 8]
  • When using enumerate, you can specify the start index, e.g., enumerate([1, 2, 3, 4], start=1) starts at index 1
  • For iterating over columns in a 2D array corresponding to a grid G, use list(zip(*G))

Math

  • sum() is part of the built-in functions provided by Python, but prod() (product) is in the math module
  • Use int() to convert to an integer, and it can handle different bases, e.g., int('1110', 2) gives 14
  • Sets’ intersection (&) is the same as set(a).intersection(set(b))
  • Utilize collections.Counter to count occurrences of a given object in a list

Other

  • str.rfind(sub) complements str.find(sub) by returning the highest index where the substring sub is found
  • Be cautious with copy(), as it’s not a deep copy. For a deep copy, use copy.deepcopy()
  • If you need to write your own sorting function, functools.cmp_to_key allows you to do this. Use it like: res = sorted(lines, key=cmp_to_key(my_compare))