Functions That Produce Answers

The last lab week is matrices again, and this time the functions do the mathematics rather than the reading and printing. The transpose function fills a second matrix from the first, which is a function producing an array result through a parameter. The trace and norm functions each compute a single number from a matrix and return it, which is chapter 9's return value carrying the answer. Both programs reuse readMatrix and printMatrix from week 11 unchanged, and that reuse is the argument for having written them as functions in the first place.

Program 1: The Transpose of a Matrix

The transpose of an M by N matrix is the N by M matrix in which rows and columns change places: element [i][j] of the input becomes element [j][i] of the output.

Algorithm:

  1. Start.
  2. Read the rows and columns; if either is outside 1 to 10, print a message and stop. Read the matrix.
  3. For each row i and column j, set transpose[j][i] to matrix[i][j].
  4. Print the matrix and its transpose under headings.
  5. Stop.

Enter 2 3, then 1 2 3 and 4 5 6: the transpose has three rows of two, 1 4, 2 5, 3 6. The function's whole body is one assignment with the indices swapped, and the arrangement around it is what the marks are for. The transpose goes into a second array, t, passed in as a parameter, because the function cannot return an array in C and because a 2 by 3 matrix cannot be transposed in place: its transpose has a different shape, and overwriting the input while reading it would destroy elements not yet copied. The print call for t passes cols, rows in that order, since the transpose has as many rows as the input had columns; get that backwards and a 2 by 3 input prints a 2 by 3 block whose third column comes from cells of t that were never written. Those values are indeterminate, so reading them is undefined behaviour; they happen to print as zeros in this sandbox and could be anything elsewhere. It is the classic mistake in this program.

The array t reaches the function the same way m does, as a pointer to its first row, so the assignments inside transpose write into main's array directly and nothing needs to be copied back. That is chapter 11's rule that array parameters are never copies, and it is why a function can "return" a matrix through a parameter at all.

Program 2: The Trace and the Norm

The trace of a square matrix is the sum of its diagonal, the elements [i][i]. The norm used here is the square root of the sum of the squares of every element, which is defined for any shape. Trace needs a square matrix, and the program has to check.

Algorithm:

  1. Start.
  2. Read the rows and columns with the usual check, and read the matrix.
  3. Print the matrix.
  4. If the matrix is square, compute and print the trace; otherwise print that the trace is not defined for this shape.
  5. Compute and print the norm.
  6. Stop.

Enter 2 2, then 1 2 and 3 4: the trace is 1 + 4 = 5, and the norm is the square root of 1 + 4 + 9 + 16, which is 5.48. Then try 2 3 with 1 2 3 and 4 5 6: the trace line reports that it is not defined, and the norm is still printed, because it is defined for any shape and there is no reason to withhold it.

trace takes a single size, n, because a square matrix has only one, and it is main's job to establish that the matrix is square before calling it; the function is written for the case it is valid in, and the check lives with the code that has the information. It returns a long so that ten diagonal int values can be summed without overflow. norm accumulates in double and casts the element before squaring, (double) m[i][j] * m[i][j], because the product of two ints is an int and squares of values above 46340 overflow before they ever reach the double; the cast on the first operand converts the whole multiplication. That one cast is the difference between a norm that is right for every int and one that is right for small examples.

Weeks 13 to 15: Revision and the Lab Test

The syllabus gives two weeks to revise weeks 1 to 12 and then sits you at a machine with a program to write from memory. Twelve weeks of programs come down to a short list of shapes, and revising means being able to write each shape without looking:

  • The guarded read: if (scanf(...) != count) then a message and return 1, in front of every input.
  • The range guard: refuse N outside what the array holds or what the arithmetic can bear, before any loop.
  • The accumulator: a total starting at 0 and a counted for adding one term per pass.
  • The digit peeler: n % 10 and n / 10 until n is 0, with a copy of n if it is needed later.
  • The ladder: if, else if, else for exclusive cases, slabs included, with the lower slabs folded into each branch.
  • The switch: constant cases, break on every one, default always present.
  • The array: a #define capacity, a checked count, a guarded fill loop, and printing with a space before every element but the first.
  • The function: prototype above main, definition below, a status returned by anything that reads, and the column count in every two-dimensional parameter.

Every program in this chapter is one or two of those shapes with a formula in the middle. In the test, write the shape first and the formula second, and check the output against the sample in your record before calling it done.

Key Takeaways

  • A function returns a matrix through an array parameter; transposing a non-square matrix needs a second array because the shape changes.
  • Print a transpose with the dimensions swapped, cols then rows.
  • The trace is the diagonal sum of a square matrix and main checks squareness before calling; the norm is the root of the sum of squares and is defined for any shape.
  • Cast before squaring, (double) m[i][j] * m[i][j], so the product cannot overflow an int.
  • Reuse readMatrix and printMatrix unchanged across programs; that is what functions are for.
  • The lab test is a handful of shapes with a formula in the middle; revise the shapes.