The function I propose allows to find the minimum and maximum values of a collection and to check that it is sorted up to a certain element. Basically it is a combination of std::minmax_element
and std::is_sorted_until
in a single pass function, without any significant overhead compared to a call to std::minmax_element
.
template<typename ForwardIterator, typename Compare = std::less<>>
auto minmax_element_and_is_sorted_until(ForwardIterator first, ForwardIterator last,
Compare compare={})
-> decltype(auto)
{
// Function-local result type, only the names of the
// data members matter
struct result_type
{
ForwardIterator min;
ForwardIterator max;
ForwardIterator sorted_until;
} result = { first, first, last };
// 0 or 1 elements
if (first == last) return result;
auto next = std::next(first);
if (next == last) return result;
// While it is sorted, the min and max are obvious
auto current = first;
while (not compare(*next, *current)) {
++current;
++next;
// The range is fully sorted
if (next == last) {
result.max = current;
return result;
}
}
// The range is not sorted, use a regular minmax_element algorithm
result.min = first;
result.max = current;
result.sorted_until = next;
auto tmp = std::minmax_element(next, last, compare);
if (compare(*tmp.first, *result.min)) {
result.min = tmp.first;
}
if (not compare(*tmp.second, *result.max)) {
result.max = tmp.second;
}
return result;
}
The algorithm minmax_element
was introduced in Boost a long time ago before making its way into C++11. Its principal advantage is that finding both the min and max element of a collection only costs at most \$max(\lfloor \frac{3}{2}(N−1) \rfloor, 0)\$ comparisons vs. approximately \$2N\$ comparisons with separate calls to std::min_element
and std::max_element
.
The algorithm minmax_element_and_is_sorted_until
takes the optimization logic one step further and allows to also find until which element a collection is sorted with virtually no additional comparison, while a separate call to std::is_sorted_until
would have added another \$O(n)\$ comparisons.
While a bit obscure, this algorithm notably helps to optimize counting sort: some flavours of this sorting algorithm first need to know the minimal and maximal values of the collection to sort. In such a case, checking for free whether the collection is sorted allows to return early when the collection is already sorted, which is always something nice to have since it's free.
Do you think anything could be improved with this algorithm, be it a matter of correctness, style or performance?
std::next(first)
and++next
. In "the sorted-loop", an alternative to finding successors to bothcurrent
andnext
would seem to be to drop the pre-loop init of current and always set it tonext
at the top. If comparison is costly, it might be beneficial to compare pairs of elements and only each non-greater to the "global min candidate", each non-lesser to the "global max candidate". To split a hair,minmax_element_and_is_sorted_until()
doesn't check for sorted, but for ascending. \$\endgroup\$std::is_sorted_until
:p \$\endgroup\$current
tonext
though, That said, It might be cheaper to copystd::deque
-like iterators than to increment them. \$\endgroup\$