Section 2 - STL Maps

9B.2.1 Map as Key-Value Pairs

The STL map is very much like a set of STL pairs (first, second) in which the first item of the pair determines equality (i.e., two pairs are considered "equal" if and only if the first items in each pair are equal). In fact, the map is implemented, internally as just such a container.

There are other properties of maps, but these are the big points. Let's see an example.

// A Map
// CS 2C Foothill College
#include <iostream>
#include <string>
#include <map>
#include <utility>
using namespace std;

int main()
{
   typedef map<string, int> FriendAgePair;
   typedef FriendAgePair::iterator FAIter;
 
   FriendAgePair myFriends;
   FAIter iter;

   myFriends["lisa"] = 49;
   myFriends["keith"] = 26;
   myFriends["adrian"] = 4;


   cout << "---------- The contents of the Map ------ " << endl;
   for ( iter = myFriends.begin(); iter != myFriends.end(); iter++)
      cout << (*iter).first << " is " 
         << iter->second << " years old. " << endl;

   // let's give adrian a birthday;
   myFriends["adrian"] ++;

   cout << "\nNow, Adrian is " << myFriends["adrian"] << endl;
}

The run:

---------- The contents of the Map ------
adrian is 4 years old.
keith is 26 years old.
lisa is 49 years old.

Now, Adrian is 5
Press any key to continue . . .

The example shows two different ways to access the iterator (*iter).first and iter->second -- you will see them both and neither should feel uncomfortable. As iterators act like pointers, this is nothing new to you.

I used the typedef notation again, to reinforce the syntax for you.

9B.2.2 Ordering in Maps

Like sets, maps imply an ordering. However in the case of maps, the ordering only need be present in the key type - the value does not have to be ordered. You will note that when you iterate through the map, the result is listed in increasing order, regardless of how we insert the elements. Indeed, this was true of sets (look at the last page -- the output was arranged alphabetically or numerically, depending on the < operation).

Everything we said about sets and ordering holds true here. You can change the definition of the < operator and get a totally different behavior. In the above example we used primitive types, but if we had user-defined types, we would easily be able to change < to mean whatever we wanted.

9B.2.3 A Little More Layered Example

Let's layer the data structure just a bit now. We start with a data structure called Star, similar to an Employee - it has a name (string) and mag (meaning magnitude, which is a double). Here is an outline of the class:

class Star
{
public:
   string name;
   double mag;
   Star(string n, double m) : name(n), mag(m) {}
   void show() { cout << name << " " << mag << endl; }
   bool operator<(const Star &rhs) const
   {
      return (name < rhs.name);
   }
};

As is commonly done with classes in C++, we will work with pointers to the objects, rather than the objects themselves. To this end, we define a StarPtr type:

   typedef Star* StarPtr;

We'll make a map whose pairs consist of StarPtrs and ints. The int associated with the Star object (pointed to) will represent the distance (in light years) from that start to some reference that is application dependent. In our application, we'll create a map with each element being a star (pointer) and its distance to Earth. Don't confuse the distance (an int that will be the value in the key-value pair of the map, completely outside the Star object) with the mag (which is a double and lives inside the Star object).

Here is some code to clarify that description.

typedef Star* StarPtr;
typedef map<StarPtr, int> StarList; // (star ptr, dist to reference)
typedef StarList::iterator StarListIter;

int main()
{
   Star sir("sirius", -1.42), can("canopus", -0.72), arc("archernar", 0.45),
      sun("sun", -26.7);

   StarList earthNeighbors;
   StarListIter iter;

   sir.show();  can.show(); arc.show(); sun.show();

   // put three stars into the list along with their dist to earth
   earthNeighbors[&sir] = 8 ;
   earthNeighbors[&can] = 316 ;
   earthNeighbors[&arc] = 144 ;

   cout << "---------- The contents of the Map ------ " << endl;
   for ( iter = earthNeighbors.begin(); 
      iter != earthNeighbors.end(); iter++)
      cout << iter->first->name << " (mag "<<  iter->first->mag 
         << ") is " << iter->second << " lyrs from earth. " << endl;
}

We now have more levels of indirection when we iterate through the map. We can't cout << iter->first, because first is a Star pointer and we have not overloaded the << operator for that type. Thus, it is necessary to get at the data. cout << iter->first->name is a very common construct.

cout << iter->first->name

The iterator is pointing to a pair in the map; the first element is a pointer to an object; that object has a name member that we will print.

In examples like this, I am using all public members to make the code easier to show in its entirety. If the name had been private, then we would need to write cout << iter->first->getName().

This is good code for you to study in preparation for next week's study in graphs. One of its features that you should really laser-down on is the notion that the map is built from StarPtr (or Star*) items -- pointers not objects. This is very common in C++.

The use and advantage of pointers noted here should be something you take away from the course. Pointers are not used when they're not needed, but at times like these, they really are.

Here is the output:

sirius -1.42
canopus -0.72
archernar 0.45
sun -26.7
---------- The contents of the Map ------
archernar (mag 0.45) is 144 lyrs from earth.
canopus (mag -0.72) is 316 lyrs from earth.
sirius (mag -1.42) is 8 lyrs from earth.
Press any key to continue . . .