Published December 05, 2025
Ah yes, we've reached day 5 of the 2025 Advent of Code. The first day where attempting a brute
force solution will either run out of memory or run until the heat death of the universe, if not both. Conceptually a simple problem
but with enough nuance to it to make it interesting. Our input is a list of ranges of unspoiled ingredients and a list of the ingredients
we have. For part 1 we need to determine which ingredients from those we have are actually good. This part was easy and did not take me
too long to code up. I created a Range class:
@dataclass
class Range:
start: int
end: int
def __contains__(self, item) -> bool:
return self.start <= item <= self.end
def __len__(self) -> int:
return self.end - self.start + 1
def __lt__(self, other: Range) -> bool:
return self.start < other.start or self.end < other.end
def __gt__(self, other: Range) -> bool:
return self.start > other.start or self.end > other.end
and from there the solution was just to count the number of ingredients that fell into one of the ranges:
return sum(any(ingredient in range_ for range_ in ranges) for ingredient in ingredients)
For part 2 the elves wanted to know how many ingredients, from all of the ingredients in the ranges, were considered to be good. I took a few stabs at this including trying to create a set of all of the ingredients in all of the ranges (it didn't finish running), and multiple attempts with nested loops (they passed the test data and either failed in the real data or didn't finish.) I finally ended up figuring out that my problem was in determining whether or not 2 ranges overlapped. My original check had been:
def overlap(self, other: Range) -> bool:
"""Determine if there is overlap between 2 ranges"""
return self.start >= other.start or self.start >= other.end
which led to ranges that didn't actually overlap reporting as if they did. I corrected that to:
def overlap(self, other: Range) -> bool:
"""Determine if there is overlap between 2 ranges"""
return self.end >= other.start and self.start <= other.end
and suddenly I got the correct answer. Since I have a feeling that I might need to do similar things in the future (whether this year
or in a future one) I put my Range class into my library so
I won't have to reinvent the wheel next time I need something like this. Once I had this fixed, I was able to do a pretty simple loop
to consolidate the ranges and find the answer:
for i, range1 in enumerate(ranges):
if (j := i + 1) >= len(ranges):
break
if consolidated := Range.consolidate(range1, ranges[j]):
ranges[i] = Range(1, 0)
ranges[j] = consolidated
Full solution is at https://github.com/brass75/AdventOfCode/blob/main/aoc_2025/day5.py.