SU08 Arrays
SU08 Arrays
Contents
8.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1
8.1 Introduction
On completion of this study unit the student should:
int main() {
float mark1, mark2, sumAverage = 0.0, average;
int count;
string name;
cout << ”The average is ” << sumAverage / 200.0 << ”%” << endl;
return 0;
}
Listing 8.1: Basic Average Calculation
Suppose the problem statement further specifies that if the class average is less than 50
the student’s marks should be increased by 5. The StudentMarks program isn’t suitable
2
because by the time the class average is calculated - the student’s averages aren’t available
any more. Only the last student’s average will still be in the variable average. Must we
then read in all the marks again? This does not seem to be the answer to our problem.
To solve this problem at a first glance, we need 200 variables to store each student’s
average. To increase the averages implies 200 assignment statements:
#include <iostream>
#include <string>
using namespace std;
void calculateCorrectAverage(int count, float mark1, float mark2, ..., float mark200,
float &sumAverage, float &average1, float &average2, ..., float &average200) {
switch (count) {
case 1:
average1 = (mark1 + mark2) / 2.0;
cout << average1 << endl;
sumAverage += average1;
break;
case 2:
average2 = (mark1 + mark2) / 2.0;
cout << average2 << endl;
sumAverage += average2;
break;
// Add cases for other students as needed
case 200:
average200 = (mark1 + mark2) / 2.0;
cout << average200 << endl;
sumAverage += average200;
break;
}
}
3
average200 += 5;
}
int main() {
float sumAverage = 0.0;
float average1, average2, average200;
cout << ”Adjusted average is ” << sumAverage << ”%” << endl;
return 0;
}
Listing 8.2: Adjust mark without for loop
It should be clear that the solution given in Listing 8.2 is a totally inefficient way to solve
a problem. An array should be used for problems of this kind where multiple values of
the same type are to be stored in memory. A variable of type array has one name and it
can contain many values of the same type. We can think of an array as a group of boxes
that are joined together, numbered 0, 1, 2,... etc. Each value of an array can be retrieved
by its position in the array. One variable is therefore associated with many values and an
index is used to identify a specific value. The array is a structured datatype that exists
in most programming languages. Visually an array can be represented in the following
manner:
The elements of the type array have a linear structure - each value has a unique predecessor
and successor, except the first and last values. The size of the array (an integer value,
say n) should be given at array declaration. The indexes of the array then range from 0
to n-1.
The syntax for an array of type typeName is as follows:
const int numberOf = 100;
typeName arrName[numberOf];
//eg. For an int array
int arrName[numberOf];
4
// OR
int arrName[100];
Listing 8.3: Array Declaration Syntax
Note, the numberOf field within square brackets [], representing the number of elements in
the array, must be a constant expression, since arrays are blocks of static memory whose
size must be determined at compile time, before the program runs. In other words you
cannot change the size of the array at runtime, eg. though user input.
In the above case all the elements in arrName are still uninitialised after declaration. The
elements in an array can be explicitly initialised to specific values when it is declared, by
adding those initial values in braces . For example:
int foo [5] = { 16, 2, 77, 40, 12071 };
//Or:
int foo2 [] = { 16, 2, 77, 40, 12071 }; //Since 5 values are provided, size is 5
Listing 8.4: Array Initialisation
Accessing the elements of an array is done by specifying the name of the array with the
index in square brackets, that is: arrName[index], for example, the 10th int in the array
called arrName:
int index = 9; // remember array indexing starts from 0
int arrName[index];
Listing 8.5: Array Access Syntax
If we were to execute the following statement cout << arrName endl;, we would re-
ceive a reference (memory address) on the first element of the array (or equivalent to
&matrix[0]).
The marks program is now written as follows using arrays:
#include <iostream>
#include <string>
using namespace std;
int main() {
float mark1, mark2, sumAverage = 0.0;
float average[200];
int count;
string name;
5
sumAverage /= 200.0;
if (sumAverage < 50.0) {
sumAverage = 0.0;
for (count = 0; count < 200; ++count) {
average[count] += 5;
sumAverage += average[count];
}
sumAverage /= 200.0;
}
cout << ”The total average is ” << sumAverage << ”%” << endl;
return 0;
}
Question 8.1
Why must sumAverage be re-assigned the value 0.0 in the main program?
Question 8.2
Extend the program to include an array of student names. Note, this question will be
expanded on in Question 8.14
Consider the following example that counts the number of times each number on a dice
occurred after each subsequent roll of the dice. The dice was thrown an unknown number
of times. The program without arrays would be written as follows:
#include <iostream>
using namespace std;
int main() {
int counter1 = 0, counter2 = 0, counter3 = 0;
int counter4 = 0, counter5 = 0, counter6 = 0;
int throwValue;
int total = 0;
while (throwValue != 0) {
switch (throwValue) {
case 1: counter1++; break;
case 2: counter2++; break;
case 3: counter3++; break;
case 4: counter4++; break;
case 5: counter5++; break;
case 6: counter6++; break;
default: cout << ”Invalid value!” << endl; break;
}
total++;
cout << ”Next throw: ”;
6
cin >> throwValue;
}
return 0;
}
Listing 8.6: Dice without array
Question 8.3
Change the dice program in Listing 8.6 to make use of an array to capture the number
of times a number of a 6 sided dice has been thrown.
7
When sending an array to a function as a parameter or receiving an array from a function,
either a reference to the array, or a pointer to the array is passed. Arrays can be passed
to functions by:
• specifying the exact size of the array the function should expect void printArray(int
values[5], int size );
• defining an array of no specific size, thereby enabling the function to be used for
array id varying sizes, void printArray(int values[], int size );, or
• sending a pointer to the the array, void printArray(int *values, int size );
For all three these cases, it is necessary to pass the size of the array through to the function
from the calling function or vice-versa if the array were to be declared within the function
for which it is defined as a parameter. This is because a pointer to the first element of
the array is sent to the function and the calculation to determine the size of the array will
simply return the size of the type of the first element of the array and not the number of
elements in the array.
Iterating through the array can take place either using the index or using pointer arith-
metic
void printArray(int ∗values, int size) {
for (int i = 0; i < size; i++) {
cout << values[i] << ” ”;
}
cout << endl;
}
or
void printArray(int values[], int size) {
for (int i = 0; i < size; i++) {
cout << ∗(values + i) << ” ”;
}
cout << endl;
}
8
6.C respectively translate to arrays. Plan 8.C
Question 8.4
Read 10 integer values from the keyboard and determine the average of the values. Count
the number of values below the average. Write the results to the screen.
In this plan, the number of elements entered into the array may not exceed maxArraySize.
It may however be less if the sentinal of 999 is entered for value. The variable counter
indicates how many values were entered into the array. Note the if-statemenet after the
loop that adjusts counter to not incluse the sentinal value in the elements entered into
the array.
Question 8.5
Would it be better to write the plan in terms of a do-while statement?
Question 8.6
2. What would happen if the condition counter < maxArraySize is left out?
9
Plan 8.C: Initialise array elements
All elements in the array are assigned an initial value (also advisable to do in a separate
function).
void initializeArray(int arr[], int size, int initialValue) {
for (int i = 0; i < size; ++i) {
arr[i] = initialValue;
}
}
Consider the example in which the flat number of the person for which the postman has
letters is read in. The number of letters in the post box is incremented by the number of
letters delivered. The program prints the number of letters in each of the post boxes in
the flat building. Assume that the flat building has 30 flats.
#include <iostream>
using namespace std;
int main() {
int postBox[30] = {0}; //Shorthand to initialise all box numbers to 0
int boxNumber, letterNumber;
return 0;
}
Listing 8.7: Post Box Example
Question 8.7
Why are the values of postBox initialized to 0?
Question 8.8
Change the program so that it makes use of functions.
Question 8.9
Add functionality to the program to determine which flat got the most letters. Make use
10
of the Determine Highest Value plan and adapt it for arrays. Write a separate function,
e.g. getFlatWithMostLetters().
Question 8.10
Write a program that reads the names and marks of a maximum of 100 students. Deter-
mine the name(s) of the student with:
• A mark equal to the average (NOTE: There might not be a mark exactly equal to
the average)
8.3.2.1 Sorting
Sometimes it is necessary to order the elements in an array. This can be either in ascending
or descending order. Most sorting algorithms will compare two values and then swop the
values if the sorting condition does not hold for the two values under consideration. The
following two plans will sort the array in ascending order using the Bubble sort algorithm
and the Selection sort algorithm.
11
When i = 0, j will iterate over 0 through to 5 and compare the value at arr[j] with
the one at arr[j + 1]). If the value of the element at position j is larger than the one
at position j+1, the values are swopped and j is incremented by 1. The following figure
shows how the values in the array are switched during the first pass of the algorithm (that
is when i = 0).
As i iterates from 0 to 5, there will be a total of 6 passes over the array with j iterating
over one less element than in the previous pass over subsequent passes.
Question 8.11
Draw the changes to the array and the final array after the second pass, that is for i =
1.
12
Plan 8.E: Selection sort
Selection sort selects the element containing the smallest value from the unsorted portion
of the array and places the element in the correct position by swopping it out with the
element currently in the position.
A function implementing the selection sort algorithm, assuming a valid swop function
exists, is given below.
void selectionSort(int arr[], int size) {
for (int i = 0; i < size − 1; ++i) {
int minIndex = i;
for (int j = i + 1; j < size; ++j) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
swop(arr[i], arr[minIndex]);
}
}
}
At the start of the first pass (where i = 0) of the algorithm, the first element is assumed
to hold the smallest value. The loop indexed by j will check if there are any values smaller
than this value, if there is, the index of the smallest element (minIndex) will be changed
accordingly. If the ith element is not the smallest element, the swop function is called.
For each pass i + 1, j iterates from the index of i to find the smallest element and calls
the swop function at the end of the pass as needed. The figure below shows how the array
has changed with each pass.
Overall, selection sort is considered more efficient. It generally requires less swops that
what bubble sort does.
13
8.3.2.2 Searching
We make use of searching algorithms to determine whether the array contains a specific
value. A value can be searched for in either an unsorted or a sorted array. Searching is
however more efficient on sorted arrays.
Consider the following unsorted array and assume we are wanting to determine whether
the value 6 is in the array. If we begin at the beginning of the array, it will take us
5 comparisons to determine that 6 is indeed in the array. If the array were sorted in
ascending order, it would take 2 comparisons.
Now consider the scenario where the value 13 is being looked for. In the unsorted array,
we would have to test every element in the array and only when we have reached the end
of the array can we say that the element is not in the array. If the array was sorted in
an ascending order, when we reach an element with a value that is greater than the value
we are looking for, we can proclaim that the element is not in the array.
In this section we will be considering two search algorithms that can be applied to arrays.
The first is the linear search, as described above, which can be applied to both unsorted
and sorted arrays. The second is the binary search algorithm which must be applied to a
sorted array.
Question 8.12
Rather than having the search algorithm return a boolean value giving an indication
whether the value being searched for is in the array or not, the algorithm can be adapted
to return the location in the array where the value can be found. If the value is not
in the array, a -1 can be returned. Rewrite the linear search algorithm to provide this
functionality.
14
iteration of the search, the algorithm will look in the part of the array where it is most
likely to find the element. If the element is not in the array, the boolean value of false
is returned.
bool binarySearch(int arr[], int size, int searchElement) {
int first = 0, last = size − 1;
while (first <= last) {
int middle = (first + last) / 2;
if (arr[middle] == searchElement) {
return true;
} else if (arr[middle] < searchElement) {
first = middle + 1;
} else {
last = middle − 1;
}
}
return false;
}
The following trace shows how the algorithm is applied to the sorted array with values
{1, 6, 10, 12, 30, 50 } and the value being searched for as 6. For the first iteration
of the while loop, the values of first and last are 0 and size - 1 respectively. The index
middle is calculated to be 2. Remember, integer division is applied (5/2 = 2). As the
value being searched for is less than the middle value, it is necessary look to the left of
the middle point in the array. The index for last is adjusted and middle is recalculated.
The algorothm will terminate with true, if the value being searched for is at index middle,
otherwise when index first is greater than index last, it can be safely concluded that the
element is not in the array.
15
Question 8.13
Set up your own trace to find the element with value 12 in the sorted array.
Question 8.14
Add the following functionality to your solution of Question 8.2:
• First add a function that sorts the array of marks and the array of names in ascending
order. To sort the names, you simply have to swap the i and j elements of the name
array whenever the marks are swapped.
16
• Then ask the user for a mark and determine whether there is a student that achieved
the specified mark.
Write the program twice, once using the linear search method and for a second time
using the binary search method.
If the elements of an array are again of type array, the structure is called a matrix or
a two-dimensional array. Any data that is normally represented as a table containing
values of the same type, can be stored as an array in memory. For example, the votes
that candidates achieved in different voting areas is an example of data that can be stored
as a matrix.
The matrix of float values, given below, comprises of 3 rows and 4 columns. As the row
and column headings are not of the same type as the contents of the matrix, they will
need to be stored in separate structures. These will be referred to as area and candidate
respectively.
Candidate
Area A B C D
1 10 7.5 1 0.5
2 22 15 0.7 0
3 6.5 8 5 4
The code in Listing 8.8 reads the votes from the user and generates the necessary row and
column headings when the matrix is printed to the screen. It is assumed that the votes
to be capture are for exactly 3 areas and 4 candidates.
#include <iostream>
using namespace std;
int main() {
float matrix[3][4];
int area[3];
char candidate[4];
int i, j;
17
for (j = 0; j < 4; ++j) {
cout << ”Matrix[” << i + 1 << ”][” << j + 1 << ”]: ”;
cin >> matrix[i][j];
}
}
return 0;
}
Listing 8.8: Votes Matrix Example
The loop to assign candidate designations assumes that candidates are “named” beginning
with ‘A’ and ending with ‘A’+3 or ‘D’. The next set of nested loops automatically assigns
the area value (1, 2 or 3) to the corresponding area array and reads 12 (3*4) values from
the console and places them in the matrix for each candidate in the particular area. The
final loops, write the matrix out in the given form.
Question 8.15
Why is the cout << endl necessary?
Question 8.16
What do lines for (j = 0; j < 4; ++j) do?
Question 8.17
What is the function of lines area[i] = i + 1;, cout << "Area" << ’\t’; and cout
<< area[i] << ’\t’;? What would the output be without them?
The program given in Listing 8.9 is an modified version of the program in Listing 8.8.
In this version the matrix is read in using functions and the matrix and resultant arrays
are passed to the functions using parameters. Note, arrays are passed to functions by
reference.
#include <iostream>
using namespace std;
18
void readMatrix(float matrix[ROWS][COLS]) {
cout << ”Type in the votes (in 1000’s) for each of the following\n”;
for (int i = 0; i < ROWS; ++i) {
for (int j = 0; j < COLS; ++j) {
cout << ”Voting area: ” << i + 1 << ” Candidate: ” << char(’A’ + j) << ” −−> ”;
cin >> matrix[i][j];
}
}
}
int main() {
float matrix[ROWS][COLS];
float totals[COLS];
readMatrix(matrix);
sumPerCandidate(matrix, totals);
writeResults(totals);
return 0;
}
Listing 8.9: Votes Matrix Example, Version 2
The function readMatrix reads a fixed size matrix of rows (3) and columns (4) in. Each
element in the matrix represents, the votes (in 1000’s) that the candidate gained for a
particular voting area. The function sumPerCandidate calculates the sum of the votes
per area each candidate achieved. This result is placed in the totals array which is then
passed back to the main function and into the writeResults function for displaying on
19
the screen. Note how the functions are called from the main function.
Question 8.18
Write a function to determine the candidate with the highest number of votes.
Linear algebra and particularly matrices are prevalent in computer science. Both com-
puter graphics and artificial intelligence make use of vectors and matrices.
When adding two matrices, both matrices must be of the same size. The formula for
matrix addition is given by:
For two matrices A and B of the same dimensions m × n:
a11 a12 a13 a14 b11 b12 b13 b14 a11+b11 a12+b12 a13+b13 a14+b14
a21 a22 a23 a24 + b21 b22 b23 b24 = a21+b21 a22+b22 a23+b23 a24+b24
a31 a32 a33 a34 b31 b32 b33 b34 a31+b31 a32+b32 a33+b33 a34+b34
Question 8.19
Write a program to add two matrices A and B.
Consider the formula for matrix multiplication: For matrices A of dimension m × n and
B of dimension n × p:
Xn
Cij = Aik × Bkj
k=1
const int M = 3;
const int N = 2;
int main() {
float matrixA[M][N] = {{1.0, 0.0}, {0.0, 2.5}, {1.5, 0.0}};
float matrixB[N][N] = {{1.0, 1.0}, {1.0, 1.0}};
float matrixC[M][N] = {0};
20
for (int k = 0; k < N; ++k) {
matrixC[i][j] += matrixA[i][k] ∗ matrixB[k][j];
}
}
}
return 0;
}
Listing 8.10: Matrix Multiplication
Question 8.20
Currently the two programs you have written for matrix manipulation use matrices that
have been defined at compile-time and hard-coded in the program. Change the program
in Listing 8.10 to read the values of matrixA and matrixB from the keyboard.
• Calculating the number of rows requires us to know the number of columns as well
as the size of a float:
int rows = sizeof(matrix)/sizeof(float)/cols;
The same restrictions apply. That is, you can only apply the calculations in the function
in which the array has been defined.
21
As with one-dimensional static arrays, we need to send the number of rows and columns
as parameters along with the matrix.
Consider the following function to print the values of a given matrix to the console.
void printMatrix(float m[][4], int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << fixed << setprecision(1)
<< m[i][j] << ” ”;
}
cout << endl;
}
}
Listing 8.11: Printing values into a static 2D matrix
If we define the function in Lisitng 8.11 using the pointer notation, the number of columns
still needs to be specified as follows:
void printMatrix(float (∗m)[4], int r, int c);
Question 8.21
How does the implementation of the printMatrix function change when using the pointer
notation definition?
Question 8.22
Write a function to read values from the keyboard into the matrix.
As with one-dimensional arrays, two-dimensional arrays can be stepped though using
pointer arithmetic instead of indices. Consider the following code showing the function
given in Listing 8.11 using pointer arithmetic.
void printMatrixPtrArith(float m[][4], int r, int c) {
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
cout << fixed << setprecision(1)
<< ∗(∗(m+i)+j) << ’\t’;
}
cout << endl;
}
}
Note how the row increment, *(m+i), interacts with the column increment, *(*(m+i)+j),
where i is the row increment and j the column increment.
The matrix however is limited to 4 columns and any number of rows when passed as
a parameter. If we do not use part of the array, for example, if we wish to print a
2 × 2 matrix, we will need to send a matrix that has two rows and four columns to the
function. This effectively wastes space on the stack of (2 ∗ 2 ∗ sizeof(float)) bytes on the
stack. Alternatively, if we want to use the function to print a 10 × 10 matrix, we cannot
do so with this function. The number of columns of 10 exceeds the number of columns
(4) expected by the function.
22
This problem is as a result of 2D arrays being placed in stack memory in contiguous
memory locations. In order to calculate where the next row of a 2D array begins, we
need to know how many columns we have. Each row of a 2D array placed on the stack
therefore has exactly the same number of columns.
To solve the problem of a fixed number of columns when sending a static array as a
parameter to a function, we need to consider placing the arrays on the heap. Arrays on
the heap (dynamic arrays) are defined as an array of pointers to arrays. Section 8.5 will
introduce dynamic arrays.
where size can be a constant or entered by the user. The dynamic array variable resides
on the stack and the array itself is on the heap in contiguous memory locations of int
size. The figure below illustrates how an int array of size 5 is created on heap memory.
Remember, if we allocate memory on the heap, we need to delete the memory from the
heap when we no longer need it. To do so for a dynamic array we use the delete operation
in the following statement for the given example: delete [] arr;.
The following program illustrates defining, using and deleting a dynamic one-dimensional
array where the user specifies the size of the array.
23
#include <iostream>
using namespace std;
int main() {
int ∗arr;
int size;
cout << ”Please enter your ” << size << ”values” << endl;
for (int i = 0; i < size; i++) {
cout << i + 1 << ”: ”; cin >> arr[i];
}
delete [] arr;
return 0;
}
Listing 8.12: One-dimensional dynamic array example
Each row in the two-dimensional dynamic array does not have to have the same number
of columns. This makes the array difficult to manage. There are two possible solutions
24
Figure 8.1: Two-dimensional Variable Size Array
to overcome this hurdle. The first is to manage a parallel array that stores an integer
value indicating the number of columns each respective row has. The second solution is
to merge this integer value and the pointer to the row elements into a C++ structure
called a struct. C++ structs will be covered in the following study unit.
The following code is required to manage the two-dimensional dynamic array structure
given in Figure 8.1 above. We assume the value for size (3 in this example) is entered
by the user before the arrays are declared and created. The user is also asked to enter
the number of elements for each row.
int ∗∗arr;
int ∗parallelArr;
arr is a pointer to an element, in this case represented as an array, that contains pointers
to another array holding integer values. To create the first dynamic array, which holds
the pointers to each row, we create an array with size elements of type int* in. For
each of the elements in this array, indexed from 0 to size-1, we create another dynamic
one-dimension array representing each row of the matrix. The user is asked for the sizes
of each of these rows and will enter the value into the parallel array (parallelArr) for
the specific row index. For the given example the values are 5, 2 and 4 for array indexes 0,
1 and 2 respectively. parallelArr is a normal dynamic one-dimensional dynamic array.
Once this structure has been created, it can be used.
A function to print the array given in Figure 8.1 is given in the listing below:
void printArray(int ∗∗a, int ∗pa, int size) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < pa[i]; j++) {
cout << a[i][j] << ”\t”;
}
cout << endl;
25
}
}
This function takes two array parameters, the first is the two-dimensional array structure
defined as int **a, and the second the one dimensional parallel array, pa, that holds the
number of elements in each row.
These structures, as with all memory allocated on the heap, need to be deleted from the
heap. The following code will do so.
delete [] parallelArr;
parallelArr = NULL;
for (int i = 0; i < size; i++) {
delete [] arr[i];
}
delete [] arr;
arr = NULL;
For the two-dimensional dynamic array structure, the arrays representing the column
values are deleted first, before the dynamic array with pointers to these structures.
Two-dimensional dynamic arrays with a fixed number of columns are easier to manage.
These are akin to the static matrix we considered in Section 8.4.
As an example, assume we want to create a 2 × 3 matrix using two-dimensional dynamic
arrays as in the figure below:
• We make use of double pointers to define the initial variable on the stack and
initialise the variables that represent the size of the matrix:
float ∗∗arr;
int rows = 2, cols = 3;
26
for (int i = 0; i < rows; i++) {
arr[i] = new float[cols];
}
We now have a dynamic 2 × 3 matrix that we can use. Remember to delete the matrix
before going out of scope.
Question 8.23
Write the code to read values into the matrix and delete the matrix from heap memory.
In Section 8.4.1 writing code to manipulate, particularly adding and multiply, matrices
was considered. Mention was made of this being restrictive due to the column parameter
needing to be constant. For example, the definition for a function to add two matrices
would be given by:
void addMatrices(float a[][4], float b[][4], float result[][4],int rows, int cols);
or even better:
float∗∗ addMatrices(float ∗∗a, float ∗∗b, int rows, int cols);
return matrix;
}
Question 8.24
Write the code to multiply two matrices. The header for the function is given by:
float∗∗ multiplyMatrices(float∗∗ A, int rowsA, int colsA, float∗∗ B, int rowsB, int colsB)
Remember the constraint that the number of columns of matrix A must equal the num-
ber of rows of matrix B and the resultant matrix is (number of rows of A × number of
columns of B).
You can now begin creating a library of functions for the manipulation of matrices. The
header file (Matrix.h) for this library is given by:
27
#ifndef MATRIX H
#define MATRIX H
#endif
Question 8.25
Add the implementation for all these functions to a file called Matrix.cpp.
Question 8.26
Test your matrix manipulation library with the following main program.
#include <iostream>
#include ”Matrix.h”
int main() {
28
printMatrix(matrixC,3,4);
cout << endl;
if (multiplyResult != NULL) {
cout << ”A ∗ C = ” << endl;
printMatrix(multiplyResult,2,4);
}
return 0;
}
Note the order of deallocation of the dynamic memory is the reverse over od allocation.
This is a C++ requiremnent.
Date Change
17 June 2024 Initial preparation and presentation of the document.
29