So, to be clear, loops for control flow are sometimes unavoidable, and more often would become unnecessarily opaque -- that said, a lot of programming challenges that are solved with control flow loops can be made into transformation loops without confusion, and will often become composable as a result.
To whit:
I think an example of the type of modification I am talking about is in order. Let's say I have this loop:for (float x : vector_of_floats) { std::cout << x << ' '; } std::cout << '\n';
So, you've got a bit of a bug (feature?) here with a trailing ' ' between the last value and the newline, but neither are good for composability in any case. I'd re-write this as follows:
auto it = vector_of_floats.begin();
if(it != vector_of_floats.end())
{
std::cout << *it;
for_each(++it, vector_of_floats.end(), [](float f) {
std::cout << " " << f;
});
}
The big thing here is that we aren't relying on indexing.
Now I want to format it differently, like this: "(4, 3, -1)". Now I need to do something different either for the first element or for the last one, so it's probably easiest to change the loop to indices.
std::cout << '('; for (size_t i = 0; i < vector_of_floats.size(); ++i) { if (i > 0) std::cout << ", "; std::cout << vector_of_floats[i]; } std::cout << ")\n";
Since my above is basically equivilent to this already, let me take the composability to its logical conclusion:
template <class I>
void print_separated_values(I it, I end, string separator)
{
if(it == end)
return;
std::cout << *it;
for_each(++it, end, [](float f) {
std::cout << separator << f;
});
}
// usage:
std::cout << "("; print_separated_values(vector_of_floats.begin(), vector_of_floats.end(), ", "); std::cout << ")";
If I then need to have a mask that indicates which indices are active at this time and I only need to print those, I would dostd::cout << '('; bool some_element_printed = false; for (size_t i = 0; i < vector_of_floats.size(); ++i) { if (!active[i]) continue; if (some_element_printed) std::cout << ", "; else some_element_printed = true; std::cout << vector_of_floats[i]; } std::cout << ")\n";
All I need in my solution at this point is a container which only contains active elements, or one which is partitioned with the desired elements up front. The algorithm std::stable_partition gives us that, with a slightly odd predicate -- since you're passing in the active array (I assume you mean that elements are boolean, and true if that index is to be included) we need to walk it within the predicate, so we init-capture a starting index which will be incremented each time the predicate is called, and the predicate returns what it finds at that index, selecting the active values from vector_of_floats so that they appear in order at the beginning of the vector.
Altogether that gives:
template <class I>
void print_separated_values(I it, I end, string separator)
{
if(it == end)
return;
std::cout << *it;
for_each(++it, end, [](float f) {
std::cout << separator << f;
});
}
// usage: (note: lambda init capture is a C++14 feature)
auto last = stable_partition(vector_of_floats.begin(), vector_of_floats.end(), [int i = 0](float /*ignore*/) { return active[i++]; });
std::cout << "("; print_separated_values(vector_of_floats.begin(), last, ", "); std::cout << ")";
Now, we didn't save any lines of code (and I've not run that through a compiler), but I argue that the code in my solution is better for not mixing all those responsibilities inside a single loop, and because we're left with a generalized function for printing a list of values separated by arbitrary characters.
It would take some time to attack your NegaMax, but I bet it can be done. As an aside, there aren't all that many algorithms in the standard library, but its also valid to implement your own in the same style (many of which can be implemented as combinations of those provided).
Sean Parent has given several great talks on this very subject, such as C++ Seasoning (youtube).