Part 1: Stock Management
The cafeteria was not a mirage, but I’m not sure I would want to eat here.
It turns out that they have a new stock management system which is incredibly complex for the elves to understand. They need help sorting out which of their available inventory is still fresh.
The stock management database stores the fresh products as a range, e.g. 12-24 - this represents an inclusive set of fresh products, these ranges can also overlap. This is just what is fresh however, if they are actually in stock is determined by following list of individual product IDs.
| |
I can see why they are struggling.
Having done a few of these before I can see where this is going - but I’ll play a little dumb and demonstrate for this write up. The obvious answer here is an intersection of two sets. Building one set up from the available products is easy. Building the set of fresh products is a little more involved, I would need to enumerate the values in each range (inclusive both ends).
That’s easy peasy (I’ve used a loop as that was easier for me to write than a python generator):
| |
The solution to which products are still fresh is as simple as:
| |
Hey presto, 3 of the available ingredients are good to go!
Let’s try it on the real data… oh.
I’ll have to explain to the elves that I need more resources; I can’t even load their database!
Or, I can just avoid unnecessary computation! That’ll save some effort. I said before that I could see where it was going - this was exactly that trap.
The key is to identify what I actually need to compute to solve this problem.
What do I need to know about each ingredient? I just need to know if it falls within any of the fresh ranges. Forget set theory for now, and just think of that algorithm.
I’ll parse my input without expanding anything, using a tuple representing each range:
| |
As a sort of naive approach to this I can now just do the exact algorithm I mentioned; iterate through each ingredient, and then check if it falls within any of the ranges:
| |
And that simple change is able to complete almost instantly! It’s not even that well optimised as it still checks every possible range for each ingredient - I suspect this will probably be an issue in part 2.
Part 2: Fresh Ingredients
The elves are now able to remove their spoiled inventory items. They just want to untange their ridiculous database and need a list of all ingredient IDs that are considered fresh.
That seems simple, just sum each range:
| |
Not quite, I fell for the trap because I forgot an important point: the ranges can overlap. I need to count each distinct fresh ingredient ID, regardless of how many ranges it appears within.
I already know what not to do here as that is exactly what I demonstrated at the beginning - I can’t just enumerate all the ranges into a set and count the result.
So what can be done? What if I just take the smallest start value of a range, and the largest end value?
| |
That gives me an overall range covering all ingredients, but what it doesn’t tell me about is areas of spoiled ingredients within it that weren’t covered by any individual ranges.
There’s something in that idea though, if I sort on the start of each range, I can iterate through and track the end of the range until there there is a break where the start value is outside the current range.
| |
I could then eliminate anything that has both a start and end value less than that maximum as it’s automatically consumed by it too.
| |
That leaves me with another start point which follows the same process until all ranges are consumed. I think that’ll do it - now how to actually write that in python.
| |
It’s probably not pythonic, and I don’t love it (having to “flush” the last range after the loop seems a little awkward to me) - it works though.
Hopefully the cafeteria can get back on track now - I think the dining hall might be next.
More Pythonic?
The question I ask myself once again - how do I make this more python? I don’t think it’s too bad actually; using built in sort functions, slices, a generator to sum the result.
I think the best I can do is clean up a few bits that I didn’t like, I could actually append each new range immediately, and reference it directly in subsequent iterations with [-1] and replacing it, meaning that I don’t need to flush it at the end because it’s already in the list.
| |
Also, there’s a built-in max function I wasn’t using before which is much the same as what I’ve used in Rust, it compares two values returning the largest - perfect for tweaking the end of the range.
As always, full code is available at github.com/lordmoocow/aoc25.
This is where it starts to get interesting. Naive solutions will not do anymore; it’s easy to see the problem and reason about a solution to the example only to find that the real input is designed to be near impossible to process. It really makes you think about necessary computation to achieve the goal. Just 7 days to go now.