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!

Announcing Dyalog v19.4.1

Although the core language primitives (also known as squiggles) are closest to our hearts, we spend a lot of time creating interfaces to external components such as the operating system, widely-used APIs, and file and data formats. The core language remains stable with occasional extensions, but the system functions that provide these interfaces need constant enhancements as the world evolves around us.

The last few years have seen dramatic changes to the computing world and the array of things with which an APL application needs to interact. We would like to highlight the following features of version 19.4.1 – released today – that are likely to be very useful in the years to come:

AI-related:

  • ⎕AI      Artificial Intelligence
  • ⎕DF      LLM Degrees of Freedom
  • ⎕DL      Deep Learning level
  • ⎕DQ      Data Query
  • ⎕FIX     Fix code automatically
  • ⎕ML      Machine Learning

Online safety:

  • ⎕CT      Counter-Terrorism event
  • ⎕DR      Disaster Recovery event
  • ⎕PW      Password manager
  • ⎕SHADOW  deep state integration
  • ⎕STATE   official government integration
  • ⎕WC      for when you really need to go
  • ⎕WX      weather control

Communications:

  • ⎕AT      Bluesky protocol
  • ⎕DM      send Direct Message
  • ⎕FCHK    Fact Check
  • ⎕IO      universal Input/Output
  • ⎕RL      Real Life (inverse of ⎕SM)
  • ⎕SM      Social Media access
  • ⎕VR      Virtual Reality support

Miscellaneous:

  • ⎕ATX     motherboard properties
  • ⎕FUNTIE  deliver clothing
  • ⎕FX      toggle special effects
  • ⎕NA      (not applicable)
  • ⎕PP      PowerPoint mode
  • ⎕RTL     order of execution

Download Dyalog (it is free!) and explore these features – let us know what you think. Meanwhile, we at Dyalog Ltd will continue our hard work adding ever more value to Dyalog!

Numeric Case Conversion

Dyalog version 18.0, released in June 2020, introduced the Case Convert system function ⎕C. It was a replacement for the long-lived (since version 15.0, from June 2016) I-beam 819⌶, which was then deprecated. (By the way, did you know that the digits 819 were chosen to be reminiscent of the letters BIg as in big — uppercase — letters?) It is expected that 819⌶ will be disabled in the next major version of Dyalog APL.

⎕C already has several advantages over 819⌶, for example the ability to case-fold rather than lowercase. (Did you know that Cherokee syllabary case-folds to uppercase?) With today’s release of Dyalog version 19.4.1, we’re happy to announce a further extension of ⎕C, covering scaled format (also known as scientific or E notation) and complex numbers.

By default, APL uses the letter E to separate mantissa and exponent in very large and very small numbers. Similarly, the letter J is used to separate real and imaginary parts of complex numbers:

      2 20 * 64 ¯24
1.844674407E19 5.960464478E¯32
      ¯1 ¯5*0.5
0J1 0J2.236067977

For input, however, e and j are accepted in addition to E and J:

      1E4 1J4 ≡ 1e4 1j4
1

You can now conveniently mitigate this asymmetry using ⎕C:

      ⎕C 2 20 * 64 ¯24
1.844674407e19 5.960464478e¯32
      ⎕C ¯1 ¯5 * 0.5
0j1 0j2.236067977

We hope that this added functionality will be exploited to its fullest by all our users. Please contact us if you experience any stability issues with the new feature.

Extending Structural Functions to Scalars

Traditionally, the set of monadic reversing or reflecting primitives, Reverse-First (), Transpose (), and Reverse () apply to entire arrays and are defined as identity functions on scalar arguments. Dyalog v19.0 extends the definitions to provide equivalent reflections on scalars.

Character Data

We expect that the new transformations will be most useful on characters. For example:

      (⍉'A')(⌽'P')(⊖'L')
ᗉꟼΓ

Note that you can apply the same transformation to all the items of an array using Each (¨) or Rank (⍤0):

      ⌽¨ 'New APL?'
Ͷɘw Aꟼ⅃⸮
      (⊖⍤0) 'CAN HAPPEN SOON!'
C∀N H∀ЬЬEN ꙄOOͶ¡

Composition allows the combination of reflections to perform rotation:

      ⊖∘⍉¨ '→A/common'  ⍝ 90° clockwise rotation
↓ᗆ\ᴒoᴟᴟoᴝ
      ⌽∘⊖¨ '→A/common'  ⍝ 180° rotation
←∀/ɔoɯɯou

We can combine the above techniques with additional structural functions, including reflection of the entire array, to achieve more advanced effects that are often needed in modern user interfaces:

      ⌽¨ ⌽ 'New APL?'     ⍝ mirror
⸮⅃ꟼA wɘͶ
      ⊖∘⍉¨ ⍪ '→A/common'  ⍝ vertical
↓
ᗆ
\
ᴒ
o
ᴟ
ᴟ
o
ᴝ
      ⌽∘⊖¨ ⌽ '→A/common'  ⍝ upside-down
uoɯɯoɔ/∀←

Numeric Data

Although the transformations are more easily applicable to characters, many numbers are also in the domain of the extended functions:

      ⌽¨ 1.618 2.71828 3.1415
816.1 82817.2 5141.3
      ⌽¨ 3J4 0J1 0.5
4J3 1 5
      ⌽∘⊖¨ 60 69 908    ⍝ 180° rotation
9 69 806
      ⍉⌽ 8              ⍝ 90° counter-clockwise rotation
1.797693135E308

Notes

Character Data

  • Although the new definitions are available in both 32-bit and 64-bit Unicode editions of Dyalog, very few characters can be reflected in the Classic edition.
  • A TRANSLATION ERROR will be signalled if the required result cannot be represented.

For example, using the Classic Edition where the Rank operator is represented as ⎕U2364:

      (⊖ ⎕U2364 0) 'PHI!'
bHI¡
      (⊖ ⎕U2364 0) 'ABC'
TRANSLATION ERROR: Unicode character ⎕UCS 8704 not in ⎕AVU
      (⊖⎕U2364 0)'ABC'
      ∧

Numeric Data

  • The result of numeric reflections can depend on the value of ⎕FR.
  • A DOMAIN ERROR will be signalled if the required result cannot be represented.

For example, beginning with the default value ⎕FR←645:

      ⌽ 1.2345E67
DOMAIN ERROR
      ⌽1.2345E67
      ∧
      ⎕FR←1287
      ⌽ 1.2345E67    ⍝ 76×10*5432.1
9.56783313E5433

Conclusion

Although it is extremely unlikely that real applications rely on the current behaviour, the extensions are potentially breaking changes and are, therefore, being released as part of a major version upgrade.

We are somewhat surprised that these obviously useful extensions have been ignored by the APL community for such a long time, and are very pleased to finally make them available to commercial, educational and hobbyist users. Please contact ʇɹoddns@dyalog.com if you would like to test a pre-release of Dyalog v19.0 and help us understand the potential impact on existing applications.

Dyalog Version 18.4.1

During the recent APL Seeds ’22 meeting, it was suggested that we introduce keywords that could be used as an alternative to APL symbols. Several historical APL systems have provided such mechanisms. However, rather than adopting one of the old keyword schemes, we have decided to go for a more future-proof solution, recognising that the modern equivalent of keywords is emojis.

Emojis are already in widespread usage: they are included in fonts, and there is support for entry of emojis on a wide variety of devices. We have decided to adopt the existing proposal by StavromulaBeta, with minor changes. Examples include:

New Emoji Legacy Glyph New Emoji Legacy Glyph
? ?
' ?
? : ?
? ( ? )
? { ? }

In addition to usage of the language bar, and platform-specific emoji input methods, input via emoticons like :) and shortcodes like :slightly_smiling_face: (as used in chat clients and on GitHub, respectively), can be toggled on.

Screen-shot of a representative 18.4.1 sample session.


Backwards compatibility will be provided by an automatic translation mechanism on )LOAD of existing workspaces, and Link will update all source files, on first use of the new version 18.4.1.

For users of the Classic edition, new ⎕Uxxxxx spellings will be introduced. For example, the last expression in the above screen-shot will be:

⎕U1f31c⎕U1f9ee⎕U1f9ec⎕U1f914⎕U1f910⎕U1f534⎕U1f642⎕U1f4da⎕U1f4c8⎕U1f33f⎕U1f914⎕U1f31b ⎕U1f621⎕U1f910 k6174⎕U1f351 ⎕U1f931⎕U1f4da 9999⎕U1f385⎕U1f645 1111⎕U1f9ed⎕U1f4da9

Unfortunately, we are not able to make the new version available on the Apple macOS® platform, where the use of a certain of fruit emoji makes it impossible to introduce the new spelling.

We will be announcing the release date for version 18.4.1 shortly. Please contact support@dyalog.com if you would like to participate in testing the pre-release, or have suggestions for improving the choice of emojis.

Speed versus Accuracy: the User’s Choice

At Dyalog we have long striven for both correctness and high performance in our implementation. However, our views on this matter have recently undergone an historic shift in paradigm which we are excited to share with our users. We now intend to provide the best experience to the user of Dyalog APL not by providing correctness and speed, but rather correctness or speed, with a user-specified tradeoff between the two.

The upcoming release of version 17.1 includes a powerful new feature: the correctness–performance slider. To find this option, select Options>Configure>General in the IDE, or Edit>Preferences>General in RIDE. The slider is labelled “Execution Properties” and may be set at any time, although users should note that the effective correctness may be reduced if this is done while an in-progress function is on the stack.

With the slider at its default position near the middle, Dyalog will make an effort to balance performance and correctness. Computations will proceed at a reasonably brisk pace, and slightly wrong answers will appear occasionally while very wrong ones come up only rarely. As the slider is moved to the left, correctness is increased at the expense of performance. You’ll have to wait for your results but when you get them they’ll be numbers you can trust. Moving the slider to the right will have the opposite effect, increasing speed at the expense of more frequent misparsings and significant floating point error. Perfect for startups!

Motivation

The seasoned programmer has most likely experienced the same issues as us, and may already be rushing to incorporate our ideas in his or her own code. In the interest of transparency, however, we wish to explain a bit further our experiences with the speed-correctness tradeoff.

Most often we encounter this tradeoff in one direction: when writing to improve the performance of a particular interpreter operation we sometimes find the results returned are different. In the past such cases were seen as bugs to be corrected, but we now understand them to simply be instances of a universal rule. Conversely, fixes for obscure parsing issues which slow down parsing of equally obscure but already correct cases are no longer cause for concern: we simply condition them on the appropriate slider threshold.

In the graph below we plot the accuracy and performance of several algorithms to compute the inner product !.○ on two large array arguments. Performance is measured in throughput (GB/s) while accuracy is defined to be the cosine similarity of the returned solution relative to a very precise result worked out with paper and pencil.

On plotting these results the nature of our plight became clear, and we added the performance-correctness slider to Dyalog version 17.1 as fast as possible. This post was written as accompaniment, with similar haste.

Results

We profiled a large sample program with many different execution settings and obtained the results shown below. As you can see, Dyalog can be quite stable, or quite fast, depending on how the performance-correctness slider is set.

We believe these results demonstrate excellent value for all of our clients. Large and conscientious businesses can set the slider to correctness to encounter very few errors in execution. Rest assured, if errors are reported with these settings, we will do our best to shift them to the right side of the performance-correctness continuum! In contrast, the APL thrill-seeker will find much to like at the speedy end of the spectrum, as more frequent crashes are compounded by an interpreter that gets to them faster.

Future extensions

Although we believe the provided options will satisfy most users, some tasks require an implementation so fast, or so correct, that Dyalog cannot currently offer satisfactory performance along the relevant axis. To rectify this in the future we intend to offer more powerful facilities which extend the extremes of the correctness-performance slider. Potential clients who are exceptionally interested in correctness, such as NASA and Airbus, should contact us about an interpreter which runs multiple algorithms for each operation and chooses the majority result. For users interested in speed above all else we propose to offer an interpreter which only computes a part of its result and leaves the rest uninitialised, thus obtaining for example a 50% correct result in only half the time.

Even more extreme tradeoffs are possible. For the most correct results we are considering an algorithm which adds the desired computation to Wikipedia’s “List of unsolved problems in mathematics”, and then scans mathematical journals until a result with proof is obtained. For extremely fast responses we propose to train a shallow neural network on APL sessions so that it can, without interpreting any APL, print something that basically looks like it could be the right answer. Such an option would be a useful and efficient tool for programmers who cannot use APL, but insist on doing so anyway.

Although our plans for the future may be much grander, we’re quite excited to be the first language to offer user-selectable implementation tradeoffs at all. We’re sure you’ll be happy with either the correctness or the performance of Dyalog 17.1!