Working on Data Shape
Contents
8. Working on Data Shape#
In many programming languages, the programmer has to declare the dimensions of an array statically, and it is often possible to operate on the individual items using programmerwritten loops.
In contrast to this, because APL processes arrays in their entirety, it is important to be able to manage the dimensions of an array dynamically. This is why this chapter presents a number of new tools that will help you perform these tasks.
We have already studied functions which create arrays with specific shapes:
Name 
Syntax 
Explanation 
Section 

Reshape 

Creates a new array with dimensions 

Catenate 

Creates a new array by gluing 

Ravel 

Creates a vector from any array. 

Compress 

Selects parts of an array. 

Replicate 

Generally replicates the items of an array. 

Indexing 

Creates a new array, often with modified dimensions. 

Index function 

Creates a new array, often with modified dimensions. 
8.1. Take and Drop#
8.1.1. Take and Drop Applied to Vectors#
8.1.1.1. Starter#
There are two functions, take (↑
) and drop (↓
), that can be used to extract or remove a number of elements from the beginning or end of a vector.
The number is the left argument and its sign controls if the extraction/removal is done from the beginning or end of the vector.
Take extracts the vector’s head or tail; and
Drop removes the vector’s head or tail, and hence selects the remaining part.
Let us test these functions:
nums ← 56 66 19 37 44 20 18 23 68 70 82
With a positive left argument n
, take extracts the first n
items:
4↑nums
It works on any kind of data (numbers, text),
5↑'My name is Bond'
including nested arrays:
2↑(6 2)(35 33 26 21)(42 73)(1 2 3)
With a negative left argument n
, it extracts the last n
items of the vector, in their normal order:
¯3↑nums
¯6↑'Mississippi'
Sometimes, these two operations are referred to as taking the “head” of the vector (with a positive left argument) and taking the “tail” of the vector (with a negative left argument).
With a positive left argument n
, drop removes the first n
items and returns the tail:
4↓nums
5↓'My name is Bond'
With a negative left argument n
, it removes the last n
items and returns the head:
¯6↓'Mississippi'
Remark
You will have noticed that 4↑nums
and ¯7↓nums
both gave the same result 56 66 19 37
.
At first sight it would appear that there is no need for both of these functions, and that one or other of take and drop is redundant. There are, however, some differences that make it necessary to have both functions, as we will soon see.
8.1.1.2. Be Careful#
Do not confuse the two following expressions.
We can take the last three items of a vector:
¯3↑nums
Or we can take the first three items, and then change their sign:
3↑nums
On another note, the result of take and drop applied to a vector remains a vector, even if it has only one item:
⍴1↑nums
⍴10↓nums
Although the results of the two expressions above would contain only one item, they are 1item vectors, and not scalars.
8.1.1.3. Produce Empty Vectors#
Of course, if you take no items, or if you drop all the items, the result is an empty vector of the same type (numeric or character) as the original vector:
0↑nums
22↓nums
14↓'Empty'
Although all of the results above are empty vectors, we know that they are different if the original vectors have different types of data:
(22↓nums)≡(14↓'Empty')
Take a look at Section 3.7 for a reminder of how empty arrays work.
8.1.1.4. Take More Cash Than You Have#
The take function has a very special property: it allows you to take more items than there really are. If so, it pads the result with fill items; zeroes for a numeric vector, and blanks for a text vector:
cash ← 45 23 18 92
If we take too many items, zeroes are appended to the vector:
7↑cash
With negative left arguments, starting from the tail, the zeroes are placed before the existing items:
¯9↑cash
For character vectors, spaces are appended:
12↑'Invisible'
However, you have to be careful because those do not show:
(12↑'Invisible'),'.'
Starting from the right, with a negative left argument, the blanks are appended to the left and are a bit easier to see:
¯12↑'Visible'
In fact, the concept of fill item is a bit more complex than this; it will be studied in detail in Section 10.9.
The concept of “taking more than you have” is sometimes referred to as overtaking. This is an application of take that cannot be performed using drop alone.
This property applies equally to empty vectors; they are filled with as many zeroes or blanks as specified. This means that the result will be different for empty numeric and empty character vectors:
]display 4↑⍬
]display 4↑''
8.1.2. Three Basic Applications#
8.1.2.1. Determine the Type of a Variable#
The property we have just seen can be used to determine whether an array is numeric or character, provided that it is simple and homogeneous (neither mixed nor nested).
The method is simple: create an empty vector “filled” with the array (0⍴⍵
), then take one item of it (1↑
), and compare with 0
.
This will return 1
(true) for a numeric array, and 0
(false) for a character array.
A little dfn will do that for us:
TypeOf ← {0=1↑0⍴⍵}
TypeOf nums
TypeOf 'Character vector'
This function wouldn’t work on a mixed or nested variable. Again, you will understand why when we talk about fill items in Section 10.9. We shall see in a later chapter that APL has a system function that does the job much better.
8.1.2.2. Change a Vector into a Matrix#
Sometimes you want a variable var
to be a matrix, although you are not sure of its current rank.
If it is already a matrix, you want to leave it unchanged, and if it is a vector, you want to change it into a onerow matrix. The following function should help:
HorMat ← {(¯2↑1,⍴⍵)⍴⍵}
Notice that the shape of the following matrix remains unchanged:
⍴HorMat 3 5⍴⍳5
But the vector is “promoted” into a matrix:
⍴HorMat 'This is a vector'
Let’s break down how HorMat
works:
we first append
1
to the shape of the argument, which gives1 3 5
for the matrix and1 16
for the vector;next, we keep only the last two items, which gives
3 5
for the matrix and1 16
for the vector;the final reshape returns an unchanged matrix, or transforms a vector intro a matrix.
We can change vectors into 1column matrices with a very similar function, which also leaves matrices unchanged:
VerMat ← {(2↑(⍴⍵),1)⍴⍵}
VerMat 'Vertically?'
8.1.2.3. Calculate Growth Rates#
Let us imagine a business with a turnover which has grown over 12 years.
The variable tome
is TurnOver in Millions of Euros:
tome ← 56 59 67 64 60 61 68 73 78 75 81 84
We want to calculate the difference between each year and the next; how do we achieve this?
1↓tome
¯1↓tome
All that remains is to subtract the results of these expressions one from the other, item by item:
(1↓tome)(¯1↓tome)
If instead of subtraction we used division, we would calculate (with some adjustments) the rates of growth instead of the differences. Let us upt that in a small dfn, and apply it:
Growth ← {100×((1↓⍵)÷(¯1↓⍵))1}
2⍕Growth tome
8.1.3. Take and Drop Applied to Arrays#
The functions take and drop can be applied to any array so long as the left argument contains a number of items that is equal to or smaller than the number of dimensions (the rank) of the array.
In the expressions ns↑array
and ns↓array
, ≢ns
must be less than or equal to ≢⍴array
.
For the examples that follow, we shall work on the following matrix:
mat ← 3 4⍴13 52 33 81 42 62 70 47 51 73 28 19
mat
is an integer matrix but take and drop work the same for character arrays, and even for nested and/or mixed arrays.
8.1.3.1. MaximalLength Left Argument#
We start by exploring what happens when the left argument has the maximal length it can have, that is, when (≢ns)=≢⍴array
in the expression ns↑array
or ns↓array
.
If we want to take the 2 top rows and the 3 left columns of mat
, we write
2 3↑mat
The elements of the left argument are paired up with the axes of the right argument array, and that is why 2 3↑mat
takes 2 rows and then 3 columns, because the first axis specifies the number of rows and the second one the number of columns.
In order to drop the top row and 2 columns from the right, we write
1 ¯2↓mat
If we take 5 rows starting from the bottom and 3 columns starting from the left, we get
¯5 3↑mat
The two extra rows are added on the top of mat
because we asked for 5 rows starting from the bottom.
Dropping 1 row starting from the bottom and 3 columns starting from the left, we get
¯1 3↓mat
In the example above, we have dropped 3 out of the 4 columns available, so there is only one left, but it is still a 1column matrix; it has not been changed into a vector.
This is similar to how 1↑vector
in a vector returns a 1element vector, not a scalar.
In short, the functions take and drop return a result with the same rank as the right argument, regardless of the number of elements in the result.
As for vectors, it is often possible to use take or drop interchangeably to obtain the same result:
2 ¯3↑mat
¯1 1↓mat
8.1.3.2. Short Left Argument#
If one or more of the trailing dimensions of the array are to remain unchanged, then one need only specify the parameters for the leading dimensions.
For example, suppose that we want to extract the first 2 rows of mat
.
We know its shape is 3 4
, so we can easily write
2 4↑mat
But if we didn’t know its shape in advance, we would have to resort to something like
(2,¯1↑⍴mat)↑mat
which is a bit cumbersome and, as it turns out, far from optimal.
In a matrix, the first axis denotes the rows, so we can pick the first 2 rows by just specifying the 2
in the left argument to take:
2↑mat
This works for arrays of arbitrarily high rank.
For example, recall the 3D array prod
, that contained monthly (12) production values of 2 assembly lines, throughout the course of 5 years:
⎕RL ← 73
⎕← prod ← ?5 2 12⍴50
We can get the production values for the first 2 years with:
2↑prod
But we can also get the production values for the first 4 years and only for the first assembly line:
4 1↑prod
Naturally, drop can also be used with a left argument whose length is smaller than the rank of the argument. For example, we can drop the first row of a matrix:
1↓mat
Or we can drop the production values of the last year and of the first assembly line:
¯1 1↓prod
In Section 8.7 you can find a rule to compute the shape of the final result of a take (or drop) operation in terms of the left argument and the shape of the right argument.
8.1.3.3. Short Left Argument with Axis Specification#
When you provide a left argument that has less elements than the number of axes on the right argument, the left argument defaults to referring to the leading axes of the right argument.
However, we can use take and drop with axis []
to specify other axes.
For example, the first 2 rows of mat
were obtained with
2↑mat
Using axis, we can just as easily get the first 2 columns of mat
:
2↑[2]mat
This also extends to higher rank. If we want the information regarding the first 3 years, and only the final 6 months of the year (but for all assembly lines), we can write:
3 ¯6↑[1 3]prod
8.2. Laminate#
We have previously used catenate to glue one array to another; let us now look at a new method.
We shall work with the following two character matrices:
⎕← names ← 3 4⍴'AlexAnnaMark'
⎕← surnames ← 3 4⍴'GainNairTyte'
The catenation of those two matrices will not change the rank of the arrays, and thus give another matrix, as we saw in Section 4.11:
names,surnames
names⍪surnames
If both matrices have exactly the same shape, it is possible to join them together along a new dimension to make a 3D array. Because this operation produces a result of higher rank than its arguments, it is called laminate rather than catenate.
The symbol representing catenate and laminate is the same (,
), but when the comma is used as laminate it is always used with a fractional axis.
The two arrays we intend to laminate have the same shape: 3 4
.
Because we are going to laminate 2 arrays, the new dimension will have a length of 2, and the shape of the result will be some combination of 3
, 4
, and 2
.
Let us examine all the possibilities:
2 3 4
– the new dimension is inserted before the first dimension;3 2 4
– the new dimension is inserted between the first and second dimensions;3 4 2
– the new dimension is inserted after the second dimension.
To obtain these three different results, we shall use laminate with a fractional axis to specify where the new dimension is to be inserted:
names,[0.5]surnames
will produce a result of shape2 3 4
;names,[1.5]surnames
will produce a result of shape3 2 4
; andnames,[2.5]surnames
will produce a result of shape3 4 2
.
Beware that we could have equally used ⍪
.
Here are the three cases:
names,[0.5]surnames
names,[1.5]surnames
names,[2.5]surnames
In fact, the value of the axis specifier just identifies the position of the new dimension relative to the values 1 and 2, so it could be any other fractional value between 0 and 1, or 1 and 2, or 2 and 3, respectively.
Hence, the three matches that we see below:
(names,[0.295]surnames)≡names,[0.5]surnames
(names,[1.643]surnames)≡names,[1.5]surnames
(names,[2.107]surnames)≡names,[2.5]surnames
Of course, it would be somewhat obtuse to use such axis specifications, and programmers conventionally use “n.5
” values, like the ones in our examples.
8.2.1. Applications to Vectors and Scalars#
Now that we understand the reason for the fractional axis, which is perhaps initially somewhat surprising, we can apply laminate to all kind of arrays.
8.2.1.1. Laminate Applied to Vectors#
Let us use both character and numeric vectors:
t1 ← 'tomatoes'
t2 ← 'potatoes'
n1 ← 14 62 32 88
n2 ← 10×⍳4
If we catenate them, we still obtain vectors:
t1,t2
n1,n2
But if we instead laminate them, we obtain matrices with either 2 rows or 2 columns:
t1,[0.5]t2
t1,[1.5]t2
n1,[0.5]n2
n1,[1.5]n2
Of course, since we are working with 1dimensional arrays, we cannot specify an axis equal to or greater than 2.
There is also no reason for why you wouldn’t be able to laminate a character vector with a numeric vector, for example:
t1,[0.5]n1,n2
8.2.1.2. Laminate Scalars with Vectors#
Scalars can be laminated with any array: they are repeated as many times as necessary to match the length of the new dimension:
n1,[0.5]0
1,[1.5]n1
This can be used, for example, to underline a title:
title ← 'Laminate is good for you'
Without laminate, we must create a matrix with 2 rows, and as many columns as the length of title
, filled with title
itself, followed by as many dashes as the length of title
: boring!
(2,≢title)⍴title,(≢title)⍴''
Now, with laminate, we just have to laminate a single dash; it will be repeated as many times as necessary:
title,[0.5]''
8.2.2. Applications#
8.2.2.1. Interlace Matrices#
Do you remember that, in the chapter about userdefined functions, we wrote a function to interlace two matrices? It is no longer relevant; we can solve the problem more simply using laminate.
Can you see how? Give it some thought, if you will.
Take a look at the result of names,[2.5]surnames
above.
You will see that the names are on the left and the surnames are on the right.
If we reshape that result with appropriate dimensions, we shall obtain names
and surnames
interlaced 😊 :
(1 2×⍴names)⍴names,[2.5]surnames
The result isn’t easy to read, but the expression works!
Hence, our new implementation of Interlace
could be
Interlace ← {(1 2×⍴⍺)⍴⍺,[2.5]⍵}
We can apply the same technique to matrices of forecasts and actuals, provided we define them first:
⎕RL ← 73
forecast ← 10×?4 6⍴55
⎕RL ← 73
actual ← forecast + ¯10+?4 6⍴20
forecast Interlace actual
8.2.2.2. Show Vectors#
Sometimes you will have two vectors that are related, and you would like to display them side by side. You might have a character vector and you would like to display it side by side with a Boolean vector indicating the positions of vowels in the character vector, so that you can verify visually if the result is correct:
text ← 'National Aeronautics and Space Administration'
vowels ← 0 1 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0 0 1 0 1 1 0
text,[0.5]vowels
By looking at the above, you would be able to see more easily that your result missed the capital vowels, and you would then proceed to fixing your function.
This was a simple example of where laminating two vectors might prove useful, but this adhoc technique might also be useful in slightly more complex scenarios. Suppose that we have four vectors containing information about certain products: their price, their current discount, their quantity in stock, and their availability (physical store, online, or both):
⎕← price ← 2 4 15 8 23
⎕← discount ← 0 0 25 25 35
⎕← stock ← 103 98 50 23 64
⎕← availability ← 'SSBOB'
The output shown above is not ideal, because each individual vector is displayed using its own natural format, and it is extremely difficult to connect the four related pieces of information to a specific product. We can have these values displayed much better if we create a matrix.
To produce a matrix we will need to laminate two of the vectors and catenate the others (in rows or in columns). The results are much easier to read:
price⍪discount⍪stock⍪[0.5]availability
price,discount,stock,[1.5]availability
In these examples, there is only one laminate, followed by as many catenates as needed.
8.3. Expand#
8.3.1. Basic Use#
You remember that simple compress uses a Boolean vector of 1s and 0s as a mask to include or exclude specific items of an array.
Simple expand (specified by the \
symbol) also uses a Boolean vector of 1s and 0s, but the 0s insert new items into the array.
It is used as follows: r ← pattern\argument
.
In this form, the Boolean vector left argument contains a 1 for each item of the right argument, and a 0 for each item to insert. For example:
1 1 0 1 0 0 1 1 1\11 28 32 40 57 69
1 1 0 1 0 0 1 1 1\'Africa'
If the right argument is numeric, expand inserts zeroes, and if it is a character vector, expand inserts blanks as fill items. For mixed or nested arrays, the concept of fill item is more complex and, as mentioned before, will be explained in Section 10.9.
8.3.2. Extended Definition#
We can extend the behaviour of extend to handle cases where the left argument is not a simple Boolean vector, but contains integers other than just 0s and 1s:
the amount of positive numbers in the left argument should match the length of the right argument;
for each positive item in the left argument, the corresponding item in the right argument is replicated as many times as is specified by that value;
each negative item in the left argument inserts an equivalent number of fill items in the same position; and
zeroes in the left argument mean the same as
¯1
, and they each insert one fill item.
In the description above, we assumed that the right argument is a vector. We will cover the fully generic case in a bit.
Because it is an extension of it, this new definition is fully compatible with the Boolean case we described before.
Here is an example:
1 1 0 3 ¯2 1 1 1\11 28 32 40 57 69
The first two items remain unchanged.
Then a zero inserts a zero in the result.
The next value is repeated 3 times, and the value ¯2
inserts 2 zeroes.
The last 3 items are unchanged.
The same thing can be done using a character vector:
1 1 0 3 ¯2 1 1 1\'expand'
Because 0
and ¯1
have the same effect when used in the left argument of expand, we can obtain the same result with a different pattern:
1 1 ¯1 3 ¯2 1 1 1\'expand'
Naturally, the function can work on any shape of array, in which case \
acts on the last axis of the right argument, by default.
For example, if we take our chemistry
matrix:
⎕← chemistry ← 3 5⍴'H2SO4CaCO3Fe2O3'
We can insert one extra column by using expand with a Boolean left argument:
1 1 0 1 1 1\chemistry
We can further specify the axis along which we want to act, to change the default:
1 0 1 0 1\[1]chemistry
The axis specified can be the same as the default, making it redundant, but otherwise completely correct:
1 ¯3 1 1 1 0 3\[2]chemistry
With arrays of rank higher than 1, the description provided above remains the same, except that extend no longer operates on single items of the right argument, but on its subarrays along the given axis (the last one, if left unspecified) instead.
Expand can also be used on scalars; they are repeated as many times as necessary to fit the number of positive values in the left argument:
0 0 1 1 0 0 1 1\'A'
The above is equivalent to
0 0 1 1 0 0 1 1\'AAAA'
0 1 3 ¯2 1 1\71
8.3.3. Expand Along First Axis#
Like it was mentioned before, expand works on the last dimension of an array.
To work on the first dimension, one can use the function ⍀
(APL+.):
1 1 ¯2 1⍀chemistry
If one places an axis indication after the symbol \
or ⍀
, the operation is processed according to the axis operator, whichever of the two symbols is used.
For example:
vec⍀[3]prod
andvec\[3]prod
would be equivalent tovec\prod
; andvec\[1]forecast
andvec⍀[1]forecast
would be equivalent tovec⍀forecast
.
8.4. Reverse and Transpose#
APL is also well endowed with functions which pivot data about an axis, and the axis is suggested by the shape of the symbol used.
The functions apply to both numeric and character data.
In the examples we are going to use a character matrix called towns
:
⎕← towns ← 6 10⍴'Canberra Paris WashingtonMoscow Martigues Mexico '
The symbols ⌽
(APL+Shift+5) and ⊖
(APL+Shift+7) are used for two variants of the same function, which is called reverse.
The function ⍉
is called transpose and its symbol is typed with APL+Shift+6.
Here are their effects:
(⊢towns) (⌽towns) (⊖towns) (⍉towns)
The symbols used (⌽ ⊖ ⍉
) are selfdescribing, no effort is required to remember any of them because the position of the bar visually indicates which kind of transformation they stand for.
If you insert an axis specification after the symbols ⌽
or ⊖
, the operation is processed according to the axis operator, whichever of the two symbols is used.
So, for example:
⌽[1]matrix
and⊖[1]matrix
are both equivalent to⊖matrix
; and⌽[2]matrix
and⊖[2]matrix
are both equivalent to⌽matrix
.
8.4.1. Caveats to be Aware Of#
Transpose has no effect on a vector, because it has only one axis:
⍉'I shall not move'
On a similar note,
The distracted APLer might expect
⊖
to do nothing on vectors, but that is not what happens.
Visually, ⊖
hints at the fact that it “flips” its argument upside down, and a vector can’t be flipped upside down because it is onedimensional.
However, ⊖
is called reverse first, which means it always reverses the argument along its first dimension, even if that dimension is the only dimension of the argument:
⊖'I shall not move...!?'
This confusion might arise from the visual similarity of the character vector above and
1 21⍴'I shall not move...!?'
The latter is a 1row matrix, which is why it is left unchanged by ⊖
:
⊖1 21⍴'I shall not move...!?'
Transpose cannot be modified by an axis specifier, because it always operates on all of the dimensions of its argument.
Transpose can be applied to arrays of any rank; let us try it with a 3D character array:
⎕← big ← names,[0.5]surnames
⍉big
⍴big
⍴⍉big
You can see that ⍴⍉big
is equal to ⌽⍴big
, and the explanation of why this is the case will follow shortly.
All three primitives we just discussed also have (related) dyadic meanings, and those are introduced in two following sections.
8.5. Rotate#
The symbols ⌽
and ⊖
, when used dyadically, shift the items of the right argument in a circular manner.
The dyadic functions are called rotate.
8.5.1. Rotate Vectors#
Much like in the monadic case, the dyadic usage of ⌽
and ⊖
is identical when applied to vectors (we shall use ⌽
in our examples because of the visual clue that the symbol ⌽
gives with respect to the operation it performs).
Dyadic ⌽
(and ⊖
) expects an integer as the left argument, when the right argument is a vector, like so: r ← n⌽vector
.
when
n
is positive, the firstn
items ofvector
are moved to the end. In other words, the vector is rotated to the left; andwhen
n
is negative, the lastn
items ofvector
are moved to the beginning. In other words, the vector is rotated to the right:
7⌽'What did they do to my song?'
In the example above, the first 7 items of the vector ('What di'
) have been moved to the back, whereas if we use a negative argument:
¯7⌽'What did they do to my song?'
the last 7 items of the vector ('y song?'
) are moved to the front of the vector.
Rotate can of course be applied to numeric vectors as well:
nums
3⌽nums
Do not confuse the following two expressions:
¯3⌽nums
3⌽nums
The first one moves 3 items to the beginning, whilst the second expression moves the first 3 items to the end, and then changes the sign of the result (we saw something very similar when we talked about take). It is all about being careful with the normal and high minus symbols!
8.5.2. Rotate HigherRank Arrays#
When applied to a matrix or higherorder array, ⌽
works on the last dimension, while ⊖
works on the first dimension.
This default behaviour can be overriden by an axis specification.
To obtain a rotation along any other dimension, the axis specification is mandatory.
Rotate can be applied to any array, but we shall only demonstrate its application to matrices.
8.5.2.1. Uniform Rotation#
In its simplest form, rotate applies the same rotation to all the rows or columns of a matrix; let us see the result produced on a character matrix:
⎕← monMat ← 6 8⍴'January FebruaryMarch April May June '
2⌽monMat
2⊖monMat
¯2⊖monMat
8.5.2.2. Multiple Rotations#
It is possible to apply a different rotation to each of the rows or to each of the columns.
In this case, the rotation is no longer indicated by a single value, but by a vector which specifies the amount by which each row or column will be moved.
chemistry
¯1 0 2⌽chemistry
Notice how ¯1
was used to rotate the first row, 0
was used to rotate the second row, and 2
was used to rotate the third row.
Using ⊖
, we can rotate columns:
monMat
1 0 2 ¯2 0 0 2 2⊖monMat
8.5.2.3. Application#
Rotate can provide very simple solutions to many tasks.
For example, let us count how many blanks appear at the end of each row of monMat
:
+/' '=monMat
We can then use these values to move the blanks to the beginning of each row, thereby rightaligning the matrix:
(+/' '=monMat)⌽monMat
8.6. Dyadic Transpose#
Dyadic transpose is interesting only for arrays of rank higher than 2. It rotates an array as if to show it from different angles.
Remember our variable called prod
:
prod
It is an array with 3 dimensions, which are respectively 5 years, 2 assembly lines, and 12 months.
Suppose we now want to reorganise it into an array of 2 assembly lines, 5 years, and 12 months: a dyadic transposition can do that for us.
The left argument of dyadic transpose specifies the position that you want each dimension to appear in the result.
The shape of prod
was
⍴prod
but now we want it to become 2 5 12
.
The years should become the 2nd dimension, the assembly lines should become the 1st dimension, and the months should remain the 3rd dimension, hence the transposition vector will be 2 1 3
:
2 1 3⍉prod
Alternatively, if we want to reorganise it into an array of 2 assembly lines, 12 months, and 5 years, the method will be the same:
The required new shape is 2 12 5
.
When all dimensions are different from each other, the transposition vector can be generated using dyadic iota:
⎕← tv ← 2 12 5 ⍳ ⍴prod
tv⍉prod
8.7. Exercises#
Given the matrix xg1
below, try to produce each of the three following matrices; first using only take, and then only drop.
5 3 6
8 2 3
5 4 8 2
7 7 6 2
9 5 3
4 8 2
7 6 2
⎕← xg1 ← 3 5⍴1 9 5 3 6 5 4 8 2 3 7 7 6 2 6
With xg1
again, how could you produce this:
1 9 5 3 6 0
5 4 8 2 3 0
7 7 6 2 6 0
0 0 0 0 0 0
Write two dyadic functions TakeShape
and DropShape
.
TakeShape
computes ⍴ns↑array
and DropShape
computes ⍴ns↓array
.
The left argument to both functions will be ns
and the right argument will be ⍴array
.
You can assume that the left argument and right arguments are valid and make sense together.
2 ¯3 TakeShape 1 5 10
15 4 ¯3 DropShape 10 10 10
Write two dyadic functions, TakeAsDrop
and DropAsTake
, that take an array on the right and a vector on the left.
r ← v TakeAsDrop a
is such that (r↓a)≡v↑a
whenever possible, and conversely for DropAsTake
: r ← v DropAsTake
is such that (r↑a)≡v↓a
whenever possible.
Test your functions with your solutions to the first exercise.
Write a function which “highlights” all the vowels of a given character vector by placing an arrow under them.
ShowVowels 'This function works properly'
Write two monadic functions:
Contract
takes a sparse matrix and returns a more compact representation of the same matrix; andRestore
takes a compact representation of a sparse matrix and builds the full matrix.
For reference, a sparse matrix is a matrix that contains mostly zeroes, like the matrix xg6
shown below:
xg6 ← 4 7⍴0
xg6[(1 3)(1 6)(2 2)(3 1)(3 3)(3 7)(4 5)] ← 8 3 7 6 2 1 4
xg6
A large sparse matrix may occupy a lot of memory. To reduce the memory consumption, we can consider an alternative, more compact representation. For example, we can start by ravelling the matrix:
,xg6
Then, we retain only the nonzero values together with their position in the ravel:
values ← 8 3 7 6 2 1 4
positions ← 3 6 9 15 17 21 26
Now, if we add the shape of the matrix on the left, we have all the necessary information to restore the original matrix when required:
4 8 3 7 6 2 1 4 ⍝ values
7 3 6 9 15 17 21 26 ⍝ positions
⍝ ^ shape of the original matrix
Contract xg6
xg6 ≡ Restore Contract xg6
Write a dyadic function Whiten
that replaces all occurrences of the character scalar left argument in the character vector right argument with blanks.
'a' Whiten 'Panama is a canal between Atlantic and Pacific'
Can you find a solution that uses the function expand?
Write a dyadic function OnTop
to centre a title above a character matrix.
monMat ← 6 8⍴'January FebruaryMarch April May June '
'2022' OnTop monMat
Determine the result of the expression ¯3 ¯1 3⌽(2 1 0 1 0 2 1 2 0)⊖xg9
for the matrix xg9
given below.
⎕← xg9 ← 3 9⍴'oeornlhtu n siduothf uogYti'
Write a dyadic function Free
which looks for a sequence of contiguous zeroes in a Boolean vector.
The right argument is the Boolean vector in which to look for the sequence of zeroes and the left argument is the integer that specifies the sequence length to look for.
The function should return the position of the first zero of the first such sequence and, if no such sequence exists, it should return 0
.
⎕← xg10 ← 1 0 0 1 1 1 0 0 1 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 1 0 0
1 Free xg10
2 Free xg10
3 Free xg10
4 Free xg10
6 Free xg10
Write a dyadic function ToColumns
that takes an integer scalar left argument and a character matrix right argument.
The function should split the matrix into as many columns as the left argument specifies. Columns should be separated by, at least, a blank.
⎕← xg11 ← 11 8⍴'Emily Luciano Paul Oxana Thor Carmen VeronicaWilliam VladimirMonica Colette '
3 ToColumns xg11
Write a dyadic function ShowCodes
that takes a numeric vector left argument and a character matrix right argument.
The function should display the numerical codes on the left of the rows of the argument matrix, but with a blank row every time the code changes.
Notice that, in the example below, the vector xg12
with the codes has length equal to the number of rows in xg11
.
xg12 ← 22 22 74 74 74 74 30 65 65 65 19
xg12 ShowCodes xg11
8.8. The Specialist’s Section#
If you are exploring APL for the first time, skip this section and go to the next chapter.
8.8.1. More About Laminate#
Here is a formal definition of the conditions required to laminate two variables a
and b
.
In the expression r ← a,[axis]b
,
it is mandatory that (⍴a)≡(⍴b), unless one of them is a scalar;
axis
must be a value between⎕IO1
and⎕IO+≢⍴a
; andthe shape of
r
is given by the expression((⌊axis↑⍴a),2,((⌈axis)↓⍴a)
.
Examine the second rule: it is obvious that axis
can be negative if the index origin is set to 0.
The axis can also be negative for mix and ravel with axis.
Let us use the following two vectors:
a ← 41 27 88 11
b ← 39 63 12 69
In order to produce the matrix
2 4⍴a,b
we write
a,[0.5]b
when ⎕IO ← 1
, but if we set
⎕IO ← 0
then the expression becomes
a,[¯0.5]b
⎕IO ← 1
8.8.2. More on Dyadic Transpose#
8.8.2.1. Conditions#
We said that dyadic transpose can be thought of as a way to observe an array from different positions. For this usage there is a certain rule to follow.
To transpose an array with a⍉b
, we must have a[⍋a]≡⍳≢⍴b
.
In other words, a
must be composed of all the values of ⍳≢⍴b
taken in any order, or, mathematically speaking, a
must be a permutation of the axes of b
.
8.8.2.2. Diagonal Sections of an Array#
Dyadic transpose can also be used to select the items from an array which have two or more identical coordinates. Such selections are called “diagonal sections” of the array.
For example, let us use the following array:
⎕← y ← 3 3 4⍴⍳36
Look at the result of the following expression:
1 1 2⍉y
We have specified that both the first and second dimensions are to become the first dimension of the result. This conflict is resolved by extracting the items on the diagonal between the dimensions which are merged.
The items on this diagonal have identical first and second coordinates, much like the identical nature of the first and second values in the left argument 1 1 2⍉y
.
The result is a section of the “cube”.
The expression 2 2 1⍉y
would give the same result, but transposed:
2 2 1⍉y
(1 1 2⍉y)≡⍉2 2 1⍉y
Rules
This special use of a⍉b
must also follow some rules:
(≢a)≡(≢⍴b)
∧/a∊⍳≢⍴b
∧/(⍳⌈/a)∊a
The first two rules are not unique to this special usage of dyadic transpose, and the third rule means that the items of a
must be consecutive integers, starting from ⎕IO
.
So, for the array y
shown above, the only possible sections are:
(1 1 1)(1 1 2)(1 2 1)(1 2 2)(2 1 1)(2 1 2)(2 2 1)(⊣,[.5]⍉¨)⊂y
If the required conditions are satisfied, the selection can be thought of as having two steps:
the left argument
a
is examined to see which of the coordinates are identical.
For example, a left argument equal to 1 2 1
or 2 1 2
will select items which have their first and their coordinates identical.
A left argument equal to 1 1 2
or 2 2 1
will select items which have their first two coordinates identical.
1 2 2⍉y
and 2 1 1⍉y
would select the items which have their last two coordinates equal, that is,
1 6 11
13 18 23
25 30 35
So, now we know which items from y
we must work with in the next step.
the selected values are repositioned using a normal dyadic transposition, the left argument of which is composed of the unique values of
a
obtained by∪a
.
For example, we said that both 1 2 2⍉y
and 2 1 1⍉y
would select the same set of values, shown above.
This little matrix will then be transposed using a left argument equal to 1 2
(the matrix remains unchanged) or 2 1
(the matrix is transposed):
1 2 2⍉y
2 1 1⍉y
We recommend that you try to work through the other possibilities to make sure you understand them.
8.8.2.3. Diagonal Section of a Matrix#
In the case of a matrix, the only possible diagonal section is specified by 1 1
.
It selects what is called the main diagonal of the matrix.
For example, here is how you would select the values along the main diagonal of forecast
:
1 1⍉forecast
forecast
Notice that for a matrix m
, we have (≢1 1⍉m)=⌊/⍴m
.
This means that the diagonal section of a rectangular matrix has as many elements as the shorter side of the matrix, like in the forecast
example above.
8.9. Solutions#
The following solutions we propose are not necessarily the “best” ones; perhaps you will find other solutions that we have never considered. APL is a very rich language, and due to the general nature of its primitive functions and operators there are always plenty of different ways to express different solutions to a given problem. Which one is “the best” depends on many things, for example the level of experience of the programmer, the importance of system performance, the required behaviour in border cases, the requirement to meet certain programming standards and also personal preferences. This is one of the reasons why APL is so pleasant to teach and to learn!
We advise you to try and solve the exercises before reading the solutions!
Solution to Exercise 8.1
Starting from xg1
,
⎕← xg1 ← 3 5⍴1 9 5 3 6 5 4 8 2 3 7 7 6 2 6
we want to extract submatrices using take and drop. Because we are dealing with a matrix – a rank 2 array – the left argument must be a 2element vector. The first element determines how many rows we take (or drop) and from where (top or bottom), while the second element determines how many columns we take (or drop) and from where (left or right).
Let us go through the three matrices, one by one:
1.
5 3 6
8 2 3
This matrix has elements from the first two rows and from the last three columns of xg1
, so we can build it with
2 ¯3↑xg1
Conversely, we can build that matrix by ignoring the last row and the first two columns:
¯1 2↓xg1
2.
5 4 8 2
7 7 6 2
This matrix has elements from the last two rows and from the first four columns, so we build it with
¯2 4↑xg1
Conversely, we can build it by ignoring the first row and the last column of xg1
:
1 ¯1↓xg1
3.
9 5 3
4 8 2
7 6 2
Finally, this matrix contains all three rows but the 3 columns that make it up are from the middle of xg1
, so there is no single number that we can use to build this matrix in a single take.
Instead, what we can do is two successive takes: first we take the first 4 columns and then the last 3 columns of that, or first we take the last 4 columns and then the first 3 of that:
3 3↑3 ¯4↑xg1
We do a similar thing with drop:
0 1↓0 ¯1↓xg1
Solution to Exercise 8.2
We want to produce the matrix
1 9 5 3 6 0
5 4 8 2 3 0
7 7 6 2 6 0
0 0 0 0 0 0
from xg1
.
We immediately notice that xg1
is in the upperleft corner of this larger matrix, and we see there are an extra row and column of zeroes, which are certainly the result of overtaking:
4 6↑xg1
Solution to Exercise 8.3
Let us start with TakeShape
, that is supposed to compute the shape of the result of doing ns↑array
.
r ← ns TakeShape ⍴array
should be such that r≡⍴ns↑array
, and notice that the right argument to TakeShape
is ⍴array
, not the array itself.
From reading the specification of take and from the previous exercises, we can start to see that the absolute value of the left argument indicates the length of each dimension. However, the solution isn’t just
TakeShape ← {⍺}
Why not?
Because the left argument to take may be shorter than the number of dimensions of the array we are working with, in which case the trailing dimensions are left untouched.
To fix our function, we just need to check if there are trailing dimensions that are left untouched and, in case there are any, we should tack them to the right of ns
:
TakeShape ← {(⍺),((≢⍺)≢⍵)↑⍵}
2 ¯3 TakeShape 1 5 10
⍝ 2 3 10
How does it work?
≢⍵
is the number of dimensions of the array and≢⍺
is the number of dimensions of the left argument to take, so(≢⍵)≢⍺
is the number of trailing dimensions that are left untouched;we then use take to reach the lengths of those dimensions, so they show up in the final result. However, we want to take from the end, so we would need
(≢⍵)≢⍺
; instead, we flip the subtraction in the first place to get(≢⍺)≢⍵
; andwe tack those trailing dimensions to the absolute value of the left argument.
The problem statement specifies that the arguments to TakeShape
will always be relative to a usage of take that makes sense, so we also know that (≢⍺)≢⍵
is always 0 or negative.
We can follow a similar train of thought with DropShape
, except that now ⍺
doesn’t tell us the length of each dimension, but rather the number of elements that are dropped along that dimension.
For DropShape
, we can take advantage of overtaking to make sure that the left argument has the same length as the right argument.
Finally, we just need to make sure we don’t try to drop too many elements along any dimension, because the final result cannot have dimensions with negative length:
DropShape ← {0⌈⍵(≢⍵)↑⍺}
15 4 ¯3 DropShape 10 10 10
⍝ 0 6 7
Solution to Exercise 8.4
This exercise wants us to convert left arguments of take into left arguments of drop and viceversa, and doing so means we need to understand the lengths of the resulting dimensions, but that is what we have been doing in the previous exercises.
Start by imagining how it works for a vector v
of length l
.
if we write
t↑v
, for positivet
, the corresponding left argument for drop would belt
; butif we write
(t)↑v
, then the corresponding left argument for drop would belt
.
Now we just need to find a way of unifying the two cases:
TakeAsDrop ← {(×⍺)×⍵⍺}
The expression ⍵⍺
determines the magnitude of the corresponding drop left argument and the expression (×⍺)
corrects the sign.
This is a great starting point, but our implementation has three issues that we need to solve. One of them should become apparent here:
10 TakeAsDrop 5
If we try to take 10 elements from a vector of length 5, we end up with a (larger) vector of length 10, so there is no left argument to drop that would have the same effect.
Arguably, the most sensible thing to do here would be to return 0
, because that is the left argument for a drop that would have the closest result possible to the original take.
To make this change, what we need to do is make sure that the magnitude of the argument to drop is never below zero:
TakeAsDrop ← {(×⍺)×0⌈⍵⍺}
10 TakeAsDrop 5
The second issue, that really is a bug, has to do with what happens if we take 0 elements:
0 TakeAsDrop 5
If we take 0 elements from a vector of length 5, that would be the same as dropping 5 elements from that vector.
However, we say it is the same as dropping 0 elements from that vector, which is wrong.
The problem is that (×⍺)
evaluates to 0
in this case, whipping out the correct magnitude computed by 0⌈⍵⍺
.
One way to fix that is by nudging the values of ⍺
slightly, so that their signs remain the same for all integers, except for 0:
TakeAsDrop ← {(×⍺0.5)×0⌈⍵⍺}
0 TakeAsDrop 5
The third and final issue, is that TakeAsDrop
doesn’t handle cases where the left argument is shorter than the right argument:
3 4 TakeAsDrop 10 8 6
LENGTH ERROR: Mismatched left and right argument shapes
TakeAsDrop[0] TakeAsDrop←{(×⍺0.5)×0⌈⍵⍺}
∧
The simplest way to fix this is by shortening ⍵
to match the length of ⍺
.
This works because the trailing dimensions that are not covered by the left argument to take can also be omitted in the left argument to drop:
TakeAsDrop ← {(×⍺0.5)×0⌈((≢⍺)↑⍵)⍺}
3 4 TakeAsDrop 10 8 6
As for the DropAsTake
function… It is exactly the same as the TakeAsDrop
one!
Solution to Exercise 8.5
It is clear that an integral part of this exercise will be finding the vowels in the given character vector, and we can use the membership primitive for that:
'This function works properly'∊'aeiouyAEIOUY'
Next, we want to turn that Boolean mask into a character vector with arrows ↑
where the ones are.
If we take advantage of the fact that the blank space ' '
is the fill element for character vectors, we can use the expand primitive that we just learned about:
('This function works properly'∊'aeiouyAEIOUY')\'↑'
To wrap it up, all we need to do is laminate this character vector with the original vector:
ShowVowels ← {⍵,[.5](⍵∊'aeiouyAEIOUY')\'↑'}
ShowVowels 'This function works properly'
Solution to Exercise 8.6
Let’s work with the following matrix:
xg6 ← 4 7⍴0
xg6[(1 3)(1 6)(2 2)(3 1)(3 3)(3 7)(4 5)] ← 8 3 7 6 2 1 4
xg6
In order to contract the matrix, we start by raveling it:
,xg6
And we also list all the indices of all the positions in the matrix:
⍳≢,xg6
What we now want is to keep only the indices of nonzero values. If we start by laminating the numbers and the positions together, we can then use a Boolean mask to figure out what things to keep:
xg6r ← ,xg6
(xg6r≠0)/xg6r,[.5]⍳≢xg6r
The final step is catenating the original shape of the matrix on the left:
]dinput
Contract ← {
r ← ,⍵
(⍴⍵),(r≠0)/r,[.5]⍳≢r
}
Contract xg6
In order to implement the function Restore
, we can create an empty ravel of the matrix which we then populate with the correct values.
After populating, we just need to reshape the ravel into the original matrix by making use of the shape information that is on the first column of the input:
]dinput
Restore ← {
shape ← ⍵[;1]
ravel ← (×/shape)⍴0
ravel[1 1↓⍵] ← ¯1 1↓⍵
shape⍴ravel
}
Restore Contract xg6
Solution to Exercise 8.7
When the problem statement mentions blanks and expand, it hints at the fact that the blank space is the fill element for character vectors, and we know that expand inserts fill elements when it finds zeroes.
A first hunch might be that we just need to create a Boolean mask that has a 1 for each character we want to keep, and then we use expand with that, but unfortunately that does not work:
sentence ← 'Panama is a canal between Atlantic and Pacific'
(sentence≠'a')\sentence
LENGTH ERROR
(sentence≠'a')\sentence
∧
The problem is that the left argument to expand needs to have as many positive elements as there are elements in the right argument. In order to fix this, what we can do is remove the letter we want to replace with blanks and only then use expand. We can remove the occurrences of the letter with a compress or with the without primitive:
Whiten ← {(⍵≠⍺)\⍵~⍺}
'a' Whiten sentence
]dinput
Whiten ← {
m ← ⍵≠⍺
m\m/⍵
}
'a' Whiten sentence
Solution to Exercise 8.8
If we want to centre something, that means we will need to rotate it to the correct place. When we receive the title, we first pad it with enough blank spaces so that it can be catenated on top of the matrix:
padded ← (1↓⍴monMat)↑'2007'
≢padded
We overtake to achieve this effect. The next thing is rotating some of the trailing spaces to the front, so that the title becomes centred. To do that, we need to figure out how much larger the matrix is in relation to the title, and then we divide that by 2 and round, in case there are an odd number of spaces:
rot ← ⌈((1↓⍴monMat)≢'2007')÷2
Now we can rotate the padded title by minus that amount (otherwise we would be rotating in the wrong direction):
(rot)⌽padded
Now it is just a matter of catenating this to the top of the matrix, together with the 
underline.
Let us put everything together in a dfn:
]dinput
OnTop ← {
c ← 1↓⍴⍵
rot ← ⌈(c≢⍺)÷2
padded ← c↑⍺
((rot)⌽padded)⍪''⍪⍵
}
'2022' OnTop monMat
Notice that rounding up or down changes the position of the extra space when there are an odd number of spaces around the title:
'107' OnTop monMat
Solution to Exercise 8.9
Best way to check if your solution is correct is by actually running the code:
⎕← xg9 ← 3 9⍴'oeornlhtu n siduothf uogYti'
¯3 ¯1 3⌽(2 1 0 1 0 2 1 2 0)⊖xg9
Solution to Exercise 8.10
Let us prototype our solution with the vector that follows, and let us assume we want to find a run of 4 zeroes.
⎕← xg10 ← 1 0 0 1 1 1 0 1 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 1 0 0
We can start by asking where in our vector the 1s are:
⍸xg10
This is useful because we know that the 0s are between the 1s.
However, if our vector starts and/or ends with 0s, those will not be enclosed by 1s and so we need to prepend and append a 1 to be sure all runs of 0s are surrounded by 1s:
⎕← ones ← ⍸1,xg10,1
The next thing we do is figure out the distances between the consecutive 1s, so that we know the lengths of the runs of 0s:
⎕← runLengths ← ¯1+(1↓ones)(¯1↓ones)
Now we just use the lengths of the runs to compress the initial ones
vector.
However, we can’t do it directly:
(4=runLengths)/ones
LENGTH ERROR
(4=runLengths)/ones
∧
The problem is that the vector ones
contains the beginning and end of each run of zeroes, whereas the vector runLengths
only has the corresponding length.
In short, ones
contains one element too many, that corresponds to the 1 that was catenated to the end of the argument vector.
So, we need to drop the last element of ones
before compressing:
(4=runLengths)/¯1↓ones
Finally, we take the first element of that:
1↑(4=runLengths)/¯1↓ones
At this point, we can figure out the approximate location of our runs of zeroes, we just need to make sure that we return the exact index required by the problem statement. The problem statement wanted us to return the index of the first 0 of such a run. We worked with indices of 1s, and not 0s, but we also catenated a 1 at the beginning of the vector, thus shifting all the indices of the 1s. This means that, even though we are working with indices of 1s, the final result matches the corresponding index of the first 0.
Let’s wrap our code in a dfn:
]dinput
Free ← {
ones ← ⍸1,⍵,1
runLengths ← ¯1+(1↓ones)(¯1↓ones)
1↑(⍺=runLengths)/¯1↓ones
}
What if the right argument does not have a run with the required length? Does our function return 0 like the problem statement requires?
6 Free xg10
It does… But how?
When we filter for runs of the required length, we get nothing, because there are no such runs:
(6=runLengths)/¯1↓ones
In other words, we get an empty numeric vector:
⍬≡(6=runLengths)/¯1↓ones
If our vector is empty, then trying to use take to get its first element corresponds to over taking, because we are trying to retrieve too many elements. When that is the case, we know that the function take will put as many fill elements as needed, and so it returns a single 0 as a result of doing “one take” on an empty vector:
1↑⍬
Here is an alternative solution.
First, we stack the vector on top of itself as many times as required by the desired run length (let us say it is n ← 4
):
n ← 4
⎕← mat ← n (≢xg10)⍴xg10
Next, we use rotate ⌽
to rotate each row one more element than the previous one, so that each column represents n
consecutive elements of the vector:
⎕← rot ← (¯1+⍳n)⌽mat
In other words, if you look at the top row, you will notice that the 3 elements below a number match the 3 elements to its right. For example, the 4th element in the 1st row is
rot[1;4]
The 3 elements to its right are
rot[1;5 6 7]
and the three elements below it are
rot[2 3 4;4]
Now, the runs that we care about correspond to columns that are completely 0. Using a reduction, we can figure out which columns have 1s and which do not:
∨⌿rot
To finish up, we can do something similar to what we did before:
1↑⍸~∨⌿rot
This is what this solution would look like in a dfn:
]dinput
Free2 ← {
mat ← ⍺ (≢⍵)⍴⍵
rot ← (¯1+⍳⍺)⌽mat
1↑⍸~∨⌿rot
}
3 Free2 xg10
6 Free2 xg10
4 Free2 xg10
Solution to Exercise 8.11
We start with our matrix of names:
⎕← xg11 ← 11 8⍴'Emily Luciano Paul Oxana Thor Carmen VeronicaWilliam VladimirMonica Colette '
Let us also assume we want to make n ← 3
columns:
n ← 3
If we look at the problem statement, we see that the names must be laid out top to bottom, and only then left to right. However, in APL, the ravel order of arrays is first left to right, and then top to bottom. So it seems like we have to fight APL’s natural order in some way… The key to doing that is understanding that we actually have 3 dimensions along which we want to navigate:
we want to walk along one dimension that holds a single name,
then we want to go down along the column of names,
and then we want to go right along the columns of names.
This hints at the fact that we should reshape our data to have 3 dimensions, where one dimension holds the columns, another holds all the names, and the last one holds the characters that compose a name.
The first thing that we need to do, in order to break up the matrix with the names, into an array with 3 dimensions, is figure out the length of each dimension.
n ← 3
indicates that we want 3 columns, but how many names are there in each slice?
(height width) ← ⍴xg11
height÷n
This doesn’t divide evenly, so we use the ceiling primitive to round up, and then we use take to create empty names to pad the column:
colSize ← ⌈height÷n
⎕← names ← (colSize×n)↑xg11
Maybe you didn’t notice, but now there is an empty row added to the matrix.
So, the next step is reshaping the matrix names
into a 3D array:
⎕← columns ← n colSize width ⍴ names
Now we have an array where the dimensions match the dimensions we want, but now we need to reorder the dimensions so that they do what we want when we reshape the 3D array back into a matrix. We see that the 3rd dimension is already correct, because the names are laid out along the 3rd (last) dimension. This means the only thing we can try to do is reorder the first and second dimensions:
2 1 3⍉columns
After the reordering of the two leading dimensions, we can now reshape the 3D array into a matrix:
colSize (n×width)⍴2 1 3⍉columns
This is almost perfect, we just need to add blanks between all the columns. In order to do so, we can catenate a blank space at the end of the array (making it one character wider) and then dropping the last column of superfluous blanks:
0 ¯1↓colSize (n×width+1)⍴2 1 3⍉columns,' '
This can be written as a dfn:
]dinput
ToColumns ← {
(h w) ← ⍴⍵
nh ← ⌈h÷⍺
names ← (nh×⍺)↑⍵
columns ← ⍺ nh w⍴names
0 ¯1↓nh (⍺×w+1)⍴2 1 3⍉columns,' '
}
3 ToColumns xg11
For reference, we provide another implementation that is not correct, where we just do some reshaping. In this alternative solution, the names get listed from left to right, and then from top to bottom.
]dinput
ToColumnsWrong ← {
(h w) ← ⍴⍵
nh ← ⌈h÷⍺
0 ¯1↓nh(⍺×w+1)⍴(⍺×nh×w+1)↑,xg11,' '
}
3 ToColumnsWrong xg11
Solution to Exercise 8.12
For this exercise, we are given an extra vector xg12
:
xg12 ← 22 22 74 74 74 74 30 65 65 65 19
It is fairly straightforward to put the labels next to the names:
⎕← labeled ← ⍕xg12,' ',xg11
The final step is creating the blank lines that separate the different labels.
For that, we need to create a Boolean vector that we can give to expand. This is what we need to create:
1 1 0 1 1 1 1 0 1 0 1 1 1 0 1⍀labeled
In order to create that vector, we will want to figure out the indices of the above vector that are 0. To do so, we start by finding where the labels change:
⎕← indices ← ⍸(¯1↓xg12)≠1↓xg12
However, if we look at the Boolean vector above, these are the indices of the 0s:
⍸~1 1 0 1 1 1 1 0 1 0 1 1 1 0 1
Can you see the pattern?
Here is the mapping that we need to do:
2 goes to 3
6 goes to 8
7 goes to 10
10 goes to 14
indices+⍳≢indices
Every index that we found in indices
points to the last position of a run of labels, and we wanted to insert a new 0 in the position after the run ends and before the next run begins.
However, if we imagine we are working from the left, every time we insert a new position between different labels, we shift part of the vector to the right.
That is why we need to add the iota of the tally of indices
, to correct for those shifts.
Now we use these indices to create the Boolean vector we need:
⎕← exp ← ~(⍳(≢indices)+≢xg12)∊indices+⍳≢indices
exp⍀labeled
]dinput
ShowCodes ← {
labeled ← ⍕⍺,' ',⍵
indices ← ⍸(¯1↓⍺)≠1↓⍺
exp ← ~(⍳(≢indices)+≢xg12)∊indices+⍳≢indices
exp⍀labeled
}
xg12 ShowCodes xg11