Other Topics

Two dimensional arrays

The arrays we have seen so far are one-dimensional. To uniqely identify any element of an array, we need only one piece of data: its index. This helps us model many kinds of things, for instance, a list of student ID numbers, sound (as we saw in program 6), and character strings. Some data are inherently two-dimensional and have a natural representation in the computer as a two-dimensional array, where we use two indices instead of one to refer to an array element. Some examples are:

In C, two-dimensional arrays are represented by arrays of arrays. For example, if we want to have a two-dimensional array of integers, we would declare them like this:

	int	v[10][20];
This object v is now an array of size 10 of arrays of size 20 of integers. It's easier to think of it as 10 rows, where each row has 20 integers. Indexing the array takes two integers. The following code initializes each element of the array to 0:
	int	i, j;

	for (i=0; i<10; i++) for (j=0; j<20; j++) v[i][j] = 0;
So, v[i] is a single row of the array, or a single one-dimensional array that's part of the 2D array. In terms of pointers, v[i] is the same as &v[i][0].

Let's look at a program that uses a 2D array of characters like a graphics frame buffer to draw and print a circle on the screen (a more robust version of this program is available in the C examples as circle.c .

#include <stdio.h>
#include <math.h>

/* height and width (minus 1 to be safe) of a VT320 terminal screen */

#define WIDTH  79
#define HEIGHT 24

/* clear the screen buffer by putting blanks in each array element */

void clear (char screen[][HEIGHT]) {
	int	i, j;

	for (i=0; i<WIDTH; i++) for (j=0; j<HEIGHT; j++) screen[i][j] = ' ';
}

/* output the screen buffer to the standard output */

void print (char screen[][HEIGHT]) {
	int	i, j;

	for (j=0; j<HEIGHT; j++) {
		for (i=0; i<WIDTH; i++) putchar (screen[i][j]);
		putchar ('\n');
	}
}

/* draw a circle by placing stars in the array using the formula
 * for a unit circle y = sqrt (1 - x*x) 
 */
void circle (char screen[][HEIGHT], int i, int j, int radius) {
	float	x, y;
	int	di, dj;

	/* x will go from 0 through 1 in steps of 0.01 */

	for (x=0.0; x<=1.0; x+=0.01) {

		/* y is the y coordinate of the point (x,y)
		 * on the unit circle
		 */
		y = sqrt (1.0 - x*x);

		/* multiply by the radius (factor of 1.7 makes it look more
		 * circular on some monitors
		 */
		di = (int) (x * radius * 1.7);
		dj = (int) (y * radius);

		/* put a star in each quadrant for this point,
		 * offset from the center of the circle
		 */

		screen[i + di][j + dj] = '*';
		screen[i + di][j - dj] = '*';
		screen[i - di][j + dj] = '*';
		screen[i - di][j - dj] = '*';
	}
}

int main () {
	char	screen[WIDTH][HEIGHT];

	/* clear (initialize) the screen buffer */

	clear (screen);

	/* put a circle of radius 10 at (x,y)=(40,12) */

	circle (screen, 40, 12, 10);

	/* print the screen buffer to standard output */

	print (screen);
	return 0;
}	
The output of this program looks like this:

                                                                               
                                                                               
                                        *                                      
                                 ***************                               
                              ****             ****                            
                            ***                   ***                          
                           **                       **                         
                          **                         **                        
                         **                           **                       
                        **                             **                      
                        *                               *                      
                        *                               *                      
                        *                               *                      
                        *                               *                      
                        *                               *                      
                        **                             **                      
                         **                           **                       
                          **                         **                        
                           **                       **                         
                            ***                   ***                          
                              ****             ****                            
                                 ***************                               
                                        *                                      
                                                                               

Arrays of Strings

We can use 2D arrays to act as arrays of strings, since a string is in an array of char and a 2D array of char is an array of arrays of char. We can then read in a bunch of strings from a file into the 2D array and work on them in memory to do editing, spell-checking, or whatever the program is supposed to do, e.g.:
int main () {
	char	stuff[100][80]; /* 100 strings of up to 79 characters */
	int	i;

	i = 0;
	while (!feof (stdin)) 
		fgets (s, 80, &stuff[i++]);
	...
}

This approach is limited. Since array dimensions have to be specified at compile time, we have to know two things in advance: what is the maximum number of strings we will allow in a file, and what is the maximum string length we will allow? If the text file is typical, it will have many short strings, some with length 1 or 2, and a few long strings exceeding 80 characters. But each string in our 2D array must allow for the maximum number of characters, wasting a lot of storage. Even with memory sizes the way they are today, this is a real issue because if a file has just one 1000 length string, an array of 1000 of these strings will consume an entire megabyte of storage.

Another approach is to use an array of pointers instead of a 2D array of characters. There is a function called malloc that returns a pointer to an array of as many bytes as you want; we can use this function to allocate dynamically only the amount of storage that we need:

int main () {
	char	*stuff[100],	/* 100 pointers to char */
		s[1000];	/* one string of length 1000 */
	int	i;

	i = 0;
	while (!feof (stdin)) {

		/* get a string */

		fgets (s, 1000, stdin);

		/* allocate storage for the length of the string
		 * plus one extra byte for the null
		 */
		stuff[i] = (char *) malloc (strlen (s) + 1);

		/* copy the string we read in to the array */

		strcpy (stuff[i++], s);
	}
	...
}
This approach can also be used with other data types to allocate, say, a matrix with variable-length rows.

Here is another C program that uses two-dimensional arrays. It plays "The Game Of Life":

/*
 * The Game of Life
 *
 * a cell is born, if it has exactly three neighbours 
 * a cell dies of loneliness, if it has fewer than two neighbours 
 * a cell dies of overcrowding, if it has more than three neighbours 
 * a cell survives to the next generation, if it does not die of loneliness 
 * or overcrowding 
 *
 * In my version, a 2D array of ints is used.  A 1 cell is on, a 0 cell is off.
 * The game plays 100 rounds, printing to the screen each time.  'x' printed
 * means on, space means 0.
 *
 */
#include <stdio.h>

/* dimensions of the screen */

#define BOARD_WIDTH	79
#define BOARD_HEIGHT	24

/* set everthing to zero */

void initialize_board (int board[][BOARD_HEIGHT]) {
	int	i, j;

	for (i=0; i<BOARD_WIDTH; i++) for (j=0; j<BOARD_HEIGHT; j++) 
		board[i][j] = 0;
}

/* add to a width index, wrapping around like a cylinder */

int xadd (int i, int a) {
	i += a;
	while (i < 0) i += BOARD_WIDTH;
	while (i >= BOARD_WIDTH) i -= BOARD_WIDTH;
	return i;
}

/* add to a height index, wrapping around */

int yadd (int i, int a) {
	i += a;
	while (i < 0) i += BOARD_HEIGHT;
	while (i >= BOARD_HEIGHT) i -= BOARD_HEIGHT;
	return i;
}

/* return the number of on cells adjacent to the i,j cell */

int adjacent_to (int board[][BOARD_HEIGHT], int i, int j) {
	int	k, l, count;

	count = 0;

	/* go around the cell */

	for (k=-1; k<=1; k++) for (l=-1; l<=1; l++)

		/* only count if at least one of k,l isn't zero */

		if (k || l)
			if (board[xadd(i,k)][yadd(j,l)]) count++;
	return count;
}

void play (int board[][BOARD_HEIGHT]) {
/*
	(copied this from some web page, hence the English spellings...)

	1.STASIS : If, for a given cell, the number of on neighbours is 
	exactly two, the cell maintains its status quo into the next 
	generation. If the cell is on, it stays on, if it is off, it stays off.

	2.GROWTH : If the number of on neighbours is exactly three, the cell 
	will be on in the next generation. This is regardless of the cell's 		current state.

	3.DEATH : If the number of on neighbours is 0, 1, 4-8, the cell will 
	be off in the next generation.
*/
	int	i, j, a, newboard[BOARD_WIDTH][BOARD_HEIGHT];

	/* for each cell, apply the rules of Life */

	for (i=0; i<BOARD_WIDTH; i++) for (j=0; j<BOARD_HEIGHT; j++) {
		a = adjacent_to (board, i, j);
		if (a == 2) newboard[i][j] = board[i][j];
		if (a == 3) newboard[i][j] = 1;
		if (a < 2) newboard[i][j] = 0;
		if (a > 3) newboard[i][j] = 0;
	}

	/* copy the new board back into the old board */

	for (i=0; i<BOARD_WIDTH; i++) for (j=0; j<BOARD_HEIGHT; j++) {
		board[i][j] = newboard[i][j];
	}
}

/* print the life board */

void print (int board[][BOARD_HEIGHT]) {
	int	i, j;

	/* for each row */

	for (j=0; j<BOARD_HEIGHT; j++) {

		/* print each column position... */

		for (i=0; i<BOARD_WIDTH; i++) {
			printf ("%c", board[i][j] ? 'x' : ' ');
		}

		/* followed by a carriage return */

		printf ("\n");
	}
}

/* read a file into the life board */

void read_file (int board[][BOARD_HEIGHT]) {
	int	i, j;
	char	s[100];

	for (j=0; j<BOARD_HEIGHT; j++) {

		/* get a string */

		fgets (s, 100, stdin);

		/* copy the string to the life board */

		for (i=0; i<BOARD_WIDTH; i++) {
			board[i][j] = s[i] == 'x';
		}
	}
}

/* main program */

int main (int argc, char *argv[]) {
	int	board[BOARD_WIDTH][BOARD_HEIGHT], i, j;

	initialize_board (board);
	read_file (board);

	/* play game of life 100 times */

	for (i=0; i<100; i++) {
		print (board);
		play (board);
		sleep (1);

		/* clear the screen using VT100 escape codes */

		puts ("\033[H\033[J");
	}
	return;
}
This "game" is played by the computer in a two-dimensional array and the results printed to the screen. Different initial input files lead to different games being played. Let's see the result of using this input file.