Life: Nasty, Brutish, and Short [Ho51]

E.E. McDonnell

I.P. Sharp Associates
425 Sherman Avenue, Suite 200
Palo Alto, CA 94306, U.S.A.



This paper describes a series of functions for performing Conway’s game of Life [Ga70] in APL, beginning with versions that go back to the early 1970’s. The paper doesn’t deal with the game itself, but rather with the expressive power of various approaches, and particularly with the increased expressiveness found in some of the new operator extensions to APL.

Given a state of the game, which we can think of as a boolean matrix, the object, from the point of view of this paper, is to produce the next state, or generation, of the game.

The rules for providing the next boolean matrix are: consider the eight cells surrounding a given cell in the current matrix (ignoring border cells); the corresponding cell in the next matrix will have the value 1 if the current value is 0 and exactly three of the surrounding cells are 1, or if the current value is 1 and either two or three of the surrounding cells are 1. Expressed this way, it is easy to see why many APL versions of Life have appeared since the game was first discussed in 1970, since APL is admirably suited to dealing with matrices in general and boolean matrices in particular.

Several APL versions of the game of Life appeared in the magazine APL Quote-Quad. The first of these was given by Duby [Du71]. I have taken the liberty of changing the display form of the function, in order to allow it to be studied more easily. In its original form, this was a seven-line function. I have broken it into many more lines by putting on separate lines all uses of assignment, and removing all uses of locutions such as ,0⍴ . The function uses index origin 0.

 ∇ LifeGame
   ⎕ ← 'Enter coordinates of organism'
   m ← 0 0 ⍴ 0
   x ← ⎕
   y ← (⍴ x) ÷ 2
   f ← (2+(⌈/x[2×⍳y]),⌈/x[1+2×⍳y])⍴m
Set:
   f[x[0]; x[1]] ← 1
   x ← 2 ↓ x
 → (0 ≠ ⍴x) / Set
Next:
   c ← +⌿ f
   l ← +/ f
   f ←(((⍴l)+3≤l[¯2+⍴l]),(⍴c)+3≤c[¯2+⍴c])↑f
   f ←((-(⍴f)[0]+3≤l[1]),-(⍴f)[1]+3≤c[1])↑f
   f ←⊖⌽⍉((0=+/l[1 2]),0=+/c[1 2])↓f
   f ←⊖⌽⍉((0=+/(⌽c)[1 2]),0=+/(⌽l)[1 2])↓f
   x ← 1
   m ← m + x
   ⎕ ← m
   p ← (0 × m) + f
   ⎕ ← ' ∘'[p]
Loopx:
   y ← 1
Loopy:
   n ←+/+/p×(1≥(x-⍳(⍴p)[0])∘.×1≥|y-⍳(⍴p)[1]
   f[x ; y] ← (n - 3) ∨ p[x ; y] ^ 4 = n
   y ← y + 1
 → ((⍴p)[1] ≥ 1+y) / Loopy
   x ← x + 1
 → (((⍴p)[0]≥1+x),0≠+/+/f)/(Loopx,Next)
 ∇

The part of this function that we are interested in begins at label Next , and goes to the end of the function. This is the part where the next state is computed. Unlike most of the other versions we will study, this one attends to the matter of growing and shrinking the size of the matrix in accordance with the location of the living cells in each state. The assignment to f in Loopy is the actual development of the next state. Note that it treats the creation of the next state as a scalar operation, computing Loopy x times y times, where x is the number of rows in the matrix and y is the number of columns. For each cell, the values in the neighbor cells are computed, and used to determine the value of the corresponding cell in the next state. Thus, this first recorded attempt doesn’t make much use of APL’s ability to describe matrix operations “all at once”.

The obvious inefficiencies of the approach of Algorithm 70 led to the appearance of two new versions in the following year. The first of these that I will discuss is due to Bonyun [BO72].

 ∇ Age Life Gen; a; b; c; h; k
St:
   mat ← mat ≠ 0
   a ← ∨/ ,mat
   ⍞ ← (18 ⍴ a)/ 'Population extinct'
 → E × a
 E:
   a ← ∨⌿ mat
   c ← a ⍳ 1
   h ← 2 + ⍴mat
   a ← h[2] - ((⌽a) ⍳ 1) + c
   b ← ∨/ mat
   c ← b ⍳ 1
   b ← h[1] - ((⌽b) ⍳ 1) + c
   mat ← (0, (b ⍴ 1), 0) ⍀ (0, (a ⍴ 1), 0) \ mat[¯1 + c + ⍳b]
   h ← ⍴mat
   ⎕←'Generation ';Age;'. *'[1+(0,(h[1]⍴1),0)⍀
         (0,(h[2]⍴1),0)\mat+1]
   Age ← Age + 1
 → 0 × ⍳(Gen < Age)
   h ← ¯1 ⊖ mat ⍝
   k ← 1 ⊖ mat ⍝
   h←(1⌽mat)+(1⌽k)+(¯1⌽k)+k+(1⌽h)+(¯1⌽h)+(¯1⌽mat)+h ⍝ (*)
   mat ← (h = 3) ∨ mat ^ 2 = h ⍝
   h ← 0 ⍴ mat ⍝
 → St
 ∇

This function as it originally appeared had only four (very long) lines. I have performed the same untangling for it that I did for Algorithm 70. This function uses index origin 1. It also takes care of growing and shrinking the matrix mat according to the size of the living cell population. The five lines of the function that I have marked with a comment symbol determine the next state, using a strategy that will become familiar. In the line marked (*), eight terms enter into the formation of a sum; each of these terms gives one of the eight neighbors of each cell, and the sum gives the total number of living neighbors of each cell. Thus, the function makes good use of APL’s matrix-handling abilities. The eight neighbors are found by appropriate rotations of the matrix, in various combinations. Thus, the neighbor cell to the upper left of the cell is found by ¯1⌽¯1⊖ (the term shown as (¯1⌽h)), and so forth. The function uses a global variable mat , and I shall stigmatize a function which uses a global variable as nasty.

In the same issue as Bonyun’s algorithm appeared the next solution to the problem. It was provided by W.J. Jones [Jo72]. It is in index-origin 1. It assumes a fixed sized state matrix of 20 rows by 20 columns.

 ∇ Life a; v; t; i
   i ← 0
   a ← 20 20 ↑ (¯10 - ⌊0.5 × ⍴a) ↑ a
   r ← ¯1 0 1 ⌽ 3 20 ⍴ ⍳20
Lp:
   t ← ∨/ a
   ' +'[1+((¯1+t⍳1),0)↓((1-(⌽t)⍳1),0)↓a]
   i ← i + 1
   i; 16 ⍴ '-'; +/ ,a
   t ← +⌿ +/[3] a[r; r] ⍝
   a ← (3 = t - a) ∨ 3 = t ⍝
 → (1 ∊ a) ⍴ Lp
 ∇

Jones determines the next state in the two lines marked with the comment symbol. Essential to his solution is the matrix r , which gives the indices of the state matrix in staggered form:

 2 3 4 5 6 7 8 9 10 … 18 19 20  1 
 1 2 3 4 5 6 7 8  9 … 17 18 19 20 
20 1 2 3 4 5 6 7  8 … 16 17 19 19 

This is used to give all possible vertical and horizontal alignments of an element with adjacent elements. A solution which uses such a brute-force approach to a solution I shall call brutish. The first line marked with a comment symbol develops t by using r as both a row and a column index to the state matrix a , thereby giving a four-dimensional result, and summing this along the first and third dimensions; this is used in the next commented line to determine the living cells in the next state via a simple pair of tests. Jones displays the successive states followed by a separator line which includes the state number and the number of living elements.

The next version of Life appeared in APL Quote-Quad in 1974 [Si74]. The authors were high school students, who describe their version as follows:

   

[The function] has the syntax:

      Increment Life Matrix

where Increment is how often the pattern is to be printed (1 = every generation, 2 = every other generation, etc.) and Matrix is a binary matrix which has 1’s for occupied cells and 0’s for unoccupied cells.

The function operates in either origin and prints the character 0 for occupied cells. It also prints a set of statistics with each generation printed, showing: population, births, deaths, and survivals. The program will stop and print an explanatory message if the pattern dies out or becomes stable.

The program operates on the principle of creating eight identical matrices of the pattern and offsetting each one in a different direction. When these matrices are summed together the resulting matrix represents how many neighboring occupied cells each cell has. The matrix (called sum in the program) is then used to compute the succeeding generations.

This program uses little CPU time because the only primitive functions used to compute the sum matrix are + and which are two of the fastest primitives.

   
 ∇ Inc Life Mat;Gen;Sum;Srv;Pop;POP;Bir;Dth;R1;R2
   Mat ←   Mat
   Gen ← 0
   POP ← +/ Mat
   ' '
   'Generation: 0, population: '; POP
   ' '
   ' 0'[⎕io + Mat]
   ' '
Go:
   R2 ← 2 + ¯1 ↑ ⍴Mat
   R1 ← 2 + 1 ↑ ⍴Mat
   Sum ← ((R1,R2)↑(((R1-2)⍴0),Mat)+(((-R1),
         R2)↑Mat)+((R1,R2)↑((1,R2-2),⍴0),[
         ⎕io]Mat)+((R1,R2)↑Mat)+((R1,-R2)↑
         (((R1-2),1)⍴0),Mat)+((R1,-R2)↑((1,
         R2-2)⍴0),[⎕io]Mat)+(((-R1),R2)↑(((
         R1-2),1)⍴0),Mat)+(-R1,R2)↑Mat
   Mat ← ((-2+⍴Mat)↑(1+⍴Mat)↑Mat)^ Sum∊ 2 3
   Bir ← ( Mat) ^ Sum = 3
   Mat ← Bir ∨ Mat
   Mat ← ((∨\∨/ Mat) ^ ⌽ ∨\∨/ ⌽ Mat) / Mat
   Mat ← ((∨\∨⌿ Mat) ^ ⌽ ∨\∨⌿ ⌽ Mat) / Mat
   Sum ← 0
   Bir ← +/ (,Bir), Sum
   Pop ← +/ , Mat
   Srv ← Pop - Bir
   Dth ← POP - Srv
  →(0 ≠ Bir + Dth) ⍴ End
   Gen ← Gen + 1
   POP ← Pop
  →(0 ≠ Inc | Gen) ⍴ 0
   'Generation: ';Gen;'Births: ';Bir;
       'Deaths:'; Dth;'Survivals: ';Srv;
       'Population: ';Pop
   ' '
   ' 0'[⎕io + Mat]
   ' '
  →Go
End:
   'Population: ';(((Pop≠0)^ 0=Bir+Dth)/
         'Stable'),(((Pop+Bir+Dth)=0)/
         'Dead'   
 ∇

I’ve tried to make this function more readable by various means, but it does not seem to be a notational improvement over the ones we have already seen.

Our next example comes from the year 1984. In that year one of the commercial PC publications contained an article [Wy84] which compared several programming languages in terms of their ability to describe the game of Life. One of the languages used was APL. The example in APL was nicely structured, and there were several functions, of which I shall show only two: the outermost function called LifeGame and the function which produces the next generation, called NextGen :

  ∇ LifeGame
    InitBorder
    Random
    Colony ← Colony ^ BorderMask
    Population ← +/ ,Colony
    Generation ← 1
Loop:→End ×⍳ Population = 0
      ShowColony
      Colony ← NextGen Colony
      Generation ← NextGen Colony
      Colony ← Colony ^ BorderMask
      Population ← +/ ,Colony
     →Loop
End:⎕ ← ' All organisms extinct'
    ⎕ ← 'Goodbye'
  ∇

  ∇ ColonyOut ← NextGen ColonyIn; 
         Neighbors; Born; Lonely; 
         Crowded; Died
    Neighbors ← NumNeighbors ColonyIn
    Born ← Neighbors = 3
    Lonely ← ColonyIn ^ Neighbors < 2
    Crowded ← ColonyIn ^ Neighbors > 3
    Died ← Lonely ∨ Crowded
    ColonyOut ← Born ∨ ColonyIn ^ Died
  ∇

The best comment on this comes from Donald McIntyre, who wrote:

   

This is an excellent representation for the purpose of the beginning tutorial for which it was written. Its clarity is admirable. But it is not the final formulation that I would want my students to aim for. Long names for variables are no doubt necessary in programs such as Fortran and COBOL that commonly run to hundreds of lines, but just as long names are never used in formulas by mathematicians, physicists, or engineers, so they are unnecessary in short functions (often of a single line) defined in APL. Long names have a superficial appearance of good documentation, but in my opinion they tend to conceal rather than illuminate the operation of functions containing them.

   

This quotation comes from an article submitted by McIntyre to the same journal, but which was never printed [McI84]. He shows how the logic of the NextGen function can be considerably simplified, by applying the rules of logic, and then presents a suite of functions of his own, embodying the principles he espouses. I found out about this unprinted article in a conversation with Don, and asked him if he would send me the article. He did that, as well as a floppy disk containing an STSC APL workspace for the IBM PC, that played the game using Don’s functions. There are just six functions in the suite, each having just one line. The two of interest to us are those which compute the next state of the system:

Next: (3 = n) ∨ ⍵ ^ 2 = n ← CN ⍵ 
 CN: (1⌽⍵)+(¯1⌽⍵)+(1⊖⍵)+(¯1⊖⍵)+(1⌽1⊖⍵)+(1⌽¯1⊖⍵)+
     (¯1⌽1⊖⍵)+(¯1⌽¯1⊖⍵) 

I trust you will agree that McIntyre’s Next function is easy to read, given the knowledge that the function CN computes the number of neighbors for each cell. It says that a cell in the next state is alive if the cell in the current state has three neighbors, or if it is now living and has two neighbors. You may recognize that the CN function uses a technique almost exactly the same as that used by Bonyun in 1972: summing the results of all eight rotations of the state matrix. McIntyre uses the direct definition style popularized by Iverson and Orth in their mathematical textbooks [Iv76, Or76].

McIntyre’s effort inspired me to see whether it might not be possible to abbreviate even further. Suppose, I asked, that instead of using eight separate rotation expressions, containing twelve separate instances of one or the other rotation function, one were to assume eight copies of the current state matrix, forming a three-dimensional array, and that this three dimensional array were to be rotated by two phrases, one to give horizontal rotations, and one to give vertical rotations. For example, one might write: v⊖h⌽(8,⍴⍵)⍴⍵ , where v is the vertical rotations desired, 1 1 1 0 0 ¯1 ¯1 ¯1 , and h is the horizontal rotations desired, 1 0 ¯1 1 ¯1 1 0 ¯1 .

You may have already perceived the difficulty. Ordinarily, the rank of the left argument to rotate must be one less than that of the right argument: thus a scalar is used to rotate a vector, a vector to rotate a matrix (one element for each row), and so forth. However, a scalar left argument may be used with a matrix, and it is extended in the usual manner: 1⌽M means to rotate each row one to the left. Unfortunately, there is no corresponding rule in the case of a three-dimensional array: it is not possible to supply a vector argument, and have the elements of the vector apply as scalars to the matrices of the right argument. This means that, instead of the vector 1 1 1 0 0 ¯1 ¯1 ¯1 , I’d need to have the matrix 8 8⍴1 1 1 0 0 ¯1 ¯1 ¯1 , and so forth. It seemed inefficient to produce a matrix in which all the rows were identical. It would be nice if I could use a vector, and have each scalar element of the vector apply to a corresponding matrix.

The APL system I was using was SHARP APL, which had implemented the rank operator, as proposed by Arthur Whitney [Wh82] and discussed by Iverson [Iv83]. The rank operator allows one to specify the applicable ranks of the subarrays used as arguments to the function. In the case at hand, it suggested that to get scalars from a vector on the left to apply to matrices on the right, an expression of the form rotate rank 0 2 (that is, ⌽⍤0 2) would be appropriate, since I wanted to use rank 0 elements (that is, scalars) on the left, and rank 2 elements (that is, matrices) on the right. The function Nbr below, illustrates this usage.

New: ∨⌿ (0 1 ∘.∨ ⍵) ^ 2 3 ∘.= Nbr ⍵
Nbr: +⌿ v ⊖⍤0 2 h ⌽⍤0 2 ⍵
  h: 1 0 ¯1 1 ¯1 1 0 ¯1
  v: 1 1 1 0 0 ¯1 ¯1 ¯1

Since the major object of this paper is to show the importance of new operators in APL, I’m going to spend a fair amount of time discussing the Nbr function. The first phrase to be executed is h ⊖⍤0 2 ⍵ . In words, this says rotate the rows of in accordance with h , using scalars from h to rotate the matrices of . The way to read a phrase is to look from the left for the first operator, in this case . Sinceis dyadic, to find its left argument we look to its left for the longest operator expression. In our case, this is the dyadic function . Its right argument is the vector 0 2 . Thus we have as our derived function ⊖⍤0 2 , which can be read as rotate rank 0 2 , that is, use scalars from the left argument to rotate matrices from the right argument.

This derived function is dyadic, with left argument h and right argument  . The result of h is a vector of eight elements. If we assumeto be a matrix of 20 rows and 20 columns, what is the shape of h ⊖⍤0 2 ⍵ ? Well, we want each scalar element of the left argument to be used with the sole matrix right argument, so we’d like to have a result with shape 8 20 20 . This is exactly what happens!

We call the elements of the derived function’s arguments that have the ranks indicated by the rank argument (0 2 in our case) the cells. The remaining parts of the derived function’s arguments are called the frames. For the left argument, the scalars (the rank zero elements) are the cells, and the vector is the frame. For the right argument, the matrix (the rank two element) is the cell, and the frame is empty. When the frame is empty, the cell is used with each cell of the other argument. You are familiar with the idea of scalar extension: the corresponding notion in connection with the rank operator is a generalization of scalar extension called cell extension; in the case at hand, the cell (that is, the matrix), is replicated eight times to conform to the frame of the left argument.

So, now we have the result of the horizontal rotation phrase h ⊖⍤0 2 ⍵ , with shape 8 20 20 . This becomes the argument to the vertical rotation phrase v ⊖⍤0 2 . This time, the left argument has shape 8 and the right argument shape 8 20 20 , so there is no cell extension, because the frame shapes are both 8 and thus agree. The shape of the result of this phrase is also 8 20 20 . The next phrase is +⌿ , which sums the matrices along the leading dimension, giving as the final result a 20 by 20 matrix containing in each cell the number of neighbors for the corresponding cell of the original matrix argument.

The New function uses the Nbr function to obtain the count of neighbors for each cell, then tests to see which counts are equal to either 2 or 3, giving a 2 by 20 by 20 result, with the first matrix plane having a 1 where the neighbor count is 2, and the second matrix plane having a 1 where the neighbor count is 3. This provides the right argument to the ^ function. The left argument is also a 2 by 20 by 20 array, where the first plane is simply the original argument (formed by 0∨⍵) and the second plane all 1’s (formed by 1∨⍵). And-ing the first plane of the left argument with the first plane of the right argument produces a 1 for each cell with a neighbor count of 2 which is presently 1; and-ing the second plane of the left argument with the second plane of the right argument simply produces a 1 wherever the neighbor count is 3. Finally, or-ing across the first dimension of this 2 by 20 by 20 result gives the desired new state as a 20 by 20 boolean matrix.

Those of you familiar with IBM’s APL2 may at this point feel that a function to Nbr could be written using APL2’s each operator [Gh73], and so it can. The function Nbre below is in APL2:

Nbre: +⌿ ⊃ v ⊖¨ h ⌽¨ ⊂ ⍵ 

The number of tokens in Nbre is the same as in Nbr , but the approach reflects the difference between the rank and the each operators. The each operator, deriving a dyadic function, is used to pair each item of h with , sincehas been made into a scalar by enclosing it; the result has the shape of h , in this case, eight. This gives us a list of enclosed matrices, each rotated on the last axis by the corresponding element of h . This becomes the argument to v ⊖¨ , which results in another eight-element list, again of enclosed matrices, with these rotated along the first axis, according to the corresponding element of v . This result is disclosed, giving an 8 by 20 by 20 result; summing this on the first axis gives the desired 20 by 20 result.

The difference in the viewpoints fostered by SHARP APL and IBM’s APL2 is worth noting. Although the number of tokens is the same in each function, there are only three functions in the SHARP APL expression (+⌿ , ⊖⍤0 2 , and ⌽⍤0 2), but five functions in IBM’s APL2 (+⌿ , , ⊖¨  ⌽¨  and). IBM’s APL2 approach requires the enclose function to make the matrix a scalar, in order to get the benefit of scalar extension. It then needs the each operator to modify the rotates, in order to have them apply itemwise. After this, the disclose is required (it could go to the left of the +⌿ as well as to the right) in order to give back the simple matrix result. In contrast, the SHARP APL approach doesn’t require the Sharp equivalent of an enclosed array, and its subsequent disclosure, since it uses the generalization of scalar extension called cell extension, which is built-in to the rank operator.

This was the state of things until early 1986. I was reasonably happy with what I had. Then I chanced to read (for reasons having nothing to do with the game of Life) Donald Knuth’s book on his typefont designing system, Metafont [Kn86]. Among the data types in the METAFONT language is the picture. Looked at from the APL point of view, a METAFONT picture is a numeric matrix. The elements of a picture describe the way a plane surface is marked, that is, they describe a picture. Pictures can be added or subtracted, and can be shifted, reflected, and rotated by multiples of 90 degrees. In other words, METAFONT has stumbled through a back door into the same area where APL has held sway all alone for so many years. The book gives the following Exercise 13.24:

   

In John Conway’s “Game of Life”, pixels are said to be either alive or dead. Each pixel is in contact with eight neighbors. The live pixels in the (n+1)st generation are those who were dead and had exactly three live neighbors in the nth generation, or those who were alive and had exactly two or three live neighbors in the nth generation. Write a short METAFONT program that displays successive generations on your screen.

   

Turning to the answers section of the book, I found Answer 13.24:

   

13.24 (We assume that currentpicture initially has some configuration in which all pixel values are zero or one; one means “alive”.)

picture v; def c = currentpicture enddef; 
forever: v := c; showit; 
  addto c also c shifted left + c shifted right;
  addto c also c shifted up   + c shifted down; 
  addto c also c - v; cull c keeping (5, 7); endfor.

(It is wise not to waste too much computer time watching this program.)

   

I was impressed by this algorithm, because it reduced the number of vertical and horizontal rotations required from the eight that I had used in Nbr to three. Racing to my APL machine I translated Knuth’s METAFONT algorithm into APL. I also began counting tokens at this point. The APL version of Knuth’s algorithm used 49 tokens.

∇z ← LifeKnuth ⍵;v 
 v ← ⍵ 
 ⍵ ← ⍵ + (1 ⌽ ⍵) + ¯1 ⌽ ⍵ 
 ⍵ ← ⍵ + (1 ⊖ ⍵) + ¯1 ⊖ ⍵ 
 ⍵ ← ⍵ + ⍵ - v 
 z ← ⍵ ∊ 5 6 7
∇

This APL algorithm isn’t fully equivalent to Knuth’s solution, since his METAFONT shifted primitive shifts in zeros, whereas the APL rotate primitive uses nondestructive cyclic rotation. By ensuring that the argument is bordered by an edge of zeros all around, the APL and METAFONT programs will be completely equivalent.

The workings of this algorithm may not be immediately evident. What is happening was expressed as follows [Hu86]: in the pattern

   x x x 
   x ∘ x
   x x x 

encode ones in the x cells by 2, and in thecell by 1, then sum. “Winning” cells are those where sums are 5 6 7 .

Like McIntyre, I also prefer to use the direct definition style of programming. I further impose on myself the burden of using no assignment statements, and as few parentheses as possible. Using these criteria, I translated LifeKnuth into the function lf , which uses 23 tokens:

lf:((2×+⌿¯1 0 1⊖⍤0 2+⌿¯1 0 1⌽⍤0 2 ⍵)-⍵)∊5 6 7 

A similar program in APL2, also 23 tokens long, is lfe :

lfe:((2×+⌿⊃¯1 0 1⊖¨+⌿¯1 0 1⌽¨⊂⍵)-⍵)∊5 6 7 

At the time, the commute operator had been defined [Iv87], but not yet implemented in SHARP APL, and I was intrigued by it as a way of avoiding parentheses, so I wrote the function lv , which uses 21 tokens. The commute operator applied to a dyadic function reverses the order of the arguments. For example 3 -¢ 8 is 5 .

Using commute (21 tokens):

lv:5 6 7∊¢⍵-¢2×+⌿¯1 0 1⊖⍤0 2+⌿¯1 0 1⌽⍤0 2⍵ 

If APL2 had a commute operator, a similar function lve , also 21 tokens long, could be written:

lve:5 6 7∊¢⍵-¢2×+⌿⊃¯1 0 1⊖¨+⌿¯1 0 1⌽¨⊂⍵ 

I communicated the functions lf and lv to a group on the I.P. Sharp internal system mailbox whose members are especially interested in coding techniques. I should have known better. In less than half an hour I received a reply from one of the members of the group [Hu86], entitled “shorter life”:

lw: 2 3 ∊¢(,3) ¯3⍤(+.∧⍤,¨(4 ≠ ⍳9)) ⍵ 
This function uses the tesselation operator ¯3⍤v but I won’t be describing this until a bit later. I won’t describe the function lw at all, since it was soon superseded.

The author of this reply claimed that his lw was shorter than my lv . In fact, it does have fewer characters, but I pointed out to him that I was counting tokens, not characters (¯1 0 1 is one token).

Before exploring the issues this led to, let me give you the contribution of another member of this mailbox group [Sc86], who claimed that + could serve at least as well as - :

lh1:6 7 9∊¢⍵+2×+⌿¯1 0 1⊖⍤0 2+⌿¯1 0 1⌽⍤0 2⍵

The explanation of lh1 is like that for LifeKnuth , where the encoding still gives a weight of two to x , but gives a weight of three to . By using + instead of - the need for one commute operator disappears, so that lh1 is only 20 tokens long, compared to the 21 needed by lv . The same author also contributed a highly contrived solution, just for fun, using the ○⍵ and ⍺○⍵ functions, in just 22 tokens (I haven’t verified that this works):

lh2: (0 1 1○3 3 4) ∊¢⍵○+⌿¯1 0 1⊖⍤0 2+⌿¯1 0 1⌽⍤0 2⍵ 

My first respondent retorted (in an hour and a half) to my rule about token counting with two new functions, called “such is life”, of 19 and 14 tokens, respectively [Hu86], which I will spare you because (as pointed out by my second respondent [Sc86]) they failed to conform to the rules of the game (they erroneously gave birth whenever the neighbor count was 2, regardless of whether the cell was currently alive or dead). In any event, the first respondent was undeterred, and in 12 hours sent the function lz [Hu86], calling it “life is like that” (15 tokens long):

lz:5 6 7∊¢(⍵¯3⍤,¢3 3)+.×2 2 2 2 1 2 2 2 2

His claim was that, once he had the 3 by 3 cells in hand, any computable rule is straightforward. This requires a bit of a detour while I explain to you about the tesselation operator.

The tesselation operator was introduced in [Iv83], as a subcase of the cut operator, but has undergone minor changes since then. The current definition is as given in [Iv87]. There, you may read:

   

The case 3⍤v … has left rank 2, and ⍺ 3⍤v ⍵ applies v to each element produced by a tesselation ofusing a size ⍺[1;] , and beginning points that are multiples of the “shift” ⍺[0;] . For example:

        ⍺        ⍵          ⍺ 3⍤< ⍵
      3 2      abcdef     ┌───┬───┬──┐
      2 3      ghijkl     │abc│cde│ef│
               mnopqr     │ghi│ijk│kl│
               stuvwx     ├───┼───┼──┤
               yzABCD     │stu│uvw│wx│
                          │yzA│ABC│CD│
                          └───┴───┴──┘

… The case ¯3⍤v is equivalent to 3⍤v except that shards of shape less than ⍺[1;] are omitted.

   

(Note: The word “tesselation” means a covering by tiles. In the context of the cut operator, it means to produce subarrays of a given size, and with a given spacing, derived from an argument array.)

With this explanation of the tesselation operator digested, we can attempt to understand lz . This function says something like this: tesselateinto all possible three by three submatrices, ravel these, and form the inner product of each nine-element vector so formed with the vector 2 2 2 2 1 2 2 2 2 , thus giving as many scalar elements as there are submatrices; produce a result in which, for each of these scalar elements having a value of 5 , 6 , or 7 , a 1 appears, with 0’s appearing elsewhere.

Nothing daunted, I attempted to see if I could come up with something shorter still. I realized that there were 140 matrices of size three by three which correspond to “winning” arrangements; these consist of all those which have exactly two out of eight of the outer cells equal to one (2!8 of these, or 28), plus twice the number for which exactly three out of eight of the outer cells are equal to one (2 × 3!8 or 2 × 56 or 112). If one were to take the 2⊥ of the ravel of each of these, one would get a vector of 140 elements; if this were upgraded, the first and last few elements would be 7 11 13 … 408 416 432 . It would take a good bit of typing, but one could define a function lb as follows:

lb: 7 11 13…408 416 432 ∊¢ 2⊥ ⍵ ¯3⍤,¢ 3 3  

which requires 11 tokens. Also, because of the exhaustive list of integers, it qualifies as a brutish solution. What this does is to tesselate the argument into all possible three by three matrices, ravel these, and take the two base value of the ravels; it then looks to see which of the resulting items appear in the list of 140 elements (where … stands for the omitted elements), giving a 1 where the item is in the list, and 0 elsewhere.

But wait: that’s not the shortest yet. Suppose that instead of taking the two base value of the 140 matrices, one were to have instead a global (that is, nasty) 140-element vector of enclosed three by three matrices, where each matrix was a distinct one of the winners: let’s call this array O .

The first and last few elements of the global vector O of winning matrices look like this:

0 0 0  0 0 0  0 0 0   1 1 0  1 1 0  1 1 0 
0 0 0  0 0 0  0 0 1 … 0 1 1  1 0 0  1 1 0 
1 1 1  0 1 1  1 0 1   0 0 0  0 0 0  0 0 0 

  7     11     13   …  408    416    432 

Then one could produce the function lnbs , which is nasty, brutish, and short (9 tokens):

      lnbs: O ∊¢⍵ ¯3⍤<¢3 3

Acknowledgment

I should like to thank Alan Graham, of IBM Santa Teresa, for verifying that the APL2 programs I’ve included do indeed work.
 

References

[Bo72]  Bonyun, David, Dept. of Computer Science, Arcadia University Wolfville, Nova Scotia, Canada; 4-line original in APL Quote-Quad III, 5, June 1972, as Algorithm Number 95, “Game of Life”. Also in APL Quote-Quad, the Early Years, APL Press, 1982, page 437. Rewritten for this paper.
[Du71]  Duby, Jean-Jacques, IBM European Systems Research Institute, Geneva, Switzerland, 7-line original in APL Quote-Quad III, 2 & 3, October 1, 1971, as Algorithm Number 70, “Conway’s Game ‘Life’”. Also in APL Quote-Quad, the Early Years, APL Press, 1982, page 311. Rewritten for this paper.
[Ga70]  Gardner. M., “Mathematical Games”. Scientific American 223 4, October 1970. See also, Gardner, Martin, Wheels, Life, and Other Mathematical Amusements, W.H. Freeman, 1983, ch 20-22.
[Gh73]  Ghandour, Z., and J. Mezei, “General Arrays, Operators, and Functions”, IBM Journal of Research and Development, 17 4, July, 1973, pp 335-352. The each operator was called itemwise in this paper (which first published the idea in an APL context), but the operator symbol ¨ preceded rather than followed the function symbol (see in particular pp 339-340).
[Ho51]  Hobbes, Thomas, Leviathan, Part I, chapter 13, 1651. The complete citation from Hobbes is worth reading. He is writing about the human state when no subordination of the individual to government obtains, and has pointed out that this is the state of war:

Whatsoever, therefore, is consequent to a time of war where every man is enemy to every man, the same is consequent to the time wherein men live without other security than what their own strength and their own invention shall furnish them withal. In such condition there is no place for industry, because the fruit thereof is uncertain; and consequently no culture of the earth; no navigation nor use of the commodities that may be imported by sea; no commodious building; no instruments of moving and removing such things as require much force; no knowledge of the face of the earth; no account of time; no arts; no letters; no society; and, which is worst of all, continual fear and danger of violent death; and the life of man solitary, poor, nasty, brutish, and short.
 

[Hu86]  Hui, R., I.P. Sharp internal system mailbox messages 1898744, 1898811, 1899521, 1900061, Sept, 1986.
[Iv76]  Iverson, K.E., Elementary Analysis, APL Press, 1976. See especially Section 1.3 and Chapter 10.
[Iv83]  Iverson, K.E., Rationalized APL, I.P. Sharp Associates, 1983, pp 9-10.
[Iv87]  Iverson, K.E., “A Dictionary of APL”, APL Quote Quad, 18 1, p.5-40, September, 1987. See in particular Section VI, “Conjunctions”, the Cut operator, for the case 3⍤v .
[Jo72]  Jones, W., Syracuse University Computing Center, 6-line original in APL Quote-Quad III, 5, June 1972, as Algorithm Number 94, “Game of Life”. Also in APL Quote-Quad, the Early Years, APL Press, 1982, page 436. Rewritten for this paper.
[Kn86]  Donald Ervin Knuth, The METAFONTbook, Addison-Wesley Longman Publishing Co., Inc., Boston, MA, 1986.
[McI84]  McIntyre, Donald, Pomona College, “Thinking about Life”, private communication.
[Or76]  Orth, D.L., Calculus in a New Key, APL Press, 1976. See especially Section 0.3 and Appendix A.
[Sc86]  Schueler, J.H., I.P. Sharp internal system mailbox messages 1898870, 1898874, Sept, 1986.
[Si74]  Sinykin, Marc, and David Vorgang, Newport Harbor High School, Newport Beach, CA 92660; 8-line original in APL Quote-Quad 5, 4, Winter, 1974, as Algorithm Number 124. Rewritten for this paper.
[Wh82]  Whitney, Arthur, private communication.
[Wy84]  Wyn, Pardner, “An APL Tutorial: Life is simpler with APL”, IBM PC Tech Journal 2, 3, September 1 84, pp 129-147.


First appeared in the APL88 Conference Proceedings, APL Quote-Quad, Volume 18, Number 2, 1987-12.

created:  2009-11-08 23:15
updated:2011-12-20 20:15