Introduction to Computer Science:
Assignment 20a:  By-Reference versus By-Value Parameters
Assignment 20b:  Random Numbers, Vectors, and Sorting
  Sewickley Academy, 2000-2001

See Course Home Page.
 
Date Assigned: Fri Nov-10
Date Due: Tue Nov-14

0.  You may discuss the "discussion" portions of this assignment with others, but as for the programming portions, those you must work alone on (no exceptions).  Also, due to P1 rotation on Monday, this assignment is due Tuesday.  Finally, be sure to read the whole assignment first -- there is not much programming in this assignment, but a fair bit of reading and (oh no!) thinking.  Good luck!


Part A:  By-Reference versus By-Value Parameters

A1.  Discussion of By-Reference:  addOne()

Consider the following code:

#include <stdio.h>
#include <iostream.h>

void addOne(int y)
{
 y = y + 1;
}

void main()
{
 int x;
 x = 1;
 addOne(x);
 if (x == 2)
 {
  cout << "It added one (yippee!!!)" << endl;
 }
 else
 {
  cout << "It did not add one (wimper).." << endl;
 }
 getchar();
}

Will it be happy or sad (will it add one to x or not)?  Compile it and run it to verify (do it!), but the answer is that it will be sad.  Why? So, what to do?  Perhaps we could just change the name of the variable in addOne() from "y" to "x", right?  Here is the modified code (modifications in red):
#include <stdio.h>
#include <iostream.h>

void addOne(int x)
{
 x = x + 1;
}

void main()
{
 int x;
 x = 1;
 addOne(x);
 if (x == 2)
 {
  cout << "It added one (wahoo!!!)" << endl;
 }
 else
 {
  cout << "It did not add one (wimper).." << endl;
 }
 getchar();
}

This might work, right?  Wrong.  The name of the variable in addOne() meant nothing -- even if it is named "x", it is still a different variable than the "x" in main().  So renaming the variable had no effect whatsoever on the program.

So what to do?  What we are seeking is a way that a function can directly modify the variable provided, rather than simply get a copy of that variable.  And it turns out we can do just that....

Introducing:  By-Reference Parameters (applause...).  This does precisely what we want.  If we modify addOne() so that it's parameter is intByReference, rather than just an int, then any changes it makes to the parameter will directly modify the variable provided to the function.  That is, addOne() will actually change "x" in main().

One oddity:  to do this, we need to include a "magic" line in our code:  typedef int& intByReference.  We will discuss this line in detail later, but for now please take a leap of faith and just include it in your code (many thanks).

So, here is the resulting code (modifications in red):

#include <stdio.h>
#include <iostream.h>

typedef int& intByReference;

void addOne(intByReference y)
{
 y = y + 1;
}

void main()
{
 int x;
 x = 1;
 addOne(x);
 if (x == 2)
 {
  cout << "It added one (zowie!!!)" << endl;
 }
 else
 {
  cout << "It did not add one (wimper).." << endl;
 }
 getchar();
}

Compile it and run it!  It works, and is one stunningly happy program (I can almost see it smiling).  Let's take another quick look at what happened:  void addOne(intByReference y) was called in main() as addOne(x), and because y is defined by reference, y is not a copy of x, but in fact is exactly x.  Any changes to y, then, are actually changes to x.  Voila!

A2.  More Discussion of By-Reference:  swap()

The previous example was less-than-inspiring because addOne() is basically useless.  So here we move on to a genuinely interesting and useful function:  swap().  This function takes two integers and, well, swaps them.  Thus, if x is 1 and y is 2 and we call swap(x,y), afterwards we expect x to be 2 and y to be 1.  By now you appreciate that such a function is definitely useful, since swapping is a very common programming activity.  Well, let's have a go at it...

Consider the following code:

#include <stdio.h>
#include <iostream.h>

void swap(int x, int y)
{
 int temp;
 temp = x;
 x = y;
 y = temp;
}

void main()
{
 int x, y;
 x = 1;
 y = 2;
 swap(x,y);
 if (x == 2)
 {
  cout << "x and y were swapped (hurray!!!)" << endl;
 }
 else
 {
  cout << "alas and alack, the swap failed (woe is me).." << endl;
 }
 getchar();
}

Compile it and run it.  This is very unhappy, albeit somewhat melodramatic, code.  Of course, we now know the problem: Fortunately, we also know the solution: Check it out:
#include <stdio.h>
#include <iostream.h>

typedef int& intByReference;

void swap(intByReference x, intByReference y)
{
 int temp;
 temp = x;
 x = y;
 y = temp;
}

void main()
{
 int x, y;
 x = 1;
 y = 2;
 swap(x,y);
 if (x == 2)
 {
  cout << "x and y were swapped (hot diggety dog!!!)" << endl;
 }
 else
 {
  cout << "alas and alack, the swap failed (wimper).." << endl;
 }
 getchar();
}

Now this is genuinely copacetic and downright giddy code.

One last comment:  if parameters are not "by-reference" -- that is, if they are plain old parameters like we've been using until this point in the class -- then we say they are "by-value" (meaning that you get a copy of the value of the variable, as opposed to the actual variable).  Thus, you are now experts on By-Reference versus By-Value.  Congratulations.

A3.  More Discussion of By-Reference:  addOneToVectorElements()

Now we turn the topic to vectors.  Let's say we wanted write addOneToVectorElements(), which would, curiously enough, add one to each element in a vector.  Consider the following code (to run this, of course, you will have to place apvector.cpp and apvector.h in the project directory, and add apvector.h to your project):

#include <stdio.h>
#include <iostream.h>
#include "apvector.h"

typedef apvector<int> intVector;

void addOneToVectorElements(intVector v)
{
 int i, len;
 len = v.length();
 i = 0;
 while (i < len)
 {
  v[i] = v[i] + 1;
  i = i + 1;
 }
}

void main()
{
 intVector v;
 v.resize(2);
 v[0] = 1;
 v[1] = 2;
 addOneToVectorElements(v);
 if ((v[0] == 2) && (v[1] == 3))
 {
  cout << "the vector elements were increased by one (whoopee!!!)" << endl;
 }
 else
 {
  cout << "Ixnay (wimper).." << endl;
 }
 getchar();
}

Compile and run it.  Ixnay.  Does not work.  What went wrong?  addOneToVectorElements() does add one to each element in "v".  However, the "v" in that function is by-value, not by-reference, and so it is only a copy of the "v" in main().  That is the problem.  Thus, we also know the solution -- just make it a call by reference.

Note:  To do this, we need to add another "magic" line of code:  typedef intVector& intVectorByReference.  Deal with it.

So here then is the resulting code:

#include <stdio.h>
#include <iostream.h>
#include "apvector.h"

typedef apvector<int> intVector;
typedef intVector& intVectorByReference;

void addOneToVectorElements(intVectorByReference v)
{
 int i, len;
 len = v.length();
 i = 0;
 while (i<len)
 {
  v[i] = v[i] + 1;
  i = i + 1;
 }
}

void main()
{
 intVector v;
 v.resize(2);
 v[0] = 1;
 v[1] = 2;
 addOneToVectorElements(v);
 if ((v[0] == 2) && (v[1] == 3))
 {
  cout << "the vector elements were increased by one (yahoooeee!!!)" << endl;
 }
 else
 {
  cout << "Ixnay (wimper).." << endl;
 }
 getchar();
}

Compile and run it.  Smiles galore!!!  Again, this works because the call to addOneToVectorElements() is by-reference, and so the "v" there is exactly the same "v" as in main().  And all is well.

A4.  More Discussion of By-Reference:  loadVectorWithRandomElements()

Of course, addOneToVectorElements() is, well, lame.  Ok, pathetically useless.  Let's do something more useful.  Here, we will write a function which loads a vector with random elements.  Consider the following code fragment:

void loadVectorWithRandomElements(intVector v)
{
 int i,len;
 len = v.length();
 i = 0;
 while (i < len)
 {
  v[i] = rand();
  i = i + 1;
 }
}
This will fail.  Why?  Well, it will succeed in loading the vector "v" with random values, but...  This vector is only a copy of the vector provided to the function, and that vector will remain unchanged.  Of course, we know how to fix this:  just make the parameter be by-reference, as in:
void loadVectorWithRandomElements(intVectorByReference v)
{
 int i,len;
 len = v.length();
 i = 0;
 while (i < len)
 {
  v[i] = rand();
  i = i + 1;
 }
}
This works beautifully.

A5.  Last Discussion of By-Reference:  findAverage() and Efficiency

Lastly, consider writing a function called findAverage() which takes a vector as input and which returns the average value of the elements in the vector.  Consider the following code:

int findAverage(intVector v)
{
 int result, sum, i, len;
 sum = 0;
 i = 0;
 len = v.length();
 while (i < len)
 {
  sum = sum + v[i];
  i = i + 1;
 }
 result = sum / len;
 return result;
}
The parameter "v" is by-value, not by-reference, because there is no need to change the vector here.  So, this code works, but...  It is very inefficient.  Why?  Because calling this function requires copying the vector "v".  If the vector is large, say a few million elements (or, why not, a few billion), then this may be very inefficient, no?  And for what?  Why do we need to copy the elements?  Here, frankly, it serves no purpose.

Thus, for efficiency, we want to avoid copying and thus we change "v" to be by reference, as follows:

int findAverage(intVectorByReference v)
{
 int result, sum, i, len;
 sum = 0;
 i = 0;
 len = v.length();
 while (i < len)
 {
  sum = sum + v[i];
  i = i + 1;
 }
 result = sum / len;
 return result;
}
Here, the vector "v" is not copied, but is exactly the vector provided.  This, then, is efficient.  Here is the resulting code, in a complete (if not altogether fascinating) program:
#include <stdio.h>
#include <iostream.h>
#include <stdlib.h>
#include "apvector.h"

typedef apvector<int> intVector;
typedef intVector& intVectorByReference;

void loadVectorWithRandomElements(intVectorByReference v)
{
 int i,len;
 len = v.length();
 i = 0;
 while (i < len)
 {
  v[i] = rand();
  i = i + 1;
 }
}

int findAverage(intVectorByReference v)
{
 int result, sum, i, len;
 sum = 0;
 i = 0;
 len = v.length();
 while (i < len)
 {
  sum = sum + v[i];
  i = i + 1;
 }
 result = sum / len;
 return result;
}

void main()
{
 intVector v;
 int len;
 cout << "Enter the length of the vector: ";
 cin >> len;
 v.resize(len);
 loadVectorWithRandomElements(v);
 cout << "The average of the random elements: "
      << findAverage(v) << endl;
 getchar();
}

Compile and run this.  Be sure you understand every line of it.  Extra credit:  explain why the average of the random elements is hovering around the value it tends to hover around.

A6.  By Reference Redux

The last word:  As you no doubt already guessed, this material will appear on a quiz or two next week.


Part B:  Random Numbers, Vectors, and Sorting

B1.  Discussion of SelectionSort:

As we have previously discussed, there are many algorithms for sorting.  One sorting algorithm is called SelectionSort.  We will investigate it here.  This is how SelectionSort works:

Let's see it in action.  Consider the following vector:
87  54  29  18  94  73  12  63
First, we include a cursor (which is just an index, really), which we start at the far left (index 0):
cursor: 87  54  29  18  94  73  12  63
Next, we find the smallest remaining element after the cursor, which is 12:
cursor: 87  54  29  18  94  73  12  63
Next, we swap that element with the element at the cursor (17) to get:
cursor: 12  54  29  18  94  73  87  63
Finally, we advance the cursor to the next index.
12  cursor:  54  29  18  94  73  87  63
Now we loop.  So now we find the smallest remaining element after the cursor:
12  cursor:  54  29  18  94  73  87  63
And we swap this element with the element at the cursor (54) to get:
12  cursor:18  29  54  94  73  87  63
And we advance the cursor to the next index:
12  18  cursor:   29  54  94  73  87  63
The key observation is the following:  all elements to the left of the cursor are always sorted.  (These are the bold,black characters above).

Because of this:  once the cursor moves to the right end of the vector, the entire vector is sorted.

This is the logic behind SelectionSort.  It is called "SelectionSort" because at each step we select the next smallest element in the vector.  Et voila.

B2.  More Discussion of SelectionSort:

Here is the code for SelectionSort:

void selectionSort(intVectorByReference v)
{
 int cursor,i,len,smallestIndex,temp;
 len = v.length();
 cursor = 0;
 while (cursor < len-1)
 {
  // 1.  Find the index of the smallest
  //     element in the vector starting
  //     at v[cursor] to the end of the vector.
  smallestIndex = cursor;
  i = cursor+1;
  while (i < len)
  {
   if (v[i] < v[smallestIndex])
   {
    smallestIndex = i;
   }
   i = i + 1;
  }

  // 2.  Now that we have the index of the
  //     smallest remaining element, swap
  //     that element with v[cursor].  Thus, after
  //     this step, the elements v[0], v[1],
  //     ..,v[cursor] will be the (cursor+1) smallest
  //     elements in the vector, in order.
  temp = v[cursor];
  v[cursor] = v[smallestIndex];
  v[smallestIndex] = temp;

  // 3.  Increment cursor to continue
  cursor = cursor + 1;
 }
}

Carefully study this code.  Understand every line of it.  Understand why its parameter is by reference and not by value (what would happen if it was by value?  answer:  it would sort a copy of the vector, leaving the original unchanged).  Understand how the cursor moves along, how the inner-loop (the while within the while) finds the smallest remaining element, and how it holds on to the index of that element so that in step 2 it can swap that element with the element at the cursor.  Scrutinize this code.

B3.  End of Discussion

To be clear, this ends the discussion part of this assignment.  All that follows is the programming portions, and you must work alone on the remaining portions.

B4.  Change the function loadVectorWithRandomElements() from above so that the random numbers are in the range [0,99] rather than [0,RAND_MAX].

B5.  Write a function printVector() which takes a vector as its only parameter and prints the vector.  If there are more than 30 elements in the vector, your function should just print the first 15 elements, then an ellipsis (3 dots), then the last 15 elements.

B6.  Modify selectionSort() so that it sorts from largest-to-smallest rather than smallest-to-largest.

B7.  Here is the shell of the program which you should submit.  When you are done, it must contain the functions which you wrote for parts B4, B5, and B6 -- that is, loadVectorWithRandomElements(), printVector(), and selectionSort().

#include <stdio.h>
#include <iostream.h>
#include <stdlib.h>
#include "apvector.h"

typedef apvector<int> intVector;
typedef intVector& intVectorByReference;

const int MAX_ELEMENTS_TO_PRINT = 6;

void printVector(intVectorByReference v)
{
 // YOU WRITE THIS, PART 1 of 3
}

void selectionSort(intVectorByReference v)
{
 // YOU WRITE THIS, PART 2 of 3
 // (be sure it sorts largest-to-smallest)
}

void loadVectorWithRandomElements(intVectorByReference v)
{
 // YOU WRITE THIS, PART 3 of 3
}

void main()
{
 intVector v;
 int len;
 cout << "Enter the length of the vector: ";
 cin >> len;
 v.resize(len);
 loadVectorWithRandomElements(v);
 printVector(v);
 getchar();
 selectionSort(v);
 printVector(v);
 getchar();
}

Have this program ready for on-the-spot grading at the start of class on Tuesday. Good luck and have a splendid weekend and Monday!

See Course Home Page.