Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ Programming ➜ STL ➜ Example - using maps

Example - using maps

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Nick Gammon   Australia  (23,140 posts)  Bio   Forum Administrator
Date Thu 03 Jul 2003 03:48 AM (UTC)

Amended on Fri 09 Jul 2004 01:54 AM (UTC) by Nick Gammon

Message
Maps are a useful container because they let you do keyed lookups (associative arrays). eg. You could key by string or number.

Maps are slightly more fiddly to use because whenever you look up a map (using find) you get back a pair, which consists of the "first" part (the key) and the "second" part (the value). If you want to store the same key more than once then use a multimap rather than a map.

A side-effect of maps seems to be that when you iterate through them the results appear in key order (because they are stored internally as a red/black tree).

This example illustrates:


  • A map of people, indexed by number (employee number?)
  • Inserting using two different methods ( [key] and insert function)
  • Iterating through the map, displaying everyone
  • Finding a particular person by key and displaying them
  • Finding a reference to a person by using [key]
  • Removing an entry based on the person's age. (Removing them based on the key would be easier, just do a find followed by an erase of the corresponding iterator - if any).






// disable warnings about long names
#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <string>
#include <map>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <iterator>
#include <functional>

using namespace std;

class Person
  {
  // private members
  string m_sName;
  string m_sEmail;
  int    m_iAge;

  public:

  // constructor
  Person (const string sName, 
          const string sEmail, 
          const int iAge) :
   m_sName (sName), m_sEmail (sEmail), m_iAge (iAge) {};

  // default constructor
  Person () : m_iAge (0) {};

  // copy constructor
  Person (const Person & p) :
    m_sName (p.m_sName), m_sEmail (p.m_sEmail), m_iAge (p.m_iAge) {};
    
  // operator =
  Person & operator= (const Person & rhs)
    {
    // don't assign to self
    if (this == &rhs)
      return *this;

    m_sName = rhs.m_sName;
    m_sEmail = rhs.m_sEmail;
    m_iAge = rhs.m_iAge;
    return *this;
    };

  // access private members

  string GetName ()   const { return m_sName; };
  string GetEmail ()  const { return m_sEmail; };
  int GetAge ()       const { return m_iAge; };

  }; // end of class Person

// function object to print one person
class fPrint
  {

  ostream & m_os;

  public:

  // constructor - remember which stream to use
  fPrint (ostream & os) : m_os (os) {};

  // person object arrives as a pair of key,object
  void operator() (const pair <const int, const Person> & item) const
    {
     m_os << "# " << item.first << " - name: "
          << item.second.GetName ()
          << " - "     << item.second.GetEmail ()
          << ", age "  << item.second.GetAge ()
          << endl;
    };

  };  // end of class fPrint

// declare type for storing people (numeric key, person object)
typedef map<int, Person, less<int> > people_map;

int main (void)
  {
  // make a map of people
  people_map people;

  // add items to list
  people [1234] = Person ("Nick", "nick@some-email-address.com", 15);
  people [4422] = Person ("Fred", "fred@nurk.com.au", 100);
  people [88]   = Person ("John", "john@smith.com.au", 35);
  // insert a different way ...
  people.insert (make_pair (42, Person ("Abigail", "abigail@blah.com.au", 22)));

  // best to declare this on its own line :)
  fPrint fo (cout);   // instance of function output object

  // print everyone (calls a function object to print)
  cout << "Printing all using fPrint ..." << endl;
  for_each (people.begin (), people.end (), fo);

  // find someone by key
  cout << "Finding person 4422 ..." << endl;

  people_map::const_iterator i = people.find (4422);

  if (i == people.end ())
    cout << "Not found." << endl;
  else
    {
    fo (*i);    // dereference and print

    // another way of printing - 

    // key itself is the "first" part of the map pair ...
    cout << "Found key = " << i->first << endl;

    // person object is the "second" part of the map pair...

    cout << "Found name = " << i->second.GetName () << endl;
    }

  // Note, this will not work:
  //   fPrint (cout) (*i);

  // However this will:
  //   0, fPrint (cout) (*i);

  // However I think the extra zero is a bit obscure. :)

  // An alternative way of finding someone.
  // Note - this will add them if they are not there.
  // Since this is a reference changing it will change the person in the 
  // map. Leave off the & to get a copy of the person.

  Person & p = people [1234];

  cout << "Person 1234 has name " << p.GetName () << endl;

  // Example of erasing an element correctly ...
  // If we did the j++ as part of the for loop we would end up
  // adding 1 to an iterator that pointed to an element that was
  // removed which would lead to a crash. See Josuttis p 205.

  cout << "Erasing people of age 100" << endl;

  for (people_map::iterator j = people.begin (); j != people.end (); )
    {
    if (j->second.GetAge () == 100)
      people.erase (j++); // iterator is advanced before the erase occurs
    else
      ++j;                  // advance the iterator
    } // end of erase loop


  // now display who is left
  cout << "Printing people left after erase ..." << endl;
  for_each (people.begin (), people.end (), fo);

  return 0;
  } // end of main




Output

Printing all using fPrint ...
# 42 - name: Abigail - abigail@blah.com.au, age 22
# 88 - name: John - john@smith.com.au, age 35
# 1234 - name: Nick - nick@some-email-address.com, age 15
# 4422 - name: Fred - fred@nurk.com.au, age 100
Finding person 4422 ...
# 4422 - name: Fred - fred@nurk.com.au, age 100
Found key = 4422
Found name = Fred
Person 1234 has name Nick
Erasing people of age 100
Printing people left after erase ...
# 42 - name: Abigail - abigail@blah.com.au, age 22
# 88 - name: John - john@smith.com.au, age 35
# 1234 - name: Nick - nick@some-email-address.com, age 15

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,140 posts)  Bio   Forum Administrator
Date Reply #1 on Fri 04 Jul 2003 03:00 AM (UTC)
Message
Case-insensitive maps

Just for some fun I'll try to make a map that is case-insensitive on its key. First, the problem. A small program like this illustrates that adding a key of "Nick" and searching for 'nick' reveals no result ...





// disable warnings about long names
#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <string>
#include <map>
#include <iostream>

using namespace std;

typedef map<string, int, less<string> > age_map;

int main (void)
  {
  // make a map of people
  age_map people;

  // add items to list
  people ["Nick"] = 28;
  people ["John"] = 14;
  people ["Mary"] = 88;

  // find someone by key
  cout << "Finding person 'nick' ..." << endl;

  age_map::const_iterator i = people.find ("nick");

  if (i == people.end ())
    cout << "Not found." << endl;
  else
    cout << "Found age = " << i->second << endl;

  return 0;
  } // end of main


Output

Finding person 'nick' ...
Not found.





As it turns out, it is really quite simply to achieve, and demonstrates the flexibility of STL. When you declare a map you specify how "compare less" is going to work. In the case above we said "compare less" will be simply the built-in function "less<string>". That is, a function that compare two strings for less-than.

In fact, if we look at the actual code for "less" then it gives us a clue how to write our own ...


template<class _Ty>
	struct less : binary_function<_Ty, _Ty, bool> {
	bool operator()(const _Ty& _X, const _Ty& _Y) const
		{return (_X < _Y); }
	};


Bearing in mind the use of "implementor-reserved" field names starting with underscores, the routine boils down to doing "X < Y".

To do it case-insensitively we need a function object that will compare two strings regardless of case, and to help us do that we make a function-within-a-function that compare two characters with case-independence.

Having written that (see code in bold) then we simply change the way the map is declared, and it is all done!





Case-independent map example


// disable warnings about long names
#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <string>
#include <map>
#include <iostream>

using namespace std;


// case-independent (ci) string less_than
// returns true if s1 < s2
struct ci_less : binary_function<string, string, bool>
  {

  // case-independent (ci) compare_less binary function
  struct nocase_compare : public binary_function<unsigned char,unsigned char,bool> 
    {
    bool operator() (const unsigned char& c1, const unsigned char& c2) const 
      { return tolower (c1) < tolower (c2); }
    };

  bool operator() (const string & s1, const string & s2) const
    {
  
    return lexicographical_compare 
          (s1.begin (), s1.end (),   // source range
           s2.begin (), s2.end (),   // dest range
                nocase_compare ());  // comparison
    }
  }; // end of ci_less


typedef map<string, int, ci_less> age_map;

int main (void)
  {
  // make a map of people
  age_map people;

  // add items to list
  people ["Nick"] = 28;
  people ["John"] = 14;
  people ["Mary"] = 88;

  // find someone by key
  cout << "Finding person 'nick' ..." << endl;

  age_map::const_iterator i = people.find ("nick");

  if (i == people.end ())
    cout << "Not found." << endl;
  else
    cout << "Found age = " << i->second << endl;

  return 0;
  } // end of main


Output

Finding person 'nick' ...
Found age = 28




- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


45,303 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.