Decode in depth #

Let’s begin with a basic understand of what a number system really means. When we write 123, what we really mean is

+/1 2 3×100 10 1
123

But why 100 10 1? You might say that’s 10*2 1 0, but another way to look at it is ⌽×\1,2⍴10. The 1 here is the “seed” or initial value for our running product. Now we can see a way to generalise this. Instead of 2⍴10 we could choose two different numbers, say 60 and 24. This gives us ⌽×\1 60 24 or 1440 60 1. This would be a days-hours-minutes system, 1 day being 1440 minutes. So, if we have 1 day, 2 hours, 3 minutes, how many minutes do we have?

+/1 2 3×1440 60 1
1563

This brings us to what does. It takes a mixed-radix spec as left argument, and evaluates how many of the smallest unit a given “number” (expressed as a vector of “digits”) corresponds to.

0 24 601 2 3
1563

Note the difference in the spec between the +/× method and the method. We don’t have to specify the unit (which’ll always be 1 anyway) on the little end, but instead, we pad with a 0 on the big end. The 0 is ignored, and could actually be any value. The only reason it’s needed at all is to match the length of the right argument.

Now, APL, of course, allows using a scalar and will distribute it to all positions. This allows things like:

101 2 3  ⍝ base ten
21 0 1   ⍝ binary
123
5

So is really a kind of fanciful cover for +/× or actually +.×, the latter explaining why takes a transposed argument.

10 10 10  2 31 2 3  3 2 1
100 10 1+.×⍉2 31 2 3  3 2 1
123 321
123 321

We can model as:

10 10 1 {(⌽×\)+.×} 2 31 2 3  3 2 1
24 60 1 {(⌽×\)+.×} 1 2 3
123 321
1563

Because has a specific definition rather than being some specialised type-dependent utility, it can be used for some unusual tricks that have little apparent connection to base-conversion. One that has achieved some fame is ⊥⍨ on a Boolean vector. Let’s analyse what it does.

Let’s say we have the vector 1 0 1 1 1. will cause the vector to be used both a base specification and as the count for each “type” place (“hundreds”, “tens”, ones). So we have 1 0 1 1 1⊥1 0 1 1 1. Remember, this really means:

+/(⌽×\1,11 0 1 1 1)×1 0 1 1 1
1 0 1 1 1
3
3

That’s why ⊥⍨ is “count trailing 1s”. Conceptually, we add 1s from the right (though each is multiplied by increasing powers of 1 — all 1*n being always 1 of course), until a 0 causes everything after that to become 0 (n×0 being always 0 of course). Finally, we sum.

Another trick, often used in tacit APL, is 1⊥something. Let’s analyse that one. The first thing we can recognise here is that the 1 will be expanded to match the length of the right argument, so say 1⊥3 1 4 really means 1 1 1⊥3 1 4. This is simply:

+/(⌽×\1,11 1 1)×3 1 4
13 1 4
8
8

×\ applied to a vector of 1s, is still “1”. That’s the multiplicative identity, which means that 1⊥ is equivalent to +/. But remember the transposing when dealing with multi-dimensional arguments, and you’ll soon realise that it is actually +⌿. Let’s look at that. Notice that the two numbers 271 and 314 are represented in base 10 as:

2 32 7 1  3 1 4
2 3
7 1
1 4

Why? Because then we can do:

100 10 1+.×⍉2 32 7 1  3 1 4
271 314

which is the same thing as:

+100 10 1×0 12 32 7 1  3 1 4
271 314

Or, in other words, we multiply each row by its place weight (big endian) and then sum vertically. Then, if the weight is a constant 1, we have a simple vertical summation, or +⌿.

Another trick, also sometimes used in tacit APL is 0⊥something. Let’s analyse that one. First, the left argument is extended to match the shape of the right argument: 0⊥314 is the same as 0 0 0⊥3 1 4. Again, recall that this is the same as

(⌽×\1,10 0 0)×3 1 4
0 0 4

Summing that gives us 4; the last element of the vector:

+/(⌽×\1,10 0 0)×3 1 4
03 1 4
4
4

What happens if we apply this to a higher-rank array? If we examine the rank, we can see it returns the last major cell of its argument:

m3 39?9
c0m
⊃⍴c
4 1 6
5 2 9
7 8 3
7 8 3
3

Since we’re returning the last major cell unmodified, it is the same as ⊢⌿.