the stl the heart of the stl is a collection of algorithms and data structures each algorithm works...
Post on 21-Dec-2015
225 views
TRANSCRIPT
The STL• The heart of the STL is a collection of
algorithms and data structures
• Each algorithm works with a large number of data structures
• If you create a new data structure it will work with the algorithms
• If you create a new algorithm is will work with the data structures
• It introduces some new concepts to allow this
Interface
• How do the algorithms use the data structures?
• The data structures provide a consistent way of access
• We call them Iterators
Designing the Interface
• An interface which is as generic as possible is wanted
• It should work with arrays - since they are probably the simplest container
• It should work with linked lists
• It should work with trees and hash tables
Iterating through an array
for (int index=0;index<length;index++)// do something with array[index]
for (T* ptr = array; ptr!=&array[length];ptr++)//do something with *ptr
Iterating through a linked list
for (Node *node=list.head; node!=0;node=node->next)
// do something with node->value
Generic Iterating
• Set current element to first element
• If current element is not past the end
• Do something with the current element
• Set current element to the next element
STL Iterators
• The key to the STL is understanding that it is designed to work with arrays
• Arrays are built in to the language
• You can’t inherit from them - so using OO inheritance to write generic algorithms won’t work
• You can’t change the way arrays work - so you need to be compatible with them
The Basic Pointer Operations
• *ptr - used to access what is pointed to
• ptr++, ++ptr - used to advance to the next element
• ptr == ptr2, ptr != ptr2 - used to check if two pointers point to the same element
The STL Find Algorithm
template <typename Iterator, typename T>
Iterator find(Iterator first,
Iterator last, const T& value) { while (first!=last && *first != value) ++first;
return first;
}
• Note that the following will work:int array[5] = {1,2,3,4,5};cout << *(find(array,array+5,3)); //not safe!
Requirement, Concepts, Models
• A concept is a set of requirements
• A requirement is some operation doing a useful thing
• A model, is something that fulfills a concept.
• An Iterator is a concept
• A pointer is a model of an Iterator.
Basic Concepts• The fundamental concepts, that seem too obvious to
state
• Assignable Possible to copy values
Possible to assign new values
• Default Constructable Possible to construct an object with no arguments
T() creates an object T t; declares a variable
Basic Concepts Continued
• Equality Comparable Possible to compare two object for equality
x == y and x != y must do so
• LessThan Comparable Possible to test if one object is less than or greater than
another
x<y ,x>y, x<=y, x>=y must do so
• A regular type models all the above in a consistent fashion
Input Iterator
• The simplest Iterator
• Three kinds of values: dereferencable, past the end, and singular
• Possible to compare for equality
• Possible to copy and assign
• Possible to dereference to get associated value type
• Possible to increment with ++i and i++
Input Iterators
• That is all, for example, the following do not have to be possible Modify the dereferenced value
Other comparison operators
Read a value more than once
Output Iterators
• The other simple Iterator
• Possible to copy and assign
• Possible to write a value with *i = x• Possible to increment
• (Again, writing twice isn’t required, testing for equality isn’t required, etc)
Using Iterators Example
template <typename InputIterator, typename OutputIterator>
OutputIterator copy(InputIterator first,InputIterator last,OutputIterator result) {
for (;first!=last;++result,++first)*result = *first;
return result;}
Iterator Implementation Example
template <typename T>class ostream_iterator {public:ostream_iterator(ostream& s,const char *c=0): os(&s),string( c ) {}ostream_iterator(const ostream_iterator& o): os(i.os), string(i.string) {}ostream_iterator& operator=(constostream_iterator &o) {os = i.os;string = i.string;return *this;}
ostream_iterator<T>& operator=(const T& value) {
*os << value;if (string) *os << string;return *this;
}ostream_iterator<T>& operator*() {
return *this;}ostream_iterator<T>& operator++() {
return *this;}ostream_iterator<T>& operator++(int) {
return *this;}private:
ostream *os;const char* string; };
Forward Iterators
• Both an InputIterator and an OutputIterator
• Allows multi-pass algoithms
• p=q; ++q; *p = x; works
Bidirectional Iterators• Does everything a ForwardIterator does.
• Also supports decrement template <typename BidirectionalIterator,
typename OutputIterator >
OutputIterator reverse_copy( BidirectionalIterator first, BidirectionalIterator last,
OutputIterator result) {while (first != last)
*(result++) = *(--last);return result;
}
Random Access Iterators• Supports everything that a Bidirectional Iterators does
• Supports random access Addition and subtraction i + n and i - n
Subscripting i[n]
Subtraction of iterators i1 - i2
Ordering i1 < i2
• Random access must be in constant time
• Basically acts just like a real pointer
Ranges
• Iterators are often used in ranges
• A range is a start and end iterator
• The start iterator is part of the range
• The end iterator is not
• In maths it’s [start, end)
• The empty range is start==end
Containers Containers contain elements Container - accessed via Input Iterators
begin() and end() get iterators
• Forward Container - accessed via Forward Iterators Reversible Container - accessed via Bidirectional Iterators
rbegin() and rend() get reverse iterators
Random Access Containers - accessed via Random Access Iterators
More Container Types
There are some other Container types Sequence
Refinement of Forward Container
Can add or delete elements at any point
member functions: insert and erase
Associative Containers Every element has a key, by which it is looked up
Elements are added and deleted via keys
Sequence Abstractions
• Front Insertion Sequence Insertion at front in constant time
Access first element in constant time
• Back Insertion Sequence Append to end in constant time
Access last element in constant time
Associative Container Abstractions• Unique Associative Container
No two elements have the same key Conditional insertion
• Multiple Associative Container May contain multiple elements with the same key
• Simple Associative Container The elements are their own keys
• Pair Associative Container Associates a key with another object The value type is pair<const key, value>
Associative Container Abstractions Continued
• Sorted Associative Container Sorts elements by key in a strict weak ordering
• Hashed Associative Container Uses a hash table implementation
Vector
• Simplest container, often the most efficient
• Has both a size and a capacity
• Inserting elements can cause reallocation
• Reallocation invalidates iterators
• Random Access Container
• Back Insertion Sequence
Vector Usage Example
#include <iostream>#include <vector>#include <algorithm>#include <string>int main() {vector<string> v;
string s; while(cin>>s) v.push_back(s); copy(v.begin(),v.end(), ostream_iterator<string>(cout,"\n"));
}
List
• A doubly linked list
• Insertion and spliciing do not invalidate iterators
• Deletion only invalidates iterators at the deleted element
• Reversible Container
• Front Insertion Sequence
• Back Insertion Sequence
List Usage Example
#include <iostream>#include <list>#include <algorithm>#include <string>int main() {list<string> l;string s;while(cin>>s)
l.push_front(s);copy(l.begin(), l.end(), ostream_iterator<string>(cout,"\n"));
}
Deque
• A double-ended queue
• Insertion invalidates all iterators
• Deletion from middle invalidates all iterators
• Deletion at the ends invalidates iterators at the deleted element only
• Random Access Container
• Front Insertion Sequence
• Back Insertion Sequence
Deque Usage Example
#include <iostream>#include <deque>#include <algorithm>#include <string>int main() {deque<string> d;d.push_back("first");d.push_front("second");d.insert(d.begin()+1, "third");d[2] = "fourth";copy(d.begin(), d.end(),
ostream_iterator<string>(cout,"\n"));}
Set
• Maintains elements in sorted order
• Inserting does not invalidate iterators
• Deleting only invalidates iterators at the deleted element
• Sorted Associative Container
• Simple Associative Container
• Unique Associative Container
#include <iostream>#include <set>#include <algorithm>#include <string>int main() {int prime_a[5] = {2,3,5,7,11};int odd_a[5] = {1,3,5,7,9};int even_a[5] = {2,4,6,8,10};set<int> prime(prime_a,prime_a+5);set<int> odd(odd_a,odd_a+5);set<int> even(even_a,even_a+5);cout << "Union: ";set_union(prime.begin(), prime.end(),
even.begin(), even.end(),ostream_iterator<int>(cout, " "));
}
Multiset
• Inserting does not invalidate iterators
• Deleteing only invalidates iterators at the deleted element
• Sorted Associative Container
• Simple Associative Container
• Multiple Associative Container
Map
• Inserting does not invalidate iterators
• Deleting only invalidates iterators at the deleted element
• Sorted Associative Container
• Pair Associative Container
• Unique Associative Container
Map Usage Example
#include <iostream>#include <map>#include <string>int main(){map<string,int> days;days["January"] = 31;
days["February"] = 28; days["March"] = 31; days["April"] = 30;cout <<"March : "<<days["March"]
<<endl;}
Multimap
Sorted Associative Container Pair Associative Container Multiple Associative Container Insertion does not invalidate iterators Deletion only invalidates iterators at the
deleted element
Adapters
• Adapters are wrappers around a container
• They work with multiple containers
• They provide more specific interfaces
Stack
• LIFO• Can only insert, retrieve, and delete top element• Can use any Front Insertion or Back Insertion
Container• By default a deque is used• top() returns the top element• push() inserts at the top• pop() removes the top element
Queue
• FIFO• Elements are added to the back, and removed
from the front• Can use any Front and Back Insertion Sequence• By default a deque is used• front() retrieves element at the front• push() adds element to the back• pop() delete element at the front
Priority_queue
• Can only retrieve and delete top element
• The top element is always the largest
• Can use any RandomAccessContainer
• By default uses a vector
• top() retrieves the top element
• push() inserts an element
• pop() removes the top element
Function Objects
It is often useful to supply a function to an algorithm
It makes the algorithm more generic Sorting is the obvious example In the STL we don't actually pass a function A function object is used (which could in
fact be a function)
What is a function object?
• The simplest function object is a function pointer
• However, an object can also be used
• If operator() is overloaded the object can be used as a function
• Using an object is more flexible
• An object can maintain state
Types of function objects• Generators
Called with no arguments • Unary Functions
Called with one argument • Unary Predicate
A Unary Function that returns a boolean • Binary Functions
Called with two arguments • Binary Predicate
A Binary Function that returns a boolean
Basic Function Objects
• Strict Weak Ordering A Binary Predicate
f(x,x) is false
f(x,y) implies !f(y,x)
f(x,y) and f(y,z) implies f(x,z)
f(x,y) false and f(y,x) false implies x and y are equivalent
x and y equivalent, and y and z equivalent implies x and z equivalent
Basic Function Objects II
• Random Number Generator A Unary Function
f(N) returns an integer in the range [0,N)
Every integer in [0,N) will appear an equal number of times
• Hash Function A Unary Function
Maps an object to a size_t
Used by Hashed Associative Containers
STL Provided Function Objects
• The STL provides a large number of Function Objects
• plus, minus, multiplies, etc
• logical_or,logical_and, etc
• less, greater, etc
Find• Performs a linear search#include <iostream>#include <string>#include <list>#include <algorithm>int main() {list<string> l;l.push_back("bob");l.push_back("john");l.push_back("kate");list<string>::iterator it=
find(l.begin(),l.end(),"john");if (it==l.end())
cout << “John not found”else
cout << “John found”}
Find_if
• Finds the first element which satisfies a Predicate #include <iostream>#include <string>#include <list>#include <algorithm>#include <functional>#include <cstdlib>int main() {list<int> l;for (int i=0;i<10;i++)l.push_back(rand()%100);
cout << *find_if(l.begin(),l.end(),bind1st(less<int>(),50));
}
Adjacent_find• Performs a linear search thorugh a range of input
iterators
• Searches for two adjacent elements
• If no binary predicate is supplied finds adjacent equal elements
int main() {int array[5] = {1,2,3,4,1};const int *p = adjacent_find(array,
array+5, greater<int>());if (p!=array+5)
cout << *p << " is wrong\n";}
Find_first_of• Finds first occurance of a number of values
• Can be passed a comparison function object to use
int main() {string sentence = ”here is a string.";string vowels = "aeiou";string::iterator it =
find_first_of(sentence.begin(),sentence.end(), vowels.begin(),vowels.end());
cout << *it;}
Search
• Similar to find and find_if
• Finds subranges instead of single elements int main() {char sentence[] = "this is a sentence";char word[] = "is";char *r = search(sentence,
sentence+strlen(sentence),word, word+strlen(word));
cout << "Found " << word << " at ” << r-sentence << endl;
}
Find_end• Should be called search_end
• Same as search, except returns the last subrange that matches
int main() {char sentence[] = "this is a sentence";char word[] = "is";char *r = find_end(sentence,sentence+strlen(sentence),word, word+strlen(word));cout << "Found " << word << " at ” << r-sentence << endl;
}
Search_n
• Searches for a subsequence of n consequitive equal elements
• Can be passed a Binary Predicate with which to determine equality
int main() {int A[] = {1,1,2,3,1,1,1,2,3,1,1,1,1,2,3};int N = sizeof(A) / sizeof(A[0]);int *r = search_n(A,A+N,4,1);cout << "Sequence of 4 1's at element : ”
<< r-A << endl;}
Count
• Counts the elements that are equal to a value
• There is also a count_if which uses a Predicate int main() {
int A[] = { 1,2,3,1,2,3,1,2,3,1,2,1,1,2,3};
int N = sizeof(A)/sizeof(A[0]);
cout << "Number of 2's : ”
<< count(A,A+N,2) << endl;
}
For_each• Applies a Unary Function to each element of the range
• Returns the Unary Function struct sum {int sum;sum() : sum(0) {}void operator()(int i) { sum+=i; }
};int main() {int A[] = { 1,2,3,1,2,3,1,2,3,1,2,1,1,2,3};int N = sizeof(A) / sizeof(A[0]);sum s = for_each(A,A+N,sum());cout << s.sum << endl;
}
Equal• Compares two ranges
• Can use a BinaryPredicate for comparisons bool compare_nocase(char c1, char c2) {
return toupper(c1) == toupper(c2);
}
int main() {
const char *s1 = "a string";
const char *s2 = "A string";
if(equal(s1,s1+strlen(s1),s2,compare_nocase))
cout << "Strings are equal" << endl;
}
Mismatch• Returns the first position that two ranges differ
• Returns a pair holding two iterators
• Can use a BinaryPredicate for comparisons int main() {const char *s1 = "a string";const char *s2 = "A string";const char *s1e = s1+strlen(s1);const char *s2e = s2+strlen(s2); pair<const char*,const char*> p =
mismatch(s1,s1e,s2,compare_nocase); if (p.first == s1e && p.second == s2e)
cout << "Strings are equal" << endl;}
Lexicographic_compare• Returns true if first range is lexicographically less than
second
• Can use a BinaryPredicate for comparisons int main() {const char *s1 = "a string";const char *s2 = "A string";const int N1 = strlen(s1);const int N2 = strlen(s2);if (lexicographic_compare(s1,s1+N1,s2,
s2+N2,compare_nocase))cout << "s1 less than s2" << endl;
}
min_element and max_element• Returns an iterator at the smallest/largest element of a
range
• Can use a BinaryPredicate for comparisons int main() {list<int> l;generate_n(front_inserter(l),1000,rand);list<int>::const_iterator min =
min_element(l.begin(),l.end());list<int>::const_iterator max =
max_element(l.begin(),l.end());cout << *min << " - " << *max << endl;
}
Adapters
• Adapters transform one interface into another
• The STL provides function object adapters
• In fact it provides a lot of them
• We'll look at some of the most useful
Binder1st• Transforms a Binary Function into a Unary
Function
• It binds the first argument to a constant
• To use it a helper function bind1st is provided int main() {vector<int> v;for (int i=0;i<10;i++) v.push_back(i);vector<int>::iterator vi =
find_if(v.begin(), v.end(),bind1st(less<int>(),5));
}
Binder2nd
• Just like binder1st
• Binds the second argument to a constant int main() {vector<int> v;for (int i=0;i<10;i++) v.push_back(i);vector<int>::iterator vi =
find_if(v.begin(), v.end(),bind2nd(less<int>(),5));
}
Unary_negate and Binary_negate
• unary_negate Negates a Unary Predicate
• Easiest to use with the not1 helper function
• binary_negate Negates a Binary Predicate
• Easiest to use with the not2 helper function
Unary_compose and Binary_compose• unary_compose creates composition of two
Unary Functions
• Best to use the compose1 helper function
• compose1(f,g)(x) is the same as f(g(x))
• binary_compose creates composition of three Functions
• Best to use the compose2 helper function
• compose2(f,g1,g2)(x1,x2) is the same as f(g1(x1),g1(x2))