Algorithm Selection

Conor Hoekstra · March 7, 2021

Yesterday, a contributor to one of my open source projects (Robert) pointed out to me that std::find_if(f, l, pred) != l is just std::any_of(f, l, pred). I totally missed this while refactoring even though it is implicitly covered in my STL Algorithm Cheatsheet:

image

So thank you Robert for pointing it out! And because of this I decided to write up a short blog about the following:

Always choose the most specialized algorithm.

Below is a diagram that captures the specializations / relationships between std::find_if, std::any_of and all of the std::mismatch algorithms.

image

I assert that you should always choose the most specialized algorithm, aka the algorithm that is furthest from mismatch that works for your problem. Here are a couple of C++ snippets that show how you can implement these in terms of each other. The implementations also double as examples of code that should just be replaced by the more specialized / better named algorithm.

auto adjacent_find(auto f, auto l, auto pred) {
    return std::mismatch(f, std::prev(l), std::next(f), pred).first;
}

auto is_sorted_until(auto f, auto l, auto comp = std::less_equal{}) {
    return std::adjacent_find(f, l, std::not_fn(comp));
}

auto is_sorted(auto f, auto l, auto comp = std::less_equal{}) {
    return std::is_sorted_until(f, l, comp) == l;
}

auto equal(auto f, auto l, auto f2) {
    return std::mismatch(f, l, f2, std::not_equal_to{}).first == l;
}

auto find_if(auto f, auto l, auto pred) {
    return std::mismatch(f, l, f, [&](auto a, auto b) {
        return pred(a);
    }).first;
}

auto any_of(auto f, auto l, auto pred) {
    return std::find_if(f, l, pred) != l;
}

auto find(auto f, auto l, auto val) {
    return std::find_if(f, l, [&](auto e) { return e == val; });
}

Godbolt link


Update

Stephan T. Lavavej (aka STL, one of the MSVC Standard Library implementers) pointed out that is_partitioned_until and is_partitioned could be added to the diagram.

So I updated my diagram. Note that is_partitioned_until isn’t actually in the C++ Standard Library which is why it is in grey. The code snippets have been provided as well.

image

auto is_partitioned_until(auto f, auto l, auto pred) {
    return std::next(std::adjacent_find(f, l, [&](auto a, auto b) { 
        return not pred(a) and pred(b);
    }));
}

auto is_partitioned(auto f, auto l, auto pred) {
    return is_partitioned_until(f, l, pred) != l;
}

Updated Godbolt

Finally, I have received a couple comments on Reddit and Twitter asking “Why?” or stating that I did not provide any reasoning. That was an oversight on my part.

The motivation for choosing the most specialized algorithm is that it leads to simpler and more readable code.

The more specialized algorithms contain more meaning and therefore communicates more information to future readers of the code. For example, is_sorted is much more meaningful than adjacent_find with the greater function object or the less_equal function object + not_fn. Furthermore, code using more specialized algorithms is simpler. An example of this is that using many of the specialized algorithms avoids having to write != container.end().

That’s all. Happy coding!

Feel free to leave a comment on the reddit thread or tweet.

Twitter, Facebook