ShareMyScreen/AdventOfCode/2022/10/CathodeRayTubeJP
Entire solution
i10=: freads '10.txt'
NB. Part 1: signal strength
NB. parse directly to matrix to apply +/\ for cycle,count pairs in rows
ins=: ".@rplc&('addx';'2 ';'noop';'1 0';'-';'_');._2
run=: [: +/\ 0 1, ins
NB. sample time 20+40*i. ...
st =: i.@>:@<.&.:(%&40)&.:(-&20)@{.@{:
NB. "lower" interpolate to find values for time samples
int=: ([ ,: {:@] {~ {.@] <:@I. [)
part1=: [: +/ */@(st int |:)@run
NB. Part 2: find 6x40 scanning display:
scand =: ('.#'{~6 40$ 1>:|@:(- 40|<:)~)
part2=: scand/@((>:i.240) int |:)@run
(part1;part2)i10
Input parsing
A fun one: The input is a very simple assembly language with 2 instructions: noop, doing nothing and taking one clock cycle, and addx, adding a value to the X register taking two clock cycles. Given these simple instructions, I chose to convert each line in an increment in cycle count and an increment in X value by string replacement, followed by ". to turn the line into a list of numbers:
ins=: ".@rplc&('addx';'2 ';'noop';'1 0';'-';'_');._2
The result is a matrix of increments in cycle count and X value.
Part 1
Part 1 asks for the "signal strength" being the sum of the product of the cycle counts 20 60 100 140 180 220 and the corresponding value of X. The values for cycle count are simply the running sum of our parsed input (with adjustment because X happens to start at 1):
run=: [: +/\ 0 1, ins
Expecting part 2 to be something like repeating instructions until a certain condition holds on X, I over-engineered the sampling, thinking the cycles to be selected would be repeated further than 220, writing a verb st that samples times along the same scheme:
st =: i.@>:@<.&.:(%&40)&.:(-&20)@{.@{:
In hindsight, it would have been easier to write:
st=: 20+40*i.6
Now, I thought I'd just use # to select the right time spots from the result of run, but that didn't work, as not all of them are guaranteed to be present. So I had to write a verb to interpolate "lower" (as it's called e.g. in NA handling in Python's Pandas library), taking the value of the row of the first lower cycle count present:
int=: ([ ,: {:@] {~ {.@] <:@I. [)
It uses interval index (I.) to look up the first lower point in the cycle count column, use the corresponding X value and pair it with the sampled time. Working on the transposed matrix is easier because it avoids explicit ranks ("1) and makes subsequent operations far easier. This is an important issue in writing J: formatting data correctly can make code far easier to write, and often faster to execute.
After this, the remainder is trivial:
part1=: [: +/ */@(st int |:)@run
Part 2
Part 2 was a bit more difficult to understand, but not so much more difficult to implement. A dot scans over a 6 x 40 display. If the dot happens to be in a horizontal window of length 3 (the problem calls it a sprite) around the column position indicated by X, it's on.
First, we need to get all X values for every time point from 1 to 240 (as often, one-based indexing); after that, we need to do the scanning thing:
part2=: scand/@((>:i.240) int |:)@run
The hook (- 40|<:)~
yields the difference between X (x) and the column indicated by the clock cycle count (y). If this difference is _1, 0 or 1, i.e. the absolute value 1 or 0, the dot is in the sprite and the current pixel is on. Reshaping the result and visualising it gives the state of the display, completing the solution to part 2:
scand =: ('.#'{~6 40$ 1>:|@:(- 40|<:)~)