Object-oriented programming B, Lecture 13e 1

5.4. Deque and list.

The term “deque” is an abbreviation for “double-ended queue”. It is a that is implemented so that it can grow in both directions.

Fig. 5.7. Logical structure of a deque.

Thus, inserting and removing elements both at the end and at the beginning is fast. However, inserting and removing elements in the middle takes time because elements must be moved. To provide this ability, the deque is implemented as an array of arrays – individual blocks, with the first block growing in one direction and the last block growing in the opposite direction.

Fig. 5.8. Internal structure of a deque.

Deque operations differ from vector operations only as follows: 1. Deques do not provide the functions for capacity (capacity() and reserve()); 2. Deques do provide direct functions to insert and to delete the first element (push_front() and pop_front()).

A list is implemented as a doubly of elements. This means each element in a list has its own segment of memory and refers to its predecessor and its successor.

Fig. 5.9. Structure of a list.

Because the internal structure of a list is totally different from a vector or a deque, a list differs in several ways compared with them: 1. Lists provide neither a subscript operator nor at() because a list does not provide . For example, to access the fifth element, one have to navigate the first four elements step by step following the chain of links. Thus, accessing an arbitrary element using a list is slow. 2. Inserting and removing elements is fast at each position, not only at one or both ends. We can always insert and delete an element in constant time because no elements have to be moved. Instead, only some pointer values are manipulated internally. 3. Lists don’t provide operations for capacity or reallocation because neither is needed. Each element has its own memory that stays valid until the element is deleted. So, inserting and deleting elements does not invalidate pointers, references, and iterators to other elements. 4. Lists provide many special member functions (like splice family and sort) for moving elements. These member functions are faster versions of the STL generic algorithms that have the same name. They are faster because they only redirect pointers rather then copy and moves values. Since some of the STL generic algorithms requiring Object-oriented programming B, Lecture 13e 2

random access, like sort, cannot be applied to a list, the list’s member function sort is specially introduced in order to implement such important procedure.

The next example illustrates usage of some deque’s and list’s member functions: Example 5.5. 01: #pragma warning(disable: 4786) 02: #include 03: #include 04: #include 05: #include 06: #include 07: using namespace std; 08: 09: int main() 10: { 11: deque ; 12: d.push_front("one"); 13: d.push_front("two"); 14: cout << d[0] << " " << d.at(1) << endl; // two one 15: d.pop_front(); 16: d.push_front("zero"); 17: d.push_back("two"); 18: cout << d.front() << " " << d.back() << endl; // zero two 19: sort(d.begin(), d.end()); //the STL generic algorithm sort 20: for(int i = 0; i < d.size(); i++) 21: cout << d.at(i) << " "; 22: cout << endl; // one two zero 23: 24: list l(d.size()), l2; 25: copy(d.begin(), d.end(), l.begin()); 26: string s[] = { "four", "five", "six"}; 27: l2.insert(l2.begin(), s, s + 3);//call like .insert(p, first, last) 28: //inserts at iterator position p a copy of all elements of the range 29: //[first,last) 30: l2.splice(l2.end(), l);//call like c1.splice(p, c2) moves all 31: //elements of c2 to c1 in front of the iterator position p 32: list::iterator it; 33: for(it = l2.begin(); it != l2.end(); it++) 34: cout << *it << " "; 35: cout << endl; // four five six one two zero 36: 37: l2.sort(); //sort all elements with operator < 38: for(it = l2.begin(); it != l2.end(); it++) 39: cout << *it << " "; // five four one six two zero 40: cout << endl; // 41: 42: return 0; we cannot use operator < for lists 43: }

5.5. and multiset.

Set and multiset containers sort their elements automatically according to a certain sorting criterion (in ascending order, by default). The difference between the two is that multisets allow duplicates, whereas sets do not. The elements of a set or multiset may have any type T that is assignable, copyable, and comparable according to the sorting criterion. Sets and multisets are usually implemented as balanced binary trees.

Object-oriented programming B, Lecture 13e 3

4

2 ++ ++ ++ ++ 6 ++

1 3 5

Fig. 5.10. Internal structure and iterating over elements of a set

The major advantage of automatic sorting is that a binary performs well when elements with a certain value are searched. However, automatic sorting also imposes an important constraint on sets and multisets: we may not change the value of an element directly because this might compromise the correct order. Therefore, to modify the value of an element, we must remove the element with the old value and insert a new element that has the new value. Set and multiset provide special search functions: • count(elem) – returns the number of elements with value elem; • find(elem) – returns the position of the first element with value elem or end(); • lower_bound(elem) and upper_bound(elem) – returns the first and last position respectively, at which an element with the passed value elem would be inserted; • equal_range(elem) – return both return values of lower_bound() and upper_bound() as a pair. The following example illustrates use of some of these functions: Example 5.6. 01: #pragma warning(disable: 4786) 02: #include 03: #include 04: #include 05: using namespace std; 06: 07: int main() 08: { 09: // STL set container 10: set s; //creates an empty set without any elements 11: s.insert(7); 12: s.insert(2); 13: s.insert(-6); 14: s.insert(-6); //duplicate element: ignore 15: set::iterator it; 16: for(it = s.begin(); it != s.end(); it++) 17: cout << *it << " "; 18: cout << endl; // -6 2 7 //Obs!: the order has changed 19: 20: int key; 21: cin >> key; 22: it = s.find(key); 23: if(it != s.end()) 24: cout << *it << endl; 25: else 26: cout << "no such an element" << endl; 27: 28: // STL multiset container 29: string a[] = {"one", "two", "three"}; 30: multiset ms(a, a + sizeof(a)/sizeof(string)); 31: ms.insert("three"); 32: ms.insert("three"); 33: cout << "there are " << ms.count("three") << " 'three'" << endl; Object-oriented programming B, Lecture 13e 4

34: multiset::iterator jt; 35: for(jt = ms.begin(); jt != ms.end(); jt++) 36: cout << *jt << " "; 37: cout << endl; // one three three three two 38: 39: cout << "lower bound of 'three': " <<*ms.lower_bound("three") 40: << " " 41: << "upper bound of 'three': " << *ms.upper_bound("three") 42: << endl; 43: //lower bound of 'three': three upper bound of 'three': two 44: return 0; 45: }

That is, lower_bound() and upper_bound()return the following iterators:

one three three three two

lower_bound(”three”) upper_bound(”three”)

5.6. The STL function objects.

The types set and multiset are declared and defined as class templates inside namespace std: template, class Allocator =allocator > class set;

The optional second template argument defines the sorting criterion. If a special sorting criterion is not passed, the default criterion less is used, less being the STL function object. As we know (see section 2.4.2), the function object is an object of function-like class in which the function call operator() is overloaded. So, a function object encapsulates a function in an object for use by other components. The STL provides many useful function objects. To aid the writing of function objects, the library provides two base classes:

template struct unary_function { typedef Arg argument_type; typedef Result result_type; };

template struct binary_function { typedef Arg1 first_argument_type; typedef Arg2 second_argument_type; typedef Result result_type; };

Each template class in point serves as a base for classes that define a function call operator() in the form:

result_type operator()(first_argument_type, second_argument_type)

Object-oriented programming B, Lecture 13e 5

The purpose of these classes is to provide standard names for the argument and return types for use by users of classes derived from unary_function and binary_function. The STL function objects are divided into two groups: predicates and arithmetic function objects. A predicate is a function object (or function) that returns a bool.

For example, the header defines template struct less: public binary_function { bool operator()(const T& x, const T& y) const { return x < y; } };

The predicates provided by STL are as follows: Table 5.1. equal_to Binary arg1 == arg2 not_equal_to Binary arg1 != arg2 greater Binary arg1 > arg2 less Binary arg1 < arg2 greater_equal Binary arg1 >= arg2 less_equal Binary arg1 <= arg2 logical_and Binary arg1 && arg2 logical_or Binary arg1 || arg2 logical_not Unary !arg

The STL also provides some useful standard arithmetic functions as function objects. Table 5.2. plus Binary arg1 + arg2 minus Binary arg1 - arg2 multiplies Binary arg1 * arg2 divides Binary arg1/arg2 modulus Binary arg1 % arg2 negate Unary - arg

The next example illustrates the use of both user-defined predicate Greater and the STL predicates in set/multiset containers.

Example 5.7. 01: #pragma warning(disable:4786) 02: #include 03: #include 04: #include 05: using namespace std; 06: 07: template 08: struct Greater 09: { 10: bool operator()(const T& lhs, const T& rhs) { return lhs > rhs; } 11: }; 12: 13: int main() 14: { 15: int a[5] = { 4,2,1,5,3}; 16: set > s(a, a+5); 17: set >::iterator i; 18: for(i = s.begin(); i != s.end(); i++) 19: cout << *i << " "; 20: cout << endl; // 5 4 3 2 1 21: Object-oriented programming B, Lecture 13e 6

22: set > s2(a, a + 5); 23: set >::iterator i2; 24: for(i2 = s2.begin(); i2 != s2.end(); i2++) 25: cout << *i2 << " "; 26: cout << endl; // 1 2 3 4 5 27: 28: set > s3(a, a + 5); 29: set >::iterator i3; 30: for(i3 = s3.begin(); i3 != s3.end(); i3++) 31: cout << *i3 << " "; 32: cout << endl; // 5 4 3 2 1 33: return 0; 34: }

The function objects as sorting criteria are also used in the STL map/ containers. As a whole, the STL predicates and arithmetic function objects are widely used in the STL generic algorithms as parameters.

5.7. Map and multimap.

The map and multimap associative containers are used for fast storage and retrieval of keys and associated values (often called key/values pairs ). So, the elements of maps and are pairs of keys and values ( pair objects) instead of individual values. The internal structure of map/multimap is a binary tree, as for sets.

4 "x"

2 "x" 6 "z"

1 "y" 3 "w" 5 "x"

Fig. 5.11. Internal structure of a map

The declaration of the STL map class is as follows: template, class A = allocator > class map { public: //type definitions, e.g. typedef pair value_type; //constructors explicit map(const Pred& comp = Pred(), const A& a1 = A()); map(const value_type* first, const value_type* last, const Pred& comp = Pred(),const A& a1 = A()); map(const map& x); // other member functions protected: A allocator; }; The ordering of the keys is determined by a comparator (predicate) function object. For example, in a map that uses integers as the key type, keys will be sorted by default in ascending order by ordering the keys with comparator function object of the STL class less. Object-oriented programming B, Lecture 13e 7

In a map, only a single value can be associated with each key (so-called one-to-one- mapping) like in the map being used, e.g., to hold associations between names and telephone numbers. A map is commonly called an , multimap is called a dictionary. Actually, a map is an array for which the index need not be an integer. Header file must be included to use both class map and class multimap. The next example demonstrates the STL map container; the code extracts each digit from the input string, finds its corresponding entry in the map (the word/string equivalent) and print it.

Example 5.8. 01: #pragma warning( disable: 4786 ) 02: #include 03: #include 04: #include 05: #include 06: using namespace std; 07: 08: int main() 09: { 10: map m; 11: typedef pair p; 12: m.insert(p(8, "eight")); 13: m.insert(p(4, "four")); 14: m.insert(p(0, "zero")); 15: m.insert(p(2, "two")); 16: m.insert(p(3, "three")); 17: m.insert(p(9, "nine")); 18: m.insert(p(5, "five")); 19: m.insert(p(1, "one")); 20: m.insert(p(7, "seven")); 21: m.insert(make_pair(6, string("six"))); 22: 23: map::iterator it; 24: for(it = m.begin(); it != m.end(); it++) 25: cout << (*it).first << " " << it->second << endl; 26: // 0 zero 27: // 1 one 28: // 2 two 29: // 3 three 30: // 4 four 31: // 5 five 32: // 6 six 33: // 7 seven 34: // 8 eight 35: // 9 nine 36: string s; 37: for( ; ; ) //infinite loop, like while(true) 38: { 39: cout << "Enter \"q\" to quit, or enter a Number: " ; 40: 41: cin >> s; 42: if( s == "q" ) 43: break; 44: for(int i = 0; i < s.length(); i++ ) 45: { 46: it = m.find(s[i] - '0'); 47: if( it != m.end()) // is 0 - 9 48: cout << ( * it ).second << " " ; 49: else // some characters other than 0 - 9 50: cout << " [ err ] "; 51: } Object-oriented programming B, Lecture 13e 8

52: cout << endl; // input: 234 53: } // output: Two Three Four 54: 55: cin >> s;// = "123"; 56: assert(s[0]== '1'); // OK 57: cout << (int)'1' << " " << (int)'0' << endl; // 49 48 58: cout << (int)('1' - '0') << endl; // 1 59: return 0; 60: }

In the example, the data are inserted into the map in an arbitrary order with respect to their key, but afterwards the map's elements are re-arranged in an ascending order. The map's member function find returns the iterator that points to the unique element with the key k (line 46).