r/adventofcode • u/musifter • 19h ago
Other [2023 Day 1] In Review (Trebuchet?!)
For 2023, we're back to fixing snow production, but on a global scale so the weather machine from 2015 won't do. And so we need to travel to the sky islands. The map this time is a stack of sky islands, which we will work our way up from the bottom and then come back down. Making this another year where the numbers are out of order. And it all starts with us being launched to the sky by trebuchet. Which naturally needs us to do calibration for.
The input for this one is not just numbers this time... it includes lowercase letters which sometimes spell out the digits one through nine. Zero is not involved in any way, which is convenient for some solutions. I will note that vowels do not occur outside of the digit words (where they're required)... and quick test reveals that Chrome does not detect this as any language. So there is some protection against the autotranslate + copy-and-paste issue. Which is an untended gotcha that really don't want people to run into on the first day.
In general, 2023 stepped up difficulty and complexity a bit... and that's particularly noticeable on the early days. Which makes me wonder how much of that was the influence of AI... a little complexity and gotchas to mess a bit with the LLMs so that just maybe, they fail the first try and give the competition programmers a chance (because, they're really fast, especially when allowed to bring their toolkit).
The problem this time is simple... we want the two digit number made of the first and last digit on each line. For part 2, that includes interpreting the words as well. And the gotcha for part 1 is tested for in the word "treb7uchet"... it's a small wrinkle I special cased when the test failed and I saw it:
say "Part 1: ", sum map {(m/^\D*(\d)\D*$/) ? "$1$1" : (m/^\D*(\d).*(\d)\D*$/ and "$1$2")} <>;
For part 2, the gotcha is "twone", "eightwo" and "oneight". Which are present the given test input, but they aren't actually tested. I didn't really notice this issue until after, because I just took my regex to:
m#(one|two|three|four|five|six|seven|eight|nine|\d)#;
$part2 += 10 * $table{$1};
m#.*(one|two|three|four|five|six|seven|eight|nine|\d)#;
$part2 += $table{$1};
With table being a hash of of the words and the digits to their value:
my %table = ('one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5,
'six' => 6, 'seven' => 7, 'eight' => 8, 'nine' => 9,
map {$_ => $_} (1 .. 9));
This avoids special casing, and this is because I was thinking of how I was going to do dc for part 2.
perl -pe's#(.)#ord($1)." "#eg' <input | dc -e'[+d]sF[s.z3<Lq]sN0?[0d[3R48-d9<Nrd0=F3Rs.z3<L]dsLxrA*++?z1<M]dsMxp'
perl -pe's#(.)#ord($1)." "#eg' <input | dc -e'1dd:t18996:t2dd:t32285:t3dd:t24203441:t4dd:t1299471:t5dd:t694023:t6dd:t43444:t7dd:t39325060:t8dd:t49523414:t9dd:t683663:t[dsf]sF[lf0=Fdsl]sN[39-]sS0?[0ddslsf[r48-d9<Sr36*+36 5^%5[d3Rd3R36r^%;td0!=Ns.r1-d0<I]dsIxs.z2<L]dsLxs.llA*lf++?z1<M]dsMxp'
dc doesn't have any string processing, and so these are taking the input as lines of ASCII values. And so it does use ? to process the input (this would be the point where I fully adopted that and trusted it... although it's back to non-functional, so these are dc v1.4.1 solutions).
Half of that part 2 is building the table:
1d d:t 18996:t # 14 23 24 eno
2d d:t 32285:t # 24 32 29 owt
3d d:t 24203441:t # 14 14 27 17 29 eerht
4d d:t 1299471:t # 27 30 24 15 ruof
5d d:t 694023:t # 14 31 18 15 evif
6d d:t 43444:t # 33 18 28 xis
7d d:t 39325060:t # 23 14 31 14 28 neves
8d d:t 49523414:t # 29 17 16 18 14 thgie
9d d:t 683663:t # 14 23 18 23 enin
This maps both the digits and the strings (as essentially base-36 numbers) to the value. The ASCII values are converted to values on the range of 1-35 (0 not being a value in the problem is good here). Note that the strings are backwards because the lines are loaded on a stack and so we parse the lines backwards.
The trick to matching strings here is keeping a window of a five digit base-36 number (36*+36 5^ shifts, adds in the new, and the chops off the oldest with a mod of 536 ). Then there's a loop that takes progressively shorter mods of the window to compare them against the table until it gets a match. When a match is found, the N macro is called, which always sets l to the value, but only sets f once. Yes, first and last do end up backwards, so we add 10 * l + f.
I also did a Smalltalk version, and chose to run with someone's comment about doing string substitutions on the words... I saw "o1e" as a substitute for "one" and immediately knew what was up, and ran to do it. The overlapping means you can't just replace all the words willy-nilly, but if you replace them in a way such that the later substitutions still can match, you're fine. And "o1e" is doing that... it's leaving the "o" at the front for a "twone" and the "e" at the back for an "oneight". Had 7 been spelt "sevon", then you'd need "on1e". Or, alternately, you could change the order of the substitutions so that the "sevon" is done first and "one" doesn't have to worry about it. In any case, it was easy to hand solve for a table of substitutions to start, but then I followed up with code that generates that table (and so is flexible for other languages). It was a fun little exercise that got some extra mileage from the problem.
And so we get a heavier start than we've seen before... numbers and strings together. This helps get us ready for the fact that more of the early problems are going to be heavier than in the past. I seem to recall a bunch of "good dog/scary dog" memes being posted about the shift back and forth between days.
