Sets and Maps

There is one very familiar associative container type that predates any notion of a data structure and even digital computer: the set. All of us have used sets in a mathematical context, usually without much thought as to their "associative" nature.

The set was arguably the first abstract data type. Sets may contain elements of a type T and are restricted to contain no more than one copy of each element. For example,

is a set with four elements a, b, c, and d.

Related abstract data types are the multiset, map, and multimap. The multiset is simply a set in which elements may be repeated any finite number of times, for example

is a multiset containing the seven elements a, a, b, b, b, c, and d. The map is typically defined as a set of ordered pairs, such as

which is a map with four elements. The interpretation of a map is that of an association, or mapping, or abstract function, associating (or mapping or assigning) the first element of each pair to the second. The map M1 could be interpreted as the dictionary, mapping, or function illustrated as:

The multimap is a map where pairs may be repeated. Thus a multimap is nothing more than a multiset of ordered pairs. Another name for multimap is relation.

Notice how pairs are treated by the equality operator in this context: pairs (x1,y1) and (x2,y2) are equal iff x1 == x2: the value of the second elements of pairs is ignored by operator ==(). Please refer to your discrete mathematics references to refresh your understanding of set and related concepts.

Client Needs

All of the associative containers in all data structures texts are used by client programs as one of the fours ADTs set, multiset, map, and multimap. In acknowledgement of this fact, the C++ STL has these four associative containers defined. We take it a step further by defining adaptor classes for these four types which adapt various associative data structures, beginning with sorted lists and on through more exotic structures such as red-black trees, to the set (or multiset, map, multimap) interface. These adaptor classes allow the experimentation with various implementation strategies for the four basic associative ADTs. Client programs may be written to the abstract ADT interface, and the implementing data structure can by changed without affecting the functionality of the client program.

Example Set Clients

Inventory. The client program needs to store records of items on hand. Each record corresponds to a specific item and contains such things as the name of the item stock number / barcode, the number of items currently in stock, the number to re-order when stock is low, definition of "low" for the item, the supplier of the item, and other information that facilitates a just-in-time computerized / barcoded inventory policy:

Customer Accounts. The client program needs to store customer identity records for use in accessing data base of purchase transactions.

Exceptional Instances. Client program needs to find all mispelled words in a text.

Instances in Common. Client program needs to find all records common to two different applications.

Password Server. Provide basic password services to users and system administrators.

Any Map Client. A map is essentially a set of pairs.


Example Map Clients

The Password Server could be built with a map instead of a set:

An internet router could use a map object

to look up the routing for a given packet destination. A dictionary could be declared as

We could store the results of a word count analysis of a text using a map declared as

A key word index, or concordance, is a map declared as

that stores the page numbers where each key word may be found. The key word index for a text would be built in three stages.

First the key words are identified through some process and the concordance is initialized with the key words paired with empty lists. Second, the text is read, searching the concordance for each word and pushing the page number onto the back of the list each time a word of text is found in the concordance. Finally, the key word index is obtained by traversing the concordance and outputting the key words followed by the list of page numbers, as in this program fragment:


Set and Map Tools (Sorted)

Sorted Sets. The CSet<T,C> class is an adaptor class using any sorted associative container class C as template parameter to define the set or multiset abstract data type. Typically, but not necessarily, the template parameter C will be a sorted associative container template class C<value_type,predicate_type>. Classic choices for C include sorted list, binary search tree, AVL tres, and red-black tree.

Note that either normal sets (unimodal, type "U" semantics) or multi-sets (multimodal, type "M" semantics) can be made with CSet<T,C> by choosing unimodal or multimodal versions of the container C.

There is an associated iterator type CSet<T,C>::Iterator that is a bidirectional iterator to const.

Sorted Maps. There are two versions of map adaptor classes, unimodal and multimodal. These are distinguished by insert semantics and also the presence of the associative array bracket operator in CMap. Because this operator is essentially unimodal, it does not appear in the public interface of CMultiMap.

Both CMap and CMultiMap have an associated iterator type that is a bidirectional iterator.

CMap and CMultiMap are adaptors creating sorted associative containers implementing "map" and "multimap" respectively. There is an implicit assumption that the underlying container C is a sorted associative container. In practice C may be a sorted list, a binary search tree, or a self-balancing tree such as red-black tree.

CMap has unimodal insert semantics and an associative array bracket operator. CMultiMap has multimodal insert semantics and does not have a bracket operator.

Thus we have the following classes defined:

Operations on Sorted Sets and Maps. CSet, CMap, and CMultiMap are sorted associative containers and have the following in common: They have three search operations, LowerBound(), UpperBound(), and Includes(), and they have the property that a standard traversal encounters elements in sorted order. (Recall that a standard traversal of a container s is defined by the loop

where the loop body is variably defined to suit client needs.) Typical search times for these containers is the same as that of the adaptee C. The most efficient containers result in search times of O(log size).

Client usage of sorted sets and maps. These adaptors require an underlying sorted container and an element type. The sorted container itself will also require a predicate class used to maintain the sorted order on elements. Typical declarations are as follows:

CSet < char , TUSList < char > >                                  s1; 
CSet < char , TMSList < char , TGreaterThan < char > > >          s2;
CMap < String , int , TUSList < TAssociation < String , int > > > s3; 

s1 is a set of char based on sorted list with default order, s2 is a multiset of char based on sorted list with reverse order, and s3 is a map of <String,int> based on sorted list with default order. The situation can get more complicated, and it is usually more readable (and more efficient - symbol length is reduced considerably in the symbol table) to use type definitions, as in:

typedef String                                key_type;
typedef int                                   data_type;
typedef TAssociation < key_type , data_type > pair_type;
typedef special_class                         predicate_type;
typedef TMBST < pair_type , predicate_type >  container_type;
CMultiMap < key_type , container_type >       M1;

M1 is a multimap of <String,int> based on binary search tree and the special predicate class.

Set and Map Tools (Unsorted)

An alternative to the sorted set and map types is provided by the hashing technique, used to define CHashTable in another chapter. Four adaptor classes may be defined, along with associated iterator classes:

The hash containers are unsorted containers. They each have two search operations Includes(k) and Includes (k,v). A standard traversal of the container encounters elements in hashed, or pseudo-random, order. Typical search times for these hashed containers is O(1), assuming that the defining parameters have been chosen appropriately. These can be made into proper types, with default constructors, copy constructors, etc, although it may not be worth the effort due to the lack of interest. Hashed containers have a niche, but it is not a general-purpose niche. Adding parameter self-adjustments and other niceties just adds to the overhead to what is for most client needs replaceable with the more human-friendly sorted varieties at only modest increase in client cost-of-use.

Creating the multimodal versions of hashed containers requires some additional design consideration. When two equal pairs are inserted into a hashed structure, they will end up in the same bucket because equal keys will result in equal hash values. To keep tarck of these equally keyed pairs, it is sensible to use a sorted bucket structure (or a more complicated "list of lists" bucket structure) so that equal pairs end up contiguous in a table traversal. Then the operations LowerBound() and UpperBound() make sense, even though the global table is unsorted, because the pairs with equal keys will be in the same bucket which is sorted. These could be implemented in terms of the bucket operations of the same name.

The CSet<> Adaptor Class


aContainer protocols used. The following public interface operations are assumed for the container C adapted by CSet:

where c is an object of type C. Here is the complete CSet adaptor, including implementation.

template < typename T , class C >
class CSet
{
  friend class CSetIterator<T,C>;

public:

  typedef T                 value_type;
  typedef C                 container_type;
  typedef CSetIterator<T,C> Iterator;

  // element operations - modality determined by C
  Iterator       Insert  (const value_type& t);
  int            Insert  (Iterator& i, const value_type& t);
  size_t         Remove  (const value_type& t);
  int            Remove  (Iterator& i);
  void           Clear   ();

  // locator operations
  Iterator  LowerBound   (const value_type& t) const;
  Iterator  UpperBound   (const value_type& t) const;
  Iterator  Includes     (const value_type& t) const;
  Iterator  Begin        () const;
  Iterator  End          () const;
  Iterator  rBegin       () const;
  Iterator  rEnd         () const;

  // size operations
  int            Empty   () const;
  size_t         Size    () const;

  // proper type
  CSet  ();
  CSet  (const CSet<T,C>& s);
  ~CSet ();
  CSet<T,C>& operator  = (const CSet<T,C>& s);

protected:
  C c;
} ; // class CSet

template < typename T , class C >
CSet<T,C>& CSet<T,C>::operator  = (const CSet<T,C>& s)  // assignment
{
  if (this != &s)
    c = s.c;
  return *this;
}

template < typename T , class C >
CSet<T,C>::Iterator       CSet<T,C>::Insert    (const value_type& t)
{
  CSet<T,C>::Iterator i;
  i.i = c.Insert(t);
  return i;
}

template < typename T , class C >
int CSet<T,C>::Insert (CSet<T,C>::Iterator& i, const value_type& t)
{
  return c.Insert(i.i, t);
}

template < typename T , class C >
size_t   CSet<T,C>::Remove    (const value_type& t)
{
  return c.Remove(t);
}

template < typename T , class C >
int   CSet<T,C>::Remove    (CSet<T,C>::Iterator& i)
{
  return c.Remove(i.i);
}

template < typename T , class C >
void           CSet<T,C>::Clear     ()
{
  c.Clear();
}

template < typename T , class C >
CSet<T,C>::Iterator CSet<T,C>::LowerBound (const value_type& t) const
{
  CSet<T,C>::Iterator  i;
  i.i = c.LowerBound(t);
  return i;
}

template < typename T , class C >
CSet<T,C>::Iterator CSet<T,C>::Includes (const value_type& t) const
{
  CSet<T,C>::Iterator  i;
  i.i = c.Includes(t);
  return i;
}

template < typename T , class C >
CSet<T,C>::Iterator  CSet<T,C>::UpperBound (const value_type& t) const
{
  CSet<T,C>::Iterator  i;
  i.i = c.UpperBound(t);
  return i;
}

template < typename T , class C >
CSet<T,C>::Iterator  CSet<T,C>::Begin  () const
{
  CSet<T,C>::Iterator  i;
  i.i = c.Begin();
  return i;
}

template < typename T , class C >
CSet<T,C>::Iterator  CSet<T,C>::End  () const
{
  CSet<T,C>::Iterator  i;
  i.i = c.End();
  return i;
}

template < typename T , class C >
CSet<T,C>::Iterator  CSet<T,C>::rBegin  () const
{
  CSet<T,C>::Iterator  i;
  i.i = c.rBegin();
  return i;
}

template < typename T , class C >
CSet<T,C>::Iterator  CSet<T,C>::rEnd  () const
{
  CSet<T,C>::Iterator  i;
  i.i = c.rEnd();
  return i;
}

template < typename T , class C >
int            CSet<T,C>::Empty     () const
{
  return c.Empty();
}

template < typename T , class C >
size_t   CSet<T,C>::Size      () const
{
  return c.Size();
}

template < typename T , class C >
CSet<T,C>::CSet  () : c()
{}

template < typename T , class C >
CSet<T,C>::CSet  (const CSet<T,C>& s) : c()
{
  c = s.c;
}

template < typename T , class C >
CSet<T,C>::~CSet ()
{
  Clear();
}


Global Operators on CSet

The ADT set plays a central role in associative structures. Operations on sets have also been useful for at least two centuries: set union, set intersection, set difference, and set containment. In a previous chapter we already introduced generic algorithms that perform these operations on sorted ranges. These algorithms can be used to define the operations on set objects as well, as follows.

template <typename T , class C>
int operator <= (const CSet<T,C>& s1, const CSet<T,C>& s2)
// containment
{
  return g_subset_of(s1.Begin(), s1.End(), s2.Begin(), s2.End());
}

template <typename T , class C>
int operator <  (const CSet<T,C>& s1, const CSet<T,C>& s2)
// proper containment
{
  if (s1.Size() < s2.Size() && s1 <= s2)
    return 1;
  return 0;
}

template <typename T , class C>
int operator >= (const CSet<T,C>& s1, const CSet<T,C>& s2)
// containment
{
  return s2 <= s1;
}

template <typename T , class C>
int operator >  (const CSet<T,C>& s1, const CSet<T,C>& s2)
// proper containment
{
  return s2 < s1;
}

template <typename T , class C>
CSet<T,C>  operator + (const CSet<T,C>& s1, const CSet<T,C> & s2)
// union
{ 
  CSet<T,C> s;
  InsertIterator < CSet < T , C > > i(s);
  g_set_union (s1.Begin(), s1.End(), s2.Begin(), s2.End(), i);
  return s;
}

template <typename T , class C>
CSet<T,C>  operator - (const CSet<T,C>& s1, const CSet<T,C> & s2)
// difference
{
  CSet<T,C> s;
  InsertIterator < CSet < T , C > > i(s);
  g_set_difference (s1.Begin(), s1.End(), s2.Begin(), s2.End(), i);
  return s;
}

template <typename T , class C>
CSet<T,C>  operator * (const CSet<T,C>& s1, const CSet<T,C> & s2)
// intersection
{
  CSet<T,C> s;
  InsertIterator < CSet < T , C > > i(s);
  g_set_intersection (s1.Begin(), s1.End(), s2.Begin(), s2.End(), i);
  return s;
}

template < typename T , class C >
int operator == (const CSet<T,C>& s1, const CSet<T,C>& s2)
{
  if (s1.Size() != s2.Size())
    return 0;
  CSet<T,C>::Iterator i1(s1), i2(s2);
  while (i1 != s1.End())
    if (*(i1++) != *(i2++))
      return 0;
  return 1;
}

template <typename T , class C>
int operator != (const CSet<T,C>& s1, const CSet<T,C>& s2)
{
  return !(s1 == s2);
}

template <typename T , class C>
std::ostream& operator << (std::ostream& os, const CSet<T,C>& s)
{
  CSet<T,C>::Iterator i;
  for (i = s.Begin(); i != s.End(); ++i) os << *i;
  return os;
}


The CSet<T,C>::Iterator Adaptor

The CSet Iterator type will be a standard bidirectional iterator, which defines its public interface.

aContainer::Iterator protocols used. The following public interface operations are assumed for the iterator C::Iterator adapted by CSetIterator:

Note that the above is precisely the interface defining a "const" bidirectional iterator. Note the iterator return type is const, which means among other things that *i is not allowed on the left side of an assignment. Allowing such an assignment would make it easy for a client program to inadvertantly destroy the internal structure of the set. This safety feature creates inconveniences when the client needs to replace an element with another "equal" element. If the client needs to replace the element at i with x in set s:

  1. Under unimodal semantics: s.Insert(i,x) will do the trick
  2. 
    
  3. Under multimodal semantics, first remove the old element and then insert: s.Remove(i) leaves i at the next element, then s.Insert(i,x) puts x at i; in a red-black tree implementation of set, it is asymptotically equivalent cost and simpler to use the associative insert s.Insert(x).

Here is a complete definition and implemention of the CSetIterator class:

template <typename T , class C>
class CSetIterator
{
public:
  typedef T                 value_type;
  typedef C                 container_type;
  typedef CSetIterator<T,C> Iterator;

  // to adapt operations of C::Iterator
  friend     CSet<T,C>;

  // constructors
  CSetIterator ();
  CSetIterator (const CSet<T,C>& s);
  CSetIterator (const CSetIterator<T,C>& itr);

  // initializers
  void Initialize  (const CSet<T,C>& s);
  void rInitialize (const CSet<T,C>& s);

  // informationals
  const value_type&  Retrieve  () const;  // Return const ref to current value
  int                Valid     () const;  // cursor is valid 

  // operators
  int                operator == (const CSetIterator<T,C>& itr2) const;
  int                operator != (const CSetIterator<T,C>& itr2) const;
  value_type&        operator *  () const; // Return reference to current value
  CSetIterator<T,C>& operator =  (const CSetIterator <T,C> & itr);
  CSetIterator<T,C>& operator ++ ();    // prefix
  CSetIterator<T,C>  operator ++ (int); // postfix
  CSetIterator<T,C>& operator -- ();    // prefix
  CSetIterator<T,C>  operator -- (int); // postfix

protected:
  typename C::Iterator i;
} ; // class CSetIterator

template <typename T , class C>
CSetIterator<T,C>::CSetIterator () : i()
{}

template <typename T , class C>
CSetIterator<T,C>::CSetIterator (const CSet<T,C>& s) : i()
{
  i = s.c.Begin();
}

template <typename T , class C>
CSetIterator<T,C>::CSetIterator (const CSetIterator<T,C>& itr) : i(itr.i)
{}

template <typename T , class C>
void CSetIterator<T,C>::Initialize (const CSet<T,C>& s)
{
  i.Initialize(s.c);
}

template <typename T , class C>
void CSetIterator<T,C>::rInitialize (const CSet<T,C>& s)
{
  i.rInitialize(s.c);
}

template <typename T , class C>
const CSetIterator<T,C>::value_type&  CSetIterator<T,C>::Retrieve  () const 
{
  return i.Retrieve();
}

template <typename T , class C>
int CSetIterator<T,C>::Valid () const // cursor is valid 
{
  return i.Valid();
}

template <typename T , class C>
int CSetIterator<T,C>::operator == (const CSetIterator<T,C>& itr2) const
{
  return i == itr2.i;
}

template <typename T , class C>
int CSetIterator<T,C>::operator != (const CSetIterator<T,C>& itr2) const
{
  return i != itr2.i;
}

template <typename T , class C>
const CSetIterator<T,C>::value_type& CSetIterator<T,C>::operator *  () const
{
  return *i;
}

template <typename T , class C>
CSetIterator<T,C>& CSetIterator<T,C>::operator =  (const CSetIterator <T,C> & itr)
{
  i = itr.i;
  return *this;
}

template <typename T , class C>
CSetIterator<T,C>& CSetIterator<T,C>::operator ++ ()    // prefix
{
  ++i;
  return *this;
}

template <typename T , class C>
CSetIterator<T,C> CSetIterator<T,C>::operator ++ (int) // postfix
{
  CSetIterator<T,C> itr = *this;
  CSetIterator<T,C>::operator ++();
  return itr;
}

template <typename T , class C>
CSetIterator<T,C>& CSetIterator<T,C>::operator -- ()    // prefix
{
  --i;
  return *this;
}

template <typename T , class C>
CSetIterator<T,C> CSetIterator<T,C>::operator -- (int) // postfix
{
  CSetIterator<T,C> itr = *this;
  CSetIterator<T,C>::operator --();
  return itr;
}


The CMap Adaptor

template <typename K, typename V, class C>
class CMap
{
  friend class CMapIterator <K,V,C>;

public:
  typedef K                        key_type;
  typedef C                        container_type;
  typedef typename C::value_type   value_type;
  typedef CMapIterator<K,V,C>      Iterator;

  // associative array operator - unimodal
  V&             operator [] (const K& k);
  
  Iterator       Insert      (const K& k, const V& v);
  int            Insert      (Iterator& I, const K& k, const V& v);
  size_t         Remove      (const K& k);
  int            Remove      (Iterator& i);
  void           Clear       ();

  // size operations
  size_t         Size        () const;
  int            Empty       () const;

  // locator operations
  Iterator       LowerBound  (const K& k)   const;
  Iterator       UpperBound  (const K& k)   const;
  Iterator       Includes    (const K& k)   const;
  Iterator       Begin       () const;
  Iterator       End         () const;
  Iterator       rBegin      () const;
  Iterator       rEnd        () const;

  // proper type
  CMap  ();
  CMap  (const CMap<K,V,C>&);
  ~CMap ();
  CMap&  operator =  (const CMap&);

  protected:
    C c;

} ;

template <typename K, typename V, class C>
class CMapIterator
{
  friend class CMap <K,V,C>;
  public:
    typedef K                        key_type;
    typedef C                        container_type;
    typedef typename C::value_type   value_type;
    typedef CMapIterator<K,V,C>      Iterator;

    CMapIterator ();
    CMapIterator (const CMapIterator<K,V,C>& itr);
    int Valid        () const;
    CMapIterator <K,V,C>& operator =  (const CMapIterator <K,V,C>& itr);
    CMapIterator <K,V,C>& operator ++ ();
    CMapIterator <K,V,C>  operator ++ (int); // postfix
    CMapIterator <K,V,C>& operator -- ();
    CMapIterator <K,V,C>  operator -- (int); // postfix
    TAssociation <K,V>&       operator *  () const;
    int operator == (const CMapIterator<K,V,C>& itr2) const;
    int operator != (const CMapIterator<K,V,C>& itr2) const;

  protected:
    typename C::Iterator i;
} ;

//-----------------------------------
//     CMap <K, V, C> implementations
//-----------------------------------

template <typename K, typename V, class C>
V& CMap<K,V,C>::operator [] (const K& k)
{
  TAssociation<K,V> p;
  p.key = k;
  typename C::Iterator i = c.LowerBound(p);
  if (!i.Valid() || p != *i)
    c.Insert(i,p);
  return (*i).value;
}

template <typename K, typename V, class C>
int CMap<K,V,C>::Insert (Iterator& i, const K& k, const V& v)
{
  TAssociation<K,V> p(k,v);
  return c.Insert(i.i,p);
}

template <typename K, typename V, class C>
CMapIterator<K,V,C> CMap<K,V,C>::Insert (const K& k, const V& v)
{
  TAssociation<K,V> p(k,v);
  Iterator i;
  i.i = c.LowerBound(p);
  if (!i.Valid() || p != *i)  // not found
  {
    if (c.Insert(i.i, p))
      return i;
    else
      return End();
  }
  (*i).value = v;
  return i;
}

template <typename K, typename V, class C>
size_t CMap<K,V,C>::Remove (const K& k)
{
  TAssociation <K,V> p;
  p.key = k;
  return c.Remove(p);
}

template <typename K, typename V, class C>
int CMap<K,V,C>::Remove (Iterator& i)
{
  return c.Remove(i.i);
}

template <typename K, typename V, class C>
CMapIterator<K,V,C> CMap<K,V,C>::Includes (const K& k) const
{
  CMapIterator<K,V,C> i;
  TAssociation <K,V> p;
  p.key = k;
  i.i = c.LowerBound(p);
  if (i.Valid())
    if (k == (*i).key)
      return i;
  return End();
}

template <typename K, typename V, class C>
CMap <K,V,C>::CMap () : c()
{}

template <typename K, typename V, class C>
CMap <K,V,C>::~CMap ()
{
  Clear();
}

template <typename K, typename V, class C>
void CMap<K,V,C>::Clear ()
{
  c.Clear();
}

template <typename K, typename V, class C>
CMapIterator<K,V,C> CMap<K,V,C>::Begin () const
{
  CMapIterator<K,V,C> i;
  i.i = c.Begin();
  return i;
}

template <typename K, typename V, class C>
CMapIterator<K,V,C> CMap<K,V,C>::End () const
{
  CMapIterator<K,V,C> i;
  i.i = c.End();
  return i;
}

template <typename K, typename V, class C>
unsigned long CMap<K,V,C>::Size () const
{
  return c.Size();
}

template <typename K, typename V, class C>
int CMap<K,V,C>::Empty () const
{
  return c.Empty();
}

//-------------------------------------------
//     CMapIterator <K, V, C> implementations
//-------------------------------------------

template <typename K, typename V, class C>
CMapIterator<K,V,C>::CMapIterator () : i()
{}

template <typename K, typename V, class C>
CMapIterator<K,V,C>::CMapIterator (const CMapIterator<K,V,C>& itr)
  : i(itr.i)
{}

template <typename K, typename V, class C>
CMapIterator <K,V,C>&
CMapIterator<K,V,C>::operator = (const CMapIterator <K,V,C>& itr)
{
  if (this != &itr)
    i = itr.i;
  return *this;
}

template <typename K, typename V, class C>
CMapIterator <K,V,C>& CMapIterator<K,V,C>::operator ++ ()
{
  ++i;
  return *this;
}

template <typename K, typename V, class C>
CMapIterator <K,V,C> CMapIterator<K,V,C>::operator ++ (int)
{
  CMapIterator <K,V,C> itr = *this;
  operator ++();
  return itr;
}

template <typename K, typename V, class C>
TAssociation<K,V>& CMapIterator<K,V,C>::operator * () const
{
  return *i;
}

template <typename K, typename V, class C>
int CMapIterator<K,V,C>
::operator == (const CMapIterator<K,V,C>& itr2) const
{
  return i == itr2.i;
}

template <typename K, typename V, class C>
int CMapIterator<K,V,C>
::operator != (const CMapIterator<K,V,C>& itr2) const
{
  return i != itr2.i;
}

template <typename K, typename V, class C>
int CMapIterator<K,V,C>::Valid () const
{
  return i.Valid();
}


The CMultiMap Adaptor

template <typename K, typename V, class C>
class CMultiMap
{
  friend class CMapIterator <K,V,C>;

public:
  typedef K                        key_type;
  typedef C                        container_type;
  typedef typename C::value_type   value_type;
  typedef CMapIterator<K,V,C>      Iterator;

  Iterator       Insert      (const K& k, const V& v);
  int            Insert      (Iterator& i, const K& k, const V& v);
  size_t         Remove      (const K& k);
  int            Remove      (Iterator& i);
  void           Clear       ();

  // size operations
  unsigned long  Size        () const;
  int            Empty       () const;

  // locator operations
  Iterator       LowerBound  (const K& k)   const;
  Iterator       UpperBound  (const K& k)   const;
  Iterator       Includes    (const K& k)   const;
  Iterator       Begin       () const;
  Iterator       End         () const;
  Iterator       rBegin      () const;
  Iterator       rEnd        () const;

  // proper type
  CMultiMap  ();
  CMultiMap  (const CMultiMap<K,V,C>&);
  ~CMultiMap ();
  CMultiMap&  operator =  (const CMultiMap&);

  protected:
    C c;

} ;

template <typename K, typename V, class C>
CMultiMap <K,V,C>::CMultiMap () : c()
{}

template <typename K, typename V, class C>
CMultiMap <K,V,C>::~CMultiMap ()
{
  Clear();
}

template <typename K, typename V, class C>
CMultiMap<K,V,C>::Iterator CMultiMap<K,V,C>::Includes (const K& k) const
{
  Iterator i;
  TAssociation <K,V> p;
  p.key = k;
  i.i = c.Includes(p);
  return i;
}

template <typename K, typename V, class C>
CMapIterator<K,V,C> CMultiMap<K,V,C>::Insert (const K& k, const V& v)
{
  TAssociation<K,V> p(k,v);
  Iterator i;
  i.i = c.Insert(p);
  return i;
}

template <typename K, typename V, class C>
int CMultiMap<K,V,C>::Insert (Iterator& i, const K& k, const V& v)
{
  TAssociation<K,V> p(k,v);
  return c.Insert(i.i,p);
}

template <typename K, typename V, class C>
int CMultiMap<K,V,C>::Remove (const K& k)
{
  TAssociation <K,V> p;
  p.key = k;
  return c.Remove (p);
}

template <typename K, typename V, class C>
void CMultiMap<K,V,C>::Clear ()
{
  c.Clear();
}

template <typename K, typename V, class C>
CMultiMapIterator<K,V,C> CMultiMap<K,V,C>::Begin () const
{
  CMultiMapIterator<K,V,C> i;
  i.i = c.Begin();
  return i;
}

template <typename K, typename V, class C>
CMultiMapIterator<K,V,C> CMultiMap<K,V,C>::End () const
{
  CMultiMapIterator<K,V,C> i;
  i.i = c.End();
  return i;
}

template <typename K, typename V, class C>
unsigned long CMultiMap<K,V,C>::Size () const
{
  return c.Size();
}

template <typename K, typename V, class C>
int CMultiMap<K,V,C>::Empty () const
{
  return c.Empty();
}


The CHashSet Adaptor

template <typename T, class H, class C>
class CHashSet
{
  friend class CHashSetIterator<T,H,C>

public:
  typedef T                     value_type;
  typedef H                     hash_type;
  typedef C                     bucket_type;
  typedef CHashSetIterator<T,C> Iterator;

  // element operations
  Iterator       Insert     (const value_type& t);
  int            Insert     (Iterator& i, const value_type& t);
  size_t         Remove     (const value_type& t);
  int            Remove     (Iterator& i);
  void           Clear      ();

  // locator operations
  Iterator       Includes   (const value_type& t) const;
  Iterator       Begin      () const;
  Iterator       End        () const;
  Iterator       rBegin     () const;
  Iterator       rEnd       () const;

  // size operations
  int            Empty      () const;
  size_t         Size       () const;


  CHashSet  (size_t number_of_buckets);
  ~ChashSet ();

protected:
  size_t         numBuckets;
  TVector < C >  bucketVector;
  H              hashObject;

private:
  CHashSet             (const CHashSet<T,H,C>&);
  CHashSet& operator = (const CHashSet&);
} ;

The iterator class is defined as a typical bidirectional iterator.

template <typename T, class H, class C>
class CHashSetIterator
{
  friend class CHashSet <T,H,C>;
public:
  // bidirectional iterator public interface

protected:
  const CHashSet <T,H,C> * setPtr;
  typename C::Iterator     bucketItr;
  size_t                   bucketNum;
} ;

Refer to the Tables chapter for further discussion and implementation details regarding hashed containers.