diff --git a/docs/utilities/algorithms.md b/docs/utilities/algorithms.md index e87df6d3..05a4f7ae 100644 --- a/docs/utilities/algorithms.md +++ b/docs/utilities/algorithms.md @@ -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 +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 +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 +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 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 +pair 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 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 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 diff --git a/include/etl/algorithm.h b/include/etl/algorithm.h index 57c58da2..1c544b40 100644 --- a/include/etl/algorithm.h +++ b/include/etl/algorithm.h @@ -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 @@ -2116,6 +2132,34 @@ namespace etl return ETL_OR_STD::pair(destination_true, destination_false); } + //*************************************************************************** + /// Moves the elements from the range (begin, end) to two different ranges + /// depending on the value returned by the predicate.
+ ///\ingroup algorithm + //*************************************************************************** + template + ETL_CONSTEXPR14 ETL_OR_STD::pair 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(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.
///\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 + ETL_CONSTEXPR14 TIterator stable_partition(TIterator first, TIterator last, TPredicate predicate) + { + typename etl::iterator_traits::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 + ETL_CONSTEXPR14 TIterator stable_partition(TIterator first, TIterator last, TIterator buffer_first, TIterator buffer_last, TPredicate predicate) + { + typename etl::iterator_traits::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::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 { diff --git a/test/test_algorithm.cpp b/test/test_algorithm.cpp index abc5f734..fb6f46dc 100644 --- a/test/test_algorithm.cpp +++ b/test/test_algorithm.cpp @@ -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(), std::placeholders::_1, 4)); - etl::partition_copy(std::begin(data1), std::end(data1), std::begin(data4), std::begin(data5), - std::bind(std::greater(), 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(), std::placeholders::_1, 4)); + etl::partition_copy(std::begin(data1), std::end(data1), std::begin(data4), std::begin(data5), + std::bind(std::greater(), 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 indexes = {1, 2, 3, 4, 5, 6, 7, 8}; + + TestDataM std_input[] = {TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0), + TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0)}; + TestDataM etl_input[] = {TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0), + TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0)}; + TestDataM std_true[] = {TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0), + TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0)}; + TestDataM std_false[] = {TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0), + TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0)}; + TestDataM etl_true[] = {TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0), + TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0)}; + TestDataM etl_false[] = {TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0), + TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0)}; + + auto compare = [](const TestDataM& 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 initial_data = {0, 1, 2, 3, 4, 5, 6, 7}; + + std::array std_data = initial_data; + std::array 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, 8> initial_data = {TestDataM(0), TestDataM(1), TestDataM(2), TestDataM(3), + TestDataM(4), TestDataM(5), TestDataM(6), TestDataM(7)}; + + std::array, 8> std_data = std::move(initial_data); + std::array, 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& t) { return etl::is_even(t.value); }); + auto etl_pivot = etl::stable_partition(etl_data.begin(), etl_data.end(), [](const TestDataM& 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 initial_data = {0, 1, 2, 3, 4, 5, 6, 7}; + + std::array buffer = {0, 0, 0, 0, 0, 0, 0, 0}; + + std::array std_data = initial_data; + std::array 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, 8> initial_data = {TestDataM(0), TestDataM(1), TestDataM(2), TestDataM(3), + TestDataM(4), TestDataM(5), TestDataM(6), TestDataM(7)}; + + std::array, 8> buffer = {TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0), + TestDataM(0), TestDataM(0), TestDataM(0), TestDataM(0)}; + + std::array, 8> std_data = std::move(initial_data); + std::array, 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& 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& 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) {