Jonathan Boccara's blog

Partitioning with the STL

Published October 10, 2017 - 2 Comments

Daily C++

Partitioning a collection consists in reordering it so that the elements that satisfy a given predicate are moved up to the beginning, and those that don’t satisfy it are moved down after them. The first element that does not satisfy the predicate is called the partition point. This is also the end of the subrange of elements that do satisfy the predicate:

partition STL

Performing a partitioning with the STL

std::partition accepts a range and a predicate, and reorders the elements of the range so that they are partitioned according to this predicate:

template<typename ForwardIterator, typename Predicate>
ForwardIterator partition(ForwardIterator first, ForwardIterator last, Predicate p);

std::partition returns an iterator to the partition point of the reordered range. Its complexity is O(n).

std::partition does not guarantee to keep the order of elements that satisfy (resp. don’t satisfy) the predicate. If you need this guarantee, use std::stable_partition. std::stable_partition also returns an iterator to the partition point of the reordered range.

EDIT: As Jason Turner pointed out when discussing this article on CppCast, std::stable_partition, contrary to the other algorithms, is allowed to attempt to allocate a temporary buffer. Its complexity is then O(n) if it there is enough extra memory to allocate it, and O(n.log(n)) otherwise.

If you need to leave the range unchanged and have the output somewhere else, use std::partition_copy. It writes outputs in 2 ranges: the first one for elements that satisfy the predicate, and the second one for elements that don’t. std::partition_copy returns a pair of iterators, pointing respectively to the end of the first output range and to the end of the second one. Here is its interface:

template<typename InputIt, typename OutputIt1, typename OutputIt2, typename Predicate>
std::pair<OutputIt1, OutputIt2>
        partition_copy(InputIt first, InputIt last,
                       OutputIt first_true, OutputIt first_false,
                       Predicate p);

Checking for partitioning properties of a range

To check whether a range is partitioned according to a certain predicate, use std::is_partitioned. Here is its interface:

template<typename InputIt, typename Predicate>
bool is_partitioned(InputIt first, InputIterator last, Predicate p);

And to get the partition point of a partitioned range, use std::partition_point:

template<typename ForwardIterator, typename Predicate>
ForwardIterator partition_point(ForwardIterator first,
                                ForwardIterator last,
                                Predicate p);

Much like std::is_sorted_until that we saw in Sorting with the STL, Boost adds an is_partitioned_until function. This algorithm takes a range and a predicate, and returns the iterator of the first position from which the range is no longer partitioned. Thanks to Alexander Zaitsev for pointing out this algorithm!

Examples of things that can be achieved with partitioning

lower_bound, upper_bound and equal_range

As pointed out in Elements of Programming, std::lower_bound can be implemented by using partitioning algorithms. Indeed, every element x preceding the the lower bound of a range for a given value a satisfies the predicate x < a. The lower bound is the first element that does not satisfy this predicate, so the lower bound of a is effectively the partition point for the predicate x < a.

So a possible implementation for lower_bound is:

template<typename ForwardIt, typename T>
ForwardIterator lower_bound(ForwardIt first, ForwardIt last, const T& value)
{
    return std::partition_point(first, last, [value](const auto& x){return x < value;});
}

The same applies for std::upper_bound, with the predicate !(a < x).

And lower_bound and upper_bound can themselves be used to implement std::equal_range.

gather

This example is taken from Sean Parent’s very popular talk C++ Seasoning that he gave at GoingNative 2013.

How to gather up at a given position all elements of a range that satisfy a predicate ? That is, how to get from here…:

partition STL

…to there ?

partition gather STL

This can in fact be achieved fairly easily with std::stable_partition.

The idea is to view the initial range as 2 parts: [begin, position[ and [position, end[, and

  • apply a stable partition on [begin, position[, that puts all the elements that satisfy the predicate at the end (so partition with the negation of the predicate)
  • apply a stable partition on [position, end[ that pulls up all the elements that satisfy the element of the range.

Each call to std::stable_partition returns the corresponding partition point, which happen to be respectively the beginning and the end of the gathered range. This range can be returned from the function.

template<typename BidirIterator, typename Predicate>
Range<BidirIterator> gather(BidirIterator first, BidirIterator last,
                            BidirIterator position, Predicate p)
{
    return { std::stable_partition(first, position, std::not_fn(p)),
             std::stable_partition(position, last, p) };
}

(thanks to /u/tcanens on Reddit for pointing out the not_fn function from C++17 that supersedes the old std::not1 to negate a function).

Range being a class that can be initialized with 2 iterators representing a begin and an end, like boost::iterator_range or the one in range-v3 for example. An std::pair of iterators could also be used, like it is for std::equal_range, but in a more clumsy manner (as seen in How to (std::)find something efficiently with the STL for more on this type of interface).

Note that the gather algorithm is available in boost with the boost::algorithm::gather function, that returns a pair of iterators.

In conclusion, knowing how to achieve partitioning with the STL is useful, as this concept appears in more situations than meets the eye. It is yet another tool in our C++ toolbox.

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed