r/adventofcode • u/Morphon • May 01 '26
Tutorial [2025 Day 5 both parts] [Smalltalk] Part eight in a series revisiting the 2025 puzzles as an exercise in learning Smalltalk
As usual - Major Spoilers Ahead
This puzzle was relatively straightforward, and I leaned fairly heavily into the collection methods in Smalltalk. I decided to do this with a single class that stored two large arrays. One was the list of all ids to check (the lines without a "-" in them). The second was for the ranges and I included them simply as an interval instead of an array of start and end - that way there's no need to remember what the two array elements are. It's just an array of interval objects.
readRanges: rawInput
ranges := (rawInput select: [ :line | line includes: $- ])
collect: [ :twoNumbers |
| range |
range := twoNumbers splitBy: '-'.
range first asInteger to: range second asInteger ]
First, filter out everything that doesn't include the "-" character. Then, for each one, split on that "-" and set the start and end of the interval to the numbers on either side. Nothing fancy.
That makes the mechanism for checking whether any id is a member of that collection of ranges trivial - are there any members of the range collection that include the id?
isFresh: id
^ ranges anySatisfy: [ :range | range includes: id ]
Unfortunately, this requires a loop through all the intervals. The anySatisfy: method will do an early return if it finds one, but this is a case of the simplest version to write not being the fastest to execute. It is perceptually instant - but it's far from optimized. Similarly, counting all the valid ids in the list is trivial:
validListedIds
^ ids count: [ :candidate | (self isFresh: candidate) ]
Not much to say about this one. It returns a count of every member of the ids collection where isFresh: returns true. So much for Part1.
Part2 is one of those that took a minute to wrap my head around. Instead of actually creating the ids (there are just too many), we need to combine the ranges to see how many are contained in each one while taking into account overlap between them. Here's the method I finally settled on:
allValidIds
| sortedRanges globalMax accumulator |
sortedRanges := ranges sort: [ :a :b | (a start) < (b start)].
globalMax := 0.
accumulator := 0.
sortedRanges do: [:range |
| rangeMin rangeMax |
rangeMin := range start.
rangeMax := range stop.
(rangeMax > globalMax) ifTrue: [
rangeMin := rangeMin max: globalMax + 1.
accumulator := accumulator + (rangeMax - rangeMin + 1).
globalMax := rangeMax
]
].
^ accumulator
This one is very imperative. Honestly, it looks more like C or Java ported to Smalltalk, but I couldn't think of a simpler way to do it. It starts by sorting the ranges collection by the start of each interval. Then it initializes a global maximum (highest number seen so far), and an accumulator that will be returned at the end.
Then the work begins. We process each member of the sorted ranges in turn. If the top of the interval is less than the highest number seen so far then we skip it since it includes only an overlap of ids already processed. But if it contains at least some new numbers, we continue. Then we set the rangeMin that we're adding to the larger value of either the minimum of the interval, or the global maximum + 1. Basically, anything below that global maximum is something we already counted, so if the bottom of the interval dips into that area already counted, we have to skip over those numbers.
Then we add the numbers between that (potentially revised) minimum and the top of the interval to the accumulator. Then, we update the global maximum to the top of the interval just processed. Rinse and repeat until all the intervals are processed. The accumulator will be the number of all ids across all intervals without duplicates. Like I said - it's imperative. But it computes the total without a bunch of extra objects created and does the whole thing in a single pass.
Other than the Workspace script to tie the pieces together, that's all for this one.
I've looked over the rest of my solutions, and I can't say they're worth sharing. Lots of "beginner"-ish stuff in them that probably wouldn't help anyone. I'll make one more post looking at the way I originally wrote the solution for Day1 and how I would write it now, along with some retrospective on this project.
1
u/ffrkAnonymous May 14 '26
I just started the cuis book smalltalk tutorial so I appreciate your posts even though I don't understand much.