std::ranges::stable_partition

From cppreference.com
< cpp‎ | algorithm‎ | ranges
 
 
Algorithm library
Constrained algorithms and algorithms on ranges (C++20)
Constrained algorithms, e.g. ranges::copy, ranges::sort, ...
Execution policies (C++17)
Non-modifying sequence operations
Batch operations
(C++17)
Search operations
(C++11)                (C++11)(C++11)

Modifying sequence operations
Copy operations
(C++11)
(C++11)
Swap operations
Transformation operations
Generation operations
Removing operations
Order-changing operations
(until C++17)(C++11)
(C++20)(C++20)
Sampling operations
(C++17)

Sorting and related operations
Partitioning operations
Sorting operations
Binary search operations
(on partitioned ranges)
Set operations (on sorted ranges)
Merge operations (on sorted ranges)
Heap operations
Minimum/maximum operations
(C++11)
(C++17)
Lexicographical comparison operations
Permutation operations
C library
Numeric operations
Operations on uninitialized memory
 
Constrained algorithms
All names in this menu belong to namespace std::ranges
Non-modifying sequence operations
Modifying sequence operations
Partitioning operations
stable_partition
Sorting operations
Binary search operations (on sorted ranges)
       
       
Set operations (on sorted ranges)
Heap operations
Minimum/maximum operations
       
       
Permutation operations
Fold operations
Numeric operations
(C++23)            
Operations on uninitialized storage
Return types
 
Defined in header <algorithm>
Call signature
template< std::bidirectional_iterator I, std::sentinel_for<I> S,

          class Proj = std::identity,
          std::indirect_unary_predicate<std::projected<I, Proj>> Pred >
requires std::permutable<I>
ranges::subrange<I>

    stable_partition( I first, S last, Pred pred, Proj proj = {} );
(1) (since C++20)
(constexpr since C++26)
template< ranges::bidirectional_range R, class Proj = std::identity,

          std::indirect_unary_predicate<
              std::projected<ranges::iterator_t<R>, Proj>> Pred >
requires std::permutable<ranges::iterator_t<R>>
ranges::borrowed_subrange_t<R>

    stable_partition( R&& r, Pred pred, Proj proj = {} );
(2) (since C++20)
(constexpr since C++26)
1) Reorders the elements in the range [firstlast) in such a way that the projection proj of all elements for which the predicate pred returns true precede the projection proj of elements for which predicate pred returns false. The algorithms is stable, i.e. the relative order of elements is preserved.
2) Same as (1), but uses r as the range, as if using ranges::begin(r) as first and ranges::end(r) as last.

The function-like entities described on this page are niebloids, that is:

In practice, they may be implemented as function objects, or with special compiler extensions.

Parameters

first, last - the range of elements to reorder
r - the range of elements to reorder
pred - predicate to apply to the projected elements
proj - projection to apply to the elements

Return value

1) An object equal to {pivot, last}, where pivot is an iterator to the first element of the second group.
2) Same as (1) if r is an lvalue or of a borrowed_range type. Otherwise returns std::ranges::dangling.

Complexity

Given N = ranges::distance(first, last), the complexity is at worst N·log(N) swaps, and only 𝓞(N) swaps in case an extra memory buffer is used. Exactly N applications of the predicate pred and projection proj.

Notes

This function attempts to allocate a temporary buffer. If the allocation fails, the less efficient algorithm is chosen.

Feature-test macro Value Std Feature
__cpp_lib_constexpr_algorithms 202306L (C++26) constexpr stable sorting

Possible implementation

This implementation does not use extra memory buffer and as such can be less efficient. See also the implementation in MSVC STL and libstdc++.

struct stable_partition_fn
{
    template<std::bidirectional_iterator I, std::sentinel_for<I> S,
             class Proj = std::identity,
             std::indirect_unary_predicate<std::projected<I, Proj>> Pred>
    requires std::permutable<I>
    constexpr ranges::subrange<I>
        operator()(I first, S last, Pred pred, Proj proj = {}) const
    {
        first = ranges::find_if_not(first, last, pred, proj);
        I mid = first;
        while (mid != last)
        {
            mid = ranges::find_if(mid, last, pred, proj);
            if (mid == last)
                break;
            I last2 = ranges::find_if_not(mid, last, pred, proj);
            ranges::rotate(first, mid, last2);
            first = ranges::next(first, ranges::distance(mid, last2));
            mid = last2;
        }
        return {std::move(first), std::move(mid)};
    }
 
    template<ranges::bidirectional_range R, class Proj = std::identity,
             std::indirect_unary_predicate<
                 std::projected<ranges::iterator_t<R>, Proj>> Pred>
    requires std::permutable<ranges::iterator_t<R>>
    constexpr ranges::borrowed_subrange_t<R>
        operator()(R&& r, Pred pred, Proj proj = {}) const
    {
        return (*this)(ranges::begin(r), ranges::end(r), std::move(pred), std::move(proj));
    }
};
 
inline constexpr stable_partition_fn stable_partition {};

Example

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
 
namespace rng = std::ranges;
 
template<std::permutable I, std::sentinel_for<I> S>
constexpr void stable_sort(I first, S last)
{
    if (first == last)
        return;
 
    auto pivot = *rng::next(first, rng::distance(first, last) / 2, last);
    auto left = [pivot](const auto& em) { return em < pivot; };
    auto tail1 = rng::stable_partition(first, last, left);
    auto right = [pivot](const auto& em) { return !(pivot < em); };
    auto tail2 = rng::stable_partition(tail1, right);
 
    stable_sort(first, tail1.begin());
    stable_sort(tail2.begin(), tail2.end());
}
 
void print(const auto rem, auto first, auto last, bool end = true)
{
    std::cout << rem;
    for (; first != last; ++first)
        std::cout << *first << ' ';
    std::cout << (end ? "\n" : "");
}
 
int main()
{
    const auto original = {9, 6, 5, 2, 3, 1, 7, 8};
 
    std::vector<int> vi {};
    auto even = [](int x) { return 0 == (x % 2); };
 
    print("Original vector:\t", original.begin(), original.end(), "\n");
 
    vi = original;
    const auto ret1 = rng::stable_partition(vi, even);
    print("Stable partitioned:\t", vi.begin(), ret1.begin(), 0);
    print("│ ", ret1.begin(), ret1.end());
 
    vi = original;
    const auto ret2 = rng::partition(vi, even);
    print("Partitioned:\t\t", vi.begin(), ret2.begin(), 0);
    print("│ ", ret2.begin(), ret2.end());
 
 
    vi = {16, 30, 44, 30, 15, 24, 10, 18, 12, 35};
    print("Unsorted vector: ", vi.begin(), vi.end());
 
    stable_sort(rng::begin(vi), rng::end(vi));
    print("Sorted vector:   ", vi.begin(), vi.end());
}

Possible output:

Original vector:        9 6 5 2 3 1 7 8
Stable partitioned:     6 2 8 │ 9 5 3 1 7
Partitioned:            8 6 2 │ 5 3 1 7 9
Unsorted vector: 16 30 44 30 15 24 10 18 12 35
Sorted vector:   10 12 15 16 18 24 30 30 35 44

See also

divides a range of elements into two groups
(niebloid)
copies a range dividing the elements into two groups
(niebloid)
determines if the range is partitioned by the given predicate
(niebloid)
divides elements into two groups while preserving their relative order
(function template)