I recently discovered the exponential search algorithm which can be used to find whether a value exists in an ordered range. An exponential search checks whether a value is smaller than the \$2^n\$th element of the range, then if the searched value is indeed smaller, it performs a binary search in the range \$[2^n, 2^{n-1})\$ to check whether the value exists in this range, otherwise it starts again by comparing the value to the \$2^{n+1}\$th element, etc... Here is my implementation of the algorithm for random-access iterators (I could make it work with forward iterators too but it has a cost):
#include <algorithm>
#include <functional>
#include <iterator>
template<
typename Iterator,
typename T,
typename Compare = std::less<>
>
auto exponential_search(Iterator first, Iterator last,
const T& key, Compare compare={})
-> bool
{
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
difference_type size = std::distance(first, last);
if (first == last) return false;
difference_type bound = 1;
while (bound < size && not compare(key, first[bound]))
{
bound *= 2;
}
auto end = first + std::min(bound, size);
return std::binary_search(first + bound / 2, end, key, compare);
}
However, while implementing this algorithm, I remembered a nice property of binary search, which is notably exploited by the Ford-Johnson merge-insertion sort: searching a value in \$2^n\$ elements and in \$2^{n+1}-1\$ takes the same number of comparisons (e.g. binary search in collections of size \$16\$ and \$31\$ requires the same number of comparisons). Therefore I modified the algorithm so that it would always search in sequences whose size is \$2^n-1\$ to maximize its efficiency:
#include <algorithm>
#include <functional>
#include <iterator>
template<
typename Iterator,
typename T,
typename Compare = std::less<>
>
auto exponential_search(Iterator first, Iterator last,
const T& key, Compare compare={})
-> bool
{
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
difference_type size = std::distance(first, last);
if (first == last) return false;
difference_type bound = 1;
while (bound < size && not compare(key, first[bound]))
{
first += bound;
size -= bound;
bound = std::min((bound + 1) * 2 - 1, size);
}
return std::binary_search(first, first + bound, key, compare);
}
My tests show that it always performs at worst as many comparisons as the original version, and often fewer when looking for the same element in the same sequence (except when looking for the second element of the collection for some reason).
I know that the algorithm could be more efficient when equivalent values appear in the collection, but I didn't find any elegant way to solve this, even though three-way comparators could have been a nice solution.
Is there any way I can improve this algorithm, be it from a style, correctness or efficiency point of view?