CSCI 2300 | Data Structures and Algorithms | Spring 2000 |
In chapter 4 we will study trees and balanced trees. The standard library has an implementation of balanced (red-black) trees, but these implementations are not directly accessible to programmers. Instead, they are used as internal representations in the container classes set, multiset, map, and multimap. In this lab we will study the map and multimap, which are somewhat more complicated than set and multiset, but substantially more useful. Students completely unfamiliar with these container classes or wishing to review them in more detail than is covered here should see Chapter 7 and Sections 19.6-19.9 of Musser and Saini or Section 17.4 of Stroustrup.
Maps and multimaps store entries, each of which is a pair of the form
pair< const Key, T >Here Key and T are both templates for types. This pair can be thought of as an association between a Key and a T, which is why maps and multimaps are called "associative container classes". Maps (and multimaps unless otherwise stated) are ordered by an ordering of the Key type, while the type T is "just" the value stored in the map. The difference between a map and a multimap is that in a map the keys must be unique whereas in a multimap keys can be repeated. The Key type may be integers, floats, strings, or anything for which an ordering comparison operator such as < or a comparison function is defined (perhaps in the class definition itself). Because of this ordering, when your program gets access to a pair that is stored in a map, it can not change the Key. This dictates the need for const in the declaration. In turn this can be a source of frustrating compiler errors. Remember, anything touching a Key must treat it as const!!!
The declaration for a map looks like
template < class Key, class T, class Compare = less< Key > > class mapThe first two templated arguments are the template types forming the pair. The third is the ordering comparison function, which defaults to the less than operator through the less function adaptor. If operator< is defined on type Key you need not worry about this third argument. If you do want to provide your own comparison function it must be of the form
bool Comp( const Key& lft, const Key& right );Remember the const!
Of course maps (and multimaps) define iterators, and each iterator is in essence a pointer to a pair as defined above. As we will see, this leads to some complicated looking syntax. Iterating through a map looks like the iterations for any other container class, but the operations are implemented internally as an in-order tree traversal.
Enough of the preliminaries; it is time to start doing something. As you work through this lab, refer back to the foregoing discussion to help make sense of what's going on.
Look quickly at student.h and student.cpp. The code there is straightforward. Now look at the main.cpp in light of the following discussion.
students["Stewart"] = StudentRecord( "stewac", "freshman", "12234567890" );is to enter a StudentRecord into the map using the key "Stewart" (implicitly converted to a string). What really happens using the [] operator is that the underlying tree is searched for a matching string: if it is there, the associated StudentRecord is returned (as a reference); if it is not there, an entry is created at the appropriate place in the tree using the default constructor for StudentRecord. In either case, the returned StudentRecord is replaced by the new record by the assignment statement.
The other way to insert is with the insert function. There are several flavors of "insert", but the basic one is shown here (see main.cpp). The first thing to do is to create the value (a pair) to be inserted. For a map, the actual insert operation returns a pair, with the "first" being an iterator and "second" being a bool.
Note: the values stored in any pair can be accessed through the public member variables first and second.For a multimap, the insert just returns the iterator. The difference in return types is because maps can not have entries with duplicate keys, so the insert will fail if the key is already in the map.
Obviously, therefore, maps (and multimaps) define iterators. Each iterator is in essence a pointer to a pair as defined above. As we will see, this leads to some complicated looking syntax. Iterating through a map looks like the iterations for any other container class, but the operations are implemented internally as an in-order tree traversal.
So, here are two exercises: (a) compile and run the program to see what happens, and then (b) write code at the line in main.cpp labeled C to output the key and associated student record referenced (pointed to, actually) from the result of the insert. Compile and test the resulting program. (To get rid of all the silly looking warnings, set the "Warning level" to "None" under Project/Settings/C++. In general, this is a bad thing to do, but some the reaction of some compilers to the templates warrants it.)
void PrintMap( const StudentMap& students )Be sure to print both the string and StudentRecord for each entry.
At the conclusion of this lab, take a moment to review the following:
You have been reintroduced to the map and multimap container classes from the standard library (STL). You have also explored their primary member functions. The main difficulty in compiling code that uses maps and multimaps is that the Key is and must remain a const.
Maps and multimaps use a balanced tree implementation, but this is not apparent from the public interface. The only way a balanced tree implementation could be inferred is from the time-bound requirements on the operations (find, insert and erase must be logarithmic and ordered output must be linear). This is evidence of a good class design: the public interface reflects an intuitive view of how the class is to be used, not how it is implemented.