In order to participate in these challenges, you will need to register with the Advent of Code site, so that you can retrieve your own datasets for the puzzles. When you get an answer, you will need to submit it to the Advent of Code site to determine whether the answer is correct. I have already completed the 2025 set using python, so I will know when my SPL generates the correct result.
Each day's puzzle is split into two parts; part one is usually easier than part two, and you cannot normally reach part two until you have successfully completed part one. Day 5 is about counting numbers which appear in the number ranges listed. Please visit the website for full details of the puzzle.
This article contains spoilers!
In fact, the whole article is a spoiler as it contains solutions to the puzzle. If you are trying to solve the puzzle yourself and just want some pointers to get you started, stop reading when you have enough, and return if you get stuck again, or just want to compare your solution to mine!
As with all the Advent of Code puzzles, the description of the problem is aided by some example input; in this case, the input is a list of number ranges followed by a list of numbers to be found. The aim of the puzzle is to determine, for your own dataset, how many numbers appear in the ranges.
The first thing to do is initialise your data. One way to do this is to save it to a csv file and use inputlookup to load it. Alternative, you could just use makeresults (as I have done here), and set a field to the data:
| makeresults
| fields - _time
| eval _raw="3-5
10-14
16-20
12-18
1
5
8
11
17
32"The next step is to create two lists, one for the number ranges and the other for numbers to be checked. The rex command is ideal for this as the format of the ranges is distinctly different from the numbers to be checked.
| rex max_match=0 "(?<range>\d+-\d+)"
| rex max_match=0 "(?m)([^-0-9])(?<ingredient>\d+)$"The regular expression for the ranges is reasonably self-explanatory (a series of one or more digits, followed by a minus (-), followed by another series of one or more digits). (max_match=0 allows the expression to be evaluated multiple times.) The regular expression for the ingredients (numbers to be checked) needs a little more explanation.
| ingredient | range |
1 5 8 11 17 32 | 3-5 10-14 16-20 12-18 |
Part One is looking for the number of ingredients which are present in the ranges provided. In order to check each ingredient against each range, list the ranges for each ingredient, then expand the list so we have an event for each ingredient / range combination.
| stats list(range) as range by ingredient
| mvexpand rangeYou may get a warning about list having reached a limit (100). This can be overcome by setting the limit to zero (0),
| stats list(range) as range by ingredient limit=0
| mvexpand rangeNow, for each range, we can use rex to extract the low and high limits of the range.
| rex field=range "(?<low>\d+)-(?<high>\d+)"We can determine whether the ingredient is fresh if it falls within the range.
| eval fresh=if(ingredient >= low and ingredient <= high, 1, 0)Since an ingredient could fall into more than one range, we simply need the maximum value of fresh for each ingredient.
| stats max(fresh) as fresh by ingredientNow, we can sum all the values of fresh to effectively count the number of fresh ingredients.
| stats sum(fresh) as freshThe second part of the puzzle requires that we determine, for your own dataset, total number of fresh ingredients represented by the ranges, bearing in mind that some of the ranges overlap.
We can start in a similar way to Part One, except we only need the low and high values of the ranges.
| makeresults
| fields - _time
| eval _raw="3-5
10-14
16-20
12-18
1
5
8
11
17
32"
| rex max_match=0 "(?<low>\d+)-(?<high>\d+)"
| fields - _rawNow, we want to determine whether any range overlaps with any other range. So, as we have done in other solutions, we need to generate some field names that we can use in a foreach command to iterate over the ranges.
| appendpipe
[
| eval list=mvrange(0,mvcount(low))
| mvexpand list
| eval list="f".list
| eval name="day_5"
| chart values(eval(0)) as zero by name list useother=f limit=0
| fields - name
]
| where isnotnull(low)Now, we can compare each range with every other range to see if they overlap. It is easier to think of this as determining whether they do not overlap, which is where the high limit of one is less than the low limit of the other, or where the low limit of one is greater than the high limit of the other. Comparing a range against itself is treated as not overlapping.
| foreach f*
[
| eval y=0
| foreach f*
[
| eval overlap=if((tonumber(mvindex(high, x)) < tonumber(mvindex(low, y))-1) or (tonumber(mvindex(low, x)) > tonumber(mvindex(high, y))+1) or x==y, "N", "Y")For overlapping ranges, find the lowest of the low and the highest of the high.
| eval least=if(overlap="Y", min(mvindex(low, x), mvindex(low, y)), 0)
| eval most=if(overlap="Y", max(mvindex(high, x), mvindex(high, y)), 0)Now, reset the low and high multi-value fields so that both of the overlapping ranges have the minimum and maximum values from those ranges respectively. If there is no overlap, the multi-value fields stay the same.
| eval new_low=if(overlap="Y", mvappend(if(x = 0, null(), mvindex(low, 0, x - 1)), least, mvindex(low, x + 1, -1)), low)
| eval low=if(overlap="Y", mvappend(if(y = 0, null(), mvindex(new_low, 0, y-1)), least, mvindex(new_low, y+1, -1)), new_low)
| eval new_high=if(overlap="Y", mvappend(if(x = 0, null(), mvindex(high, 0, x - 1)), most, mvindex(high, x + 1, -1)), high)
| eval high=if(overlap="Y", mvappend(if(y = 0, null(), mvindex(new_high, 0, y-1)), most, mvindex(new_high, y+1, -1)), new_high)Increment the iteration indexes.
| eval y=y+1
]
| eval x=x+1
]We can now remove duplicate ranges. Since overlapping ranges have been merged, we simply deduplicate low and high separately.
| eval low=mvdedup(low)
| eval high=mvdedup(high)Create a multi-value field for each range, and expand it create an event for each unique range.
| eval range=mvzip(low, high, "-")
| mvexpand rangeCalculate the size of each range.
| eval start=mvindex(split(range, "-"), 0)
| eval end=mvindex(split(range, "-"), 1)
| eval size=1 + end - startCalculate the total number of fresh ingredients.
| stats sum(size) as totalBy using nested foreach iterations, we can collapse the ranges into non-overlapping ranges, to make working with these unique ranges more efficient.
Have questions or thoughts? Comment on this article or in Slack #puzzles channel. Whichever you prefer.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.