Add etl::stable partition (#1466)

* Added two versions of etl::stable_partition

One is O(N) time , O(N) space
The other is O(Nlog(N)) time

* Added partition_move

* Updates to tests and documentation

* Review changes

* std::size -> sizeof

* etl::size -> ETL_OR_STL::size

* Changed index array typer to std::array

---------

Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com>
This commit is contained in:
John Wellbelove 2026-06-20 10:27:20 +01:00 committed by GitHub
parent 55429eddd2
commit 2dabc42cbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 488 additions and 22 deletions

View File

@ -886,6 +886,22 @@ bool none_of(TIterator begin, TIterator end, TUnaryPredicate predicate)
**Description**
Checks if `predicate` returns `true` for no elements in the range [`begin`, `end`).
## next_permutation
```cpp
template <typename TIterator, typename TCompare>
ETL_CONSTEXPR14 bool next_permutation(TIterator first, TIterator last, TCompare compare)
```
**Description**
Creates the range [`first`, `last`) into the next permutation.
`compare` should return `true` if its first argument is logically less that its second.
**Complexity**
O(N)
At most N/2 swaps.
**Return**
`true` if there is a next permutation.
## is_permutation
```cpp
@ -940,6 +956,70 @@ bool is_permutation(TIterator1 begin1,
Returns `true` if there exists a permutation of the elements in the range [`begin1`, `end1`) that makes that range equal to the second range.
Uses `predicate` to checks elements in the range.
## partition
```cpp
ETL_CONSTEXPR14 TIterator partition(TIterator first, TIterator last, TPredicate predicate)
```
**Description**
Reorders elements in a range based on a predicate.
It moves all elements that satisfy the predicate to the front, and those that do not to the back.
Does not guarantee to keep the original relative order.
**Complexity**
O(N)
**Return**
An iterator to the start of the group that do not satisfy the predicate.
## stable_partition
### In-place
```cpp
template <typename TIterator, typename TPredicate>
ETL_CONSTEXPR14 TIterator stable_partition(TIterator first,
TIterator last,
TPredicate predicate)
```
**Description**
Reorders elements in a range based on a predicate.
It moves all elements that satisfy the predicate to the front, and those that do not to the back.
Keeps the original relative order.
In-place
**Complexity**
Recursive function.
O(NlogN) time.
O(NlogN) space.
**Return**
An iterator to the start of the group that do not satisfy the predicate.
### External buffer
```cpp
template <typename TIterator, typename TPredicate>
ETL_CONSTEXPR14 TIterator stable_partition(TIterator first,
TIterator last,
TIterator buffer_first,
TIterator buffer_last,
TPredicate predicate)
```
**Description**
Reorders elements in a range based on a predicate.
It moves all elements that satisfy the predicate to the front, and those that do not to the back.
Keeps the original relative order.
Requires a user supplied buffer that must be at least the same size as the range [`first`, `last`).
**Complexity**
Non-recursive function.
O(N) time.
O(N) space.
Raises an `etl::stable_partition_buffer_too_small` is the buffer is not large enough to accommodate the range [`first`, `last`).
**Return**
An iterator to the start of the group that do not satisfy the predicate.
## is_partitioned
```cpp
@ -977,7 +1057,33 @@ pair<TDestinationTrue, TDestinationFalse> partition_copy(TSource begin
**Description**
Copies the elements from the range [`begin`, `end`) to two different ranges depending on the value returned by the `predicate`.
The elements that satisfy `predicate` are copied to the range beginning at `destination_true`.
The rest of the elements are copied to the range beginning at `destination_false`.
The rest of the elements are copied to the range beginning at `destination_false`.
This partition is stable.
**Complexity**
O(N)
## partition_move
```cpp
template <typename TSource,
typename TDestinationTrue,
typename TDestinationFalse,
typename TUnaryPredicate>
pair<TDestinationTrue, TDestinationFalse> partition_move(TSource begin,
TSource end,
TDestinationTrue destination_true,
TDestinationFalse destination_false,
TUnaryPredicate predicate)
```
**Description**
Moves the elements from the range [`begin`, `end`) to two different ranges depending on the value returned by the `predicate`.
The elements that satisfy `predicate` are moved to the range beginning at `destination_true`.
The rest of the elements are moved to the range beginning at `destination_false`.
This partition is stable.
**Complexity**
O(N)
## partition_transform
**ETL extension**
@ -1000,9 +1106,10 @@ pair<TDestinationTrue, TDestinationFalse>
TUnaryPredicate predicate)
```
**Description**
Transforms data from the source to one of two destinations.
If `predicate` returns `true` then the source data if modified by `function_true` and stored in `destination_true`.
If `predicate` returns `false` then the source data if modified by `function_false` and stored in `destination_false`.
Transforms and copies data from the source to one of two destinations.
If `predicate` returns `true` then the source data is modified by `function_true` and stored in `destination_true`.
If `predicate` returns `false` then the source data is modified by `function_false` and stored in `destination_false`.
This partition is stable.
---
@ -1026,9 +1133,10 @@ pair<TDestinationTrue, TDestinationFalse>
TBinaryPredicate predicate)
```
**Description**
Transforms data from the source to one of two destinations.
If `predicate` returns `true` then the source data if modified by `function_true` and stored in `destination_true`.
If `predicate` returns `false` then the source data if modified by `function_false` and stored in `destination_false`.
Transforms and copies data from the source to one of two destinations.
If `predicate` returns `true` then the source data is modified by `function_true` and stored in `destination_true`.
If `predicate` returns `false` then the source data is modified by `function_false` and stored in `destination_false`.
This partition is stable.
## rotate
@ -1107,7 +1215,7 @@ TOutputIterator transform_if(TInputIterator i_begin,
```
**Description**
Transforms data from the source based on a predicate.
If `predicate` returns `true` then the source data if modified by function and stored in the output range.
If `predicate` returns `true` then the source data is modified by `function` and stored in the output range.
If `predicate` returns `false` then the source data is ignored.
---
@ -1128,7 +1236,7 @@ TOutputIterator transform_if(TInputIterator1 i_begin1,
```
**Description**
Transforms data from the source based on a predicate.
If `predicate` returns `true` then the source data if modified by function and stored in the output range.
If `predicate` returns `true` then the source data is modified by `function` and stored in the output range.
If `predicate` returns `false` then the source data is ignored.
## transform_n_if
@ -1149,7 +1257,7 @@ TOutputIterator transform_if(TInputIterator i_begin,
```
**Description**
Transforms data from the source based on a predicate for `n` items.
If `predicate` returns `true` then the source data if modified by function and stored in the output range.
If `predicate` returns `true` then the source data is modified by `function` and stored in the output range.
If `predicate` returns `false` then the source data is ignored.
---
@ -1171,7 +1279,7 @@ TOutputIterator transform_if(TInputIterator1 i_begin1,
```
**Description**
Transforms data from the source based on a predicate for `n` items.
If `predicate` returns `true` then the source data if modified by function and stored in the output range.
If `predicate` returns `true` then the source data is modified by `function` and stored in the output range.
If `predicate` returns `false` then the source data is ignored.
## shell_sort

View File

@ -98,6 +98,22 @@ namespace etl
//*****************************************************************************
namespace etl
{
struct stable_partition_exception : etl::exception
{
stable_partition_exception(string_type reason_, string_type file_, numeric_type line_)
: etl::exception(reason_, file_, line_)
{
}
};
struct stable_partition_buffer_too_small : stable_partition_exception
{
stable_partition_buffer_too_small(string_type file_, numeric_type line_)
: stable_partition_exception(ETL_ERROR_TEXT("stable_partition:buffer too small", ETL_ALGORITHM_FILE_ID"A"), file_, line_)
{
}
};
namespace private_algorithm
{
template <bool use_swap>
@ -2116,6 +2132,34 @@ namespace etl
return ETL_OR_STD::pair<TDestinationTrue, TDestinationFalse>(destination_true, destination_false);
}
//***************************************************************************
/// Moves the elements from the range (begin, end) to two different ranges
/// depending on the value returned by the predicate.<br>
///\ingroup algorithm
//***************************************************************************
template <typename TSource, typename TDestinationTrue, typename TDestinationFalse, typename TUnaryPredicate>
ETL_CONSTEXPR14 ETL_OR_STD::pair<TDestinationTrue, TDestinationFalse> partition_move(TSource begin, TSource end, TDestinationTrue destination_true,
TDestinationFalse destination_false, TUnaryPredicate predicate)
{
while (begin != end)
{
if (predicate(*begin))
{
*destination_true = etl::move(*begin);
++destination_true;
}
else
{
*destination_false = etl::move(*begin);
++destination_false;
}
++begin;
}
return ETL_OR_STD::pair<TDestinationTrue, TDestinationFalse>(destination_true, destination_false);
}
//***************************************************************************
/// copy_if
///\ingroup algorithm
@ -3058,7 +3102,7 @@ namespace etl
}
//***************************************************************************
/// Transforms the elements from the range (begin, end) to two different
/// Transforms and copies the elements from the range (begin, end) to two different
/// ranges depending on the value returned by the predicate.<br>
///\ingroup algorithm
//***************************************************************************
@ -3088,7 +3132,7 @@ namespace etl
}
//***************************************************************************
/// Transforms the elements from the ranges (begin1, end1) & (begin2)
/// Transforms and copies the elements from the ranges (begin1, end1) & (begin2)
/// to two different ranges depending on the value returned by the predicate.
///\ingroup algorithm
//***************************************************************************
@ -3531,6 +3575,85 @@ namespace etl
return first;
}
//***************************************************************************
/// stable_partition
/// O(NlogN) time.
/// In-place.
//***************************************************************************
template <typename TIterator, typename TPredicate>
ETL_CONSTEXPR14 TIterator stable_partition(TIterator first, TIterator last, TPredicate predicate)
{
typename etl::iterator_traits<TIterator>::difference_type n = etl::distance(first, last);
if (n <= 1)
{
// Empty or single-element range: trivially partitioned either way.
return first;
}
TIterator mid = first;
etl::advance(mid, n / 2);
etl::stable_partition(first, mid, predicate);
etl::stable_partition(mid, last, predicate);
TIterator left_partition_start = etl::find_if_not(first, last, predicate);
TIterator right_partition_start = etl::find_if(left_partition_start, last, predicate);
TIterator right_partition_end = etl::find_if_not(right_partition_start, last, predicate);
return etl::rotate(left_partition_start, right_partition_start, right_partition_end);
}
//***************************************************************************
/// stable_partition
/// O(N) time.
/// O(N) extra space.
//***************************************************************************
template <typename TIterator, typename TPredicate>
ETL_CONSTEXPR14 TIterator stable_partition(TIterator first, TIterator last, TIterator buffer_first, TIterator buffer_last, TPredicate predicate)
{
typename etl::iterator_traits<TIterator>::difference_type n = etl::distance(first, last);
ETL_ASSERT((n <= etl::distance(buffer_first, buffer_last)), ETL_ERROR(stable_partition_buffer_too_small));
if (n <= 1)
{
// Empty or single-element range.
return first;
}
TIterator input_first = first;
TIterator buffer_true = buffer_first;
// Find where the partition point will be in the buffer.
typename etl::iterator_traits<TIterator>::difference_type true_count = etl::count_if(first, last, predicate);
TIterator buffer_false = buffer_first + true_count;
// Move them to the correct places in the temporary buffer.
while (first != last)
{
if (predicate(*first))
{
*buffer_true++ = etl::move(*first);
}
else
{
*buffer_false++ = etl::move(*first);
}
++first;
}
// Move them back to the original range.
TIterator buffer_end = buffer_first;
etl::advance(buffer_end, n);
etl::move(buffer_first, buffer_end, input_first);
etl::advance(input_first, true_count);
return input_first;
}
//*********************************************************
namespace private_algorithm
{

View File

@ -2853,18 +2853,78 @@ namespace
int data4[] = {0, 0, 0, 0, 0, 0, 0, 0};
int data5[] = {0, 0, 0, 0, 0, 0, 0, 0};
std::partition_copy(std::begin(data1), std::end(data1), std::begin(data2), std::begin(data3),
std::bind(std::greater<int>(), std::placeholders::_1, 4));
etl::partition_copy(std::begin(data1), std::end(data1), std::begin(data4), std::begin(data5),
std::bind(std::greater<int>(), std::placeholders::_1, 4));
bool complete = false;
bool are_equal;
while (!complete)
{
std::partition_copy(std::begin(data1), std::end(data1), std::begin(data2), std::begin(data3),
std::bind(std::greater<int>(), std::placeholders::_1, 4));
etl::partition_copy(std::begin(data1), std::end(data1), std::begin(data4), std::begin(data5),
std::bind(std::greater<int>(), std::placeholders::_1, 4));
are_equal = std::equal(std::begin(data2), std::end(data2), std::begin(data4));
CHECK(are_equal);
bool are_equal;
are_equal = std::equal(std::begin(data3), std::end(data3), std::begin(data5));
CHECK(are_equal);
are_equal = std::equal(std::begin(data2), std::end(data2), std::begin(data4));
CHECK(are_equal);
are_equal = std::equal(std::begin(data3), std::end(data3), std::begin(data5));
CHECK(are_equal);
complete = !std::next_permutation(std::begin(data1), std::end(data1));
}
}
//*************************************************************************
TEST(partition_move)
{
std::array<int, 8> indexes = {1, 2, 3, 4, 5, 6, 7, 8};
TestDataM<int> std_input[] = {TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0),
TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0)};
TestDataM<int> etl_input[] = {TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0),
TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0)};
TestDataM<int> std_true[] = {TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0),
TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0)};
TestDataM<int> std_false[] = {TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0),
TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0)};
TestDataM<int> etl_true[] = {TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0),
TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0)};
TestDataM<int> etl_false[] = {TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0),
TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0)};
auto compare = [](const TestDataM<int>& element)
{
return (element.value > 4);
};
bool complete = false;
while (!complete)
{
// Rebuild the input arrays.
for (size_t i = 0; i < indexes.size(); ++i)
{
std_input[i].value = indexes[i];
etl_input[i].value = indexes[i];
}
// Simulate what etl::partition_move should do using stable_partition and move.
auto boundary = std::stable_partition(std::begin(std_input), std::end(std_input), compare);
std::move(std::begin(std_input), boundary, std::begin(std_true));
std::move(boundary, std::end(std_input), std::begin(std_false));
// Execute etl::partition_move.
etl::partition_move(std::begin(etl_input), std::end(etl_input), std::begin(etl_true), std::begin(etl_false), compare);
bool are_equal;
are_equal = std::equal(std::begin(std_true), std::end(std_true), std::begin(etl_true));
CHECK(are_equal);
are_equal = std::equal(std::begin(std_false), std::end(std_false), std::begin(etl_false));
CHECK(are_equal);
complete = !std::next_permutation(std::begin(indexes), std::end(indexes));
}
}
//*************************************************************************
@ -4291,6 +4351,181 @@ namespace
}
}
//*************************************************************************
TEST(stable_partition_in_place_bidirectional_iterator_container)
{
// 40,320 permutations.
std::array<int, 8> initial_data = {0, 1, 2, 3, 4, 5, 6, 7};
std::array<int, 8> std_data = initial_data;
std::array<int, 8> etl_data = initial_data;
bool complete = false;
while (!complete)
{
auto std_pivot = std::stable_partition(std_data.begin(), std_data.end(), [](int i) { return etl::is_even(i); });
auto etl_pivot = etl::stable_partition(etl_data.begin(), etl_data.end(), [](int i) { return etl::is_even(i); });
auto std_distance = std::distance(std_data.begin(), std_pivot);
auto etl_distance = std::distance(etl_data.begin(), etl_pivot);
CHECK_EQUAL(*std_pivot, *etl_pivot);
CHECK_EQUAL(std_distance, etl_distance);
for (auto itr = std_data.begin(); itr != std_pivot; ++itr)
{
CHECK_TRUE((etl::is_even(*itr)));
}
for (auto itr = std_pivot; itr != std_data.end(); ++itr)
{
CHECK_FALSE((etl::is_even(*itr)));
}
complete = !std::next_permutation(initial_data.begin(), initial_data.end());
std_data = initial_data;
etl_data = initial_data;
}
}
//*************************************************************************
TEST(stable_partition_in_place_bidirectional_iterator_container_for_move_only_elements)
{
// 40,320 permutations.
std::array<TestDataM<int>, 8> initial_data = {TestDataM<int>(0), TestDataM<int>(1), TestDataM<int>(2), TestDataM<int>(3),
TestDataM<int>(4), TestDataM<int>(5), TestDataM<int>(6), TestDataM<int>(7)};
std::array<TestDataM<int>, 8> std_data = std::move(initial_data);
std::array<TestDataM<int>, 8> etl_data = std::move(initial_data);
bool complete = false;
while (!complete)
{
auto std_pivot = std::stable_partition(std_data.begin(), std_data.end(), [](const TestDataM<int>& t) { return etl::is_even(t.value); });
auto etl_pivot = etl::stable_partition(etl_data.begin(), etl_data.end(), [](const TestDataM<int>& t) { return etl::is_even(t.value); });
auto std_distance = std::distance(std_data.begin(), std_pivot);
auto etl_distance = std::distance(etl_data.begin(), etl_pivot);
CHECK_EQUAL(*std_pivot, *etl_pivot);
CHECK_EQUAL(std_distance, etl_distance);
for (auto itr = std_data.begin(); itr != std_pivot; ++itr)
{
CHECK_TRUE((etl::is_even(itr->value)));
}
for (auto itr = std_pivot; itr != std_data.end(); ++itr)
{
CHECK_FALSE((etl::is_even(itr->value)));
}
complete = !std::next_permutation(initial_data.begin(), initial_data.end());
std_data = std::move(initial_data);
etl_data = std::move(initial_data);
}
}
//*************************************************************************
TEST(stable_partition_external_buffer_bidirectional_iterator_container)
{
// 40,320 permutations.
std::array<int, 8> initial_data = {0, 1, 2, 3, 4, 5, 6, 7};
std::array<int, 8> buffer = {0, 0, 0, 0, 0, 0, 0, 0};
std::array<int, 8> std_data = initial_data;
std::array<int, 8> etl_data = initial_data;
bool complete = false;
while (!complete)
{
auto std_pivot = std::stable_partition(std_data.begin(), std_data.end(), [](int i) { return etl::is_even(i); });
auto etl_pivot = etl::stable_partition(etl_data.begin(), etl_data.end(), buffer.begin(), buffer.end(), [](int i) { return etl::is_even(i); });
auto std_distance = std::distance(std_data.begin(), std_pivot);
auto etl_distance = std::distance(etl_data.begin(), etl_pivot);
CHECK_EQUAL(*std_pivot, *etl_pivot);
CHECK_EQUAL(std_distance, etl_distance);
for (auto itr = std_data.begin(); itr != std_pivot; ++itr)
{
CHECK_TRUE((etl::is_even(*itr)));
}
for (auto itr = std_pivot; itr != std_data.end(); ++itr)
{
CHECK_FALSE((etl::is_even(*itr)));
}
complete = !std::next_permutation(initial_data.begin(), initial_data.end());
std_data = initial_data;
etl_data = initial_data;
}
}
//*************************************************************************
TEST(stable_partition_external_buffer_bidirectional_iterator_container_for_move_only_elements)
{
// 40,320 permutations.
std::array<TestDataM<int>, 8> initial_data = {TestDataM<int>(0), TestDataM<int>(1), TestDataM<int>(2), TestDataM<int>(3),
TestDataM<int>(4), TestDataM<int>(5), TestDataM<int>(6), TestDataM<int>(7)};
std::array<TestDataM<int>, 8> buffer = {TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0),
TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0), TestDataM<int>(0)};
std::array<TestDataM<int>, 8> std_data = std::move(initial_data);
std::array<TestDataM<int>, 8> etl_data = std::move(initial_data);
bool complete = false;
while (!complete)
{
auto std_pivot = std::stable_partition(std_data.begin(), std_data.end(), [](const TestDataM<int>& t) { return etl::is_even(t.value); });
auto etl_pivot = etl::stable_partition(etl_data.begin(), etl_data.end(), buffer.begin(), buffer.end(),
[](const TestDataM<int>& t) { return etl::is_even(t.value); });
auto std_distance = std::distance(std_data.begin(), std_pivot);
auto etl_distance = std::distance(etl_data.begin(), etl_pivot);
CHECK_EQUAL(std_pivot->value, etl_pivot->value);
CHECK_EQUAL(std_distance, etl_distance);
for (auto itr = std_data.begin(); itr != std_pivot; ++itr)
{
CHECK_TRUE((etl::is_even(itr->value)));
}
for (auto itr = std_pivot; itr != std_data.end(); ++itr)
{
CHECK_FALSE((etl::is_even(itr->value)));
}
complete = !std::next_permutation(initial_data.begin(), initial_data.end());
std_data = std::move(initial_data);
etl_data = std::move(initial_data);
}
}
//*************************************************************************
TEST(stable_partition_external_buffer_too_small)
{
int buffer[7];
int etl_data[8];
CHECK_THROW((etl::stable_partition(std::begin(etl_data), std::end(etl_data), std::begin(buffer), std::end(buffer),
[](int i) { return etl::is_even(i); })),
etl::stable_partition_buffer_too_small);
}
//*************************************************************************
TEST(nth_element_with_default_less_than_comparison)
{