ASSOCIATIVE CONTAINERS ■ 769 Sequences store objects in linear order. Searching for a given object will thus require a linear runtime. If you have only a few objects to deal with, this will not cause any signifi- cant delay. However, it is a major disadvantage in large collections of objects. ᮀ Representing Sets and Maps Associative containers with different classes that represent sets and maps allow you opti- mize runtimes. They manage objects in so-called heaps, that is in trees with a minimum height. Operations are performed by sortable keys. One of the characteristics of a heap is that the object with the smallest key is always stored at the top of the heap. Insertion, deletion, and search operations in sets and maps can be performed with log- arithmic runtimes. That is, the response is proportional to log(n), where n is the num- ber of objects in a container. Since a logarithmic function grows very slowly, response will be phenomenally quick. ᮀ Unique and Ambiguous Keys In a set, each object contains a key. This is why we refer to embedded keys. The relation- ship between the objects is defined by reference to this key. Besides sets, which have unique keys, you can also define multisets, where multiple objects can have the same key. Maps are used to manage key/object pairs. In other words, the key is not embedded in the object but stored separately from it. The type of the key is Key, and T is the object type. The relationship between the objects is again defined by the keys. Besides maps, which contain only unique keys, you can also define multimaps, where several objects can exist for a single key. ᮀ Associative Container Classes The opposite page shows various classes used to represent sets, multisets, maps, and mul- timaps. The template parameter Compare is a comparator class and Allocator is an allocator class. Both parameters have default values, which we already saw in the context of sequences. The methods begin() and end() are defined for access to the positions in all asso- ciative container classes. They return the position of the first element or the position after the last element. 770 ■ CHAPTER 33 CONTAINERS // set_t.cpp: Tests sets and multisets // #include <set> #include <cstdlib> #include <ctime> #include <iostream> using namespace std; typedef set<int> IntSet; // Define type and typedef IntSet::iterator SetIter; // iterator type typedef multiset<int> IntMultiSet; // Multiset and typedef IntMultiSet::iterator MultiSetIter; // iterator int main() { IntSet lotto; // Create a set. SetIter pos; // Bidirectional iterator srand((unsigned) time(NULL)); while( lotto.size() < 6) // Insert lotto.insert( rand()%50 ); cout << "These are your lotto numbers: " << endl; for( pos = lotto.begin(); pos != lotto.end(); pos++) cout << *pos << " "; cout << endl << endl; IntMultiSet ms; // Create a multiset. MultiSetIter mpos; // Bidirectional iterator for( int i=0; i < 10; i++) // Insert ms.insert( rand()%10 ); cout << "And now 10 random numbers " << " between 0 and 10: " << endl; for( mpos = ms.begin(); mpos != ms.end(); mpos++) cout << *mpos << " "; cout << endl; return 0; } ■ SETS AND MULTISETS Sample sets and multisets SETS AND MULTISETS ■ 771 Sets and multisets are used for efficient management of object collections with sortable keys, that is, insertion, deletion, and search operations can be performed with logarith- mic runtimes. Keys are always parts of objects, thus, keys are data members whose rela- tionships to one another must be defined in the corresponding class. A lesser than relationship is normally defined for this purpose, that is, the operator < will be over- loaded for the class. ᮀ Declaring Sets and Multisets The container classes set and multiset have two constructors each for creating con- tainers. You can use the default constructor to create sets and multisets with a length of 0. The second constructor inserts objects from a range of iterators into the new set or multiset. Example: typedef set<Account> AccountSet; AccountSet mySet(first, last); Here, [first, last) is a range of iterators in an existing container whose objects are of Account type. The copy constructor is also defined, and this allows you to use an existing container of the same type to initialize a new container. ᮀ Inserting and Deleting The insert() method is available for insertions. This allows for insertion of individual objects or multiple objects from a given range of iterators. Example: mySet.insert(Account(1234,"May, Tom",100)); In contrast to multisets, a new object is only inserted in a set if it does not already exist in the container. You can use the erase() method to delete objects. To do so, you can either specify the object itself or its position in the container. Example: mySet.erase(mySet.begin()); This deletes the first element in the AccountSet set. You can delete all objects in a container with the following statement: Example: mySet.erase( mySet.begin(), mySet.end() ); For erasing a whole container you can also use the clear() method. Calling empty() will tell you whether the container is empty. The size() method returns the number of objects in the container. 772 ■ CHAPTER 33 CONTAINERS // mulmap_t.cpp: Testing multimaps // #include <map> #include <string> #include <iostream> using namespace std; typedef multimap<int, string> MULTI_MAP; typedef MULTI_MAP::iterator ITERATOR; int main() { MULTI_MAP m; // Create a multimap. ITERATOR pos; // Iterator. // To insert: m.insert(pair<int, string>(7, "Bob") ); m.insert(pair<int, string>(3, "Sarah")); m.insert(pair<int, string>(1, "Diana")); m.insert(pair<int, string>(1, "Lisa")); cout << "This is the multimap: " << endl; for(pos = m.begin(); pos!= m.end(); pos++) cout << pos->first << " " << pos->second << endl; cout << endl; pos = m.find(3); // Search for the pair // with the given key 3 if( pos != m.end()) // and output the pair cout << pos->first << " " << pos->second << endl; int key = 1; // Determine the quantity of // pairs with key value 1: cout << "There are " << m.count(key) << " pairs with key " << key << endl; return 0; } ■ MAPS AND MULTIMAPS Using multimaps MAPS AND MULTIMAPS ■ 773 ᮀ Representing Pairs of Keys/Objects Maps and multimaps store pairs of sorted keys and objects. The key is used again to iden- tify the object but is stored separately from the object. The comparison criterion is applied to the keys. The C++ Standard Library contains the class template pair<const Key,T> with two public data members first and second, a default constructor, and a copy con- structor to represent key/object pairs. The first template parameter, Key, is the key type and the second is the object type T. The data member first is used to store the keys, and second stores the associated object. Given that pos is the position of an object in a map or multimap, you can reference the key with pos->first, and the associated object itself with pos->second. ᮀ Using Maps and Multimaps The container classes map and multimap contain constructors with the same function- ality as the set and multiset classes. Thus, you can create a container with a length of 0, or use the objects in an existing container to initialize a new container. The copy constructor is also defined. The methods insert() for insertion, and erase() and clear() for deletion have the same interfaces as in the container classes set and multiset. The methods size(), which you can use to discover the length of the container, and empty(), which ascertains whether the container is empty, are also defined. The find() method is used to look up key/object pairs and expects a key as an argu- ment in the map and multimap classes. Its return value is the associated position in the container. In the case of multimaps where several objects can have the same key, it returns the first position with that key. If the search fails, the value end() is returned as a pseudo position. You can use the count() method to discover the number of key/object pairs with a given key in the container. The method expects a key as an argument. The method returns 0 or 1 for maps, depending on whether a pair exists or not. In the case of multi- maps, the return value can be greater than 1, of course. 774 ■ CHAPTER 33 CONTAINERS // bitmap.h : Defines the template Bitmap<N> // representing raster images. // #ifndef _BITMAP_ #define _BITMAP_ #include <bitset> #include <stdexcept> using namespace std; template <int N> class Bitmap : public bitset<N> { private: int lines, cols; // Number of rows and columns int ax, ay; // Current cursor position int ai; // Current index in the bitset public: Bitmap(int l, int c); void move(int x, int y); void draw(int x, int y); }; template <int N> Bitmap<N>::Bitmap(int l, int c) { if (l*c <= N) { reset(); // Set all bits to 0 lines = l; cols = c; // Rows and columns ax = 0; ay = 0; ai = 0; // Current position } else throw invalid_argument("Invalid argument \n"); } template <int N> void Bitmap<N>::move(int x, int y) { if( x >= 0 && x < lines && y >= 0 && y < cols) { ax = x; ay = y; ai = x * cols + y; } else throw invalid_argument("Invalid argument\n"); } // to be continued ■ BITSETS Representing raster images with bitmaps BITSETS ■ 775 ᮀ Declaring Bitsets A bitset stores a bit sequence of a given length. This allows storage of mass bit coded data, such as raster images, with minimum memory used. The container class bitset<N> provides the functionality needed to manage bitsets. The template parameter N is the length of bitset, that is the maximum number of bits stored. You can use the default constructor to create a bitset with no initial values. However, you can also use a given bit-pattern to initialize a bitset. The bit-pattern is either defined as an unsigned long value or as a string. Example: string s = "10101010"; bitset<1024> b(s); The string s can contain only the '0' or '1' characters. The last character in the string will be the first bit value (that is 0 or 1) at bit position 0, the second to last character in the string is the bit value at position 1, and so on. The remaining bits are padded with 0 up to a length of N. This also applies when an unsigned long value is used for initial- ization purposes. ᮀ Notes on the Sample Program Opposite The container class Bitmap<N>, which is defined opposite, can be used to represent simple monochrome raster images. A pixel (picture element) is represented by a bit in a bitset. If the bit is set, a pixel on screen will be illuminated (white) and otherwise turned off (black). The number of pixels that can be represented horizontally and vertically is defined by the resolution. 480 by 800 is a typical screen resolution and 3300 by 2300 is a typical res- olution for laser printers (at 300 dpi) for an 8 1 ⁄2 ϫ 11 page size. The value of N is the product of the number of pixels in horizontal and vertical direction. The container class Bitmap<N> is derived from bitset<N> by public inheri- tance. Thus, the class comprises a bitset and all the public bitset management methods it inherits. Additional data members are used to store the number of rows and columns of pixels, the current cursor position, and the current bitset index. The move()method moves the cursor to the position with the given coordinates. The draw()method draws a straight line from the current cursor position to the point with the given coordinates. 776 ■ CHAPTER 33 CONTAINERS // bitmap.h: In addition the Bresenham algorithm. // template <int N> void Bitmap<N>::draw(int x, int y) { if( x >= 0 && x < lines && y >= 0 && y < cols) { int savex = x, savey = y; if(ax > x) // Draw in ascending x-direction { // => possibly swap (ax,ay) and (x,y) int temp = ax; ax = x; x = temp; temp = ay; ay = y; y = temp; } int dx = x - ax, dy = y - ay; int xinc = 1, yinc; // Increment if( dy < 0) // Gradient < 0 ? { yinc = -1; dy = -dy;} // Decrement y else yinc = 1; // or else increment. int count = dx + dy; // Number of pixels to be set int d = (dx - dy)/2; // Measurement of deviation // off the line. while( count > 0) { ai = ax * cols + ay; // Index in the bitset set(ai); // Set the bit if( d < 0) // Next pixel in { ay += yinc; d += dx; } // y-direction else // or else in { ax += xinc; d -= dy; } // x-direction } ax = savex; ay = savey; // Current cursor position ai = ax * cols + ay; // Current index in bitset } else throw invalid_argument("Invalid argument\n"); } #endif ■ BITSETS (CONTINUED) Bresenham algorithm BITSETS (CONTINUED) ■ 777 ᮀ Manipulating Bits The container class Bitset<N> provides the get() and set() methods for reading and writing individual bits. These methods expect a bit position as an argument. You can additionally pass a value of 0 or 1 to the set method, which writes this value at the bit position stated. The default value here is 1. If you call the set() method without any arguments, all the bits in the bitset are set to 1. In contrast, the reset() method deletes all the bits. Bits can be inverted by a call to the flip() method. Each 0-bit in the bitset is set then to 1 and each 1-bit is set to 0. Bits at specific coordinates can be referenced by the subscript operator. The index is a bit position, that is a number between 0 and N-1. As you would expect, bitwise operators can also be used for bit manipulation. The bit operators &, |, and ^ are globally overloaded for bitsets. The operator functions for the NOT operator ~, the shift operators << and >>, and the operators for compound assign- ments, &=, |=, ^=, are implemented as methods of the container class. ᮀ The Bresenham Algorithm The opposite page shows the draw() method that draws a line from the current cursor position pixel by pixel to the given coordinates. The Bresenham algorithm used here applies incremental techniques. Starting at the current cursor position, it sets the neigh- boring pixel in the x- or y-direction. To do this, you only need to increment or decre- ment the x- or y-coordinate by 1. This avoids time consuming floating-point arithmetic, which would be required to solve a linear equation. To allow drawing to take place along a positive x-axis, the starting and target points of the straight line can be swapped. The difference between the y-coordinates of the starting and target points dy = y - ay then determines whether to increment or decrement by 1 along the y-direction. Drawing neighboring pixels creates a “staircase” effect, which deviates from the straight line. The variable d = (dx - dy)/2 represents this deviation. If the value of d is negative, the line is seen to be growing along y-direction and the next pixel is drawn along the y-direction. Then the deviation is corrected by adding dx. As soon as d becomes positive, the next pixel is drawn along the x-direction and the deviation is cor- rected with dy. exercise 778 ■ CHAPTER 33 CONTAINERS 9 queues have been created. The queues will now be filled using the hot potato algorithm. Some elements of randomly selected queues are removed. Output the queues: 1.queue: 28 88 70 60 6 2.queue: 64 6 54 1 3.queue: 2 88 64 30 66 29 11 74 49 41 4.queue: 17 25 5.queue: 96 97 47 27 71 34 87 58 6.queue: 77 82 54 7.queue: 35 65 23 40 5 83 92 8.queue: 32 23 54 9.queue: 28 55 54 73 28 82 21 99 Router Exits 1. Queue 2. Queue 3. Queue 4. Queue Entrance ■ EXERCISE Hot potato algorithm Test output . multisets, maps, and mul- timaps. The template parameter Compare is a comparator class and Allocator is an allocator class. Both parameters have default values, which we already saw in the context of sequences. The. multimap<int, string> MULTI_MAP; typedef MULTI_MAP::iterator ITERATOR; int main() { MULTI_MAP m; // Create a multimap. ITERATOR pos; // Iterator. // To insert: m.insert(pair<int, string>(7,. y-coordinate by 1. This avoids time consuming floating-point arithmetic, which would be required to solve a linear equation. To allow drawing to take place along a positive x-axis, the starting and