5.4. Deque and List
Total Page:16
File Type:pdf, Size:1020Kb
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 dynamic array 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 linked list 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 random access. 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<iostream> 03: #include<deque> 04: #include<string> 05: #include<list> 06: #include<algorithm> 07: using namespace std; 08: 09: int main() 10: { 11: deque<string> d; 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<string> 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 c.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<string>::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. Set 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 tree 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<iostream> 03: #include<set> 04: #include<string> 05: using namespace std; 06: 07: int main() 08: { 09: // STL set container 10: set<int> 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<int>::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<string> 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<string>::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 T, class Compare = less<T>, class Allocator =allocator<T> > 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<class Arg, class Result> struct unary_function { typedef Arg argument_type; typedef Result result_type; }; template<class Arg1, class Arg2, class Result> 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 <functional> defines template<class T> struct less: public binary_function<T, T, bool> { bool operator()(const T& x, const T& y) const { return x < y; } }; The predicates provided by STL <functional> 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 <functional> 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