• No results found

r Skip list

11.6 STL BY EXAMPLE: MY FIRST EXAMPLE

We have given a bird’s eye view of data structures and their realisation in STL. We did not give any code samples because we wished to give the reader an overview of the offerings before we jumped into the details of C++ and STL. However, it is a good idea to ‘get our hands dirty’ by examining a number of concrete STL data containers that are very important for QF applications. These containers are:

r

vector: a sequence container that supports random access to elements, constant time inser-tion and removal of elements at the end, and linear time inserinser-tion and removal of elements at the beginning or in the middle. The number of elements in a vector is variable and memory management is automatic

r

valarray: a vector that contains functionality for arithmetic operations such as +, -, multi-plication by a scalar and so on

Please note that indexing starts at index 0 for both vector and valarray. This is in keeping with C in general but Fortran programmers are accustomed to defining the start index to be equal to 1.

We shall concentrate on vector first.

11.6.1 Constructors and memory allocation

There are different ways to create a vector. First, we can create a vector of a fixed size, second we can create an empty vector and add elements as the need arises and finally we can reserve a block of memory for a vector and then modify this memory using the indexing operator. In this section we concentrate on the first option and to this end we shall create several vectors of fixed size and containing various data types. The first example is straightforward:

size t n = 10;

double val = 3.14;

vector<double> myVec(n, val); // Create n copies of val print(myVec);

// Access elements of the vector by using indexing operator []

// Change some values here and there myVec[0] = 2.0;

myVec[1] = 456.76;

int last element= myVec.size() - 1;

myVec[last element] = 55.66;

print(myVec);

We shall have need to use constructors later on in this chapter.

11.6.2 Iterators

We give an example of an iterator that navigates in a vector and prints each element on the console display. Note that we are using a constant iterator because we do not change the elements in the vector. To this end, we give the code for a template function that realises this requirement:

template <class T>

void print(const vector<T>& l, string s = string("data")) { // A generic print function for vectors

cout << endl << s << ", size of vector is " << l.size() << "\ n[";

// Use a const iterator here, otherwise we get a compiler error.

vector<T>::const iterator i;

for (i = l.begin(); i != l.end(); i++) {

cout << *i << ",";

}

cout << "]\ n";

}

An important remark: the iterator ‘i’ points to one specific element in the vector at any one moment in time; hence, if we dereference it we get the element itself!

11.6.3 Algorithms

We now discuss how to apply some STL algorithms to vector<T>. These can also be applied to many other container types as well.

The first algorithm is copy() and it copies the elements of a source range (that is bounded by two iterators written as aclosed-open interval [first, last) ) into a target range:

vector<double> myVec2(myVec.size());

list<double> myList;

// Copy source range of type T1 into target range of type T2 copy(myVec.begin(), myVec.end(), myVec2.begin());

print(myVec2, string("copy to a vector"));

copy(myVec.begin(), myVec.end(), myList.begin());

In this case we have copied a vector into another vector and also into a list.

We now discuss the rotate() algorithm that shifts the elements of a sequence to the left in such a way that those elements that ‘fall off’ at the beginning as it were are inserted back at the end:

// Shift elements of vector to left; those that fall off // are inserted at the end

int N = 6;

vector<double> myVec3(N);

for (int i = 0; i < myVec3.size(); i++) {

myVec3[i] = double (i);

}

int shiftFactor = 2;

rotate(myVec3.begin(), myVec3.begin() + shiftFactor,

myVec3.end());

print(myVec3, string("Rotated vector by 2 units"));

The replace() algorithm replaces each and every occurrence of the value of an element by a new value:

// Now replace each occurrence of one value by a new value double oldVal = 2;

double newVal = 999;

replace(myVec3.begin(), myVec3.end(), oldVal, newVal);

print(myVec3, string("Modified value of vec3"));

The remove() algorithm removes all elements from a sequence that equal to a given value (or that satisfy some predicate):

// Now remove this element

remove(myVec3.begin(), myVec3.end(), newVal);

print(myVec3, string("Removed element from vec3"));

It is possible to sort a vector. Of course, we must define how to compare two elements of a vector:

r

By ‘default’ operator

r

By defining our own comparison function

An example of the first option is:

// Sort the random access container vector<T> class myVec3[myVec3.size() - 1] = 9999.0;

stable sort(myVec3.begin(), myVec3.end());

print(myVec3, string("Sorted vec3 with "<" ASCENDING "));

We now wish to sort a vector in descending order and this is possible but we first must define a comparison function:

bool myGreater(double x, double y) {

return x > y;

}

We can now sort the elements in the vector by using this function ascomparitor:

stable sort(myVec3.begin(), myVec3.end(), myGreater);

print(myVec3,

string("Sorted vec3 with DESCENDING comparitor function "));

An example of how to merge vectors is:

// Merge two sorted vectors vector<double> myVec4(N, 2.41);

vector<double> myVec5(myVec3.size() + myVec4.size()); // Output merge(myVec3.begin(), myVec3.end(), myVec4.begin(), myVec4.end(),

myVec5.begin());

print(myVec5, string("Merged vector"));

Our final example is somewhat more advanced than a simple copy() algorithm, for example.

In this case we copy elements while at the same time transforming them in some way. As an example, we create two vectors of strings, transforming the first vector to upper case and then joining the two vectors to form a new vector. First of all, here is the code to convert a string to upper case:

string UpperCase(string s)

{ // Convert a string to upper case

for (int j = 0; j < s.length(); j++) {

if (s[j] >= "a" && s[j] <= "z") {

We now create two vectors whose elements are strings:

vector<string> First(3);

First[0] = "Bill";

First[1] = "Abbott";

First[2] = "Bassie";

vector<string> Second(3);

Second[0] = "Ben";

Second[1] = "Costello";

Second[2] = "Adriaan";

and we create another vector that will be their join in some sense:

vector<string> Couples(3);

We now transform the first vector’s elements to upper case:

// Now convert the First names to upper case

transform (First.begin(), First.end(), First.begin(), UpperCase);

print(First, string("An upper case vector"));

In this case we say that the function is aunary transformation.

Finally, we define abinary transformation Join() that stores results in another container:

class Join {

public:

// Overloading of operator ()

string operator () (const string& s1, const string& s2) {

return s1 + " and " +s2;

} };

The transform is now: (use CD to check this output) // Now join to make a team

transform (First.begin(), First.end(), Second.begin(), Couples.begin(), Join());

print(Couples, string("Joined couples"));

The output from all this code is:

data, size of vector is 10

[3.14,3.14,3.14,3.14,3.14,3.14,3.14,3.14,3.14,3.14,]

data, size of vector is 10

[2,456.76,3.14,3.14,3.14,3.14,3.14,3.14,3.14,55.66,]

copy to a vector, size of vector is 10

[2,456.76,3.14,3.14,3.14,3.14,3.14,3.14,3.14,55.66,]

An upper case vector, size of vector is 3 [BILL,ABBOTT,BASSIE,]

Joined couples, size of vector is 3

[BILL and Ben,ABBOTT and Costello,BASSIE and Adriaan,]

Rotated vector by 2 units, size of vector is 6

[2,3,4,5,0,1,]

Reversed vector vec3, size of vector is 6 [1,0,5,4,3,2,]

Modified value of vec3, size of vector is 6 [1,0,5,4,3,999,]

Removed element from vec3, size of vector is 6 [1,0,5,4,3,999,]

Sorted vec3 with "<" ASCENDING , size of vector is 6 [0,1,3,4,5,9999,]

Sorted vec3 with DESCENDING comparitor function , size of vector is 6 [9999,5,4,3,1,0,]

Merged vector, size of vector is 12

[2.41,2.41,2.41,2.41,2.41,2.41,9999,5,4,3,1,0,]

You can run the program from the code on the CD.

11.7 CONCLUSIONS AND SUMMARY

We have given an introduction to Complexity Analysis. We have introduced this topic for two main reasons; first, we wish to bring the material to the attention of the reader and second once we have given an overview of these topics we shall see that the transition to, and the understanding of STL will progress more easily than otherwise.

This is an important chapter and it is advisable to read it several times. It goes without saying that the best way to really learn the material is to code some examples in C++ in conjunction with STL.

11.8 EXERCISES AND PROJECTS

1. (Creating a Stack class) Even though STL has an implementation for a stack, we would like you to implement your own. It must contain the usual constructors, non-virtual destructor (why non-virtual?) and member functions for:

r

Clear the stack

r

Is the stack empty?

r

Is the stack full?

r

Put an element on the top of the stack ( Push() )

r

Take the topmost element from the stack ( Pop() )

Implement the stack class using an embedded deque (using Composition). All new member functions that you create will delegate to the appropriate member functions in deque. You should define the Pop() function that returns the top-most element of the stack and removes it from the Stack<T> as well:

T Pop();

Why is this not declared as a const function?

Now create a test program. Create a number of dates and push them onto the stack. Then pop them off the stack as you need them. This type of example can be used when you create a set of dates in the future, push them onto the stack and then pop each date off the stack as it is needed.