STL provides binary search functions std::lower_bound and std::upper_bound, but I tend not to use them because I\'ve been unable to remember what they do, because their contract
In this case, I think a picture is worth a thousand words. Let's assume we use them to search for 2
in the following collections. The arrows show what iterators the two would return:
So, if you have more than one object with that value already present in the collection, lower_bound
will give you an iterator that refers to the first one of them, and upper_bound
will give an iterator that refers to the object immediately after the last one of them.
This (among other things) makes the returned iterators usable as the hint
parameter to insert
.
Therefore, if you use these as the hint, the item you insert will become the new first item with that value (if you used lower_bound
) or last item with that value (if you used upper_bound
). If the collection didn't contain an item with that value previously, you'll still get an iterator that can be used as a hint
to insert it in the correct position in the collection.
Of course, you can also insert without a hint, but using a hint you get a guarantee that the insertion completes with constant complexity, provided that new item to insert can be inserted immediately before the item pointed to by the iterator (as it will in both these cases).
I accepted Brian's answer, but I just realized another helpful way of thinking about it which adds clarity for me, so I'm adding this for reference.
Think of the returned iterator as pointing, not at the element *iter, but just before that element, i.e. between that element and the preceding element in the list if there is one. Thinking about it that way, the contracts of the two functions become symmetric: lower_bound finds the position of the transition from <val to >=val, and upper_bound finds the position of the transition from <=val to >val. Or to put it another way, lower_bound is the beginning of the range of items that compare equal to val (i.e. the range that std::equal_range returns), and upper_bound is the end of them.
I wish they would talk about it like this (or any of the other good answers given) in the docs; that would make it much less mystifying!
Yes. The question absolutely has a point. When someone gave these functions their names they were thinking only of sorted arrays with repeating elements. If you have an array with unique elements, "std::lower_bound()" acts more like a search for an "upper bound" unless it finds the actual element.
So this is what I remember about these functions:
Failing to read the manual after a month or two since you last used these functions, almost certainly leads to a bug.
the source code actually has a second explanation which I found very helpful to understand the meaning of the function:
lower_bound: Finds the first position in which [val] could be inserted without changing the ordering.
upper_bound: Finds the last position in which [val] could be inserted without changing the ordering.
this [first, last) forms a range which the val could be inserted but still keep the original ordering of the container
lower_bound return "first" i.e. find the "lower boundary of the range"
upper_bound return "last" i.e. find the "upper boundary of the range"
For an array or vector :
std::lower_bound: Returns an iterator pointing to the first element in the range that is
std::upper_bound: Returns an iterator pointing to the first element in the range that is
less than value.(for array or vector in decreasing order)
greater than value.(for array or vector in increasing order)
Consider the sequence
1 2 3 4 5 6 6 6 7 8 9
lower bound for 6 is the position of the first 6.
upper bound for 6 is the position of the 7.
these positions serve as common (begin, end) pair designating the run of 6-values.
Example:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
auto main()
-> int
{
vector<int> v = {1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9};
auto const pos1 = lower_bound( v.begin(), v.end(), 6 );
auto const pos2 = upper_bound( v.begin(), v.end(), 6 );
for( auto it = pos1; it != pos2; ++it )
{
cout << *it;
}
cout << endl;
}