Advent of Code (in MiniScript), Day 20
Tags: miniscript, adventofcode, programming
Published: 2022-12-20 15:54:06

Welcome back to my [series](https://dev.to/search?q=JoeStrout%20%23adventofcode&sort_by=published_at&sort_direction=asc) of [Advent of Code](https://adventofcode.com/) solutions in [MiniScript](https://miniscript.org)!  [Day 20](https://adventofcode.com/2022/day/20) was an interesting one.  It looked so easy, but ended up taking me significant time.

The problem gives you a series of numbers, which we are to treat as a circular buffer.  For each number, we shift it forward or back in the buffer according to its value.  So if the number is 3, we shift it forward three places; if it's -12, we shift it back twelve places, always wrapping around as needed.

Sounds pretty easy, right?  There are some potential off-by-one errors lurking in the wrap-around, but those are pretty easy to catch through testing.  I coded up a solution that worked on the test problem quite quickly, ran it on the full input file, and confidently pasted the answer into the challenge page.

My answer was wrong.

There followed a good hour of checking and double-checking my code, writing a completely _different_ (and less efficient) way to shift the numbers around and debugging that just so I could compare it to the first way, coming up with and trying half a dozen additional incorrect answers (all of which generated the right answer on the sample data).  If I still had hair, I would've been tearing it out!

Finally, I had a horrifying realization.  The challenge says

> The numbers should be moved in the order they originally appear in the encrypted file. Numbers moving around during the mixing process do not change the order in which the numbers are moved.

What does this mean?  I took it to mean: if the fifth number in the original input was 42, then we should find 42 in the mixed buffer, wherever that might be, and move it 42 positions from there.

This obviously implies that there is only _one_ 42 in the input.  That seems like such a reasonable assumption that my brain accepted it without question, and for the next hour, I worked as if it were a fact, a stated part of the problem spec.  Then I realized that the spec never actually came out and _said_ this.  With trepidation I did:

![REPL code: lines.len (5000); m = mapUtil.fromTo(lines, lines); m.len (3636)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sghryqzzazvj2pew7pzw.png)

`lines` here is the set of values read from the input file.  `mapUtil.fromTo` is just a quick way to make a map out of two lists; in this case, I mapped each number to itself, which is pointless except that it eliminates duplicates.  So, `m.len` shows how many unique numbers there are.

5000 numbers in the input file, but only 3636 _unique_ numbers.

![Homer Simpson exclaiming "DOH!"](https://i.pinimg.com/originals/e3/51/9a/e3519a3eb4a6397305caab5ba5625bb4.jpg)

OK, so my assumption was wrong, and my approach of just searching the mixed numbers for the next one to do was also wrong.  It worked fine on the sample data because there were no duplicates, but failed miserably on the real input data.

To do it properly, we need to either keep our buffer as little maps or lists that contain the value _and_ the original index; or keep a separate list of indexes, and mix those instead of (or in addition to) the values.  I chose to keep a separate index list, and mix both lists.

So the working code is:

```
import "mapUtil"
import "listUtil"
import "aoc"

partTwo = false
if 1 then fname = "input.txt" else fname = "example.txt"
lines = file.readLines(fname)
if lines == null then
	print "Unable to read: " + fname
	exit
end if
if not lines[-1] then lines.pop

lines.apply @val
msg = lines
if partTwo then msg.multiplyBy 811589153

list.moveFromTo = function(idxA, idxB)
	value = self[idxA]
	self.remove idxA
	if idxB > idxA then self.insert idxB-1, value else self.insert idxB, value
end function

indexes = range(0, msg.len-1)

for iteration in range(1, 1 + 9 * partTwo)
	print "Iteration " +  iteration + "..."
	for idx in range(0, msg.len-1)
		oldIdx = indexes.indexOf(idx)
		value = msg[oldIdx]
		if value == 0 then continue
		steps = value % (msg.len - 1)
		idx = (oldIdx + steps + (steps>0)) % msg.len
		if idx < 0 then idx = idx + msg.len
		msg.moveFromTo oldIdx, (idx % msg.len)
		indexes.moveFromTo oldIdx, (idx % msg.len)
	end for
end for

result = []
zeroIdx = msg.indexOf(0)
result.push msg[(zeroIdx + 1000) % msg.len]
result.push msg[(zeroIdx + 2000) % msg.len]
result.push msg[(zeroIdx + 3000) % msg.len]

print "key numbers: " + result
print "final answer: " + result.sum
```

Part Two was a trivial modification to the first part; we only need to multiply the values by a big number, and do the mixing ten times instead of one.  The code above handles both cases (depending on the value of `partTwo` in line 5).

## Results and Conclusions

Because I wasted so much time before realizing my mistake, it took me an hour and a half to solve Part 1, giving me a rank of 2416.  Part 2 took only a few additional minutes, bumping my rank up to 2016.

It's amazing how, as we are interpreting something like a problem statement, we can make an assumption without even consciously realizing it.  And then we treat that assumption as if it's reality.  Indeed, I find that when somebody has a bug they don't understand, it's usually not something like a typo or incorrect syntax; it's something they are assuming, that is not actually true.  

The art of debugging is mostly the art of checking all your assumptions, and the hardest one to check is always the one you're not even thinking about.