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:
a --> x b --> y c --> y d --> z
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.
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.
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:
struct stockitem { unsigned long barcode; String name; unsigned int low_amount; unsigned int reorder_amount; // other info stockitem (unsigned long bc, String nm) : barcode(bc), name(nm){} }; int operator < (stockitem s1, stockitem s2) { return s1.name < s2.name; } int main client_program { Set < stockitem > inventory; Set < stockitem > :: Iterator inventitr; // code manipulating inventory, for example: stockitem item; item.barcode = 0x1234ABCD; item.name = "flower_pot"; inventitr = inventory.Insert(*itemptr); (*inventitr).current_amount = 500; // ... } void print_inventory (std::ostream& os, const Set < stockitem >& inventory) { Set < stockitem > :: Iterator i; for (i = inventory.Begin(); i != inventory.End(); ++i) os << *i; // need to overload operator <<() for type stockitem; }
Customer Accounts. The client program needs to store customer identity records for use in accessing data base of purchase transactions.
class customer_id { const String ssn; String secret_phrase; unsigned char flags; public: const String account_number; String last_name; String first_name; char middle_initial; customer_id (String soc_sec_no, String accnt_no): ssn(soc_sec_no), account_number(accnt_no) {} // other stuff }; int main() { Set < customer_id > customers; // etc }
Exceptional Instances. Client program needs to find all mispelled words in a text.
Set < String > dictionary; // code putting words in dictionary from file Set < String > wordset; // code reading words from file into wordset; Set < String > unknown = wordset - dictionary; // unknown contains the words in wordset that are not in the dictionary for (Set < String > :: Iterator i = unknown.Begin(); i != unknown.End(); ++i) std::cout << *i << '\n'; // outputs possible mispelled words to screen
Instances in Common. Client program needs to find all records common to two different applications.
Set < widget > s1, s2; // code putting elements into s1 // code putting elements into s2 Set < widget > s = s1 * s2; // s contains the widgets that are in both s1 and s2
Password Server. Provide basic password services to users and system administrators.
struct user { String username; unsigned long signature; user (String uid, unsigned long sgn) : username(uid), signature(sgn){} }; class PWServer { Set < user > users; String outfilename; public: PWServer (const String& infile, const String& outfile, unsigned int maxusers); ~PWServer (); int CheckPW (const String& uid, const String& upw) // Basic authentication service int ChangePW (const String& uid, const String& upw, const String& npw) // User service int CreateUser (const String& uid, const String& upw) // Sysadmin service int DeleteUser (const String& uid) // Sysadmin service }; int PWServer::CheckPW (const String& uid, const String& upw) { String uidupw (uid + upw); unsigned long sgn = secure_hash_function (uidupw); user u (uid,sgn); Set < user > :: Iterator i = users.Includes(u); if (i.Valid()) if ((*i).username == u.username) if ((*i).signature == u.signature) return 1; return 0; }
Any Map Client. A map is essentially a set of pairs.
typedef Set < pair < K , V > > Map < K , V >;
The Password Server could be built with a map instead of a set:
typedef String username; typedef unsigned long signature; class PWServer { Map < username , signature > users; String outfilename; public: PWServer (const String& infile, const String& outfile, unsigned int maxusers); ~PWServer (); int CheckPW (const String& uid, const String& upw) // Basic authentication service int ChangePW (const String& uid, const String& upw, const String& npw) // User service int CreateUser (const String& uid, const String& upw) // Sysadmin service int DeleteUser (const String& uid) // Sysadmin service }; int PWServer::CheckPW (const String& uid, const String& upw) { unsigned long sgn = secure_hash_function (uid + upw); Map < username , signature > :: Iterator i = users.Includes(uid); if (i.Valid()) if ((*i).key == uid) if ((*i).value == sgn) return 1; return 0; }
An internet router could use a map object
Map < ipNumber , ipNumber > routemap;
to look up the routing for a given packet destination. A dictionary could be declared as
Map < String , String > dictionary;
We could store the results of a word count analysis of a text using a map declared as
Map < String , unsigned int > wordcounter;
A key word index, or concordance, is a map declared as
Map < String , list < unsigned int > > concordance;
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:
Map < String , list < unsigned int > > :: Iterator i; list < unsigned int > :: Iterator j; for (i = concordance.Begin(); i != concordance.End(); ++i) { cout << (*i).key << ": "; // output the key word followed by ": " j = (*i).value.Begin(); cout << *j; // output the first page number ++j; while (j != (*i).value.End()) // output all subsequent page numbers preceded by ", " { cout << ", " << *j; ++j; } cout << '\n'; // end the line in the index }
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:
CSet <T, C> // unimodal or multimodal, depending on C CSet <T, C> :: Iterator // bidirectional iterator CMap <K, V, C> // unimodal; has AA bracket operator CMap <K, V, C> :: Iterator // bidirectional iterator CMultiMap <K, V, C> // multimodal; no bracket operator CMultiMap <K, V, C> :: Iterator // bidirectional iterator
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
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.
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:
CHashSet <T, H, C> // unimodal CHashSet <T, H, C> :: Iterator // forward iterator CHashMultiSet <K, V, H, C> // multimodal CHashMultiSet <K, V, H, C> :: Iterator // forward iterator CHashMap <T, H, C> // unimodal; has AA bracket operator CHashMap <T, H, C> :: Iterator // forward iterator CHashMultiMap <K, V, H, C> // multimodal; no bracket operator CHashMultiMap <K, V, H, C> :: Iterator // forward iterator
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.
aContainer protocols used. The following public interface operations are assumed for the container C adapted by CSet:
Iterator Insert (const value_type&); int Insert (Iterator, const value_type&); size_t Remove (const value_type&); void Clear (); Iterator LowerBound (const value_type&) const; Iterator UpperBound (const value_type&) const; Iterator Includes (const value_type&) const; Iterator Begin () const; Iterator End () const; Iterator rBegin () const; Iterator rEnd () const; int Empty () const; size_t Size () const; C& operator = (const C&); // assignment c(); // default constructor ~c(); // destructor // c(const c&) // copy constructor is not used.
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(); }
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 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:
Iterator (); Iterator (const C& s); Iterator (const Iterator& itr); const value_type& Retrieve () const; int Valid () const; value_type& operator * () const; Iterator& operator = (const Iterator& itr); Iterator& operator ++ (); Iterator& operator -- (); int operator == (const Iterator& itr2) const; int operator != (const Iterator& itr2) const;
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:
- 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; }
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(); }
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(); }
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.