The dfns workspace

Computers are like Old Testament gods; lots of rules and no mercy. –Joseph Campbell

Dyalog ships with a workspace called dfns. It’s perhaps best viewed as a set of diverse examples of … stuff. Hard to explain – maybe think of it as John Scholes’ scrap book, interesting snippets of APL code that may (or may not) be useful to other APLers. No other language I’m aware of has something like this – a folklore snapshot. The oral tradition of the language, written down.

As a “standard library” it’s hopelessly badly organized - so let’s not call it that, and it’s not the intention anyway. Yet it contains some real gems, and it’s outstandingly well documented, most entries accompanied by substantial essays. It contains interpreters for several languages, including Lisp, tons of utility routines, a pretty comprehensive set of functions for manipulating graphs, various non-trivial data structures (avl, splay, redblack) etc etc.

For the learner, perhaps the most productive use of the dfns ws is as inspiration: browsing through it, learning from its examples and well-documented implementations. I thought I’d highlight some of the things I’ve discovered in there and found useful.

iotag

A very useful little function is iotag, for iota-generalized, although I can’t help but read it io-tag. It fleshes out the built-in iota, , both in its monadic and dyadic forms. Let’s look at a few examples:

'iotag' ⎕CY 'dfns' ⍝ Load the iotag function

Inclusive range start to end:

10 iotag 20 ⍝ ⍺ to ⍵ inclusive range
10 11 12 13 14 15 16 17 18 19 20

…and which can also count down:

20 iotag 10
20 19 18 17 16 15 14 13 12 11 10

and is fine with negative numbers, too:

¯10 iotag ¯20
¯20 iotag ¯10
¯10 ¯11 ¯12 ¯13 ¯14 ¯15 ¯16 ¯17 ¯18 ¯19 ¯20
¯20 ¯19 ¯18 ¯17 ¯16 ¯15 ¯14 ¯13 ¯12 ¯11 ¯10

We can also specify a step-size:

10 iotag 21 2
10 12 14 16 18 20

which doesn’t even have to be integer

10 iotag 21 0.53
10 10.53 11.06 11.59 12.12 12.65 13.18 13.71 14.24 14.77 15.3 15.83 16.36 16.89 17.42 17.95 18.48 19.01 19.54 20.07 20.6

It also understands letters:

'Q' iotag 'H'
QPONMLKJIH

It can do many, many more things, also generalizing the index-of aspect of iota.

segs

The segs function splits a character vector on a set of separator chars. Whilst the implementation is simple enough to memorize – and easy enough to derive – it’s a handy tool to have access to.

'segs' ⎕CY 'dfns'
',;: ' segs 'one,two;three four,;:five,six,'
┌───┬───┬─────┬────┬────┬───┐ │one│two│three│four│five│six│ └───┴───┴─────┴────┴────┴───┘

There isn’t much to this:

',;: ' {(~)} 'one,two;three four,;:five,six,'
┌───┬───┬─────┬────┬────┬───┐ │one│two│three│four│five│six│ └───┴───┴─────┴────┴────┴───┘

cmat and pmat

cmat and pmat generates combinations and permutations respectively.

'cmat' 'pmat' ⎕CY 'dfns'

Starting with cmat, what unique ways can we combine three numbers out of 1, 2, 3, 4 and 5?

3 cmat 5
1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5

Or 3 out of 5 letters?

3{[ cmat]}'abcde'
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │abc│abd│abe│acd│ace│ade│bcd│bce│bde│cde│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

Note that cmat produces results that are lexicographically ordered.

Moving on to pmat we generate permutations instead – given an integer right argument, produce all the different ways that many items can be ordered.

pmat 4
1 2 3 4 1 2 4 3 1 3 2 4 1 3 4 2 1 4 2 3 1 4 3 2 2 1 3 4 2 1 4 3 2 3 1 4 2 3 4 1 2 4 1 3 2 4 3 1 3 1 2 4 3 1 4 2 3 2 1 4 3 2 4 1 3 4 1 2 3 4 2 1 4 1 2 3 4 1 3 2 4 2 1 3 4 2 3 1 4 3 1 2 4 3 2 1

span, stpath and scc

There is also an excellent set of routines for graph manipulation, path finding and searching. Whilst a full intro to those is beyond the scope I had in mind for this, we can show some simple examples that perhaps can serve as a jump-off point. The documentation is comprehensive and excellent.

When we deal with graphs, we want to separate the graph structure from the content. The graph routines in the dfns ws only deal with graph structure, so it’s up to you to relate that back to content. We’ll look at how this is done.

So a graph consists of a set of vertices, connected by edges that have a direction. We represent the connectivity as a vector of vectors, where the position is the “vertex id” and the content a list of other vertices reachable from here. So, for example, the graph g

⎕IO0
g    (1 2)(,2)(1 3)(0 4)(,)
┌───┬─┬───┬───┬┐ │1 2│2│1 3│0 4││ └───┴─┴───┴───┴┘

could be visualized like so:

graph1

We can easily create a separate vector that holds each vertex’s contents if we wanted to, say

vertices  'adam' 'bob' 'charlotte' 'damian' 'erica'

Let’s load up a few of the graph functions:

'span' 'stpath' 'scc'⎕CY 'dfns'

…and a little helper function to show graph structure:

show{↑⍕¨(⍳⍴),¨'→',¨}
show g
0 → 1 2 1 → 2 2 → 1 3 3 → 0 4 4 →

The function span finds the spanning tree of a graph, which is basically removing any cycles whilst not making any previously reachable vertices unreachable. So let’s find the (actually, one of potentially several) spanning tree for our graph g, rooted at vertex 0, using the function called span:

st  g span 0

Now that we have a spanning tree, we can find shortest paths in the graph. For example, what’s the shortest path from adam (vertex 0) to erica (vertex 4)? For this we apply the function stpath, spanning tree path:

vertices[st stpath 4]
┌────┬─────────┬──────┬─────┐ │adam│charlotte│damian│erica│ └────┴─────────┴──────┴─────┘

By pre-calculating the spanning tree, we can do quick path finding to any other node in the graph. Note though that this approach will only find one shortest path, even if several of equal length might exist.

The implementations for span and stpath are worth studying. Note, however, that for many path-finding applications in graphs, as the graph gets bigger there are other algorithms worth considering, like Dijkstra’s shortest path, or its heuristic cousin, A*, that would cut the search space.

Strongly Connected Components

The function scc implements Tarjan’s algorithm for finding the strongly connected components of a graph. The strongly connected components are groups of interconnected vertices, so for example a graph defined by the following adjacency index vector

⎕IO  1
  graph  (,2) (1 8)  (,10) (,9) (,10) (,9) (,2) (5 7) (4 6)
┌─┬───┬┬──┬─┬──┬─┬─┬───┬───┐ │2│1 8││10│9│10│9│2│5 7│4 6│ └─┴───┴┴──┴─┴──┴─┴─┴───┴───┘

has the following strongly connected components (graph has vertices labeled ⍳10):

1 2  8
3
4 6 10
5 7  9

Note how vertex 3 is not connected to any other vertices and so forms its own connected component. We can use scc directly on the graph adjacency index vector to label each vertex with a connected component index:

]display (10) (scc graph)
┌→───────────────────┐ ↓1 2 3 4 5 6 7 8 9 10│ │1 1 2 3 4 3 4 1 4 3│ └~───────────────────┘

In the dfns ws there are also graph algorithms operating on weighted graphs – where each edge has a cost that can differ from 1.

If you’re interested in using APL for graphs, there is a complete example provided for route planning in various tube systems of world cities for you to study.