Associative containers

•      Sets & Multisets

–    These containers sort their elements automatically according to a certain sorting criterion

–    The difference is that multisets allow duplicates; sets do not

•      Implemented as balanced binary search trees (specifically, red-black trees)

•      As the elements are inserted, they are put into a red-black tree

–    The value of a left child of an element is always less than the parent

–    The value of a right child is always greater than the parent

–    Sets do not allow duplicates

•      When an iterator iterators over the elements, it does an inorder transversal

–    An inorder traversal visits node n after all the nodes in the left subtree of n and before all the nodes in the right subtree of n

 

Example using the set container

•       #include <iostream>           #include <set>
int main()
{
 set<int> coll;       
// instantiate a set container for int values    
// insert elements from 1 to 6 in arbitrary order   value 1 gets inserted twice    
coll.insert(3);   

    coll.insert(1);   

    coll.insert(5);  

    coll.insert(4);   

    coll.insert(1);   

    coll.insert(6);   

    coll.insert(2);   


// print all elements     * - iterate over all elements    

•       set<int> set<int> ::const_iterator pos;     // a constant iterator is read only

for (pos = coll.begin(); pos != coll.end(); ++pos)
{        cout << *pos << ' ';    }  
 
cout << endl;   }

 

•      Maps & Multimaps

–    These containers manage key/value pairs as elements

–    They also sort their elements automatically according to some sorting criterion that is used for the key

–    The difference is that maps do not allow duplicates, while multimaps do

•      Pairs

–    The class pair is provided to treat two values as a single unit. 

•    It has two data members, first and second

–    It is used by maps and multimaps to manage their elements

–    One of its member functions is make_pair which creates a pair on the fly.

 

Note about Associate containers

•      To modify the value of an element, you must remove the old element and insert a new one

–    If you want to remove elements from an associative container, you must use the member functions

 

•      Algorithms that remove elements or  that reorder or modify elements cannot be used with associative containers

–    Trying will result in a compiler error

–    This is because if they could change the value or position of elements they would not be sorted anymore

 

Maps and Multimaps

•      Must #include <map> to use

•      The elements of the map or multimap must be assignable, copyable and comparable

 

•    template < class Key, class value,
          class Compare = less<T>
  class Allocator = allocator<pair<const Key,T> >
 class map;

•      Notice that there are four type parameters to this template class

–   By default, the function object less<T> sorts the elements by comparing them with the < operator

•   So to use the default, your class must overload
operator <

 

Sorting your multimap

•      Functors: a predefined functor less<T> is the default in the class template definition

–   Sorts the container in ascending order

–   This can be replaced by a different predefined functor

•      When you call the constructor for the map or multimap, you can either accept the default sorting criteria, or specify greater<T>

•      Examples

–    multimap<int, string> IntStringMMap coll;
This will accept the default, and sort in ascending order

–    multimap<int, string, greater<int> > IntStringMMap coll;
This will specify to sort in descending order

 

The pair class

•      Pairs: each element in maps and multimaps is a pair

–   The first part of the pair is a key, by which the item is sorted and put into the BST

–   The second part of the pair is the value (often a class or struct)

–   The key of the elements inside a map is constant, but the value is not

•      Also used when you have a function that you want to return two values.

 

The pair class template

•      Must #include  <utility> to use the pair class

•    template<class T1, classT2 >
struct pair
{  T1 first;   T2 second; 
//data members
   pair();  
//constructors
   pair(const T1& key, const T2& value)
}

 

•      Example of use (calls the constructor):
           pair<int, string>(34, “Hi”)

•      This is defined as a struct so the data members will be public

 

Function make_pair( )

•      The template function make_pair makes it easier to create pairs

–   make_pair takes two objects as arguments, and returns an object of type pair

•      Example:  make_pair(56, 3.14159)

–    This is the same as
         pair<int, double>(56, 3.14159)

•      This enables your to create a value pair without writing the types explicitly.

–    Unqualified floating literals have type double

 

•      Using pairs

–    Create an object of type pair:  
      make_pair(1649, “whatever”) myPair;

–    Access the data members of the pair: 
      string st = myPair.second;
  int myKey = myPair.first;

 

Searching

•      Maps sort their elements automatically according to the element’s keys

•      Searching for elements with certain keys have good performance but

–    Searching for elements with certain value have bad performance.

 

Special search operations

count (elem) —returns the number of element with value elem

find (elem) —returns an iterator where the first occurrence of elem is found

lower_bound (elem) —returns an iterator for the first element that has the same or greater value than elem

upper_bound (elem) —returns an iterator for the first element that has a greater value than elem

equal_range(elem) —returns a pair, the first and last position where elem would get inserted

 

Search operation for values

•      If you want to search for a value, not a key then you can’t use member function find( )

•      You can use the general algorithm find_if( ) or program it yourself as follows

Multimap<string, float>::iterator pos;

For (pos = coll.begin(); pos != coll.end(); ++pos)

{   if (pos->second == value)

   {do_something( ); }

     }

•      The iterator returned is pointing to a pair, not a single item

 

Examples using a maps

 

    #include <iostream>       

    #include <map>

    #include <string>
    using namespace std;
    int main()
    {   
// type of the collection   
    typedef multimap<int,string> IntStringMMap;  
     IntStringMMap coll
;        // set container for int/string values  

 

     // insert some elements in arbitrary order  a value with key 1 gets inserted twice   

    coll.insert(make_pair(5,"tagged"));   

    coll.insert(make_pair(2,"a"));   

    coll.insert(make_pair(1,"this"));

    coll.insert(make_pair(4,"of"));

    coll.insert(make_pair(6,"strings"));

    coll.insert(make_pair(1,"is"));

    coll.insert(make_pair(3,"multimap")); 

  

// print all element values   - iterate over all  element member second is the value   

IntStringMMap::iterator pos;
    for (pos = coll.begin(); pos != coll.end(); ++pos)
{        cout << pos->second << ' ';    }
    cout << endl;}

 

Another map example  *************************************************

#include <string>

#include <map>

#include <iostream>

using namespace std;

 

// this is just to give you an idea of the syntax necessary to get the map (or multimap) to work

// with objects as both the key and value.  The code is very incomplete

class personName   // objects of this type will be the key

{          

private:

            string const lastName;  string const firstName;

 

public:

            personName(string first="", string last=""):  lastName(last),firstName(first){ }  //constructor

 

            bool operator < (personName p) const

            {           if (lastName == p.lastName)

                                    return firstName < p.firstName;

                        else return lastName < p.lastName;

            } 

};  // end presonName class

 

class personInfo  // objects of this type will be the value

{          

private:

            string phone;

            string address;

public:

            personInfo( )    {    phone=""; address="";}// default constructor

            personInfo(string ph, string ad)  {    phone=ph; address=ad;}  // constructor

};

 

int main ( )

{

            map<personName, personInfo> adBook;   // the types of the pair

            map<personName, personInfo>::iterator pos;

 

            personName tempP("Anne", "DeFrance");// getting information for the key

            personInfo tempI("585-2693", "601 W. Arnold");   // getting information for the value

//this is an alternative to using make_pair

            // pair<personName const, personInfo> onePair(tempP, tempI);  

            // adBook.insert(onePair);      

            //    or

            adBook.insert(make_pair(tempP, tempI));  // this uses  make_pair insert the data into the multimap

            return 0;

}

 

 

Access to elements

•      The usual way to access elements in a map is via iterators

•      But, maps can also be used as an associative array

–   An associative array’s index can be any type, not just an integer

–   You cannot have a wrong index with an associative array

•   If you use a key as the index, and that does not exist, it will be created and inserted into the map

–   This is the one associative container that allows direct access with the subscript operator

•      Look at using a map as an associative array and as a dictionary

 

Maps and Multimaps can be used as associative arrays

 

#include <iostream>

#include <map>

#include <string>

using namespace std;

 

int main()

{

    typedef map<string,float> StringFloatMap;

 

    StringFloatMap stocks;                // create empty container

                                                                                   

    stocks["BASF"] = 369.50;             // insert some elements

    stocks["VW"] = 413.50;

    stocks["Daimler"] = 819.00;

    stocks["BMW"] = 834.00;

    stocks["Siemens"] = 842.20;

 

                                                                                                // print all elements

    StringFloatMap::iterator  pos;

    for (pos = stocks.begin( ); pos != stocks.end( ); ++pos) {

        cout << "stock: " << pos‑>first << "\t"

             << "price: " << pos‑>second << endl;

    }    cout << endl;

 

    // boom (all prices doubled)

    for (pos = stocks.begin(); pos != stocks.end(); ++pos)

{        pos‑>second *= 2;    }

 

    // print all elements

    for (pos = stocks.begin(); pos != stocks.end(); ++pos) {

        cout << "stock: " << pos‑>first << "\t"

             << "price: " << pos‑>second << endl;

    }    cout << endl;

 

    // rename key from "VW" to "Volkswagen"   

    stocks["Volkswagen"] = stocks["VW"];

    stocks.erase("VW");

 

    // print all elements

    for (pos = stocks.begin(); pos != stocks.end(); ++pos) {

        cout << "stock: " << pos‑>first << "\t"

             << "price: " << pos‑>second << endl;

    }

}

 

 

********* Other information about the STL not covered in class***********************

Making the STL more generic

•      The algorithms arguments are of a specific type…sometimes you want to use the algorithm, but your argument is of a different type than expected

•      Container adaptors;

–    stacks,

–    queues,

–    priority queues (heaps)

 

 

Container adapters :

•      The container adapters are

–     stacks

–    queues

–    priority queues

•      They are not containers  but classes that provide a limited subset of container operations

•      They are implemented in terms of an underlying container

 

Stacks

•      There is no way to access any of a stack’s elements except for the top element; stack does not allow iteration through its elements

•      This restriction is the only reason stack exists, since you could implement a stack with a sequence container, using back, push_back, and pop_back

 

Implementing a stack

•      The default stack is implemented using a deque

•      Template <class T, class Containter = deque<T> >
class stack;

 

•      Member function of stack
push( ), pop( ), top( ), empty( ), size( )

•      The stack implementation maps these operations to function calls to the deque container

 

top( ) & pop( )

•      pop( ) does not return a value because it is inefficient to do so.

•      If you want to process the top of the stack after it is popped you must use top( )

•      This is true for all three container adapters, queues and priority queues as well a stacks

 

 Queues

•      A queue is a first in first out data structure

•      By default uses a deque as the underlying container

•      There is no way to access any of a queue’s elements except for the ones at the front and back; it has no iterators

•      The only reason to use the container adaptor queue rather than deque is to make it clear you are using only queue operations.

 

Member functions of queue

•      push( ) inserts an element at the back of the queue

•      back( ) returns the last element in the queue

•      pop( ) removes the element at the front of the queue

•      front( ) returns the element at the front of the queue

•      size( ) returns the actual number of elements

•      empty( ) returns true when the queue is empty