Tables (5 of 14)
This is another practical session, so please continue to type the examples you see in the manual.
The general subject is tables, and the first topic in connection with tables is how to set them up. That's a topic that could involve you in a lot of keying. (Imagine typing in 50 different values to fill a modest five-row by ten-column table!) To avoid such drudgery we'll look first at two functions that will generate the numbers for you.
The Roll function
The ? function ( usually called Random, Roll or Deal) generates random numbers. Here's an example:
? 100 53
You asked for a random number in the range 1 to 100. You probably didn't get 53. That was the one-argument form of ? It returns a single number between 1 and the number you specify as the right-hand argument.
The two-argument form will be more useful for filling tables because it generates as many numbers as you ask for:
50 ? 100
On your screen you should have 50 numbers in the range 1 to 100. Look at your numbers carefully and see how many duplicates you can count in 20 seconds.
Given up? In fact you won't find any. The ? function generates unique random numbers in the given range. That's why this example produces an error message:
20 ? 10 DOMAIN ERROR 20 ? 10 ^
The domain of numbers in the range 1 to 10 can't supply 20 different whole numbers. You can use a variable as the right-hand argument of ? We'll set one up then use it :
RANGE ← 15 3 ? RANGE 1 5 13
Equally, you can use a variable to specify how many random numbers you want:
QTY ← 7 QTY ? RANGE 5 14 10 1 15 2 4
And you can assign the result to a variable too:
BINGO ← QTY ? RANGE BINGO 6 14 9 2 3 11 6
The Iota function
This is an example of the ⍳ function ( called Iota or Index and found above the 'I' key):
Your screen should now be filled with the numbers from 1 to 100 in ascending order. We're using the one-argument form of ⍳. It generates the series of numbers from 1 to whatever number you specify as its right-hand argument.
Here's an example which puts the series from 1 to 10 in a variable called X:
X ← ⍳10 X 1 2 3 4 5 6 7 8 9 10
If you got the numbers from 0 to 9 instead, it's because your APL is using a different default Index Origin. Set the index origin to 1 using:
⎕IO ← 1
Now we can safely tackle the topic of tables. But for clarity, we'll start by entering values explicitly, rather than generating them randomly or producing them with the ⍳ function.
Setting up tables
This statement will take the 12 numbers on the right of the ⍴ symbol, and set them up as a four-by-three table:
4 3 ⍴ 10 20 30 40 50 60 70 80 90 100 110 120 10 20 30 40 50 60 70 80 90 100 110 120
⍴ (called Rho, Shape or Reshape and found above the 'R' key) is a function and like any other function, it operates on arguments. We're using the two-argument form of ⍴.
(We'll see what the one-argument form does later in the chapter.)
4 3 ⍴ 10 20 30 40 50 60 70 80 90 100 110 120
The argument to the left specifies how many rows and columns are in the table. The argument to the right specifies what data is to be put into the table.
Here again is the table produced by the last example, this time with the rows and columns labelled:
You always specify the number of rows before the number of columns, and APL fills the table row-by-row rather than column-by-column. (This may seem a trivial point, but if APL first filled column 1, then column 2 then column 3, the effect would be quite different).
The data to be put in the table can itself be in a variable. This next statement puts 12 random numbers in the range 1 to 100 into a variable called DATA:
DATA ← 12 ? 100
Now use the ⍴ function again, but specify DATA as the right-hand argument:
4 3 ⍴ DATA 15 57 30 51 50 97 18 26 38 67 22 69
(Your numbers are unlikely to be the same!)
The next example looks doomed to failure because there are insufficient numbers on the right to fill a table of the specified dimensions. But try it anyway and see what APL does:
4 3 ⍴ 1 2 3 4 5
As you saw, when the numbers ran out, APL went back to the first number and went through them again, giving a table like this:
1 2 3 4 5 1 2 3 4 5 1 2
It follows that if you supply only one number, APL will use that to fill the whole table:
3 5 ⍴ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
On the other hand, if you supply too many numbers, APL uses what it needs to fill the table and ignores the rest:
2 3 ⍴ 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6
Try setting up some tables before you read on. Remember that you can use the ? and ⍳ functions.
Arithmetic on tables
Now please set up a 3-row 3-column table called SALES, containing the numbers shown:
SALES ← 3 3⍴20 13 8 30 43 48 3 50 21 SALES 20 13 8 30 43 48 3 50 21
Arithmetic on SALES will automatically affect every number in the table. For example:
SALES×10 200 130 80 300 430 480 30 500 210
Suppose you now set up another table called, say, PRICES, will you be able to do operations like SALES times PRICES? Let's find out:
PRICES ← 2 3 ⍴ 21 2 12 47 33 1 SALES×PRICES
The attempt caused an error:
LENGTH ERROR SALES×PRICES ^
A LENGTH ERROR message means that the arguments contain different numbers of elements. The problem, obviously, is that SALES is three rows by three columns, and therefore contains nine numbers, while PRICES is two rows by three columns, and therefore contains six numbers. How could APL know which items you want multiplied by which? Since it doesn't, it gives an error message.
Let's redefine the SALES table so that it too contains six items:
SALES ← 3 2 ⍴ SALES
(We used the numbers already in SALES as data. The effect of the statement is to take the first six numbers in the old SALES table and rearrange them as three rows and two columns to form the new SALES table.)
Now that SALES, like PRICES, contains six numbers, let's try the multiplication again:
SALES×PRICES LENGTH ERROR SALES×PRICES ^
We're still getting an error message. If we look at both tables the problem will be apparent:
SALES 20 13 8 30 43 48 PRICES 21 2 12 47 33 1
The tables may now have the same number of items, but they're still a different 'shape'. It's impossible to know, with any certainty, which item in SALES corresponds to which item in PRICES.
We'll have to redefine SALES again so that it has the same number of rows and columns as PRICES:
SALES ← 2 3 ⍴ SALES
(Again we've used the numbers already in SALES as data for the new version of the table.)
Now we'll check that SALES and PRICES are the same shape (i.e. have the same number of rows and columns):
SALES 20 13 8 30 43 48 PRICES 21 2 12 47 33 1
They now match exactly in shape and size, so we can try again.
SALES×PRICES 420 26 96 1410 1419 48
Success at last! The elements in the two tables now match-up, or conform, and arithmetic involving both tables is possible. Let's check by trying another operation on them:
SALES - PRICES ¯1 11 ¯4 ¯17 10 47
That worked too. (Remember that the previous multiplication didn't change the contents of the tables.)
Incidentally, you don't have to create every table explicitly. You can create one simply by assigning a result to it. Here you're creating a table called TOTAL.
TOTAL ← SALES×PRICES TOTAL 420 26 96 1410 1419 48
Before you read on, practice constructing tables and doing arithmetic on them. Make use of the ? and ⍳ functions to set the tables up. Don't forget about the ⌈ and ⌊ functions. They work on tables too.
You can catenate tables just as you catenated other variables in the previous session:
SALES,PRICES 20 13 8 21 2 12 30 43 48 47 33 1
The tables being catenated must have the same number of rows, but don't have to have the same number of columns.
This next example creates a two-row two-column table called LITTLE filled with 1s, and a two-row six-column table called MEDIUM filled with 5s. Then it catenates the tables and puts the result in BIG:
LITTLE ← 2 2 ⍴ 1 MEDIUM ← 2 6 ⍴ 5 BIG ← LITTLE,MEDIUM LITTLE 1 1 1 1 MEDIUM 5 5 5 5 5 5 5 5 5 5 5 5 BIG 1 1 5 5 5 5 5 5 1 1 5 5 5 5 5 5
Again, notice that though the catenated tables have different numbers of columns, they both have the same number of rows.
Catenation supplies one of many solutions to the problem of arithmetic being possible only on tables of equal size. Suppose you wanted to add LITTLE to MEDIUM:
LITTLE+MEDIUM LENGTH ERROR LITTLE+MEDIUM ^
You can't because they're different sizes. They both have two rows, but LITTLE has two columns while MEDIUM has six:
LITTLE 1 1 1 1 MEDIUM 5 5 5 5 5 5 5 5 5 5 5 5
The following example shows how LITTLE can be catenated with a table of zeroes to pad it out to the same size as MEDIUM so that the addition can take place:
ZEROES ← 2 4 ⍴ 0 ZEROES 0 0 0 0 0 0 0 0 LITTLE ← LITTLE,ZEROES LITTLE 1 1 0 0 0 0 1 1 0 0 0 0 LITTLE+MEDIUM 6 6 5 5 5 5 6 6 5 5 5 5
The addition took place successfully. Presumably we wanted the original LITTLE to be added on to the left-hand end of MEDIUM. If we wanted it on the other side we should have specified (resetting LITTLE first!):
LITTLE ← 2 2⍴1 LITTLE ← ZEROES,LITTLE LITTLE+MEDIUM 5 5 5 5 6 6 5 5 5 5 6 6
It's because that kind of ambiguity exists that APL won't do arithmetic on data of unequal size.
You may be wondering how you select single elements from tables.
First set up this table (using the numbers shown rather than the ? function), then we'll select individual elements from it :
+TABLE ← 4 3 ⍴ 2 12 15 4 11 7 1 16 8 20 19 9 2 12 15 4 11 7 1 16 8 20 19 9
Remember that the table consists of four rows and three columns.
To select the 9 in the bottom row, right-most column, type:
You've used the row number <4>, and the column number <3>, to identify which element of the table you want. Before you read on, see if you can enter a statement which adds the number in row 3 column 3, to the number in row 4 column 2. Make sure you use the square brackets and separate the row number from the column number with a semicolon.
You should have entered:
TABLE[3;3] + TABLE[4;2] 27
That shouldn't have been difficult as long as you counted the rows and columns correctly, and remembered the semicolons. You no doubt typed:
TABLE[3;2] ← TABLE[1;2] + TABLE[2;2]
Check TABLE make sure that row 3, column 2 now contains the sum of rows 1 and 2 column 2:
TABLE 2 12 15 4 11 7 1 23 8 20 19 9
It's quite easy to select entire rows or columns. Here we select all three elements in row 1:
TABLE[1;1 2 3] 2 12 15
As before, the number before the semicolon denotes the row while the number, or in this case numbers, after the semicolon denote the column(s). There is, however, a shorthand way of selecting whole rows or columns. The following statement does the same as the last, that is, it selects all columns in row 1:
TABLE[1;] 2 12 15
Using the same principle, see if you can replace the numbers in column 3 with the sum of the numbers in columns 1 and 2.
(In the course of some of these operations you may be getting an error message saying that you've made an INDEX ERROR. The process of picking elements out of a table is called 'indexing'. A mistake is therefore referred to as an 'index' error.)
To add the first two columns and put the result in column three you could have typed:
TABLE[1 2 3 4;3] ← TABLE[1 2 3 4;1] + TABLE[1 2 3 4;2] TABLE 2 12 14 4 11 15 1 23 24 20 19 39
Alternatively you could have used the shorthand way:
TABLE[;3] ← TABLE[;1] + TABLE[;2]
You can, of course, select elements from two separate tables and do arithmetic on them. If you still have the tables SALES and PRICES in your workspace, the following statement will multiply the number in row 1 column 1 of SALES by the number in row 2 column 3 of PRICES.
SALES[1;1] × PRICES[2;3] 20
Incidentally, indexing can also be used to pick single elements out of lists. With lists, of course, only one number is needed to identify the element required:
LIST ← 8 1 90 4 LIST 1
A quick digression about dimensions is in order before we tackle the next topic. APL regards data as having dimensions.
- A single number, or character is like a point in geometry. It exists but has no dimensions.
- A list is like a line. It has one dimension, length.
- The tables we've looked at are like rectangles. They have two dimensions, height and length.
- Three-dimensional 'tables', or 'arrays', as they are more often called, are like cubes. They have depth, height and length.
Arrays of many dimensions are possible in APL but we won't attempt to represent them!
The thought of three-dimensional data may intrigue you, but in practice it's quite mundane - as the next example will reveal. Suppose the ordinary two-dimensional table you're about to create represents the number of each of four products sold by each of six salesmen:
SALES ← 6 4⍴24?50 SALES 5 34 22 36 46 40 18 10 39 23 4 41 50 27 8 13 12 42 9 3 19 47 30 35
The salesmen are the rows, the different products are the columns:
Now suppose that this table relates to one sales region and that there are three such regions altogether. The following statement will create a three-dimensional array which represents this situation:
+SALES ← 3 6 4⍴72?100
On your screen are (or should be) three blocks of numbers, each of six rows and four columns. These are the three planes, so to speak, of SALES. To create SALES you specified three dimensions as the left-hand argument of ⍴ (see above). To select a particular element from SALES, you also have to give three numbers:
You specified that from SALES you wanted plane 2, row 5, column 4. In other words you wanted to know the quantity of product 4 sold by salesman 5 in area 2. You've now seen what is meant by three-dimensional data, and are aware that APL treats data as having dimensions. But the key to understanding the next function is to remember that a single number or single character has no dimensions.
Enquiring about the size of data
As you've seen, the ⍴ function used with two arguments puts specified data into a specified number of rows and columns. The same function used with one argument allows you to enquire about the size (or 'shape') of existing tables and other variables.
To remind yourself of the size of SALES (the three-dimensional data structure you recently created), type:
⍴SALES 3 6 4
As you see, you've been given the size of each dimension of SALES. Now create a two-dimensional table and ask the size of that:
TABLE ← 5 3 ⍴ 15 ? 20 ⍴TABLE 5 3
You've been given the size of each of the table's two dimensions. The height of the table is five rows, the length is three columns.
Next create a variable containing a list of numbers and ask its size:
LIST ← 1 2 3 4 5 6 ⍴LIST 6
The list is six numbers long.
Finally put a single number into a variable and ask its size:
NUM ← 234 ⍴NUM
The variable NUM has neither length, height nor any other dimension. It is, as we've already said, the equivalent of a point. APL therefore gives an empty response. By the way, the item enquired about doesn't have to be in a variable. Here we enquire about the size of a directly quoted numeric value:
⍴12 61 502 1 26 0 11 7
And here we ask for the size of a string of characters:
Before you read on, use the one-argument form of the ⍴ function to enquire about the size of some variables in your workspace. Remember that you're really asking about the size of each variable's dimensions.
Tables of characters
Characters can be arranged in rows and columns too:
ALF ← 3 5 ⍴ 'ABCDE' ALF ABCDE ABCDE ABCDE
But compare the effect of this next statement with the effect of the last:
NUM ← 3 5 ⍴ 12345 NUM 12345 12345 12345 12345 12345 12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
The fact is that 12345 is one number, while 'ABCDE' is five characters. So each single character in the first table is equivalent to each occurrence of 12345 in the second table. Despite their different appearances, both tables contain 15 elements. Notice, though, that while APL has no scruples about putting spaces between the occurrences of 12345 in the numeric table, it doesn't insert spaces in the alphabetic data. Since space is itself a character, it expects you to put in the spaces you require.
Here are a few examples to give you some experience of the way alphabetic data behaves:
MYNAME ← 'GORSUCH' ⍴MYNAME 7 3 7 ⍴ MYNAME GORSUCH GORSUCH GORSUCH
In the last example the seven characters in 'GORSUCH' were arranged as three rows each of seven columns.
In this example the same characters are arranged in three rows of 14 columns. Since MYNAME contains seven characters, this fits quite neatly, though a column of spaces between the present columns seven and eight would be an improvement.
3 14 ⍴ MYNAME GORSUCHGORSUCH GORSUCHGORSUCH GORSUCHGORSUCH
In the next example, the characters fill a three-row by eighteen-column table. This is not such a neat fit:
3 18 ⍴ MYNAME GORSUCHGORSUCHGORS UCHGORSUCHGORSUCHG ORSUCHGORSUCHGORSU
We'll try again. First we'll put a space at the end of the original character string, making it up to eight characters. Then we'll define a table with sufficient columns for the eight characters to be printed in their entirety five times on each of three rows.
MYNAME ← 'GORSUCH ' ⍴MYNAME 8 3 40⍴ MYNAME GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH GORSUCH
In this final example, this is the result we want to achieve:
ADAMS CHATER PRENDERGAST LEE
See if you can define a table which achieves that result before you look at the solution below.
You want a table of four rows. The difficulty is working out the columns. The columns must accommodate the longest name, which is PRENDERGAST with 11 characters. However, merely to put the names into four rows of 11 columns won't achieve the desired result:
4 11 ⍴ 'ADAMS CHATER PRENDERGAST LEE' ADAMS CHATE R PRENDERGA ST LEEADAMS CHATER PRE
The other names must he padded out with spaces so that each name plus following spaces exactly fills 11 columns. (For clarity, each space is represented here by a dot.)
4 11 ⍴ 'ADAMS......CHATER.....PRENDERGASTLEE........' ADAMS...... CHATER..... PRENDERGAST LEE........
(Although we have done it the slow way here for clarity, you will be relieved to learn that APL has easier ways of solving this problem).
You might remember that, in the last chapter, we made up lists which contained both characters and numbers. The examples that we have used so far in this chapter have been either characters or numbers. We can make up mixed tables in exactly the same way that we made up other tables. Here's one:
MIXTURE ← 3 3⍴'A' 1 'B' 'C' 2 'D' 'E' 3 'F' MIXTURE A 1 B C 2 D E 3 F
You can't, of course, carry out arithmetical operations on mixed tables, but you can reshape them with ⍴ and select elements just as you did with unmixed tables. Try making up some mixed tables yourself. APL will try to display the contents of these tables in as neat a fashion as it can - no easy matter when you have mixtures of characters and fractional numbers in the same columns.
Just to complete the picture, we can make up tables that contain other tables or lists. Again we follow the rules we discussed earlier when making up nested lists. We will use parentheses and quote marks as we did with lists. Here's an example:
NEST ← 2 3⍴(2 2⍴⍳4) (⍳5) 'A NAME' (2 4⍴⍳8) 23 (3 4⍴ 'NAME') NEST 1 2 1 2 3 4 5 A NAME 3 4 1 2 3 4 23 NAME 5 6 7 8 NAME NAME
What is NEST made up of? It's two rows deep and three columns wide. The first entry in the first row is a 2 row 2 column table made up of numbers, then we have a list of 5 numbers and a list of 6 characters. The second row starts with a numeric table of 2 rows 4 columns, then has a single number and ends with a 3 row 4 column table of characters. Just to check, let's see what the shape of NEST is:
⍴NEST 2 3
In order to cope with the added complication of nested data, either tables or lists, we have to bring in a new function ≡, called depth.
Depth gives an idea of the degree of nesting that can be found in a variable. This becomes important when you bear in mind that we could make up another variable where some of the elements are themselves nested variables and so on.
A single number or character has depth 0
and a list has depth 1
≡1 2 3 1
So does a table:
≡2 2⍴3 4 5 6 1
Lists and tables which are made up entirely of single numbers or characters will all have depth 1. When at least one element of a list or table already has a depth of 1 (when it is itself a list or a table), then the overall depth of the variable of 2. So our sample variable has a depth of 2:
This idea extends when we make more complicated examples. If one element is of depth 2, then the overall depth of the object is 3. The depth of a variable is always set by the deepest amount of nesting found within it.
BIG_NEST ← NEST NEST ⍴BIG_NEST 2 ≡BIG_NEST 3
BIG_NEST is made up of variables that already have a depth of 2, so it has depth 3. In fact, it's made up of the two objects NEST forming a two element vector.
The functions introduced in this session were ?, ⍳ and ⍴.
Some points worth remembering are:
1. Tables are specified and filled in row order.
2. Tables involved in an arithmetic operation must have the same number of rows and columns.
3. Catenate (,) joins tables with equal numbers of rows.
4. Data has dimensions:
- a single number or character has no dimensions
- a list has one dimension, length
- a table has two dimensions, height and length
- data can have many dimension.
5. The result returned by the one-argument form of ⍴ is the size of each dimension of the data you enquired about (e.g. how long it is, or how deep and high).
6. In character tables, every character, including space, is one column.
7. Tables can be made up of a mixture of numbers and characters.
8. Tables can be made up of lists and other tables.
9. Nested tables have depth.
The strength of APL is that almost any logical combination of functions is possible. This means (for example) that the result of an enquiry about a variable's size can be passed along the line to form the argument to the next function:
(⍴'ABC','DEF') + ⍴'GHI' 9
Or to take another example, if you've defined a table like this:
TABLE ← 10 10 ⍴100 ? 100
and you now want to select the first nine numbers in row 1, there is an alternative to typing laboriously:
TABLE[1;1 2 3 4 5 6 7 8 9]
You can instead let the ⍳ function generate the index for you:
These examples are not particularly significant in themselves. They merely indicate the variety of possibilities that exists if you care to experiment. When you've finished your experimentation, try the problems provided.
Q1. Set up a four-row one-column table called MILES containing:
300 42 25 140
And a similarly shaped table called RATES containing:
27.5 15 27.5 27.5
Multiply RATES by MILES, then multiply the result by 0.01 to produce a table called EXPENSES.
Q2. Change the number in column 1 row 3 of MILES from 25 to 250. Again, multiply RATES by MILES and the result by 0.01 to give EXPENSES, then reformat EXPENSES to produce a one-row four-column table.
Q3. Define X as a three-row ten-column table containing random numbers, and Y as a three-row four-column table also containing random numbers. Add X to Y, first taking whatever steps you think necessary to enable the operation to take place.
Q4. Using table X created in problem 4, add the first and second rows and replace the third row with the result of the addition.
Q5. Create a table which will look like this when displayed:
A P L R O C K S
Q6. What will be the result of each of these ⍴ statements? Predict each result before you press ENTER.
a) ⍴ 'ABC DEF'
b) ⍴ 480 0 1.2
c) TABLE ← 10 10 ⍴ 100 ⍴ 1000
d) ⍴ 'R'
e) ⍴ '480 0 1.2'
f) TABLE ← 2 10 3 ⍴ 100 ⍴ 100
MILES ← 4 1 ⍴ 300 42 25 140 RATES ← 4 1 ⍴ 27.5 15 27.5 27.5 +EXPENSES ← 0.01×RATES×MILES 82.5 6.3 6.875 38.5
MILES[3;1] ← 250 +EXPENSES ← 1 4 ⍴ EXPENSES ← 0.01×RATES×MILES 82.5 6.3 68.75 38.5
X ← 3 10 ⍴ 30 ? 100 Y ← 3 4 ⍴ 12 ? 100 X+( 3 6 ⍴ 0) ,Y
Since the problem didn't say which columns of X Y were to be added to, you may have put the zeroes on the other side:
X+Y,3 6 ⍴ 0
X[3;] ← X[1;]+X[2;]
9 1 ⍴ 'APL ROCKS'
Q6. You saw the answers to this problem when you entered the statements.