Laboratory 11 Array Processing Introduction In this lab we will learn how to perform basic processing of arrays. These are the most commonly performed tasks on arrays. We will see that for loops are designed to work hand-in-hand with array processing. Arrays have the potential to be very large, unlike the more fundamental types such as ints and floats. This has a number of ramifications, but primarily it means that we should be careful when writing functions that process arrays since a little bit of inefficiency will be multiplied many times over. Key Concepts Arrays as parameters Finding the maximum element Finding the minimum element Determine whether two arrays are equal Verifying order Deleting an element Inserting an element Before the Lab Read Sections 9.1 – 9. 3 in Cohoon and Davidson. Preliminary Turn the PC on, if necessary. Access the network. Copy the source files from the Lab 11 Source Files folder. Start up CodeWarrior. Open the LabWork project. Arrays as Function Parameters The array-processing algorithms that we will study in this lab are among the most commonly used procedures in programming. Therefore, we would like to write each one as a function that may be called on at any time and receive any array of any size. In other words, we want to write these functions in full generality. To accomplish this, we must pass the arrays as parameters to the functions. Furthermore, we must also pass the number of elements in the array since that information is necessary for the correct processing of the array and it is not passed automatically as part of the array. Thus, a pair of parameters that will be common to all of the functions in this lab is (int a[], int size) The first represents an array of integers. The second is the number of elements to be processed in the array (not necessarily the capacity of the array). Note the use of the empty square brackets []. This indicates that the parameter is the name of an array. It permits us to write expressions such as a[i] within the function. How do we indicate whether to pass an array by value or by reference? It turns out that arrays are always passed by reference! We have no choice. This is done for reasons of efficiency. Arrays have the potential to be huge. If one were passed by value, then the function would have to make a copy of the array before processing it. That would be a tremendous waste of time. So how do we protect an array if we do not want the function to change it? The standard method is to pass it as a constant reference. That is, the parameter should be written as const int a[] Then the elements of the array cannot be modified by the function. Finding the Maximum or Minimum Element of an Array Often we need to locate the largest element in an array. For example, in Lab 12 we will look at a sorting algorithm that repeatedly locates the largest element in an array as a way of putting the elements in order. What algorithm should we follow to accomplish this? In this case, the best guide is to imagine that you have a very long list of random numbers in front of you and you need to find the largest number in the list. How would you do it? Give this some thought. You cannot just “pick out” the largest number because there are too many of them to tell at a glance which is largest. The best method is to begin with the first number and consider it to be the largest number found so far. Then compare it to the other numbers in the list, beginning with the second number. If they are smaller, then ignore them. But if we find one that is larger, then it becomes the new “largest so far,” replacing the previous largest so far. When we reach the end of the list, the number that is then the largest so far is actually the largest in the list. Observation Open the file FindMax.cpp. Read the function findMax() and be sure you see how it applies the above algorithm. Add FindMax.cpp to the LabWork project. Run FindMax.cpp to see that it works. Experimentation The algorithm for finding the smallest member of an array is similar except that we keep track of the “smallest so far” rather then the “largest so far.” In the file FindMax.cpp, Write a function findMin() that will find the minimum element in an array. Add lines in the main function that will call findMin() and print the result. Run FindMax.cpp to be sure that findMin() works. Will the findMax() and findMin() functions work properly if the array contains only one element? Run FindMax.cpp with an array of one element to see if it works. What will the findMax() and findMin() functions do if the array is empty? Is this acceptable? Run FindMax.cpp with an empty array to see what happens. Determining Whether Two Arrays are Equal The C++ language does not automatically permit us to compare arrays using the == operator. That is, if array1 and array2 are names of arrays, then the expression array1 == array2 will not tell us whether the two arrays contain the same values. It is a legal expression, but it compares the addresses of the arrays, not the contents of the arrays. Therefore, it will be true only when the arrays are one and the same array. To compare the contents of two arrays, we must use a for loop to compare the individual elements. The condition for equality of arrays is that their sizes be equal and that for every value of i from 0 to size - 1, the i-th values be equal. If follows that if the sizes are not equal or if a single pair of elements fails to be equal, then the arrays are not equal. That means that as soon as we find an unequal pair, we may exit the function and return false. Observation Open the file EqualArrays.cpp. Add EqualArrays.cpp to the LabWork project. Read the function equal() to see that it applies the logic discussed above. Run EqualArrays.cpp using arrays of different sizes. Does it work? Run EqualArrays.cpp using arrays of the same size, but with a single pair of different elements. Note how the function was written so that it returns false as soon as an unequal pair is found. Run EqualArrays.cpp using two identical arrays. Should two empty arrays be considered equal? Run EqualArrays.cpp one more time using two empty arrays. Does it work? Note the usefulness of for loops in the processing of arrays. The values taken on by the control variable are exactly the consecutive values we wish the index of the array to have. We thus sequence through the array from “left to right,” processing each element in turn. The control value, in a sense, serves as a “pointer” to each array element. Verifying that an Array is in Order In Lab 13 we will encounter a search algorithm that is extremely efficient if the elements of the array are in ascending order or descending order. Otherwise, the algorithm does not work. Therefore, before using such an algorithm, it is important to know whether the elements of the array are in order. There are two possible orders: ascending and descending. We will first consider ascending order. Later, a minor modification will allow us to treat the other case. The elements of an array are in ascending order if each element is at least as large as its predecessor in the array. (An array of one element is trivially in order.) Therefore, the verification algorithm requires only that we inspect adjacent pairs of elements, verifying that the second member is at least as large as the first member. Note that if we are applying this algorithm to a user-define type, then it is essential that at least one of the order relations <, >, <=, or >= be defined on the type. For example, in the Rational class we overloaded the less-than operator. Experimentation Open the file VerifyOrder.cpp. Add VerifyOrder.cpp to the LabWork project. This program calls on a function verifyOrder() that returns true or false, depending on whether the elements of the array are in ascending order. As with the function equal() above, it is necessary that every pair of adjacent elements have the proper order relation. Therefore, as soon as we find a single pair that is out of order, we may exit the function and return false. Write the function verifyOrder(). Note that if there are size elements in the array, then only size - 1 comparisons are required. Therefore, the values of the index i in the for loop should go either from 0 to size - 2 or from 1 to size - 1, but not from 0 to size - 1. Run VerifyOrder.cpp to see if it works. What should verifyOrder() return if the array contains only one element? Run VerifyOrder.cpp with an array of size 1 to see if it works properly. What should verifyOrder() return if the array is empty? Run VerifyOrder.cpp with an empty array to see if it works properly. Be sure that you understand why the function handled the special cases properly. Deleting an Element from an Ordered Array In many applications, it is necessary to maintain a list in order as new elements are added and old elements are removed. Of course, if a list is ordered and an element is deleted, then the remaining list is still ordered. But when we add a new element, it must be added at the proper position or else the list will no longer be ordered. We will begin with deletions since they are simpler than insertions. Clearly, in order to delete an element from a list, we must first locate it. We will discuss search algorithms in a later lab, but for now we will use a very simple method: start and the beginning and compare to one element after another in the list. This is called a sequential search. But once we locate the element to be deleted, exactly how do we delete it? Consider an example. We wish to delete the number 50 from the following list. 10 25 30 35 50 60 80 85 60 80 85 90 90 After deleting 50, the list will be 10 25 30 35 Notice that the values 60 through 90 have shifted one position to the left. That, in fact, is our method of deleting the 50: simply shift the remainder of the list to the left one position. Observation Open the file DeleteElement.cpp. Add DeleteElement.cpp to the LabWork project. Read the function delete() to see how it works. Note the following techniques that were used: To search for the value. To determine whether the value was found. To shift the values. Be sure you understand these techniques. Variations of these techniques will appear in the insertion function in the next section. Run DeleteElement.cpp several times, testing the following cases: Delete an element from a position somewhere in the middle of the array. Delete an element from the first position in the array. Delete an element from the last position in the array. Delete an element an array containing only one element. Attempt to delete an element that is not in the array. Does the function work properly in all cases? Inserting an Element into an Ordered Array The insertion algorithm is roughly the reverse of the deletion algorithm, but there are a few more details to be concerned with. First, we must search for the location in which to insert the value. (Obviously, we do not search for the number itself since we do not expect it to be there.) For example, if the list is 10 25 30 35 60 80 85 90 and we wish to insert 50, then we must determine that it should be inserted between the 35 and the 60. However, in an array, the elements are stored contiguously; there is no space between the elements in which to insert new elements. What must we do to insert the number 50 into this list? Clearly, we need to shift the elements 60 through 90 one position to the right, thereby creating an open space for the number 50. Then we may copy 50 into that space. Experimentation Open the file InsertElement.cpp. Add InsertElement.cpp to the LabWork project. Note that the insert() function is empty. We will fill in the details. Copy the search algorithm from delete() and paste it into insert(). Is it necessary to modify the search algorithm in order to adapt it to this situation? After all, we are not searching for the value to be inserted since it is not yet in the list. It turns out that this algorithm need not be modified. This while loop will stop when it encounters the first element of the array that is greater than or equal to the value to be inserted, and that is exactly what we want. We have now found the location where the insertion is to take place and have stored it as the value of the integer pos. Next we need to shift the remaining elements to the right to make room for the new value. Copy the shift algorithm from delete() and paste it into insert() immediately after the search algorithm. This for loop must be modified. The most obvious modification is that the elements must be shifted right instead of left. To shift them left, we wrote a[i] = a[i + 1]; Therefore, to shift them right, we should write a[i + 1] = a[i]; Make that change in the for loop. Finally, we must copy the value into the open position and increment the size of the list. Add the statements a[pos] = value; size++; immediately after the shifting algorithm. Let’s test our work. Run InsertElement.cpp using a list of about 10 numbers and an insertion into about the third position. Did it work? What went wrong? There are two problems. The simpler one is that the for loop did not go far enough. It ended with the value size - 2, which makes the assignment a[size - 1] = a[size - 2]; as the last assignment. After a moment’s reflection, we realize that the last assignment should have been a[size] = a[size - 1]; That is easy to fix. But there is a more serious problem: a single value was copied into every position from the position just past the insertion to the end of the array. Why did that happen? To fix this problem, we should begin shifting at the end of the list and move towards the beginning. In other words, the first assignment should be a[size] = a[size - 1]; and the last assignment should be a[pos + 1] = a[pos]; rather than the other way around. Thus, in the for loop, if the assignment is a[i + 1] = a[i]; then the index i should range from size - 1 down to pos. In order to accomplish this, we need to decrement i instead of increment it. Write the for loop as it should be written. Run InsertElement.cpp to see if it works. Test all cases. Application Open the MS Word document Lab 11 App.doc, found in the Lab 11 App subfolder, and follow the instructions. Summary We have now learned how to do the most basic array processing. Some of these algorithms will become the building blocks of more sophisticated array-processing algorithms that we will study in the next two labs. All of the processes we studied here run in linear time. That is, the time required for them to execute is directly proportional to the size of the array; if the array is twice as large, it takes twice as long to process it. In the next two labs we will see that some important array-processing algorithms are slower than linear and others are faster. That is, in some cases, if the array is twice as large, it takes four times larger to process it, and in other cases, if the array is twice as large, it barely increases the run time.