STL Algorithms
2011-08-14 20:25 Daniel Zheng 阅读(348) 评论(0) 编辑 收藏 举报One important part of the standard template library is a set of generic functions, supplied by the header <algorithm>. that help manipulate or work with the contents of a container.
What Are STL Algorithms?
Finding, searching, removing, and counting are some generic algorithmic activities that find application in a broad range of programs. STL solves these and many other requirements in the form of generic template functions that work on containers via iterators.
Although mose algorithms work via iterators on containers, not all algorithms necessarily work on containers and hence not all algotithms need iterators. Some, such as swao, simply accept a pair of values to swao them. Similarly, min and max work directly on values, too.
Classification of STL Algorithms
STL algorithms can be broadly classified into two types: nonmutaing and mutating algorithms.
Algorithms that change neither the order nor the contents of a container are called nonmutating algorithms.
Mutating algorithms are those that change the contents or the order of the sequence they are operating on.
Usage of STL Algorithms
The best way to learning the usage of STL algorithms is to put them into real code examples.
Counting and Finding Elements
std::count, count_if, find, and find_if are algorithms that help in counting and finding elements in a range. The following code demonstrates the usage of these functions.
#include <algorithm>
#include <vector>
#include <iostream>
// A unary predicate for the *_if functions
template <typename elementType>
bool IsEven (const elementType& number)
{
// return true if the number is even
return ((number % 2) == 0);
}
int main ()
{
using namespace std;
// A sample container - vector of integers
vector <int> vecIntegers;
// Inserting sample values
for (int nNum = -9; nNum < 10; ++ nNum)
vecIntegers.push_back (nNum);
// Display all elements in the collection
cout << "Elements in our sample collection are: " << endl;
vector <int>::const_iterator iElementLocator;
for ( iElementLocator = vecIntegers.begin ()
; iElementLocator != vecIntegers.end ()
; ++ iElementLocator )
cout << *iElementLocator << ' ';
cout << endl << endl;
// Determine the total number of elements
cout << "The collection contains '";
cout << vecIntegers.size () << "' elements" << endl;
// Use the count_if algorithm with the unary predicate IsEven:
size_t nNumEvenElements = count_if (vecIntegers.begin (),
vecIntegers.end (), IsEven <int> );
cout << "Number of even elements: " << nNumEvenElements << endl;
cout << "Number of odd elements: ";
cout << vecIntegers.size () - nNumEvenElements << endl;
// Use count to determine the number of '0's in the vector
size_t nNumZeroes = count (vecIntegers.begin (),vecIntegers.end (),0);
cout << "Number of instances of '0': " << nNumZeroes << endl << endl;
cout << "Searching for an element of value 3 using find: " << endl;
// Find a sample integer '3' in the vector using the 'find' algorithm
vector <int>::iterator iElementFound;
iElementFound = find ( vecIntegers.begin () // Start of range
, vecIntegers.end () // End of range
, 3 ); // Element to find
// Check if find succeeded
if ( iElementFound != vecIntegers.end ())
cout << "Result: Element found!" << endl << endl;
else
cout << "Result: Element was not found in the collection." << endl;
cout << "Finding the first even number using find_if: " << endl;
// Find the first even number in the collection
vector <int>::iterator iEvenNumber;
iEvenNumber = find_if ( vecIntegers.begin ()// Start of range
, vecIntegers.end () // End of range
, IsEven <int> ); // Unary Predicate
if (iEvenNumber != vecIntegers.end ())
{
cout << "Number '" << *iEvenNumber << "' found at position [";
cout << distance (vecIntegers.begin (), iEvenNumber);
cout << "]" << endl;
}
return 0;
}
Searching for an Elements or a Range in a Collection
The previous sample demonstrated how you could find an element in a container. Sometimes however, you need to find a range of values. In such situationa, rather than depend on a find algorithm that works on an element-by element basis, you should use search or search_n, which are designed to work with range.
#include <algorithm>
#include <vector>
#include <list>
#include <iostream>
int main ()
{
using namespace std;
// A sample container - vector of integers
vector <int> vecIntegers;
for (int nNum = -9; nNum < 10; ++ nNum)
vecIntegers.push_back (nNum);
// Insert some more sample values into the vector
vecIntegers.push_back (9);
vecIntegers.push_back (9);
// Another sample container - a list of integers
list <int> listIntegers;
for (int nNum = -4; nNum < 5; ++ nNum)
listIntegers.push_back (nNum);
// Display the contents of the collections...
cout << "The contents of the sample vector are: " << endl;
vector <int>::const_iterator iVecElementLocator;
for ( iVecElementLocator = vecIntegers.begin ()
; iVecElementLocator != vecIntegers.end ()
; ++ iVecElementLocator )
cout << *iVecElementLocator << ' ';
cout << endl << "The contents of the sample list are: " << endl;
list <int>::const_iterator ilistElementLocator;
for ( ilistElementLocator = listIntegers.begin ()
; ilistElementLocator != listIntegers.end ()
; ++ ilistElementLocator )
cout << *ilistElementLocator << ' ';
cout << endl << endl;
cout << "'search' the contents of the list in the vector: " << endl;
// Search the vector for the elements present in the list
vector <int>::iterator iRangeLocated;
iRangeLocated = search ( vecIntegers.begin () // Start of range
, vecIntegers.end () // End of range to search in
, listIntegers.begin () // Start of range to search for
, listIntegers.end () ); // End of range to search for
// Check if search found a match
if (iRangeLocated != vecIntegers.end ())
{
cout << "The sequence in the list found a match in the vector at ";
cout << "position: ";
cout << distance (vecIntegers.begin (), iRangeLocated);
cout << endl << endl;
}
cout << "Searching for {9, 9, 9} in the vector using 'search_n': ";
cout << endl;
// Now search the vector for the occurrence of pattern {9, 9, 9}
vector <int>::iterator iPartialRangeLocated;
iPartialRangeLocated = search_n ( vecIntegers.begin () // Start range
, vecIntegers.end () // End range
, 3 // Count of item to be searched for
, 9 ); // Item to search for
if (iPartialRangeLocated != vecIntegers.end ())
{
cout << "The sequence {9, 9, 9} found a match in the vector at ";
cout << "offset-position: ";
cout << distance (vecIntegers.begin (), iPartialRangeLocated);
cout << endl;
}
return 0;
}
Intializing Elements in a Container to a Specific Value
fill and fill_n are the STL algorithms that help set the contents of a given to a specofied value. fill is used to overwrite the elements in a range given the bounds of the range and the value to be inserted. fill_in needs a starting position, a count n, and the value to fill.
#include <algorithm>
#include <vector>
#include <iostream>
int main ()
{
using namespace std;
// Initialize a sample vector with 3 elements
vector <int> vecIntegers (3);
// fill all elements in the container with value 9
fill (vecIntegers.begin (), vecIntegers.end (), 9);
// Increase the size of the vector to hold 6 elements
vecIntegers.resize (6);
// Fill the three elements starting at offset position 3 with value -9
fill_n (vecIntegers.begin () + 3, 3, -9);
cout << "Contents of the vector are: " << endl;
for (size_t nIndex = 0; nIndex < vecIntegers.size (); ++ nIndex)
{
cout << "Element [" << nIndex << "] = ";
cout << vecIntegers [nIndex] << endl;
}
return 0;
}
Just as the fill functions fill the collection with a programmer-determined value, STL algorithms such as generate and generate_n can be used to initialize collections to the contents of a file, for example, or simply to random values.
#include <algorithm>
#include <vector>
#include <list>
#include <iostream>
int main ()
{
using namespace std;
vector <int> vecIntegers (10);
generate ( vecIntegers.begin (), vecIntegers.end () // range
, rand ); // generator function to be called
cout << "Elements in the vector of size " << vecIntegers.size ();
cout << " assigned by 'generate' are: " << endl << "{";
for (size_t nCount = 0; nCount < vecIntegers.size (); ++ nCount)
cout << vecIntegers [nCount] << " ";
cout << "}" << endl << endl;
list <int> listIntegers (10);
generate_n (listIntegers.begin (), 5, rand);
cout << "Elements in the list of size: " << listIntegers.size ();
cout << " assigned by 'generate_n' are: " << endl << "{";
list <int>::const_iterator iElementLocator;
for ( iElementLocator = listIntegers.begin ()
; iElementLocator != listIntegers.end ()
; ++ iElementLocator )
cout << *iElementLocator << ' ';
cout << "}" << endl;
return 0;
}
Processing Elements in a Range Using for_each
The for_each algorithm applies a specified unary function object to every element in the supplied range. The usage of for_each is
unaryFunctionObjectType mReturn = for_each(start_of_range, end_of_range, unaryFunctionObject);
The return value indicates that for_each returns the function object( also called functor ) used to process every element in the supplied range. The implication of this specification is that the usage of a struct or a class as a function object can help in storing state information, that can later be queried once for_each is done.
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// Unary function object type invoked by for_each
template <typename elementType>
class DisplayElementKeepCount
{
private:
int m_nCount;
public:
DisplayElementKeepCount ()
{
m_nCount = 0;
}
void operator () (const elementType& element)
{
++ m_nCount;
cout << element << ' ';
}
int GetCount ()
{
return m_nCount;
}
};
int main ()
{
vector <int> vecIntegers;
for (int nCount = 0; nCount < 10; ++ nCount)
vecIntegers.push_back (nCount);
cout << "Displaying the vector of integers: " << endl;
// Display the array of integers
DisplayElementKeepCount<int> mIntResult =
for_each ( vecIntegers.begin () // Start of range
, vecIntegers.end () // End of range
, DisplayElementKeepCount<int> () );// Functor
cout << endl;
// Use the state stored in the return value of for_each!
cout << "'" << mIntResult.GetCount () << "' elements ";
cout << " in the vector were displayed!" << endl << endl;
string strSample ("for_each and strings!");
cout << "String sample is: " << strSample << endl << endl;
cout << "String displayed using DisplayElementKeepCount:" << endl;
DisplayElementKeepCount<char> mCharResult = for_each (strSample.begin()
, strSample.end ()
, DisplayElementKeepCount<char> () );
cout << endl;
cout << "'" << mCharResult.GetCount () << "' characters were displayed";
return 0;
}
Performing Transformations on a Range Using std::transform
for_each and std::transform are quite similar in that they both invoke a function object for every element in a source range. However, std::transform has two versions: one that accpets a unary function and another that accepts a binary function. So, the transform algorithm can also process a pair of elements taken from two different ranges. Both versions of the transform function always assign the result of the specified transformation function to a supplied desination range, unlike for_each, which works on only a singe range.
#include <algorithm>
#include <string>
#include <vector>
#include <deque>
#include <iostream>
#include <functional>
int main ()
{
using namespace std;
string strSample ("THIS is a TEst string!");
cout << "The sample string is: " << strSample << endl;
string strLowerCaseCopy;
strLowerCaseCopy.resize (strSample.size ());
transform ( strSample.begin () // start of source range
, strSample.end () // end of source range
, strLowerCaseCopy.begin ()// start of destination range
, tolower ); // unary function
cout << "Result of 'transform' on the string with 'tolower':" << endl;
cout << "\"" << strLowerCaseCopy << "\"" << endl << endl;
// Two sample vectors of integers...
vector <int> vecIntegers1, vecIntegers2;
for (int nNum = 0; nNum < 10; ++ nNum)
{
vecIntegers1.push_back (nNum);
vecIntegers2.push_back (10 - nNum);
}
// A destination range for holding the result of addition
deque <int> dqResultAddition (vecIntegers1.size ());
transform ( vecIntegers1.begin () // start of source range 1
, vecIntegers1.end () // end of source range 1
, vecIntegers2.begin () // start of source range 2
, dqResultAddition.begin ()// start of destination range
, plus <int> () ); // binary function
cout << "Result of 'transform' using binary function 'plus': " << endl;
cout <<endl << "Index Vector1 + Vector2 = Result (in Deque)" << endl;
for (size_t nIndex = 0; nIndex < vecIntegers1.size (); ++ nIndex)
{
cout << nIndex << " \t " << vecIntegers1 [nIndex] << "\t+ ";
cout << vecIntegers2 [nIndex] << " \t = ";
cout << dqResultAddition [nIndex] << endl;
}
return 0;
}
Copy and Remove Operations
STL supplied two promient copy functions: copy and copy_backward. copy can assign the contents of a source range into a destination range in the forward direction, whereas copy_backward assigns the contents to the desination range in the backward direction.
remove, on the other hand, deletes elements in a container that matches a specifed value. remove_if use a unary predicate and removes from the container those elements for which the predicate evaluates to true.
#include <algorithm>
#include <vector>
#include <list>
#include <iostream>
// A unary predicate for the remove_if function
template <typename elementType>
bool IsOdd (const elementType& number)
{
// returns true if the number is odd
return ((number % 2) == 1);
}
int main ()
{
using namespace std;
// A list with sample values
list <int> listIntegers;
for (int nCount = 0; nCount < 10; ++ nCount)
listIntegers.push_back (nCount);
cout << "Elements in the source (list) are: " << endl;
// Display all elements in the collection
list <int>::const_iterator iElementLocator;
for ( iElementLocator = listIntegers.begin ()
; iElementLocator != listIntegers.end ()
; ++ iElementLocator )
cout << *iElementLocator << ' ';
cout << endl << endl;
// Initialize the vector to hold twice as many elements as the list
vector <int> vecIntegers (listIntegers.size () * 2);
vector <int>::iterator iLastPos;
iLastPos = copy ( listIntegers.begin () // start of source range
, listIntegers.end () // end of source range
, vecIntegers.begin () );// start of destination range
// Now, use copy_backward to copy the same list into the vector
copy_backward ( listIntegers.begin ()
, listIntegers.end ()
, vecIntegers.end () );
cout << "Elements in the destination (vector) after copy: " << endl;
// Display all elements in the collection
vector <int>::const_iterator iDestElementLocator;
for ( iDestElementLocator = vecIntegers.begin ()
; iDestElementLocator != vecIntegers.end ()
; ++ iDestElementLocator )
cout << *iDestElementLocator << ' ';
cout << endl << endl;
/*
Remove all instances of '0':
std::remove does not change the size of the container,
it simply moves elements forward to fill gaps created
and returns the new 'end' position.
*/
vector <int>::iterator iNewEnd;
iNewEnd = remove (vecIntegers.begin (), vecIntegers.end (), 0);
// Use this new 'end position' to resize vector
vecIntegers.erase (iNewEnd, vecIntegers.end ());
// Remove all odd numbers from the vector using remove_if
iNewEnd = remove_if (vecIntegers.begin (), vecIntegers.end (),
IsOdd <int>); // The predicate
vecIntegers.erase (iNewEnd , vecIntegers.end ());
cout << "Elements in the destination (vector) after remove: " << endl;
// Display all elements in the collection
for ( iDestElementLocator = vecIntegers.begin ()
; iDestElementLocator != vecIntegers.end ()
; ++ iDestElementLocator )
cout << *iDestElementLocator << ' ';
return 0;
}
Replacing Values and Replacing Element Given a Condition
replace and replace_if are the STL algorithms that can replace elements in a collection that are equivalent to a supplied value or satisfy a given condition, respectively. Whereas the former replaces elements based on the retunr value of the comparision operator ==, the later needs a user-specified unary predicate that returns true for every value that needs to be replaced.
#include <iostream>
#include <algorithm>
#include <vector>
// The unary predicate used by replace_if to replace even numbers
bool IsEven (const int & nNum)
{
return ((nNum % 2) == 0);
}
int main ()
{
using namespace std;
// Initialize a sample vector with 6 elements
vector <int> vecIntegers (6);
// fill first 3 elements with value 8
fill (vecIntegers.begin (), vecIntegers.begin () + 3, 8);
// fill last 3 elements with value 5
fill_n (vecIntegers.begin () + 3, 3, 5);
// shuffle the container
random_shuffle (vecIntegers.begin (), vecIntegers.end ());
cout << "The initial contents of the vector are: " << endl;
for (size_t nIndex = 0; nIndex < vecIntegers.size (); ++ nIndex)
{
cout << "Element [" << nIndex << "] = ";
cout << vecIntegers [nIndex] << endl;
}
cout << endl << "Using 'std::replace' to replace value 5 by 8" << endl;
replace (vecIntegers.begin (), vecIntegers.end (), 5, 8);
cout << "Using 'std::replace_if' to replace even values by -1" << endl;
replace_if (vecIntegers.begin (), vecIntegers.end (), IsEven, -1);
cout << endl << "Contents of the vector after replacements:" << endl;
for (size_t nIndex = 0; nIndex < vecIntegers.size (); ++ nIndex)
{
cout << "Element [" << nIndex << "] = ";
cout << vecIntegers [nIndex] << endl;
}
return 0;
}
Sorting and Searching in a Sorted Collection, and Erasing Duplicates
Sorting and searching a sorted range (for sake of performance) are requirements that come up in practical applications way too often. Very often you have an array of information that needs to be sorted -- say, for presentation' sake. Sometimes this sorted information needs to be filtered on user request.
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
int main ()
{
using namespace std;
typedef vector <string> VECTOR_STRINGS;
// A vector of strings
VECTOR_STRINGS vecNames;
// Insert sample values
vecNames.push_back ("John Doe");
vecNames.push_back ("Jack Nicholson");
vecNames.push_back ("Sean Penn");
vecNames.push_back ("Anna Hoover");
// insert a duplicate into the vector
vecNames.push_back ("Jack Nicholson");
cout << "The initial contents of the vector are:" << endl;
for (size_t nItem = 0; nItem < vecNames.size (); ++ nItem)
{
cout << "Name [" << nItem << "] = \"";
cout << vecNames [nItem] << "\"" << endl;
}
cout << endl;
// sort the names using std::sort
sort (vecNames.begin (), vecNames.end ());
cout << "The sorted vector contains names in the order:" << endl;
for (size_t nItem = 0; nItem < vecNames.size (); ++ nItem)
{
cout << "Name [" << nItem << "] = \"";
cout << vecNames [nItem] << "\"" << endl;
}
cout << endl;
cout << "Searching for \"John Doe\" using 'binary_search':" << endl;
bool bElementFound = binary_search (vecNames.begin (), vecNames.end (),
"John Doe");
// Check if search found a match
if (bElementFound)
cout << "Result: \"John Doe\" was found in the vector!" << endl;
else
cout << "Element not found " << endl;
cout << endl;
VECTOR_STRINGS::iterator iNewEnd;
// Erase adjacent duplicates
iNewEnd = unique (vecNames.begin (), vecNames.end ());
vecNames.erase (iNewEnd, vecNames.end ());
cout << "The contents of the vector after using 'unique':" << endl;
for (size_t nItem = 0; nItem < vecNames.size (); ++ nItem)
{
cout << "Name [" << nItem << "] = \"";
cout << vecNames [nItem] << "\"" << endl;
}
return 0;
}
Partitioning a Range
std::partition helps partition an input range into two sections: one that satisfies a unary predicate and an another that dosen't. std::partition, however, dose not guarantee the relative order of elements within each partion. To maintain relative order, use std::stable_partition instead.
#include <algorithm>
#include <vector>
#include <iostream>
bool IsEven (const int& nNumber)
{
return ((nNumber % 2) == 0);
}
int main ()
{
using namespace std;
// a sample collection...
vector <int> vecIntegers;
// fill sample values 0 - 9, in that order
for (int nNum = 0; nNum < 10; ++ nNum)
vecIntegers.push_back (nNum);
// a copy of the sample vector
vector <int> vecCopy (vecIntegers);
// separate even values from the odd ones - even comes first.
partition (vecIntegers.begin (), vecIntegers.end (), IsEven);
// display contents
cout << "The contents of the vector after using 'partition' are:";
cout << endl << "{";
for (size_t nItem = 0; nItem < vecIntegers.size (); ++ nItem)
cout << vecIntegers [nItem] << ' ';
cout << "}" << endl << endl;
// now use stable_partition on the vecCopy - maintains relative order
stable_partition (vecCopy.begin (), vecCopy.end (), IsEven);
// display contents of vecCopy
cout << "The effect of using 'stable_partition' is: " << endl << "{";
for (size_t nItem = 0; nItem < vecCopy.size (); ++ nItem)
cout << vecCopy [nItem] << ' ';
cout << "}" << endl << endl;
return 0;
}
Inserting Elements in a Sorted Collection
It is often necessary, if not important, that elements inserted in a sorted collection be inserted at the correct positions. STL supplies functions such as lower_bound and upper_bound to assist in meeting that need.
low_bound and upper_bound hence return the minimak and the maximal positions in a sorted range where an element can be inserted without breaking the order of the sort.
#include <algorithm>
#include <list>
#include <string>
#include <iostream>
int main ()
{
using namespace std;
typedef list <string> LIST_STRINGS;
// A sample list of strings
LIST_STRINGS listNames;
// Insert sample values
listNames.push_back ("John Doe");
listNames.push_back ("Brad Pitt");
listNames.push_back ("Jack Nicholson");
listNames.push_back ("Sean Penn");
listNames.push_back ("Anna Hoover");
// Sort all the names in the list
listNames.sort ();
cout << "The sorted contents of the list are: " << endl;
LIST_STRINGS::iterator iNameLocator;
for ( iNameLocator = listNames.begin ()
; iNameLocator != listNames.end ()
; ++ iNameLocator )
{
cout << "Name [" << distance (listNames.begin (), iNameLocator);
cout << "] = \"" << *iNameLocator << "\"" << endl;
}
cout << endl;
LIST_STRINGS::iterator iMinInsertPosition;
// The closest / lowest position where the element can be inserted
iMinInsertPosition = lower_bound ( listNames.begin (), listNames.end ()
, "Brad Pitt" );
LIST_STRINGS::iterator iMaxInsertPosition;
// The farthest / highest position where an element may be inserted
iMaxInsertPosition = upper_bound ( listNames.begin (), listNames.end ()
, "Brad Pitt" );
cout << "The lowest index where \"Brad Pitt\" can be inserted is: ";
cout << distance (listNames.begin (), iMinInsertPosition) << endl;
cout << "The highest index where \"Brad Pitt\" can be inserted is: ";
cout << distance (listNames.begin (), iMaxInsertPosition) << endl;
cout << endl;
cout << "Inserting \"Brad Pitt\" in the sorted list:" << endl;
listNames.insert (iMinInsertPosition, "Brad Pitt");
cout << "The contents of the list now are: " << endl;
for ( iNameLocator = listNames.begin ()
; iNameLocator != listNames.end ()
; ++ iNameLocator )
{
cout << "Name [" << distance (listNames.begin (), iNameLocator);
cout << "] = \"" << *iNameLocator << "\"" << endl;
}
return 0;
}