By: Stefan Kruger
Recently, I was looking for a Dyalog problem to pit my wits against, and I asked in the APL Orchard if AdΓ‘m could make one up:
A CMC, if you’re unfamiliar with the APL Orchard, is a Chat Mini Challenge β typically an informal code golf challenge to produce the shortest possible bit of code that solves the set problem. Code golf practitioners usually delve into the dustiest corners of a language and, if practiced diligently, this can lead to a deep mastery over time, even if some dirty hacks techniques employed may be frowned upon in production code.
Anyway. AdΓ‘m responded with the following:
The Mysterious Case of 0 0β΄0
Hmm. What even is that thingβ½ It’s only 5 characters already β not much scope to shrink that, surely? Let’s have a look at it with full boxing:
βIOβ0
]Box on -style=max
βββββββββββββββββββ
βWas ON -style=maxβ
βββββββββββββββββββ
0 0β΄0
βββ
β½0β
β~β
In the boxed output, the β½
tells us that the leading axis has length 0, the β
means that the trailing axis has length 0, and the ~
means that the array is non-nested.
This is a simple numeric array of two dimensions, each of length 0 β think of it as a spreadsheet or table before you’ve put any rows or columns of data in it.
I found this problem difficult to get started with, so I searched for the expression on APL Cart, and discovered that:
]Box off
Was ON
]APLCart 0 0β΄0 β Dyalog 18.1 has APLCart built in! Fancy.
X,Y,Z:any M,N:num I,J:int A,B:Bool C,D:char f,g,h:fn ax:axis s:scal v:vec m:mat
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β¬β€β¬ zero-by-zero numeric matrix
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Showing 1 of 1 matches
]Box on
βββββββββ
βWas OFFβ
βββββββββ
Surely not? Let’s try that suggestion:
(0 0β΄0)β‘β¬β€β¬
1
Well, it clearly works, but why? Let’s see if we can figure that one out.
In the basic case for encode, Xβ€Y
, if X
and Y
are vectors, the result will have one column for each element in Y
and one row for each element in X
. Using the example from the language bar:
2 2 2 2 β€ 5 7 12 β Convert decimal to binary
βββββββ
β0 0 1β
β1 1 1β
β0 1 0β
β1 1 0β
β~βββββ
we have a shape of 4 3
, as the length of the vector to the left (2 2 2 2
) is 4 and the length of the vector to the right (5 7 12
) is 3.
Returning to β¬β€β¬
, given the above, we can deduce that the result should have a rank of 2 with a shape of 0 0
which, of course, is what we wanted to achieve:
β΄β¬β€β¬ β Shape 0 0
βββββ
β0 0β
β~βββ
Disappointingly, I had to cheat by looking up the answer. However, are there any more length-3 solutions? Apparently, β¬β€β¬
is “reasonably well known”. I decided to write a simple brute-force search function to look for any other solutions.
This works as follows:
- Create all possible length-3 combinations of APL glyphs
- Evaluate each in turn, skipping those that error
- Save those that evaluate to
0 0β΄0
Here’s what I ended up with:
β rβBruter target;glyphs;combo;eval
glyphs β '0β¬+-ΓΓ·*ββΉβ|βββ₯β€β£β’=β β€<>β₯β‘β’β¨β§β²β±ββββββ·βββ³βΈββ·βͺβ©~/\βΏβ,βͺβ΄β½ββΒ¨β¨β£.ββ€β₯@βΈβΊββΒ―'
r β β¬
:For combo :In ,β.,β£2β¨glyphs
:Trap 0
eval β βcombo
:Else
:Continue
:EndTrap
:If evalβ‘target
r ,β βcombo
:EndIf
:EndFor
β
I decided to leave out a few glyphs that I guessed would be unlikely to feature (ββββ βββ
), and I only added the number 0. Let’s see what that generates:
7 5β΄Bruter 0 0β΄0 βΒ 7Γ5 layout added for clarity after the fact
βββββββββββββββββββββββββββββββββ
β βββββ βββββ βββββ βββββ βββββ β
β ββ¬β€β¬β β+βΈβ¬β β-βΈβ¬β βΓβΈβ¬β βΓ·βΈβ¬β β
β βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ β
β β*βΈβ¬β βββΈβ¬β βββΈβ¬β β|βΈβ¬β βββΈβ¬β β
β βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ β
β βββΈβ¬β ββ€β¨β¬β ββ€βΈβ¬β ββ’βΈβ¬β β=βΈβ¬β β
β βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ β
β ββ βΈβ¬β ββ€βΈβ¬β β<βΈβ¬β β>βΈβ¬β ββ₯βΈβ¬β β
β βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ β
β ββ¨βΈβ¬β ββ§βΈβ¬β ββ²βΈβ¬β ββ±βΈβ¬β βββΈ0β β
β βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ β
β βββΈβ¬β βββΈβ¬β ββ·βΈβ¬β ββ©βΈβ¬β β/βΈβ¬β β
β βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ β
β ββΏβΈβ¬β ββ΄βΈβ¬β ββ½βΈβ¬β βββΈβ¬β βββΈβ¬β β
β βββββ βββββ βββββ βββββ βββββ β
βββββββββββββββββββββββββββββββββ
Wow! There are 35 of them (for βIOβ0
)!
There are clearly patterns here β we can see our friend β¬β€β¬
right at the beginning, and also its equivalent β€β¨β¬
. We can also see ββΈ0
which, in retrospect, perhaps I should have thought of in the first place.
The remaining 32 solutions all feature key. The majority of those have a scalar function operand; we can treat this whole group as equivalent. Let’s tackle that group first, with the operand +
as the example:
+βΈβ¬
βββ
β½0β
β~β
Let’s recap what key does. The operand dyadic function is called with each unique element of the argument in turn as its left argument, and a vector of indices of occurrences as its right. Key then returns a rank 2 array of the results. For example:
{βΊ β΅}βΈ'Mississippi'
ββββββββββββββββ
β βββ β
β M β1β β
β - β~β β
β ββββββββββ β
β i β2 5 8 11β β
β - β~ββββββββ β
β βββββββββ β
β s β3 4 6 7β β
β - β~βββββββ β
β ββββββ β
β p β9 10β β
β - β~ββββ β
ββββββββββββββββ
With an argument of the empty vector β¬
, in the operand function, βΊ
will always be 0
β the prototype element of our empty numeric vector:
{βββΊ}βΈβ¬
0
What about β΅
? Well, it must be an empty numeric vector (no found indices):
{βββ΅}βΈβ¬
βββ
β0β
β~β
So, with a scalar function like +
as the operand, we end up with 0+β¬
. Key returns an array where each major cell is the result of the function applied to the unique element and its indices, which in this case is an array with major cells of the structure β¬
. How many such major cells? 0. So the result is 0βΏ1 0β΄β¬
, which is, of course, 0 0β΄0
.
So for 0 f β¬
for any scalar dyadic function f
, the above holds. For example:
0>β¬
βββ
β0β
β~β
>βΈβ¬
βββ
β½0β
β~β
Then we have a lot of non-scalar operands, like β/β·ββ
. All we need to understand is why they produce β¬
when applied as 0 f β¬
, as key will call them. Some of these are pretty obvious, like find, β·
:
0β·β¬ β Find all locations of 0 in the empty vector
βββ
β0β
β~β
or take and drop, ββ
:
0ββ¬ β Take no elements from the beginning of the empty vector
βββ
β0β
β~β
0ββ¬ β Drop no elements from the end of the empty vector
βββ
β0β
β~β
However, β
gives us a dyadic transpose β and, perhaps unexpectedly, this one is βIO
-dependent:
0ββ¬
βββ
β0β
β~β
At first glance, this made no sense to me. The reason this works is that transposing a vector is an identity operation:
β'Transposing a vector returns the vector'
βββββββββββββββββββββββββββββββββββββββββ
βTransposing a vector returns the vectorβ
βββββββββββββββββββββββββββββββββββββββββ
A vector has a single axis, so “reordering the axes” doesn’t do anything. The same holds true for the dyadic form, assuming the left argument is βIO
:
0β'Same for the dyadic form. Sometimes.'
ββββββββββββββββββββββββββββββββββββββ
βSame for the dyadic form. Sometimes.β
ββββββββββββββββββββββββββββββββββββββ
This is the reason why this only works for βIOβ0
: Key always provides 0 for the left argument. For βIOβ1
we will get a DOMAIN ERROR
:
βIOβ1
0β'Same for the dyadic form. Sometimes.'
DOMAIN ERROR
0β'Same for the dyadic form. Sometimes.'
β§
A few others stand out. Replicate (and its sibling replicate-first), for example:
/βΈβ¬
βββ
β½0β
β~β
will end up as:
0/β¬ β zero empty vectors
βββ
β0β
β~β
in the left operand β zero empty vectors is still the empty vector. Reshape is similar:
0β΄β¬ βΒ zero empty vectors
βββ
β0β
β~β
Encode tries to express β¬
in the 0 radix; also an empty vector:
0β€β¬
βββ
β0β
β~β
My favourite, though, is probably ββΈ0
, and, as I said earlier, I’m a bit disappointed I didn’t find that before resorting to brute force and ignorance. Where (βΈ
) returns a vector of indices with 1 for its Boolean array right argument. If we call it with a right argument of 0, we’ll get an empty vector of empty numeric vectors. This is because the one (and only) valid index into a scalar is the empty vector, and none of them (!) are non-zero.
βΈ0
βββββββ
β βββ β
β β0β β
β β~β β
βββββββ
Trading depth for rank, we get the shape we want:
ββΈ0
βββ
β½0β
β~β
What About 0β΄ββ¬
?
The expression βΈ0
above is the only length-2 way to produce an empty vector of empty numeric vectors:
(βΈ0)β‘0β΄ββ¬
1
However, there are several interesting length-3 solutions. Deploying the Bruter
again:
8 7β΄resβBruter 0β΄ββ¬
βββββββββββββββββββββββββββββββββββββββββββββ
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β β0β0β β0ββ¬β β0ββ¬β ββ¬β0β ββ¬ββ¬β ββ¬ββ¬β β+βΈ0β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β β-βΈ0β βΓβΈ0β βΓ·βΈ0β β*βΈ0β βββΈ0β βββΈ0β β|βΈ0β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββΈ0β βββΈ0β ββ£βΈ0β ββ’βΈ0β βββ¨0β βββ¨β¬β βββΈ0β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββ¨β¬β ββ·βΈ0β ββ³Β¨β¬β ββΈ00β ββΈ0.β ββΈ+0β ββΈ-0β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β ββΈΓ0β ββΈβ0β ββΈ|0β ββΈβ0β ββΈβ0β ββΈβ£0β ββΈβ’0β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β ββΈβ‘0β ββΈβ’β¬β ββΈβ0β ββΈβ0β ββΈβ0β ββΈβ0β ββΈββ¬β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β ββΈβ0β ββΈβ·0β ββΈβ½0β ββΈβ0β ββΈβ0β ββΈ.0β ββΈΒ―0β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
β ββͺβΈ0β β~βΈ0β β,βΈ0β ββ΄Β¨β¬β ββ½βΈ0β βββΈ0β βββΈ0β β
β βββββ βββββ βββββ βββββ βββββ βββββ βββββ β
βββββββββββββββββββββββββββββββββββββββββββββ
Most are βΈ0
combined with an additional primitive that has no effect, or selfie-mirrors, so let’s restrict ourselves to the interesting subset:
4 2β΄res/β¨~'βΈβ¨'β(β¨/β)Β¨res βΒ Skip variants of βΈ0 and selfies
βββββββββββββββ
β βββββ βββββ β
β β0β0β β0ββ¬β β
β βββββ βββββ β
β βββββ βββββ β
β β0ββ¬β ββ¬β0β β
β βββββ βββββ β
β βββββ βββββ β
β ββ¬ββ¬β ββ¬ββ¬β β
β βββββ βββββ β
β βββββ βββββ β
β ββ³Β¨β¬β ββ΄Β¨β¬β β
β βββββ βββββ β
βββββββββββββββ
We can see a few patterns again β partition (β
) or partitioned enclose (β
), with 0 as left or right argument and β¬
as left or right argument, plus two variants using each (Β¨
) on β¬
. Let’s look at β
first.
Partitioned enclose groups, or partitions, stretches its right argument as specified by its Boolean vector left argument, with each new partition starting on 1, and returns a nested vector of the resulting partitions:
1 0 0 0 0 1 1 0 0 0 0β'Hello World'
βββββββββββββββββββββββ
β βββββββ βββ βββββββ β
β βHelloβ β β βWorldβ β
β βββββββ βββ βββββββ β
βββββββββββββββββββββββ
In the first case, 0β0
, we’re saying that we want 0 partitions of 0 (which gets treated as a 1-element vector), returned as a nested vector β in other words, exactly the “empty vector of empty numeric vectors” that we’re after. In fact, β
with a 0 as its left argument returns an empty vector of empty vectors of the prototype element of the right, which also helps to explain our 0ββ¬
:
0β'hello world' β Empty vector of empty character vectors
βββββββ
β βββ β
β β β β
β βββ β
βββββββ
0β1 2 3 4 5 β Empty vector of empty numeric vectors
βββββββ
β βββ β
β β0β β
β β~β β
βββββββ
0ββ¬ β Empty vector of empty numeric vectors
βββββββ
β βββ β
β β0β β
β β~β β
βββββββ
Swapping the order of that last expression:
β¬β0
βββββββ
β βββ β
β β0β β
β β~β β
βββββββ
is really the same thing: the left side is still a numeric vector with no 1s.
What about β
? If we restrict ourselves to a Boolean left argument again, partition is similar to replicate, but instead of just skipping elements of the right side corresponding to 0s in the left side, it encloses groups of elements corresponding to 1s, and skips the rest:
1 1 1 1 1 0 1 1 1 1 1β'Hello World'
βββββββββββββββββββ
β βββββββ βββββββ β
β βHelloβ βWorldβ β
β βββββββ βββββββ β
βββββββββββββββββββ
Compare with replicate:
1 1 1 1 1 0 1 1 1 1 1/'Hello World'
ββββββββββββ
βHelloWorldβ
ββββββββββββ
As with β
, β
returns a vector of vectors and, by the same reasoning, if the left argument contains no 1s, then the inner vector will be an empty vector of the prototype element of the right argument:
0β1 2 23 34
βββββββ
β βββ β
β β0β β
β β~β β
βββββββ
We should understand the remaining β
variant, β¬ββ¬
, too. You might wonder why there is no 0β0
, analogous to the 0β0
we saw earlier; a fair question. However, it results in an error:
0β0 β RANK ERROR
RANK ERROR
0β0 β RANK ERROR
β§
The reason for this is that β
is IBM’s APL2 primitive, supplied for compatibility reasons. It simply doesn’t treat a scalar right argument as a 1-element vector, unlike β
.
What remains are the two variants using the each operator: β³Β¨β¬
and β΄Β¨β¬
. Each always returns an array of the same shape as its right argument, in which each element is the result of applying the operand to the corresponding element in the argument.
By that reasoning, with an argument of β¬
we’ll always end up with an empty vector. The prototype element is itself a vector, as both β³
and β΄
returns vectors β and, in our specific case, empty numeric vectors, as given the argument β¬
.
Closing Remark
Many people β certainly including myself β find reasoning about the behaviour of empty arrays in Dyalog APL difficult. Going through exercises such as these can help to make this clearer.