Decode in depth ⊥
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 60⊥1 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:
10⊥1 2 3 ⍝ base ten
2⊥1 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 3⍴1 2 3 3 2 1
100 10 1+.×⍉2 3⍴1 2 3 3 2 1
123 321
123 321
We can model ⊥
as:
10 10 1 {(⌽×\⌽⍺)+.×⍵} ⍉2 3⍴1 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,⍨1↓1 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,⍨1↓1 1 1)×3 1 4
1⊥3 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 3⍴2 7 1 3 1 4
2 3 7 1 1 4
Why? Because then we can do:
100 10 1+.×⍉2 3⍴2 7 1 3 1 4
271 314
which is the same thing as:
+⌿100 10 1×⍤0 1⍉2 3⍴2 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,⍨1↓0 0 0)×3 1 4
0 0 4
Summing that gives us 4; the last element of the vector:
+/(⌽×\⌽1,⍨1↓0 0 0)×3 1 4
0⊥3 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:
⊢m←3 3⍴9?9
⊢c←0⊥m
⊃⍴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 ⊢⌿
.