Chapter 19 Standard Template Library 0. Introduction The Standard Template Library (STL) is a subset of the ISO/ANSI C++ standard library. The STL provides five kinds of components and facilities for C++ programmers to use: containers, iterators, generic algorithms, function objects, and allocators. A container is an object that holds other objects. We have seen linked list classes, the primitive array and the STL vector that can contain other objects. An iterator is an object used to specify a position in a container. All iterators can be incremented to move the position towards the end by one or dereferenced to fetch the value in the container to which the iterator refers. Thus iterators allow traversal of a container without compromising the internal structure of the container. A generic algorithm, or just algorithm, is a template function that uses iterator types for its parameter types. Generic algorithms are used to manipulate the contents of a container for which an iterator type with sufficient strength is available. A function object is a class that overloads the function call operator, operator () so that an object of this class can be used like a function and can be passed as a template parameter. An allocator makes room in memory for STL components. Each container is given a memory allocator that the container's constructor uses as it needs memory. Allocators are required in environments with multiple memory models. Typically, 16 bit PC applications require allocators beyond the defaults provided in the STL. Except for embedded applications, these are apparently obsolete. Our purposes are well served by the default allocators, and allocators are beyond the scope of the text and this document, we will not discuss allocators. See the book by Josuttis in the text's references. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 2 1. Outline of topics in the chapter 19.1 Iterators Iterator Basics Kinds of Iterators Constant and Mutable Iterators Reverse Iterators Pitfall: Compiler Problems Other kinds of Iterators 19.2 Containers Sequential Containers Pitfall: Iterators and Removing Elements Tip: Type Definitions in Containers The Container Adapters stack and queue The Associative Containers set and map Efficiency 19.3 Generic Algorithms Running Times and Big-O Notation Container Acce3ss Running Times Nonmodifying Sequence Algorithms Modifying Sequence Algorithms Set Algorithms Sorting Algorithms 2. General remarks on the chapter 19.1 Iterators Iterator Basics In many data structures and in containers, we need some idea of position or location of an item in the structure. In C, and in C++ before the Standard Template Library (STL), the pointer met this need. Pointers have the advantage of speed but are dangerous. The STL provides the iterator as a replacement for pointers that is as efficient as a pointer but is much safer. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 3 The iterator is an object used to specify a position in a container and is a generalization of the notion of pointer. The syntax for manipulating an iterator is a subset of the syntax of pointers. To access an item referred to by an iterator iter, one writes *iter. Such an access can be used to fetch the value of the element (used as an r-value) or to assign this value (used as an l-value). To step the iterator forward to refer to the next item in the container, one writes iter++. Thus iterators allow traversal of a container without compromising the internal structure of the container. Each STL container defines one or more iterator types, specified by syntax such as vector<int>::iterator. The specific operations and properties of the iterator defined by a container depend heavily on what can be efficiently implemented given internal the structure of the container. The inner workings of the iterator, such as the detail of advancing the position in the container are also specific to the container being used and are hidden from the user. In addition to overloading the dereferencing operator (*), the following operators are overloaded for some classes of iterators. prefix and postfix forms of the ++ and -operator, equal and unequal operators (== and !=). Each of the STL containers provides functions that return the first and last elements in the container. If c is a container, then c.front() returns the first element in the container. c.back() returns the last element in the container. There are also member functions that return iterators that refer to special places in the container. c.begin() returns an iterator that refers to the first data item in the container. c.end() returns an iterator that refers to a position beyond the end of the container. Do not to confuse the past-end iterator value and the null pointer value. That there is an essential difference between these ideas can be seen from the following scenario. Suppose you have an iterator of a type that supports the decrement operator, --, and suppose the iterator has the past-end value for a container that is not empty. You can decrement the iterator with the result being an iterator that refers to the same element that Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 4 that c.back()returns. You cannot decrement or increment the null pointer to obtain anything useful. There is a programming analogy, but you should take care not to confuse these ideas. In the next section we see that the STL classifies iterators according to the features available to the iterator. Kinds of Iterators Different STL containers have different properties, so iterators for the various containers also have different properties. Similarly, different STL algorithms require different properties of the iterators they use. For example, sorting a container requires random access; otherwise, performance suffers. Consequently, iterators are classified in increasing "strength" of properties as input iterators, output iterators, forward iterators, bidirectional iterators, and random access iterators. The following figure displays the relationships among the iterator categories. We will see later that if an STL algorithm requires an iterator of a particular category, then the programmer can use an iterator of that category or of any stronger category, but not of a weaker category. Random Access Iterators are Bidirectional are Forward are Input & stronger Iterators stronger Iterators stronger Output than than than Iterators Some additional types of iterators are reverse iterators, stream iterators, insertion iterators, and raw storage iterators. Reverse iterators allow iteration through containers from the back to the front. Stream iterators allow you to read and write files from STL algorithms. Insertion iterators allow adding new items to the container rather than overwriting the position referred to by the iterator. To say random access means that all accesses take the same time, that is the runtime for accesses are O(1). Random access iterators provide random access (in this sense) to the elements in the container that defines the iterator type. In the STL, random access iterators provide indexing and the mixed iterator expression of adding an integer constant, n, to an iterator, iter . The expression , n + iter, yields an iterator value Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 5 that refers to the element n elements away from the element referred to by iter, in a direction that depends on the sign of n, provided there is an element at that position. Input Iterators Input iterators can only be stepped forward by one item per step. Furthermore, they may be dereferenced only to fetch (but not store into) an item from a collection. The following table displays the allowable operations on an input iterator. Input Iterator Operations r and r2 are input iterators. value =*r Provides read access to the item at the position denoted by r r++ Steps r forward one position in the container; returns the old position ++r Steps r forward one position in the container; returns the new position r == r2 Returns true if r and r2 denote the same position, else returns false r != r2 Returns true if r and r2 denote different positions, else returns false Output Iterators As with input iterators, output iterators can only be stepped forward by one item per step. However, output iterators may be dereferenced only to store a value at (but not fetch a value from) the specified location. The next table shows the allowable operations on an output iterator. Output Iterator Operations r is an output iterator. *r = value Stores value at the position denoted by r r++ Steps r forward one position in the container; returns the old position ++r Steps r forward one position in the container; returns the new position At first, it might seem strange that we can modify something but not fetch it, but it begins to make more sense once you know that output iterators are most often used for sending data to an output stream such as cout. (The iostream header file provides output stream iterators.) The idea is to treat an output stream as a "container" of characters and, using an output iterator, write each new character to the stream, one by one. We will not discuss stream iterators further. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 6 Forward Iterators We have looked at two categories of iterators that step forward through a container, one permitting only fetching, the other permitting only storing. Often we would like to both fetch and store a value at a particular position as we step through a container. Forward iterators allow us to do so. Specifically, the valid operations on a forward iterator are the same as those of an input iterator plus the ability to store into the referenced item (see the next table). Forward Iterator Operations r and r2 are forward iterators. value = *r Provides read access to the item at the position denoted by r *r = value Stores value at the position denoted by r r++ Steps r forward one position in the container; returns the old position ++r Steps r forward one position in the container; returns the new position r == r2 Returns true if r and r2 denote the same position, else returns false r != r2 Returns true if r and r2 denote different positions, else returns false Bidirectional Iterators Bidirectional iterators have all the properties of forward iterators but allow backward movement through a container as well as forward. Bidirectional Iterator Operations Add the following operations to the operations for forward iterators, r-- Steps r backward one position in the container; returns the old position --r Steps r backward one position in the container; returns the new position Adding the decrement operator makes some algorithms easier to implement. We will see that all the (Standard) STL containers support either bidirectional iterators or random access iterators. The list container provides bidirectional iterators. (Hence, some people like to think of list as an abstraction of a doubly linked list because it allows efficient traversal in either direction. Actually, the STL list is more than just an abstraction of a doubly linked list because the STL list is almost always implemented as a doubly linked list.) Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 7 Random Access Iterators Random access iterators have all the properties of bidirectional iterators but support additional operations to allow random (direct) access to any item in a container. Random Access Iterator Operations Add the following operations to the operations on bidirectional iterators,. r[i] Provides indexed read or store access to the item at the position denoted by r r += i Steps r forward by i positions in the container r -= i Steps r backward by i positions in the container r + i Returns an iterator value that is i positions beyond r in the container r – i Returns an iterator value that is i positions prior to r in the container r – r2 Returns the number of items that exist between the positions denoted by r and r2 r < r2 Returns true if and only if the position denoted by r is before the position denoted by r2 r > r2 Returns true if and only if the position denoted by r is after the position denoted by r2 r <= r2 Returns true if and only if the position denoted by r is not after the position denoted by r2 r >= r2 Returns true if and only if the position denoted by r is not before the position denoted by r2 The vector container provides random access iterators, which makes sense because vector is an abstraction of a one-dimensional array. Constant and Mutable Iterators The STL containers provide constant iterators. The available iterator types are defined in the container class. vector<char>::const_iterator constP =container.begin(); You cannot modify the elements in container using the constant iterator constP. This does not make the location itself constant. If I define vector<char>::iterator p = container.begin(); While we cannot change the front element from the container through dereferenced constP, we can change the front element of container by assigning dereferenced p. Here is a code fragment illustrating this. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 8 *constP = value; // illegal *p = value; // OK This should not be surprising, since this is true of memory locations referred to by const references and const pointers as well. The notion of mutable and constant iterators refers to the ability to change what the iterator refers to, not to the iterator itself. A mutable iterator is an iterator for which the dereferenced iterator may be assigned. The iterator p above is a mutable iterator referring to the first element in container. The iterator constP is a constant iterator that also refers to the first element in container. On the other hand, a iterator variable of type const_iterator or of an ordinary iterator type may be assigned another iterator value, unless the keyword const is used in the definition. #include <vector> using std::vector; vector<char> container; std::vector<char>::const_iterator constP = container.begin(); constP = container.end(); //OK const std::vector<char>::iterator P = container.begin(); //P = container.end(); // error: l-value specifies a const object Reverse Iterators If you need to cycle through a container from back to front, it may be inconvenient or impossible to rewrite code to use -- instead of ++, especially if the code has been precompiled. (Please ignore the difficulty in pre-compiling template code.) The answer to this difficulty is the reverse iterator. Suppose you have an algorithm that normally accepts an iterator to the first element of a container and cycles from the first through to the last element. Then you can pass a reverse iterator initialized to refer to the last element of the container, and the algorithm will cycle from the back of the container through the front. Such iterators reverse the effect of ++ and --. The normal effect of ++ on an ordinary iterator is to move the place the iterator refers to toward the back of the container. The effect of ++ on a reverse iterator is to move the place towards the front of the container. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 9 Similarly, -- moves the place a reverse iterator refers to towards the back of the container. Containers that support reverse iterators provide members that provide iterator values necessary to a reverse traversal. These members are rbegin() which returns an iterator referring to the last element in the container and rend() which returns an iterator referring to a position prior to the first element of the container1. Other kinds of Iterators We examined the input and output iterator above. Pitfall: Compiler Problems You compiler might not have read the latest ANSI C++ Standard. I have pointed out several times that the Standard says what the language ought to be, but your compiler defines the language you are dealing with is. Sometimes compilers will refuse to accept the some of syntax we present here. There may be a kludge (work-around) that will make the compiler work. Such a the case is described in the Pitfall on page 802 of the text. Compilers may not accept using std::vector<char>::iterator; iterator p; But this may work. (VC++6.0 at patch level 5 accepts this workaround.) std::vector<char>::iterator p; Perhaps this will work. using namespace std; vector<char>::iterator p; Other kinds of Iterators We examined the input and output iterator above. 9.2 Containers The STL container classes are template classes for structures that hold any programmer supplied data type that satisfies conditions we will outline presently. The STL provides 1 There are some implementation details we are ignoring here. Overloaded functions and operators allow rend() to return an iterator referring to the first element, but to behave in the manner I describe here. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 10 three kinds of containers: sequence containers, container adapters, and associative containers. Sequential Containers In a sequential container, or sequence container, the position of each item in the container depends on the time and place of insertion but not on the item's value. The sequence containers specified in the C++ Standard are vector, list, and deque2. These containers are sometimes referred to as "first class containers". Each STL container provides a different set of operations that have different time and space complexities. Selection of a container should be made accordingly. The vector provides random access to its elements, and insertions and deletions at the back (but not in the middle) are very efficient. The C++ Standard suggests that vector is the sequence type that should be used by default. However, if your program requires frequent insertions and deletions in the middle of a sequence, the list should be used. The deque should be used if frequent insertions and deletions are needed at the beginning and the end of the sequence. I will not reproduce the tables of member functions provided in the text. Pitfall: Iterators and Removing Elements Be very careful using iterators after you delete elements. The list container guarantees that deletions only invalidate iterators to the deleted element. Iterators into a vector container at and after the point of deletion are invalidated. Pitfall: Iterators and Inserting Elements You also need to be careful using iterators after you insert elements. The list container guarantees that insertions will not affect iterators. Iterators into a vector container at and after the point of insertion are invalidated. If insertion of an element requires reallocation, then all iterators are invalidated. To prevent being left with invalid iterators, start out with a vector that is big enough using the member functions provided, or adjust the size yourself using resize, but remember that resizing still invalidates all iterators. The text discusses a singly linked list, the slist, but this is not in the Standard. It is not available in all STL implementations, so I will not discuss it here. 2 Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 11 Tip: Type Definitions in Containers See the Tip on page 808. The containers defined in the C++ Standard provide the iterator typenames iterator, const_iterator, reverse_iterator, and const_reverse_iterator. The typename value_type is typedef'ed to the type held by the container, that is, the type that was passed to the container in the template parameter. The typename size_type is an unsigned integral type for the size of a container. Access to these names is available by qualifying the type name in the manner we have seen. list<int>::const_iterator p; The qualified typename list<int>::value_type is typedef'ed to int in the list container template. The Container Adapters stack, queue, and priority_queue A container adapter does not directly implement the structures that hold the data items. Rather, it provides a new interface between the user and an existing container. These are called adapters because they use one of the three sequence containers (vector, list, or deque) to actually hold the items and simply present a different interface (stack, queue, or priority queue) to the programmer. It "adapts" the interface of one of these sequence containers to provide the operations appropriate for a stack or queue. The container adapters are queue, stack, and priority_queue. The container for the stack adapter A sequence container that provides members empty(), size(), push_back(), pop_back(), and back() is suitable as a container for the stack container adapter. All three sequence containers—vector<T>, deque<T>, and list<T>—satisfy these requirements. By default, deque<T> is the container used by the stack<T> adapter. The container for the queue adapter A sequence container that has members back(), front(), push_back(), pop_front(), empty(), and size() can be Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 12 used to hold queue<T> items. In particular, list<T> and deque<T> may be used. By default, deque<T> is the container used by the queue<T> adapter. Notice in Display19.9 that the queue uses push() instead of enqueue() and pop() instead of dequeue(), as you might have expected. Regardless of what we call the operation, we are still dealing with a queue—a FIFO (first-in, first-out) data structure— and push()and pop()mean insert at the rear and remove from the front, respetively. The structure still returns the oldest item in the structure, just as a queue should. The Associative Containers set and map The associative containers are set, map, multimap, and multiset. The set container stores items without repeats. The set container behaves (unsurprisingly) like the mathematical set. If an item has been inserted, inserting that item again does no effect. The fundamental operations are insertion, deletion, and a test for membership. The set container has constructors, copy constructor, assignment and destructor. There are member functions to insert, delete, and test for membership, test for being empty, request size and so on. The set container maintains the data items in sorted order to allow access in O(log n) time. This requires that the type of item inserted in the set container either support operator < or provide an ordering function. The map and multimap containers are based on the concept of an associative array, a data structure that holds pairs of values: a key and a value associated with that key. An associative array can be thought of as an array that allows index types other than integer. By default, the contents of a map are ordered by key values using the < operator, though the programmer may either overload this operator or provide a comparison function. Iterators for the map container step through the container, delivering the items in ascending order of the keys. Efficiency Efficiency was a concern in the design of the STL components. The Standard specifies a maximum runtime for each member of each container that a compliant implementation must provide. It does not specify the implementation. 19.3 Generic Algorithms Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 13 We have said that the STL provides containers, iterators, and generic algorithms. In computer science terminology, an algorithm is (roughly) a sequence of steps that solves a problem. In contrast, the term algorithm in the STL context means something much more specific, namely, a template function that has iterators for its parameter types. The STL supplies a large set of generic algorithms that operate on containers, whether they are STL containers or user-defined containers. The algorithms are generic because each algorithm is a template and the arguments in a function call are iterators, not containers. Hence, a generic algorithm may operate on any data structure that provides an iterator type that meets the iterator requirements of that algorithm. Let's look at an example. The STL supplies a sort algorithm with the following signature (the required header file and the function prototype): #include <algorithm> template <class RandomAccessIterator> void sort( RandomAccessIterator first, RandomAccessIterator pastLast ); Given any container that supports random access iterators, the sort algorithm sorts the container elements into ascending order using the < operator. We know that the STL vector container supports random access iterators, so we could use sort as follows: #include <vector> // For vector<T> class #include <algorithm> // For sort() generic algorithm vector<int> v; sort(v.begin(), v.end()); // Sort the entire vector Because sort works on any container whose iterators meet its requirements, we can sort a built-in array as follows. (Recall that pointers into a built-in array meet the requirements of a random access iterator.) int arr[100]; sort(&arr[0], &arr[100]); // Or sort(arr, arr+100); Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 14 In this code, the call to sort passes as arguments the location of the first array element and the location of the imaginary past-the-end element (because arr[99] is the last valid array element). Running Times and Big-O Notation There are two constraints on computation. One is the space required to carry out the computation, the other is the time requires. The space or time required for a specific computation for a specific data set will not be generally useful. However, the amount of time or space required as functions of the problem size turn out to be of great interest. If we know these, we can predict whether the algorithm might produce an answer in a reasonable time for a problem that is ten or a hundred times as big as the one we are presently able to solve. Similarly, we could predict whether we might have the necessary space. However, even if we do know an exact expression for the time an algorithm to run on a specific computer, this won’t be useful on a different machine, especially if the architecture is different from the computer for which the expression works. To sidestep at least some of this, we use is an "order of magnitude" estimate of the growth of the expression for runtime or memory use. I won't go into detail here, but for polynomials, the highest degree term dominates the polynomial for large size. Hence for polynomials we keep only the highest order term, dropping any lower order terms. What of log functions, square roots, and so on? For fractional powers, Nx dominates Ny if x > y. Any power, Nx, of degree x > 0 dominates any logarithm, to any base. As for polynomials, we keep only the dominant terms. Such estimates are expressed in the so called big-O notation. Crudely put, the big O notation retains only the dominant term, and ignores the lead coefficient. For a polynomial, 6*N3 – 2*N2 + 3*N + 14, the big O estimate is O(N3). Note that this is an upper bound estimate. There is a sound mathematical basis for this notation. First, notice that it is only meaningful for large values of N. Second, it is the growth rate, not the specific multiplier that is of interest. What f(N) = O(N2) means is that for some multiplier, say A, and for all Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 15 sufficiently large values of N, the value of f(N) <= A*N2 for all values of N that are sufficiently large. Container Access Running Times In what follows, N is the number of items in the vector. List insertion is O(1) anywhere. Vector insertion at the back of a vector is also O(1). For a vector or a deque, insertion in the middle takes O(N) time. Almost all operations for the set or map take O(log N) time. Nonmodifying Sequence Algorithms These are algorithms that access contents of containers but do not modify the elements nor the sequence of elements in a container. Typical of these is the find algorithm. The sequence algorithms operate on a half-open interval defined by two iterators, usually written [start, past-end), where the '[' indicates that the element referred to by the start iterator value in the range is processed, and every item is processed up to but not including the past-end iterator value. In my opinion, it is unfortunate that the names are frequently [begin, end), nevertheless, the element referred to by the iterator value end is not included in the range. Modifying Sequence Algorithms Algorithms such as swap, replace, or remove are typical of modifying algorithms. Like non-modifying algorithms, modifying sequence algorithms operate on half-open iterator ranges of a container that the iterators refer to. They are called modifying because the either can or will modify objects pointed to be the iterators or objects in the container in the iterator range. Some of these algorithms will that make copies of the input container will not modify the input container. These are still classified as modifying because the output container is modified. Sorting Algorithms Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 16 The sorting algorithms provided in a compliant implementation of the STL are required to run in O(N*log N) time. The standard specifies only the runtime requirement, but does not specify an implementation. It is proved in the algorithm course that the fastest possible algorithm for sorting that works by swapping elements runs in O(N*log N) time, so the algorithms in the STL are as fast as possible, up to a constant multiple. 3. Solutions to, and remarks on, selected Programming Projects 1. Iterator A object that behaves like an iterator is an iterator. a) Argue from the properties of a random access iterator that a pointer to an element of an array behaves exactly as a random access iterator. Answer: A random access iterator provides dereferencing and indexing of an iterator value for access to a container element, prefix and postfix -- and ++ operators and the ability to add or subtract an iterator and an int value to produce other iterator values. A value having type pointer to array base type has exactly these properties. b) Argue further that: i) The name of a pointer to double that points to the first element in an array of double so the array name can serve as a "begin" iterator. ii) The name + size of the array server as an "end" iterator value. Answer: The name of an array carries type "pointer to base type" and it points to the first element of the array. This means that the expression *(arrayName) will return the value of the first entry. Further, applying the ++ operator repeatedly will ultimately reach each array entry in its turn, and will ultimately reach the pointer value, arrayName +arraySize, which serves as the "past-end" iterator value. 2. Erase Use the remove generic algorithm to illustrate removal of several instances of a particular item from a container. No solution is provided. 3. Sieve of Eratosthenes. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 17 No solution is provided. 4. Student Records. Define struct StudentInfo; maintain records in a vector<StudentInfo>. Program prompts for and fetches data, builds vector of student records, sorts by name, calculates max and min grades (no names, just max and min numbers), class average, prints summarizing data along with class roll and grades. Test program. //File: Ch19Prog4.cpp // // Using struct StudentInfo with string name and int grade members, // prompt for and fetch names and grades. Sort by name, output class // average, maximum and minimum grades, and sorted roll with grades. // There is a quirk in VC++ getline that prevents clean exit from // i/o loop. #include #include #include #include #include <algorithm> <iomanip> <iostream> <string> <vector> // This is only needed if you are using MS VC++. // The VC++ library does not define min/max. #ifdef _MSC_VER template <class T> T max(const T& a, const T& b) { return (a > b) ? a : b; } template <class T> T min(const T& a, const T& b) { return (a < b) ? a : b;; } #else using std::max; #endif using using using using using std::cin; std::cout; std::endl; std::istream; std::setprecision; Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 18 using std::sort; using std::string; using std::vector; struct StudentInfo { string name; int grade; }; //Used by the sort algorithm bool compare(const StudentInfo& x, const StudentInfo& y) { return x.name < y.name; } int main() { vector<StudentInfo> students; StudentInfo record; string::size_type maxLength = 0; int maxGrade = 0; int minGrade = 101; int sumGrade = 0; int count = 0; double averageGrade; // Find the largest and smallest grades // Count the students and compute the sum of grades for class average // Find length of the longest string for nice output cout << "Enter a name and an integer grade between 0 and 100\n" << "Separate with white space, terminate with <eof><cr><eof>\n"; while(cin >> record.name >> record.grade) { maxLength = max(maxLength, record.name.size()); maxGrade = max(maxGrade, record.grade); minGrade = min(minGrade, record.grade); sumGrade = sumGrade + record.grade; count++; students.push_back(record); } averageGrade = static_cast<double>(sumGrade) / count; // Sort the student record vector sort(students.begin(), students.end(), compare); cout << << << << "\maximum grade is " << maxGrade "\minimum grade is " << minGrade "\average grade is " << averageGrade endl << endl; Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 19 for (std::vector<StudentInfo>::size_type i = 0; i != students.size(); ++i) { // write names, pad output on right with spaces to maxLength + 1 characters cout << students[i].name << string(maxLength + 1 - students[i].name.size(), ' ') << students[i].grade << endl; } return 0; } 5. Continue Project 4 (Student Records). In two ways (see below), write a function that separates student records from the vector of StudentInfo records into two vectors, one with records of passing students and one containing records of failing students. (Grade of 60 or better is passing.) a) Consider continuing to use a vector. You could generate the second vector of passing students and a third vector of failing students. This would keep duplicate records some of the time so don't do it this way. Instead, create a vector of failing student records and a test for failing function. Then use push_back to insert the failing student records, then use the erase member function to erase the failing student records. Code this. b) There are efficiency concerns with the solution in part a). Potentially, we ease O(N) members from the middle of a vector. Each erase from the middle take O(N) time. Give a bit O estimate of the runtime for this solution. Answer: O(N) erasures each of which takes O(N) time has a runtime of O(N2). c) If you use a list<StudentInfo>, what are the runtime estimates for the erase and insert functions? Consider how the runtime estimate for erase for a list affects the runtime for the program. Code the problem using a list instead of a vector. Remember that the list iterator is bi-directional, not random access. Answer: Insertion and deletion for a list container are each O(1). O(N) erasures (anywhere) that take O(1) time produces O(N) runtime. Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 20 CODE: PART A) //File: Ch19Prog4.cpp // // Part a) // Using struct StudentInfo with string name and int grade members, // prompt for and fetch names and grades. Sort by name, output class // average, maximum and minimum grades, and sorted roll with grades. // Separate the failing students' records from the passing students'. // Display this data. #include #include #include #include #include <algorithm> <iomanip> <iostream> <string> <vector> // This is only needed if you are using MS VC++. // The VC++ library does not define min/max. #ifdef _MSC_VER template <class T> T max(const T& a, const T& b) { return (a > b) ? a : b; } template <class T> T min(const T& a, const T& b) { return (a < b) ? a : b;; } #else using std::max; #endif using using using using using using using using std::cin; std::cout; std::endl; std::istream; std::setprecision; std::sort; std::string; std::vector; struct StudentInfo { string name; int grade; Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 21 }; typedef std::vector<StudentInfo>::iterator iterator; typedef std::vector<StudentInfo>::size_type size_type; //Used by the sort algorithm bool compare(const StudentInfo& x, const StudentInfo& y) { return x.name < y.name; } inline bool isFailing(const StudentInfo& record) { return record.grade < 60; } int main() { vector<StudentInfo> students; StudentInfo record; vector<StudentInfo> failingStudents; string::size_type maxLength = 0; int maxGrade = 0; int minGrade = 101; int sumGrade = 0; int count = 0; double averageGrade; // Find the largest and smallest grades // Count the students and compute the sum of grades for class average // Find length of the longest string for nice output cout << "Enter a last name and an integer grade between 0 and 100\n" << "Separate with white space, terminate with <eof><cr><eof>\n" << "Capitalize properly as the sort routine is case sensitive.\n"; while(cin >> record.name >> record.grade) { maxLength = max(maxLength, record.name.size()); maxGrade = max(maxGrade, record.grade); minGrade = min(minGrade, record.grade); sumGrade = sumGrade + record.grade; count++; students.push_back(record); } averageGrade = static_cast<double>(sumGrade) / count; // Sort the student record vector sort(students.begin(), students.end(), compare); Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library cout << << << << Page 22 "\nMaximum grade is " << maxGrade "\nMinimum grade is " << minGrade "\nAverage grade is " << averageGrade endl; cout << "\nClass names and grades are: \n"; size_type i; // VC++6.0, in violation of the ANSI C++ Standard, // puts for loop control variable outside the for loop. // So we put it outside as well. Saves grief. for (i = 0; i != students.size(); ++i) { // write names, pad output on right with spaces to maxLength + 1 characters cout << students[i].name << string(maxLength + 1 - students[i].name.size(), ' ') << students[i].grade << endl; } //copy failing student records to another vector, erase from students vector iterator itr = students.begin(); while(itr < students.end()) { if(isFailing(*itr)) { failingStudents.push_back(*itr); itr = students.erase(itr); } else itr++; } cout << "\nPassing students and grades are: \n"; for (i = 0; i != students.size(); ++i) { // write names, pad output on right with spaces to maxLength + 1 characters cout << students[i].name << string(maxLength + 1 - students[i].name.size(), ' ') << students[i].grade << endl; } cout << "\n\nFailing students and grades are: \n"; for (i = 0; i != failingStudents.size(); ++i) { // write names, pad output on right with spaces to maxLength + 1 characters cout << failingStudents[i].name << string(maxLength + 1 - failingStudents[i].name.size(), ' ') << failingStudents[i].grade << endl; Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 23 } return 0; } PART C //File: Ch19Prog5c.cpp // // Part c) // Using struct StudentInfo with string name and int grade members, // prompt for and fetch names and grades. Sort by name, output class // average, maximum and minimum grades, and sorted roll with grades. // Part c) Use a list<StudentInfo> rather than a list. // Separate the failing students' records from the passing students'. // Display this data. // Comment on efficiencies of erase and insert members. #include #include #include #include #include <algorithm> <iomanip> <iostream> <string> <list> // This is only needed if you are using MS VC++. // The VC++ library does not define min/max. #ifdef _MSC_VER template <class T> T max(const T& a, const T& b) { return (a > b) ? a : b; } template <class T> T min(const T& a, const T& b) { return (a < b) ? a : b;; } #else using std::max; #endif using using using using using using using std::cin; std::cout; std::endl; std::istream; std::setprecision; std::sort; std::string; Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 24 using std::list; struct StudentInfo { string name; int grade; }; typedef std::list<StudentInfo>::iterator iterator; typedef std::list<StudentInfo>::size_type size_type; //Used by operator< with ANSI C++ Standard compliant compilers bool compare(const StudentInfo& x, const StudentInfo& y) { return x.name < y.name; } #ifdef _MSC_VER //MS VC++ does cannot pass a comparison function as an argument //to list::sort, so we must overload operator< bool operator<(const StudentInfo& lhs, const StudentInfo rhs) { return compare(lhs, rhs); } #endif inline bool isFailing(const StudentInfo& record) { return record.grade < 60; } int main() { list<StudentInfo> students; StudentInfo record; list<StudentInfo> failingStudents; string::size_type maxLength = 0; int maxGrade = 0; int minGrade = 101; int sumGrade = 0; int count = 0; double averageGrade; // Find the largest and smallest grades // Count the students and compute the sum of grades for class average // Find length of the longest string for nice output cout << "Enter a last name and an integer grade between 0 and 100\n" << "Separate with white space, terminate with <eof><cr><eof>\n" Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 25 << "Capitalize properly as the sort member is case sensitive.\n"; while(cin >> record.name >> record.grade) { maxLength = max(maxLength, record.name.size()); maxGrade = max(maxGrade, record.grade); minGrade = min(minGrade, record.grade); sumGrade = sumGrade + record.grade; count++; students.push_back(record); } averageGrade = static_cast<double>(sumGrade) / count; // Sort the student record list #ifdef _MSC_VER students.sort(); #else students.sort(compare); #endif cout << << << << "\nMaximum grade is " << maxGrade "\nMinimum grade is " << minGrade "\nAverage grade is " << averageGrade endl; cout << "\nClass names and grades are: \n"; iterator itr; for(itr = students.begin(); itr != students.end(); itr++) { // write names, pad output on right with spaces to maxLength + 1 characters cout << itr->name << string(maxLength + 1 - itr->name.size(), ' ') << itr->grade << endl; } //copy failing student records to another list, erase from students list itr = students.begin(); while(itr != students.end()) { if(isFailing(*itr)) { failingStudents.push_back(*itr); itr = students.erase(itr); } else itr++; } Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16 Chapter 19 CHAPTER Standard Template Library Page 26 cout << "\nPassing students and grades are: \n"; for(itr = students.begin(); itr != students.end(); itr++) { // write names, pad output on right with spaces to maxLength + 1 characters cout << itr->name << string(maxLength + 1 - itr->name.size(), ' ') << itr->grade << endl; } cout << "\n\nFailing students and grades are: \n"; for(itr = failingStudents.begin(); itr != failingStudents.end(); itr++) { // write names, pad output on right with spaces to maxLength + 1 characters cout << itr->name << string(maxLength + 1 - itr->name.size(), ' ') << itr->grade << endl; } return 0; }