Skip to content

Instantly share code, notes, and snippets.

@mlliarm
Last active February 25, 2022 23:01
Show Gist options
  • Save mlliarm/ce69d877fabd3dca6c06594070952ab5 to your computer and use it in GitHub Desktop.
Save mlliarm/ce69d877fabd3dca6c06594070952ab5 to your computer and use it in GitHub Desktop.
An oldtimer's APL code

An oldtimer's APL code

What

Some pretty cool code from the 70s'-80s' that a Reddit user shared at this question.

Here's what u/snarkuzoid wrote:

5x5 Knight's Tour in APL

It puts an A in the middle, then randomly does a knight's tour, marking each cell with the next letter until it runs out of moves. Yes, it's terrible code by modern standards. For some reason we were obsessed with very short programs.

Here's the printout:

5x5 Knight's Tour in APL

There's more to the story of the above code snippet:

Alas, I don't know of any other code fragments I might have laying around.

I only had this one because it was published as winner of the "Nice Knight" contest and I had a copy of the announcement.

Thanks again. This really made my day.

So apparently with this code the Reddit user had won a Leetcode competition of their time.

Pretty, cool, right?

Code

In modern Dyalog APL, v.18.0:

⎕IO1  If it's set to 0 instead, there's no 'A' character in the center and thus the code behaves incorrectly!
ZEX2
  V13+I0×A25'0'
  Z 5 5 A,0A[C1V]'ABCDEFGHIJKLMNOPQRSTUVWXY'[II+1]
  R(2,V)(0.8+V÷5),1+5|¯1+V(A='0')/25
  T(2,V)(0.8+C÷5),1+5|C-1
  2×0V(?8)((10|T-R) 21 12)/V

Results

Here are the results when run the above tradfn a couple times, from RIDE IDE on a Lubuntu 18.04, when I had ⎕IO←0:

     EX2
0JG0M
00DKH
0FIB0
LC0E0
00000
      EX2
00G00
00DIF
0H0BM
JCLE0
O00NK
      EX2
0JG00
000EH
0FIB0
0000D
00C00
      EX2
0RMF0
0EHQL
0NSBG
PIDOT
K0UJC

The above results are incorrect.

Here are some results with ⎕IO←1:

      EX2
00ELG
DMHQ0
IRAFK
NCJSP
0TOB0
      EX2
00000
0E0C0
00AF0
00D0B
0000G
      EX2
GBO00
N0FIP
CHALE
0MDQJ
0RK00
      EX2
GBK00
L0F0J
CHA0E
0MDI0
000N0

The above results are correct.

Correctness of the results

There's no Alpha in the middle. Does the code work correctly with Dyalog APL? (this was because IO had the value zero: ⎕IO←0).

Results with ⎕IO←1 look correct. There's the 'A' char in the middle.

Try it out online

Issue

As is with the tradfn it doesn't work on the tryapl.org site for some reason:

∇
SYNTAX ERROR
      ∇
      ∧

It worked fine on the RIDE IDE of Dyalog APL though.

It seems that tryapl.org doesn't support tradfns.

Fix

According to Adám from Dyalog, now tryapl.org supports tradfns. But the code cannot be copy pasted as is. Instead:

Either paste it line by line, pressing Alt+Enter after each line (except the last), or begin each line (except for the first) with a tab character (not the tab key).

I've found in the docs of Dyalog APL that the Tab key has to be defined as ⎕UCS 9.

So the following code should work on tryapl.org:

⎕IO1  Without this there's no 'A' character in the center!
Tab⎕UCS 9  Defining the tab character ('\t' in C).

ZEX2_online
Tab V13+I0×A25'0'
Tab Z 5 5 A,0A[C1V]'ABCDEFGHIJKLMNOPQRSTUVWXY'[II+1]
Tab R(2,V)(0.8+V÷5),1+5|¯1+V(A='0')/25
Tab T(2,V)(0.8+C÷5),1+5|C-1
Tab 2×0V(?8)((10|T-R) 21 12)/V

Update: the above code still fails in tryapl.org.

Future work

  • Make sure that the tradfn implementation is correct for Dyalog APL. All good.
  • Rewrite the tradfn as a dfn so that it runs on the online interpreter...First understand the program. From what the folks in the APLjk discord server say, the line →2×0≠⍴V←(?8)⌽((10⊥|⍉T-R)∊21 12)/V doesn't work in dfns because the right arrow is only for tradfns and tradfns aren't supported in tryapl.org.

Thanks

  • To the user u/snarkuzoid who trusted me with their code.
  • To bubbler from APLjk discord server that found the IO bug.
  • To Adám for trying to debug the tradfn error that appeared in tryapl.org.
@steveAllen0112
Copy link

steveAllen0112 commented Feb 24, 2022

.First understand the program.

Here is the breakdown of what the tradfn is doing:

∇Z←EX2 V;Z;R;T;A;I;C
    ⍝ Initialize Variables

    ⍝ ORIGINAL LINE
    ⍝   V←13+I←0×⍴A←25⍴'0'

    ⍝ EXPANDED

    ⍝ The entire board will be represented as a 5x5 character matrix
    ⍝ But in memory we're keeping it as a 25 character vector
    ⍝ (It is reshaped for output)
    ⍝ Pre-fill the board vector with 0's for unfilled positions
    A←25⍴'0'

    ⍝ I represents the index of where we're at in the Alphabet.
    ⍝ Since it will be iterated by one in the very next step,
    ⍝ we should initialize it to 0; then A = 1, B = 2, etc.
    ⍝ NB: If we have ⎕IO←0, then it should be initialized to ¯1
    I←0

    ⍝ V represents the total set of possible moves from this position
    ⍝ Because the board is a 25-character vector,
    ⍝ the "center" is position 13; Start there
    V←13

    ⍝ Select the next letter and
    ⍝ fill it in at the first of the positions listed
    ⍝ Reshape the result for output
    ⍝ (in case there are no moves left after this)
    Z← 5 5 ⍴A,0⍴A[C←1↑V]←'ABCDEFGHIJKLMNOPQRSTUVWXY'[I←I+1]

    ⍝ The Row/Column conversions below are based on the math
    ⍝ to convert from the index in the 25-character vector
    ⍝ to the column {1+5|¯1+⍵} and row {⌊0.8+⍵÷5}

    ⍝ Calculate the remaining Row/Column index pairs
    ⍝ Store them as a vertical matrix of 2 columns
    ⍝ The first column containing the Row,
    ⍝ and the second column containing the Column
    R←⍉(2,⍴V)⍴(⌊0.8+V÷5),1+5|¯1+V←(A='0')/⍳25

    ⍝ Calculate the Row/Column pair of the last move
    ⍝ Extend it to match with every row
    T←(⌽2,⍴V)⍴(⌊0.8+C÷5),1+5|C-1


    ⍝ ORIGINAL LINE
    ⍝  →2×0≠⍴V←(?8)⌽((10⊥|⍉T-R)∊ 21 12)/V
                                  
    ⍝ EXPANDED

    ⍝ A knight's move is two in one direction, and one in another
    ⍝ Subtracting the "Remaining Possible Moves" matrix
    ⍝ from the "Previous Move" matrix   
    ⍝ yields the relative moves as row/column pairs on
    ⍝ a grid centered on the last move
    ⍝ knight's moves on such a centered grid are:
    ⍝    up 1, left 2 =>  -1 -2
    ⍝    up 1, right 2 => -1  2
    ⍝    down 1, left 2 => 1 -2
    ⍝    down 1, right 2 => 1 2
    ⍝ Due to the symmetric nature of knight's moves, we can
    ⍝ Take the absolute values of these
    ⍝ Converting that, then, to decimal using the Base operator,
    ⍝ has the effect of combining the two numbers to make the
    ⍝ membership check easier.  The only two possibilities then are 21 and 12.
    ⍝ Therefore, the Remaining Possible Moves are compressed down to
    ⍝ Remaining Possible Knight's Moves only
    ⍝ This set is then randomly rotated (equivalent of shuffle,
    ⍝ since the leftmost is the one use to pick the next move, above),
    V←(?8)⌽((10⊥|⍉T-R)∊ 21 12)/V

    ⍝ If there are any possible moves,
    ⍝ go to the beginning of the loop,
    ⍝ Otherwise, exit (returns the current board)
    →2×0≠⍴V
∇

@steveAllen0112
Copy link

From what the folks in the APLjk discord server say, the line →2×0≠⍴V←(?8)⌽((10⊥|⍉T-R)∊21 12)/V doesn't work in dfns because the right arrow is only for tradfns and tradfns aren't supported in tryapl.org.

It seems to me that recursion would be the way to go, instead?

@steveAllen0112
Copy link

This isn't complete, but it's a start, maybe in Bag'o'dfns style.

Z←EX_BoDfns {

    BOARD←25⍴'0';I←0;MOVES←13;row;col;points;grid;spacesOf;offset;knights;rel;coord;movesFrom;canSee
    ⎕IO←1

    row         ←{1+5|⍵-1}                              ⍝ Get Rows of indexes
    col         ←{⌊0.8+⍵÷5}                             ⍝ Get Columns of indexes
    points      ←{row ⍵,col ⍵}                          ⍝ Convert indexes to coordinates in-line
    grid        ←{⍉(2,⍴⍵)⍴⍵}                            ⍝ Get Coordinates as matrix
    spacesOf    ←{⍵='0'/⍳25}                            ⍝ Get the indexes of the remaining spaces
    offset      ←{(⌽2,⍴⍺)⍴⍵}                            ⍝ Spread coords to match
    knightMoves ←{(10⊥|⍉⍵)∊21 12}                       ⍝ Get the Knight's Moves in a set
    from        ←{(⍺ offset points ⍵) - grid points ⍺}  ⍝ Relativize grid to given space
    canJumpTo   ←{(knightMoves ⍵ from ⍺)/⍵}             ⍝ Get Valid Moves from space
    
    BOARD[MOVE←1↑MOVES]←⎕A[I←I+1]               ⍝ Add next letter at first move in possibles list
    Z←5 5⍴BOARD                                 ⍝ Make it an actual board for output
    MOVES←(?8)⌽MOVE canJumpTo spacesOf BOARD    ⍝ Get possible next moves, randomized
 
    ⍝ TODO: Figure out how to properly recurse it   
}

@mlliarm
Copy link
Author

mlliarm commented Feb 24, 2022

@steveAllen0112 thanks so much !

@steveAllen0112
Copy link

Ok, I did some more work on this, and I got the dfn working, with some enhancements, too. See my fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment