Advent of Code 2025 – Day 1 in APL

Advent of Code is an annual challenge in which a new programming puzzle is released every day for the first 12 days of December. We always see some great solutions in Dyalog APL. I’m going to look at the first problem, drawing on my own solution and some of those that have been shared across various message boards and in the APL Wiki.

Parsing

Day 1’s problem tasked us with uncovering the password to enter the North Pole by counting how often a dial points to zero in a sequence of rotations. Before we can solve the problem, we have to parse the input file we’re given. If you’re not interested in the parsing, you can move straight to Part One, but you’ll miss a cool new way to use the -functions!

The standard way to read a text file is to use the ⎕NGET (Native file GET) system function. A little known addition to ⎕NGET, available in the recent Dyalog v20.0 release, is the ability to load a file directly into a matrix. This is faster than loading each line as a character vector and mixing them together, and is very handy for Advent of Code problems, which often have rectangular input files.

To parse the numbers given in the input file, it’s easy to use to execute the text for each number as APL code, yielding the number as a result. For a fun exercise like Advent of Code, this is fine, but in production code, this can be very dangerous. If you’re -ing text provided by a user, they could provide a malicious input like ⎕OFF, or worse! This is why the system function ⎕VFI (Verify Fix Input) exists, to parse numbers and only numbers. ⎕VFI also allows you to configure the separators between numbers. For instance, you could use ','⎕VFI to parse numbers separated by commas.

Using ⎕NGET and ⎕VFI together gives a very neat way to parse the input, abusing the Ls and Rs as delimiters for the numbers.

      ⎕IO←0
      input←⊃⎕NGET 'example.txt' 2 ⍝ provide the flag 2, to load the file as a matrix
      input
L68
L30
R48
L5
R60
L55
L1
L99
R14
L82
      1↓,input ⍝ our input numbers are delimited by 'L's and 'R's
68L30R48L5 R60L55L1 L99R14L82
      size←1⊃'LR'⎕VFI 1↓,input ⍝ use ⎕VFI, telling it that 'L' and 'R' are the separators
      size
68 30 48 5 60 55 1 99 14 82

This is much faster than loading the input into separate lines, and executing the number on each one.

      ]Runtime -c "1⊃'LR'⎕VFI 1↓,⊃⎕NGET 'input.txt' 2" "(⍎1↓⊢)¨⊃⎕NGET 'input.txt' 1"

1⊃'LR'⎕VFI 1↓,⊃⎕NGET 'input.txt' 2 → 6.9E¯4 |    0% ⎕⎕⎕⎕⎕
(⍎1↓⊢)¨⊃⎕NGET 'input.txt' 1 → 5.9E¯3        | +754% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕

In addition to the sizes, we also need to parse the direction of each rotation. This is a simple matter of comparing the first column of the input against 'L' or 'R', choosing a 1 for each 'R' (as a right turn is considered positive), and a ¯1 for each 'L' (as a left turn is considered negative). The classic way to do this is with ¯1*.

      input[;0]
LLRLRLLLRL
      direction←¯1*'L'=input[;0]
      direction
¯1 ¯1 1 ¯1 1 ¯1 ¯1 ¯1 1 ¯1

As an alternative, Reddit user u/light_switchy found the very nice train 'R'(=-≠) for this.

      'R'(=-≠)input[;0]
¯1 ¯1 1 ¯1 1 ¯1 ¯1 ¯1 1 ¯1

Tacit programming can be tricky to read. For the benefit of those of us who prefer explicit code, 'R'(=-≠)x is evaluated as ('R'=x)-('R'≠x), which is the same as ('R'=x)-('L'=x), since x is made up of only 'R's and 'L's.

Part One

Many APLers have solved this year’s first problem, and the approach is so natural that everybody’s solution had a similar shape. By multiplying the size and direction of each rotation, we can find the difference in position that each rotation makes:

      size×direction
¯68 ¯30 48 ¯5 60 ¯55 ¯1 ¯99 14 ¯82

We can then use a sum scan to find the cumulative position of the dial after each rotation, remembering to include the starting position (50):

      +\50,size×direction
50 ¯18 ¯48 0 ¯5 55 0 ¯1 ¯100 ¯86 ¯168

We are working with a circular dial, so instead of going above 99, or below 0, we want to wrap around the numbers on the dial. We can use residue (|) to fix this:

      100|+\50,size×direction
50 82 52 0 95 55 0 99 0 14 32

Finally, we are required to find the number of times the dial is left pointing at zero. Counting this is straightforward, the hard work has already been done:

      +/0=100|+\50,size×direction
3

Part Two

For part two of today’s problem, we are asked to find not only the number of times the dial finished a rotation on zero, but also the number of times any click of the dial points it to zero.

For the size of input we are given today, generating the position of the dial after every single click is an acceptable solution. You could do this using and some transformations, but several people found a nice trick to transform the code from part one into exactly what we need.

In part one, we represented the rotation L68 by the number ¯68, meaning 68 clicks in the negative direction. For this part, we want to represent each of those clicks independently, which we can do by replicating the direction by the size of the rotation, rather than multiplying it:

      size/direction
¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ...

Using the same method as in part one, we can find the position of the dial after every single click, and count the number of times we see a zero:

      +/0=100|+\50,size/direction
6

This is a change of just one character from our part one solution, very elegant! Therefore, our solution for day one is:

      input←⊃⎕NGET 'input.txt' 2   ⍝ load the input as a matrix
      size←1⊃'LR'⎕VFI 1↓,input     ⍝ parse the size of each rotation
      direction←¯1*'L'=input[;0]   ⍝ find the direction of each rotation
      +/0=100|+\50,size×direction  ⍝ solve part 1
      +/0=100|+\50,size/direction  ⍝ solve part 2

If you’re interested in a more performant solution, there is a way to avoid generating all the intermediate positions of the dial. Handling the edge cases is tricky, but it runs much faster. This blog post is long enough already, so I’ll leave determining how it works to you!

      ]Runtime -c "+/0=100|+\50,size/direction" "SecretFastSolution"

+/0=100|+\50,size/direction → 1.2E¯3 |   0% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
SecretFastSolution → 3.2E¯5          | -98% ⎕

Advent of Code 2025 is open for submissions until 12 December 2025, so it’s not too late to get involved! Trying the problems yourself, and then reading the approaches by others, is a fantastic way to learn new programming techniques. Happy APLing!