From 87c544d64ff42ea79a7a1b6b24d977c5a5175b9e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 9 Oct 2022 17:56:07 +0200 Subject: [PATCH 01/53] Add 'inversions' distribution to test suite --- tests/testing-tools/distributions.h | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/testing-tools/distributions.h b/tests/testing-tools/distributions.h index b1e4fffb..4d2222c3 100644 --- a/tests/testing-tools/distributions.h +++ b/tests/testing-tools/distributions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_TESTSUITE_DISTRIBUTIONS_H_ @@ -8,6 +8,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include #include #include "random.h" @@ -241,6 +242,37 @@ namespace dist } }; + struct inversions: + distribution + { + // Percentage of chances that an "out-of-place" value + // is produced for each position, the goal is to test + // Inv-adaptive algorithms + double factor; + + constexpr explicit inversions(double factor) noexcept: + factor(factor) + {} + + template + auto operator()(OutputIterator out, long long int size) const + -> void + { + // Generate a percentage of error + std::uniform_real_distribution percent_dis(0.0, 1.0); + // Generate a random value + std::uniform_int_distribution value_dis(0, size - 1); + + for (long long int i = 0; i < size; ++i) { + if (percent_dis(hasard::engine()) < factor) { + *out++ = static_cast(value_dis(hasard::engine())); + } else { + *out++ = static_cast(i); + } + } + } + }; + struct median_of_3_killer: distribution { From 36b35e2956140cae90e783794ed43c6b33b6940b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 9 Oct 2022 23:11:11 +0200 Subject: [PATCH 02/53] New adapter: split_adapter (#148) --- docs/Sorter-adapters.md | 23 ++++ include/cpp-sort/adapters.h | 3 +- include/cpp-sort/adapters/split_adapter.h | 107 ++++++++++++++++++ include/cpp-sort/detail/split_sort.h | 29 +++-- include/cpp-sort/fwd.h | 2 + include/cpp-sort/sorters/split_sorter.h | 6 +- tests/CMakeLists.txt | 1 + tests/adapters/split_adapter_every_sorter.cpp | 70 ++++++++++++ 8 files changed, 226 insertions(+), 15 deletions(-) create mode 100644 include/cpp-sort/adapters/split_adapter.h create mode 100644 tests/adapters/split_adapter_every_sorter.cpp diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index 78053c68..4da6f273 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -219,6 +219,28 @@ using sorter = cppsort::hybrid_adapter< *Warning: this adapter only supports default-constructible stateless sorters.* +### `split_adapter` + +```cpp +#include +``` + +The adapter implements the "in-place" version of the *SplitSort* algorithm described in *Splitsort — an adaptive sorting algorithm* by C. Levcopoulos and O. Petersson. The algorithm works as follows: +1. It performs a O(n) pass on the collection to isolate an approximation of a longest non-decreasing subsequence in the left part of the collection, and places the removed elements in the right part of the collection. +2. It uses the *adapted sorter* to sort the right part of the collection. +3. It merges the two parts of the collection in O(n) time O(n) space if possible, otherwise it merges them in O(n log n) time O(1) space. + +The core algorithm behind `split_adapter` requires at least bidirectional iterators to work, as such the *resulting sorter* requires bidirectional iterators if the *adapted sorter* supports them, otherwise it requires the same category of iterators at that accepted by the *adapter sorter*. The *resulting sorter* is always unstable, no matter the stability of the *adapted sorter*. + +```cpp +template +struct split_adapter; +``` + +Adapting any *sorter* with `split_adapter` effectively makes it [*Rem*-adaptive][probe-rem], making it a valuable tool to add adaptiveness to existing sorters. + +*New in version 1.14.0* + ### `stable_adapter`, `make_stable` and `stable_t` ```cpp @@ -308,6 +330,7 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [issue-104]: https://github.com/Morwenn/cpp-sort/issues/104 [low-moves-sorter]: Fixed-size-sorters.md#low_moves_sorter [mountain-sort]: https://github.com/Morwenn/mountain-sort + [probe-rem]: Measures-of-presortedness.md#rem [schwartzian-transform]: https://en.wikipedia.org/wiki/Schwartzian_transform [stable-adapter]: Sorter-adapters.md#stable_adapter-make_stable-and-stable_t [self-sort-adapter]: Sorter-adapters.md#self_sort_adapter diff --git a/include/cpp-sort/adapters.h b/include/cpp-sort/adapters.h index d8ac5bbc..d8991b2e 100644 --- a/include/cpp-sort/adapters.h +++ b/include/cpp-sort/adapters.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_H_ @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/include/cpp-sort/adapters/split_adapter.h b/include/cpp-sort/adapters/split_adapter.h new file mode 100644 index 00000000..f56cbe31 --- /dev/null +++ b/include/cpp-sort/adapters/split_adapter.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_ADAPTERS_SPLIT_ADAPTER_H_ +#define CPPSORT_ADAPTERS_SPLIT_ADAPTER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/split_sort.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // Adapter + + namespace detail + { + template + struct split_adapter_iterator_category + { + using type = std::bidirectional_iterator_tag; + }; + + template + struct split_adapter_iterator_category< + Sorter, + void_t::iterator_category> + > + { + using type = detail::conditional_t< + std::is_base_of< + std::bidirectional_iterator_tag, + typename sorter_traits::iterator_category + >::value, + typename sorter_traits::iterator_category, + std::bidirectional_iterator_tag + >; + }; + + template + struct split_adapter_impl: + utility::adapter_storage + { + split_adapter_impl() = default; + + constexpr explicit split_adapter_impl(Sorter&& sorter): + utility::adapter_storage(std::move(sorter)) + {} + + template< + typename BidirectionalIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = detail::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(BidirectionalIterator first, BidirectionalIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + static_assert( + std::is_base_of< + iterator_category, + iterator_category_t + >::value, + "split_adapter requires a stronger iterator category" + ); + + split_sort(std::move(first), std::move(last), + std::move(compare), std::move(projection), + this->get()); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category + = typename split_adapter_iterator_category::type; + using is_always_stable = std::false_type; + }; + } + + template + struct split_adapter: + sorter_facade> + { + split_adapter() = default; + + constexpr explicit split_adapter(Sorter sorter): + sorter_facade>(std::move(sorter)) + {} + }; +} + +#endif // CPPSORT_ADAPTERS_SPLIT_ADAPTER_H_ diff --git a/include/cpp-sort/detail/split_sort.h b/include/cpp-sort/detail/split_sort.h index f571bc23..ce4e865e 100644 --- a/include/cpp-sort/detail/split_sort.h +++ b/include/cpp-sort/detail/split_sort.h @@ -13,32 +13,37 @@ #include #include #include "inplace_merge.h" -#include "iterator_traits.h" -#include "pdqsort.h" namespace cppsort { namespace detail { - template - auto split_sort(RandomAccessIterator first, RandomAccessIterator last, - Compare compare, Projection projection) + template< + typename BidirectionalIterator, + typename Compare, + typename Projection, + typename Sorter + > + auto split_sort(BidirectionalIterator first, BidirectionalIterator last, + Compare compare, Projection projection, Sorter&& sorter) -> void { // This algorithm tries to isolate an approximate longest - // non-decreasing subsequence in the left portion of the - // collection, and the other elements in the right portion - // of the collection, then to sort the remaining elements - // and to merge both portions (LNDS) + // non-decreasing subsequence (LNDS) in the left portion + // of the collection, and the other elements in the right + // portion of the collection, then to sort the remaining + // elements and to merge both portions - if (last - first < 2) return; + if (first == last || std::next(first) == last) { + return; + } auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); // Read and reorganize elements until middle is found auto middle = first; // Last element of the LNDS - for (auto reader_it = std::next(first) ; reader_it != last ; ++reader_it) { + for (auto reader_it = std::next(first); reader_it != last; ++reader_it) { if (comp(proj(*reader_it), proj(*middle))) { // We remove the top of the subsequence as well as the new element if (middle != first) { @@ -53,7 +58,7 @@ namespace detail } // Sort second part of the collection and merge - pdqsort(middle, last, compare, projection); + std::forward(sorter)(middle, last, compare, projection); inplace_merge(first, middle, last, std::move(compare), std::move(projection)); } }} diff --git a/include/cpp-sort/fwd.h b/include/cpp-sort/fwd.h index 058b1660..bb19ec56 100644 --- a/include/cpp-sort/fwd.h +++ b/include/cpp-sort/fwd.h @@ -92,6 +92,8 @@ namespace cppsort template class FixedSizeSorter, typename Indices> struct small_array_adapter; template + struct split_adapter; + template struct stable_adapter; template struct verge_adapter; diff --git a/include/cpp-sort/sorters/split_sorter.h b/include/cpp-sort/sorters/split_sorter.h index 607e6072..c745891e 100644 --- a/include/cpp-sort/sorters/split_sorter.h +++ b/include/cpp-sort/sorters/split_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2021 Morwenn + * Copyright (c) 2019-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPLIT_SORTER_H_ @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include "../detail/iterator_traits.h" @@ -50,7 +51,8 @@ namespace cppsort ); split_sort(std::move(first), std::move(last), - std::move(compare), std::move(projection)); + std::move(compare), std::move(projection), + cppsort::pdq_sort); } //////////////////////////////////////////////////////////// diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8eca1d16..d32a1c34 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -145,6 +145,7 @@ add_executable(main-tests adapters/self_sort_adapter_no_compare.cpp adapters/small_array_adapter.cpp adapters/small_array_adapter_is_stable.cpp + adapters/split_adapter_every_sorter.cpp adapters/stable_adapter_every_sorter.cpp adapters/verge_adapter_every_sorter.cpp diff --git a/tests/adapters/split_adapter_every_sorter.cpp b/tests/adapters/split_adapter_every_sorter.cpp new file mode 100644 index 00000000..9d927607 --- /dev/null +++ b/tests/adapters/split_adapter_every_sorter.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "every random-access sorter with split_adapter", "[split_adapter]", + cppsort::adaptive_shivers_sorter, + cppsort::cartesian_tree_sorter, + cppsort::default_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter, + cppsort::ska_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::spread_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::wiki_sorter> ) +{ + std::vector collection; + collection.reserve(1'000); + auto distribution = dist::inversions(0.2); // Big enough merges + distribution.call(std::back_inserter(collection), 1'000); + + cppsort::split_adapter sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} + +TEMPLATE_TEST_CASE( "every bidirectional sorter with split_adapter", "[split_adapter]", + cppsort::cartesian_tree_sorter, + cppsort::default_sorter, + cppsort::drop_merge_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter ) +{ + std::list collection; + auto distribution = dist::inversions(0.2); // Big enough merges + distribution.call(std::back_inserter(collection), 1'000); + + cppsort::split_adapter sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} From aaa3b3a1f51e5027ccb464c16c935b162a49ecc6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 11 Oct 2022 23:56:41 +0200 Subject: [PATCH 03/53] Add audits around merge operations --- include/cpp-sort/detail/inplace_merge.h | 13 ++++++++++-- include/cpp-sort/detail/is_sorted_until.h | 26 +++++++++++++++-------- include/cpp-sort/detail/merge_move.h | 6 +++++- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/include/cpp-sort/detail/inplace_merge.h b/include/cpp-sort/detail/inplace_merge.h index 99ce5a0b..c97f8528 100644 --- a/include/cpp-sort/detail/inplace_merge.h +++ b/include/cpp-sort/detail/inplace_merge.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_INPLACE_MERGE_H_ @@ -13,6 +13,8 @@ #include #include #include +#include "config.h" +#include "is_sorted_until.h" #include "iterator_traits.h" #include "memory.h" #include "recmerge_bidirectional.h" @@ -29,7 +31,7 @@ namespace detail template - auto inplace_merge(ForwardIterator first, ForwardIterator middle, ForwardIterator, + auto inplace_merge(ForwardIterator first, ForwardIterator middle, ForwardIterator last, Compare compare, Projection projection, difference_type_t len1, difference_type_t len2, @@ -37,6 +39,9 @@ namespace detail std::forward_iterator_tag) -> void { + CPPSORT_AUDIT(detail::is_sorted(first, middle, compare, projection)); + CPPSORT_AUDIT(detail::is_sorted(middle, last, compare, projection)); + (void)last; recmerge(std::move(first), len1, std::move(middle), len2, buff, buff_size, std::move(compare), std::move(projection)); @@ -56,6 +61,8 @@ namespace detail std::bidirectional_iterator_tag tag) -> void { + CPPSORT_AUDIT(detail::is_sorted(first, middle, compare, projection)); + CPPSORT_AUDIT(detail::is_sorted(middle, last, compare, projection)); recmerge(std::move(first), std::move(middle), std::move(last), std::move(compare), std::move(projection), len1, len2, buff, buff_size, tag); @@ -75,6 +82,8 @@ namespace detail std::random_access_iterator_tag) -> void { + CPPSORT_AUDIT(detail::is_sorted(first, middle, compare, projection)); + CPPSORT_AUDIT(detail::is_sorted(middle, last, compare, projection)); symmerge(first, 0, middle - first, last - first, std::move(compare), std::move(projection), len1, len2, buff, buff_size); diff --git a/include/cpp-sort/detail/is_sorted_until.h b/include/cpp-sort/detail/is_sorted_until.h index 07e8e3d0..be2dab4a 100644 --- a/include/cpp-sort/detail/is_sorted_until.h +++ b/include/cpp-sort/detail/is_sorted_until.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_IS_SORTED_UNTIL_H_ @@ -15,20 +15,17 @@ namespace cppsort namespace detail { template - auto is_sorted_until(ForwardIterator first, ForwardIterator last, - Compare compare, Projection projection) + constexpr auto is_sorted_until(ForwardIterator first, ForwardIterator last, + Compare compare, Projection projection) -> ForwardIterator { - if (first != last) - { + if (first != last) { auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); ForwardIterator next = first; - while (++next != last) - { - if (comp(proj(*next), proj(*first))) - { + while (++next != last) { + if (comp(proj(*next), proj(*first))) { return next; } first = next; @@ -36,6 +33,17 @@ namespace detail } return last; } + + template + constexpr auto is_sorted(ForwardIterator first, ForwardIterator last, + Compare compare, Projection projection) + -> bool + { + return detail::is_sorted_until( + first, last, + std::move(compare), std::move(projection) + ) == last; + } }} #endif // CPPSORT_DETAIL_IS_SORTED_UNTIL_H_ diff --git a/include/cpp-sort/detail/merge_move.h b/include/cpp-sort/detail/merge_move.h index 6ba497dc..fa6e6f35 100644 --- a/include/cpp-sort/detail/merge_move.h +++ b/include/cpp-sort/detail/merge_move.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_MERGE_MOVE_H_ @@ -12,6 +12,7 @@ #include #include #include "config.h" +#include "is_sorted_until.h" #include "move.h" namespace cppsort @@ -26,6 +27,9 @@ namespace detail Projection1 projection1, Projection2 projection2) -> OutputIterator { + CPPSORT_AUDIT(detail::is_sorted(first1, last1, compare, projection1)); + CPPSORT_AUDIT(detail::is_sorted(first2, last2, compare, projection2)); + using utility::iter_move; auto&& comp = utility::as_function(compare); auto&& proj1 = utility::as_function(projection1); From 4941e64ed44c960c587fdbe5ced5bf9ea643a647 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 14 Oct 2022 00:20:49 +0200 Subject: [PATCH 04/53] slab_sort: fix size passed to stable_partition Somewhat in the perimeter of issue #211 though it does not fix it. --- include/cpp-sort/detail/slabsort.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h index 618eb3c0..c41938d2 100644 --- a/include/cpp-sort/detail/slabsort.h +++ b/include/cpp-sort/detail/slabsort.h @@ -112,7 +112,7 @@ namespace detail iter_swap(pivot, last_1); auto&& pivot1 = proj(*last_1); auto middle1 = detail::stable_partition( - first, last_1, size, + first, last_1, size - 1, [&](auto&& elem) { return comp(proj(elem), pivot1); } ); From ed1b7d605d403ca27934c8d818a0ab8ed23ce943 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 14 Oct 2022 18:12:07 +0200 Subject: [PATCH 05/53] Fix slabsort with equivalent elements (#211) slab_sort had a bug where it partitioned the collection stably around the median, then recursed in each *half* of the collection instead of recursing in the two partitions created by the partitioning algorithm. It made no difference with unique elements since the partitions would always correspond to both halves of the collection. However the story was different when several elements compared equivalent to the median, leading to the error encountered in #211. --- include/cpp-sort/detail/slabsort.h | 36 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h index c41938d2..3b216354 100644 --- a/include/cpp-sort/detail/slabsort.h +++ b/include/cpp-sort/detail/slabsort.h @@ -16,6 +16,7 @@ #include #include #include "bitops.h" +#include "config.h" #include "fixed_size_list.h" #include "functional.h" #include "immovable_vector.h" @@ -79,7 +80,7 @@ namespace detail } //////////////////////////////////////////////////////////// - // Run nth_element stably and indirectly + // Run nth_element indirectly return *nth_element( iterators_buffer.begin(), iterators_buffer.end(), size / 2, size, @@ -92,7 +93,7 @@ namespace detail difference_type_t size, immovable_vector& iterators_buffer, Compare compare, Projection projection) - -> void + -> BidirectionalIterator { using utility::iter_swap; auto&& comp = utility::as_function(compare); @@ -105,19 +106,19 @@ namespace detail // difference should not have any noticeable impact on the // adaptivity to presortedness - auto pivot = slabsort_get_median(first, size, iterators_buffer, compare, projection); + auto median_it = slabsort_get_median(first, size, iterators_buffer, compare, projection); auto last_1 = std::prev(last); // Put the pivot at position std::prev(last) and partition - iter_swap(pivot, last_1); - auto&& pivot1 = proj(*last_1); - auto middle1 = detail::stable_partition( + iter_swap(median_it, last_1); + auto&& pivot = proj(*last_1); + auto middle = detail::stable_partition( first, last_1, size - 1, - [&](auto&& elem) { return comp(proj(elem), pivot1); } + [&](auto&& elem) { return comp(proj(elem), pivot); } ); - // Put the pivot back in its final position - iter_swap(middle1, last_1); + iter_swap(middle, last_1); + return middle; } template @@ -229,28 +230,29 @@ namespace detail return; } - slabsort_partition(first, last, size, iterators_buffer, compare, projection); - auto left_size = size / 2; - auto right_size = size - left_size; - auto middle = std::next(first, left_size); + auto middle = slabsort_partition(first, last, size, iterators_buffer, compare, projection); + auto size_left = std::distance(first, middle); + auto size_right = size - size_left; + CPPSORT_ASSERT(size_left <= size / 2); + if (current_p > 2) { // Partition further until the partitions are small enough - slabsort_impl(first, middle, left_size, original_p, current_p / 2, + slabsort_impl(first, middle, size_left, original_p, current_p / 2, iterators_buffer, node_pool, compare, projection); - slabsort_impl(middle, last, right_size, original_p, current_p / 2, + slabsort_impl(middle, last, size_right, original_p, current_p / 2, iterators_buffer, node_pool, compare, projection); } else { // The partitions are small enough, try to use melsort on them, // if too many encroaching lists are created, cancel and recurse bool done = try_melsort(first, middle, original_p, node_pool, compare, projection); if (not done) { - slabsort_impl(first, middle, left_size, + slabsort_impl(first, middle, size_left, original_p * original_p, original_p * original_p, iterators_buffer, node_pool, compare, projection); } done = try_melsort(middle, last, original_p, node_pool, compare, projection); if (not done) { - slabsort_impl(middle, last, right_size, + slabsort_impl(middle, last, size_right, original_p * original_p, original_p * original_p, iterators_buffer, node_pool, compare, projection); } From 775bc626f32169bb03113c773efc7f57dd9554f3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 14 Oct 2022 19:54:44 +0200 Subject: [PATCH 06/53] Use insertion_sort in try_melsort for small collections --- include/cpp-sort/detail/slabsort.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h index 3b216354..6917678d 100644 --- a/include/cpp-sort/detail/slabsort.h +++ b/include/cpp-sort/detail/slabsort.h @@ -20,6 +20,7 @@ #include "fixed_size_list.h" #include "functional.h" #include "immovable_vector.h" +#include "insertion_sort.h" #include "iterator_traits.h" #include "lower_bound.h" #include "melsort.h" @@ -123,6 +124,7 @@ namespace detail template auto try_melsort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, difference_type_t p, fixed_size_list_node_pool>& node_pool, Compare compare, Projection projection) @@ -134,7 +136,8 @@ namespace detail auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); - if (first == last || std::next(first) == last) { + if (size < 32) { + insertion_sort(first, last, std::move(compare), std::move(projection)); return true; } @@ -244,13 +247,13 @@ namespace detail } else { // The partitions are small enough, try to use melsort on them, // if too many encroaching lists are created, cancel and recurse - bool done = try_melsort(first, middle, original_p, node_pool, compare, projection); + bool done = try_melsort(first, middle, size_left, original_p, node_pool, compare, projection); if (not done) { slabsort_impl(first, middle, size_left, original_p * original_p, original_p * original_p, iterators_buffer, node_pool, compare, projection); } - done = try_melsort(middle, last, original_p, node_pool, compare, projection); + done = try_melsort(middle, last, size_right, original_p, node_pool, compare, projection); if (not done) { slabsort_impl(middle, last, size_right, original_p * original_p, original_p * original_p, @@ -276,7 +279,7 @@ namespace detail // Take advantage of existing presortedness once before the partitioning // partly gets rid of it, this makes a difference for collections with // a few bigs runs - bool done = try_melsort(first, last, 2 * detail::log2(size), + bool done = try_melsort(first, last, size, 2 * detail::log2(size), node_pool, compare, projection); if (done) { return; From 0a5db2d2a28d0987cf008bebc41a94b274e4f93c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 Oct 2022 12:48:36 +0200 Subject: [PATCH 07/53] Benchmarks: actually apply proj in dist::inversions --- benchmarks/benchmarking-tools/distributions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/benchmarking-tools/distributions.h b/benchmarks/benchmarking-tools/distributions.h index ff282e93..b0184560 100644 --- a/benchmarks/benchmarking-tools/distributions.h +++ b/benchmarks/benchmarking-tools/distributions.h @@ -359,9 +359,9 @@ namespace dist for (long long int i = 0 ; i < size ; ++i) { if (percent_dis(distributions_prng) < factor) { - *out++ = value_dis(distributions_prng); + *out++ = proj(value_dis(distributions_prng)); } else { - *out++ = i; + *out++ = proj(i); } } } From b4899dffbff4683029f2a25fab71e7267abeb2bd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 Oct 2022 13:26:07 +0200 Subject: [PATCH 08/53] Compite test suite with -Wno-inline with GNU frontends Typically some flavours of MinGW produce around 13k lines of -Winline warnings even though the library only uses inline sparringly and for ODR reasons. There is no explicit "force inline" anywhere, so the warning is just noise that clutters the compiler results and makes it impossible to find other warnings in the logs. --- cmake/cpp-sort-utils.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/cpp-sort-utils.cmake b/cmake/cpp-sort-utils.cmake index db53818e..5a422a1e 100644 --- a/cmake/cpp-sort-utils.cmake +++ b/cmake/cpp-sort-utils.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2020 Morwenn +# Copyright (c) 2019-2022 Morwenn # SPDX-License-Identifier: MIT # Add a selection of warnings to a target @@ -12,6 +12,9 @@ macro(cppsort_add_warnings target) $<$:-Wlogical-op -Wuseless-cast -Wzero-as-null-pointer-constant> # The warning when initializing an std::array is just too much of a bother $<$:-Wno-missing-braces> + # Without this we get thousands of -Winline warnings without even explicitly + # asking to inline anything, which makes compilation results unreadable + $<$:-Wno-inline> ) endif() endmacro() From 429344c01c30eb846cf108625ca3f2f7a47f8067 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 Oct 2022 17:00:38 +0200 Subject: [PATCH 09/53] Simplify implementation of split_sorter --- include/cpp-sort/sorters/split_sorter.h | 55 +++++-------------------- 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/include/cpp-sort/sorters/split_sorter.h b/include/cpp-sort/sorters/split_sorter.h index c745891e..18789584 100644 --- a/include/cpp-sort/sorters/split_sorter.h +++ b/include/cpp-sort/sorters/split_sorter.h @@ -8,64 +8,31 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include #include #include -#include -#include -#include +#include #include -#include #include -#include "../detail/iterator_traits.h" -#include "../detail/split_sort.h" -#include "../detail/type_traits.h" namespace cppsort { //////////////////////////////////////////////////////////// // Sorter - namespace detail + struct split_sorter: + split_adapter { - struct split_sorter_impl - { - template< - typename RandomAccessIterator, - typename Compare = std::less<>, - typename Projection = utility::identity, - typename = detail::enable_if_t< - is_projection_iterator_v - > - > - auto operator()(RandomAccessIterator first, RandomAccessIterator last, - Compare compare={}, Projection projection={}) const - -> void - { - static_assert( - std::is_base_of< - iterator_category, - iterator_category_t - >::value, - "split_sorter requires at least random-access iterators" - ); - - split_sort(std::move(first), std::move(last), - std::move(compare), std::move(projection), - cppsort::pdq_sort); - } + //////////////////////////////////////////////////////////// + // Construction - //////////////////////////////////////////////////////////// - // Sorter traits + split_sorter() = default; - using iterator_category = std::random_access_iterator_tag; - using is_always_stable = std::false_type; - }; - } + //////////////////////////////////////////////////////////// + // Sorter traits - struct split_sorter: - sorter_facade - {}; + // Force it for legibility + using iterator_category = std::random_access_iterator_tag; + }; //////////////////////////////////////////////////////////// // Sort function From c55ac30d48c38d9b92e83311cecb65fcbc626e12 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 Oct 2022 20:48:44 +0200 Subject: [PATCH 10/53] New adapter: drop_merge_adapter (#148) --- docs/Sorter-adapters.md | 20 +++++ include/cpp-sort/adapters.h | 1 + .../cpp-sort/adapters/drop_merge_adapter.h | 84 +++++++++++++++++++ include/cpp-sort/detail/drop_merge_sort.h | 17 ++-- include/cpp-sort/fwd.h | 2 + include/cpp-sort/sorters/drop_merge_sorter.h | 56 ++----------- tests/CMakeLists.txt | 1 + .../drop_merge_adapter_every_sorter.cpp | 84 +++++++++++++++++++ 8 files changed, 209 insertions(+), 56 deletions(-) create mode 100644 include/cpp-sort/adapters/drop_merge_adapter.h create mode 100644 tests/adapters/drop_merge_adapter_every_sorter.cpp diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index 4da6f273..953180d8 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -71,6 +71,25 @@ struct counting_adapter; Note that this adapter only works with sorters that satisfy the `ComparisonSorter` concept since it needs to adapt a comparison function. +### `drop_merge_adapter` + +```cpp +#include +``` + +[Drop-merge sort][drop-merge-sort] is a [*Rem*-adaptive][probe-rem] sorting algorithm that isolates some of the elements to sort in a buffer in O(n) time in order to leave a single sorted run in the original collection, then it uses another algorithm to sort the elements isolated in a buffer, and merges the two resulting runs back into the original collection. `drop_merge_adapter` uses the *adapted sorter* to sort the (contiguous) buffer of isolated elements. + +The *resulting sorter* always requires at least bidirectional iterators, no matter the iterator category of the *adapted sorter*. The *resulting sorter* is always unstable, no matter the stability of the *adapted sorter*. + +```cpp +template +struct drop_merge_adapter; +``` + +Adapting any *sorter* with `drop_merge_adapter` effectively makes it [*Rem*-adaptive][probe-rem], making it a valuable tool to add adaptiveness to existing sorters. + +*New in version 1.14.0* + ### `hybrid_adapter` ```cpp @@ -322,6 +341,7 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [ctad]: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort [default-sorter]: Sorters.md#default_sorter + [drop-merge-sort]: https://github.com/emilk/drop-merge-sort [fixed-size-sorters]: Fixed-size-sorters.md [fixed-sorter-traits]: Sorter-traits.md#fixed_sorter_traits [hybrid-adapter]: Sorter-adapters.md#hybrid_adapter diff --git a/include/cpp-sort/adapters.h b/include/cpp-sort/adapters.h index d8991b2e..76356291 100644 --- a/include/cpp-sort/adapters.h +++ b/include/cpp-sort/adapters.h @@ -10,6 +10,7 @@ //////////////////////////////////////////////////////////// #include #include +#include #include #include #include diff --git a/include/cpp-sort/adapters/drop_merge_adapter.h b/include/cpp-sort/adapters/drop_merge_adapter.h new file mode 100644 index 00000000..e220a86e --- /dev/null +++ b/include/cpp-sort/adapters/drop_merge_adapter.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_ADAPTERS_DROP_MERGE_ADAPTER_H_ +#define CPPSORT_ADAPTERS_DROP_MERGE_ADAPTER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/drop_merge_sort.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // Adapter + + namespace detail + { + template + struct drop_merge_adapter_impl: + utility::adapter_storage + { + drop_merge_adapter_impl() = default; + + constexpr explicit drop_merge_adapter_impl(Sorter&& sorter): + utility::adapter_storage(std::move(sorter)) + {} + + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = detail::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + static_assert( + std::is_base_of< + iterator_category, + iterator_category_t + >::value, + "drop_merge_adapter requires at least bidirectional iterators" + ); + + drop_merge_sort(std::move(first), std::move(last), + std::move(compare), std::move(projection), + this->get()); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::bidirectional_iterator_tag; + using is_always_stable = std::false_type; + }; + } + + template + struct drop_merge_adapter: + sorter_facade> + { + drop_merge_adapter() = default; + + constexpr explicit drop_merge_adapter(Sorter sorter): + sorter_facade>(std::move(sorter)) + {} + }; +} + +#endif // CPPSORT_ADAPTERS_DROP_MERGE_ADAPTER_H_ diff --git a/include/cpp-sort/detail/drop_merge_sort.h b/include/cpp-sort/detail/drop_merge_sort.h index e10ad312..962bb82f 100644 --- a/include/cpp-sort/detail/drop_merge_sort.h +++ b/include/cpp-sort/detail/drop_merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 Morwenn + * Copyright (c) 2017-2022 Morwenn * SPDX-License-Identifier: MIT */ @@ -34,7 +34,6 @@ #include #include #include "iterator_traits.h" -#include "pdqsort.h" #include "type_traits.h" namespace cppsort @@ -43,10 +42,14 @@ namespace detail { constexpr static bool double_comparison = true; - // move-only version - template + template< + typename BidirectionalIterator, + typename Compare, + typename Projection, + typename Sorter + > auto drop_merge_sort(BidirectionalIterator begin, BidirectionalIterator end, - Compare compare, Projection projection) + Compare compare, Projection projection, Sorter&& sorter) -> void { using utility::iter_move; @@ -122,10 +125,10 @@ namespace detail } // Sort the dropped elements - pdqsort(dropped.begin(), dropped.end(), compare, projection); + std::forward(sorter)(dropped.begin(), dropped.end(), + compare, projection); auto back = end; - do { auto& last_dropped = dropped.back(); diff --git a/include/cpp-sort/fwd.h b/include/cpp-sort/fwd.h index bb19ec56..112ce182 100644 --- a/include/cpp-sort/fwd.h +++ b/include/cpp-sort/fwd.h @@ -79,6 +79,8 @@ namespace cppsort struct container_aware_adapter; template struct counting_adapter; + template + struct drop_merge_adapter; template struct hybrid_adapter; template diff --git a/include/cpp-sort/sorters/drop_merge_sorter.h b/include/cpp-sort/sorters/drop_merge_sorter.h index ce3b3267..9297053c 100644 --- a/include/cpp-sort/sorters/drop_merge_sorter.h +++ b/include/cpp-sort/sorters/drop_merge_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 Morwenn + * Copyright (c) 2017-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_DROP_MERGE_SORTER_H_ @@ -8,62 +8,20 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include "../detail/drop_merge_sort.h" -#include "../detail/iterator_traits.h" -#include "../detail/type_traits.h" namespace cppsort { //////////////////////////////////////////////////////////// // Sorter - namespace detail - { - struct drop_merge_sorter_impl - { - template< - typename BidirectionalIterator, - typename Compare = std::less<>, - typename Projection = utility::identity, - typename = detail::enable_if_t< - is_projection_iterator_v - > - > - auto operator()(BidirectionalIterator first, BidirectionalIterator last, - Compare compare={}, Projection projection={}) const - -> void - { - static_assert( - std::is_base_of< - iterator_category, - iterator_category_t - >::value, - "drop_merge_sorter requires at least bidirectional iterators" - ); - - drop_merge_sort(std::move(first), std::move(last), - std::move(compare), std::move(projection)); - } - - //////////////////////////////////////////////////////////// - // Sorter traits - - using iterator_category = std::bidirectional_iterator_tag; - using is_always_stable = std::false_type; - }; - } - struct drop_merge_sorter: - sorter_facade - {}; + drop_merge_adapter + { + drop_merge_sorter() = default; + }; //////////////////////////////////////////////////////////// // Sort function diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d32a1c34..165d495d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -124,6 +124,7 @@ add_executable(main-tests adapters/container_aware_adapter_forward_list.cpp adapters/container_aware_adapter_list.cpp adapters/counting_adapter.cpp + adapters/drop_merge_adapter_every_sorter.cpp $<$>:adapters/every_adapter_fptr.cpp> adapters/every_adapter_internal_compare.cpp adapters/every_adapter_non_const_compare.cpp diff --git a/tests/adapters/drop_merge_adapter_every_sorter.cpp b/tests/adapters/drop_merge_adapter_every_sorter.cpp new file mode 100644 index 00000000..8c3b2a46 --- /dev/null +++ b/tests/adapters/drop_merge_adapter_every_sorter.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "every random-access sorter with drop_merge_adapter", "[drop_merge_adapter]", + cppsort::adaptive_shivers_sorter, + cppsort::cartesian_tree_sorter, + cppsort::default_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter, + cppsort::ska_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::spread_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::wiki_sorter> ) +{ + std::vector collection; + collection.reserve(1'000); + auto distribution = dist::inversions(0.2); // Big enough merges + distribution.call(std::back_inserter(collection), 1'000); + + cppsort::drop_merge_adapter sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} + +TEMPLATE_TEST_CASE( "every bidirectional sorter with drop_merge_adapter", "[drop_merge_adapter]", + cppsort::adaptive_shivers_sorter, + cppsort::cartesian_tree_sorter, + cppsort::default_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter, + cppsort::ska_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::spread_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::wiki_sorter> ) +{ + std::list collection; + auto distribution = dist::inversions(0.2); // Big enough merges + distribution.call(std::back_inserter(collection), 1'000); + + cppsort::drop_merge_adapter sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} From a21cc402072f0a821d630ede1e7c500fe2727f64 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 16 Oct 2022 18:36:58 +0200 Subject: [PATCH 11/53] Turn assumptions into assertions when CPPSORT_ENABLE_AUDITS is defined --- docs/Home.md | 2 +- include/cpp-sort/detail/config.h | 47 +++++++++++++++++++------------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/Home.md b/docs/Home.md index fac1ed50..135624a6 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -71,7 +71,7 @@ Some old components undergo deprecation before being removed in the following ma Some algorithms have assertions to guard against accidental logic issues (mostly in algorithms adapted from other projects), but they are disabled by default. You can enable these assertions by defining the preprocessor macro `CPPSORT_ENABLE_ASSERTIONS`. This new macro still honours `NDEBUG`, so assertions won't be enabled anyway if `NDEBUG` is defined. -A similar `CPPSORT_ENABLE_AUDITS` macro can be defined to enable audits: those are expensive assertions which are not enabled by `CPPSORT_ENABLE_ASSERTIONS` because they are too expensive, to the point that they might even change the complexity of some algorithms. +A similar `CPPSORT_ENABLE_AUDITS` macro can be defined to enable audits: those are expensive assertions which are not enabled by `CPPSORT_ENABLE_ASSERTIONS` because they are too expensive, to the point that they might even change the complexity of some algorithms. When turning on audits, internal calls to `__assume` or equivalent will also be turned into assertions to provided additional checks. *New in version 1.6.0* diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 53249423..f146d557 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -58,23 +58,6 @@ # define CPPSORT_STD_IDENTITY_AVAILABLE 0 #endif -//////////////////////////////////////////////////////////// -// CPPSORT_ASSUME - -// Assumptions may help the compiler to remove unnecessary code; -// some parts of the library may be significantly slower if this -// assumption mechanism isn't supported - -#if defined(__GNUC__) -# define CPPSORT_ASSUME(expression) do { if (!(expression)) __builtin_unreachable(); } while(0) -#elif defined(__clang__) -# define CPPSORT_ASSUME(expression) __builtin_assume(expression) -#elif defined(_MSC_VER) -# define CPPSORT_ASSUME(expression) __assume(expression) -#else -# define CPPSORT_ASSUME(cond) -#endif - //////////////////////////////////////////////////////////// // CPPSORT_UNREACHABLE @@ -90,6 +73,13 @@ # define CPPSORT_UNREACHABLE #endif +//////////////////////////////////////////////////////////// +// General: assertions + +#if defined(CPPSORT_ENABLE_ASSERTIONS) || defined(CPPSORT_ENABLE_AUDITS) +# include +#endif + //////////////////////////////////////////////////////////// // CPPSORT_ASSERT @@ -99,7 +89,6 @@ #ifndef CPPSORT_ASSERT # ifdef CPPSORT_ENABLE_ASSERTIONS -# include # define CPPSORT_ASSERT(...) assert((__VA_ARGS__)) # else # define CPPSORT_ASSERT(...) @@ -115,13 +104,33 @@ #ifndef CPPSORT_AUDIT # ifdef CPPSORT_ENABLE_AUDITS -# include # define CPPSORT_AUDIT(...) assert((__VA_ARGS__)) # else # define CPPSORT_AUDIT(...) # endif #endif +//////////////////////////////////////////////////////////// +// CPPSORT_ASSUME + +// Assumptions may help the compiler to remove unnecessary code; +// some parts of the library may be significantly slower if this +// assumption mechanism isn't supported. In audit mode, we want +// to turn them into assertions to make sure the assumptions are +// actually correct. + +#if defined(CPPSORT_ENABLE_AUDITS) +# define CPPSORT_ASSUME(...) assert((__VA_ARGS__)) +#elif defined(__GNUC__) +# define CPPSORT_ASSUME(expression) do { if (!(expression)) __builtin_unreachable(); } while(0) +#elif defined(__clang__) +# define CPPSORT_ASSUME(expression) __builtin_assume(expression) +#elif defined(_MSC_VER) +# define CPPSORT_ASSUME(expression) __assume(expression) +#else +# define CPPSORT_ASSUME(cond) +#endif + //////////////////////////////////////////////////////////// // CPPSORT_DEPRECATED From 84b5ff06a458ee711457864f05092aa61bf308ca Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 17 Oct 2022 00:44:08 +0200 Subject: [PATCH 12/53] Add a specialization for schwartz_adapter> --- include/cpp-sort/adapters/schwartz_adapter.h | 27 +++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/adapters/schwartz_adapter.h b/include/cpp-sort/adapters/schwartz_adapter.h index 9e146211..a0685baa 100644 --- a/include/cpp-sort/adapters/schwartz_adapter.h +++ b/include/cpp-sort/adapters/schwartz_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_SCHWARTZ_ADAPTER_H_ @@ -212,6 +212,31 @@ namespace cppsort {} }; + // The following specialization exists for two reasons: + // - There does not seem to be a reason to pay twice the memory and indirection + // cost when two schwarz_adapters happen to be nested, nobody should want to + // pay such a cost for no apparent benefit (please open an issue if you do). + // - Most of the time the "normal" behaviour simply does not work: during the + // first pass, the result of projections are copied. When doubly wrapping a + // sorter with schwartz_adapter, the projected type is some 'association' + // type which is not copyable, leading to various hard-to-debug issues. The + // following speciaization helps to mitigate that specific issue. + + template + struct schwartz_adapter>: + schwartz_adapter + { + schwartz_adapter() = default; + + constexpr explicit schwartz_adapter(const schwartz_adapter& sorter): + schwartz_adapter(sorter) + {} + + constexpr explicit schwartz_adapter(schwartz_adapter&& sorter): + schwartz_adapter(std::move(sorter)) + {} + }; + //////////////////////////////////////////////////////////// // is_stable specialization From 0ad66eaf4ae6099cced552adb719a9ba55b5ebe0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 4 Nov 2022 21:55:32 +0100 Subject: [PATCH 13/53] In audit mode, turn CPPSORT_UNREACHABLE into an assertion --- docs/Home.md | 2 +- include/cpp-sort/detail/config.h | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/Home.md b/docs/Home.md index 135624a6..b2b5f37f 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -71,7 +71,7 @@ Some old components undergo deprecation before being removed in the following ma Some algorithms have assertions to guard against accidental logic issues (mostly in algorithms adapted from other projects), but they are disabled by default. You can enable these assertions by defining the preprocessor macro `CPPSORT_ENABLE_ASSERTIONS`. This new macro still honours `NDEBUG`, so assertions won't be enabled anyway if `NDEBUG` is defined. -A similar `CPPSORT_ENABLE_AUDITS` macro can be defined to enable audits: those are expensive assertions which are not enabled by `CPPSORT_ENABLE_ASSERTIONS` because they are too expensive, to the point that they might even change the complexity of some algorithms. When turning on audits, internal calls to `__assume` or equivalent will also be turned into assertions to provided additional checks. +A similar `CPPSORT_ENABLE_AUDITS` macro can be defined to enable audits: those are expensive assertions which are not enabled by `CPPSORT_ENABLE_ASSERTIONS` because they are too expensive, to the point that they might even change the complexity of some algorithms. When turning on audits, internal calls to `__assume` and equivalent statements will also be turned into assertions to provide additional checks. *New in version 1.6.0* diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index f146d557..98660a17 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -58,21 +58,6 @@ # define CPPSORT_STD_IDENTITY_AVAILABLE 0 #endif -//////////////////////////////////////////////////////////// -// CPPSORT_UNREACHABLE - -// Mostly useful to silence compiler warnings in the default -// clause of a switch when we know the default can never be -// reached - -#if defined(__GNUC__) || defined(__clang__) -# define CPPSORT_UNREACHABLE __builtin_unreachable() -#elif defined(_MSC_VER) -# define CPPSORT_UNREACHABLE __assume(false) -#else -# define CPPSORT_UNREACHABLE -#endif - //////////////////////////////////////////////////////////// // General: assertions @@ -131,6 +116,23 @@ # define CPPSORT_ASSUME(cond) #endif +//////////////////////////////////////////////////////////// +// CPPSORT_UNREACHABLE + +// Mostly useful to silence compiler warnings in the default +// clause of a switch when we know the default can never be +// reached + +#if defined(CPPSORT_ENABLE_AUDITS) +# define CPPSORT_UNREACHABLE CPPSORT_ASSERT("unreachable", false); +#elif defined(__GNUC__) || defined(__clang__) +# define CPPSORT_UNREACHABLE __builtin_unreachable() +#elif defined(_MSC_VER) +# define CPPSORT_UNREACHABLE __assume(false) +#else +# define CPPSORT_UNREACHABLE +#endif + //////////////////////////////////////////////////////////// // CPPSORT_DEPRECATED From a34d3ea8871838e5d971aff91cccb9b4ef181979 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 4 Nov 2022 22:53:02 +0100 Subject: [PATCH 14/53] Remove unneeded includes + minor cleanup --- include/cpp-sort/detail/container_aware/mel_sort.h | 1 - include/cpp-sort/detail/vergesort.h | 4 ++-- include/cpp-sort/probes/block.h | 4 +--- include/cpp-sort/probes/dis.h | 5 ++--- include/cpp-sort/probes/enc.h | 1 - include/cpp-sort/probes/exc.h | 3 +-- include/cpp-sort/probes/ham.h | 3 +-- include/cpp-sort/probes/inv.h | 5 ++--- include/cpp-sort/probes/max.h | 5 ++--- include/cpp-sort/probes/mono.h | 3 +-- include/cpp-sort/probes/osc.h | 3 +-- include/cpp-sort/probes/rem.h | 3 +-- include/cpp-sort/probes/runs.h | 3 +-- include/cpp-sort/probes/sus.h | 5 +---- 14 files changed, 16 insertions(+), 32 deletions(-) diff --git a/include/cpp-sort/detail/container_aware/mel_sort.h b/include/cpp-sort/detail/container_aware/mel_sort.h index 06c1be9f..1edbbc09 100644 --- a/include/cpp-sort/detail/container_aware/mel_sort.h +++ b/include/cpp-sort/detail/container_aware/mel_sort.h @@ -22,7 +22,6 @@ #include #include #include -#include #include "../lower_bound.h" #include "../type_traits.h" diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index ff7004bc..934dc64d 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_VERGESORT_H_ @@ -20,7 +20,7 @@ #include "inplace_merge.h" #include "iterator_traits.h" #include "lower_bound.h" -#include "quick_merge_sort.h" +#include "merge_sort.h" #include "reverse.h" #include "rotate.h" #include "sized_iterator.h" diff --git a/include/cpp-sort/probes/block.h b/include/cpp-sort/probes/block.h index a66c64de..7314edb4 100644 --- a/include/cpp-sort/probes/block.h +++ b/include/cpp-sort/probes/block.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Morwenn + * Copyright (c) 2021-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_BLOCK_H_ @@ -10,9 +10,7 @@ //////////////////////////////////////////////////////////// #include #include -#include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index 738b6aad..ffd9a340 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -77,7 +76,7 @@ namespace probe // Algorithm LR: cumulative max from left to right cppsort::detail::immovable_vector lr_cummax(size); lr_cummax.emplace_back(first); - for (auto it = std::next(first) ; it != last ; ++it) { + for (auto it = std::next(first); it != last; ++it) { if (comp(proj(*lr_cummax.back()), proj(*it))) { lr_cummax.emplace_back(it); } else { @@ -92,7 +91,7 @@ namespace probe difference_type i = size; auto rl_it = std::prev(last); // Iterator to the current RL element auto rl_min_it = rl_it; // Iterator to the current minimum of RL - for (auto j = i ; j > 0 ; --j) { + for (auto j = i; j > 0; --j) { while (j <= i && not comp(proj(*lr_cummax[j - 1]), proj(*rl_min_it)) && (j == 1 || not comp(proj(*rl_min_it), proj(*lr_cummax[j - 2])))) { // Compute the next value of DM diff --git a/include/cpp-sort/probes/enc.h b/include/cpp-sort/probes/enc.h index c7a65cfc..4a174cdd 100644 --- a/include/cpp-sort/probes/enc.h +++ b/include/cpp-sort/probes/enc.h @@ -10,7 +10,6 @@ //////////////////////////////////////////////////////////// #include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/exc.h b/include/cpp-sort/probes/exc.h index c97c76e8..9add8441 100644 --- a/include/cpp-sort/probes/exc.h +++ b/include/cpp-sort/probes/exc.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_EXC_H_ @@ -10,7 +10,6 @@ //////////////////////////////////////////////////////////// #include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/ham.h b/include/cpp-sort/probes/ham.h index 03ee0086..047f99ed 100644 --- a/include/cpp-sort/probes/ham.h +++ b/include/cpp-sort/probes/ham.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_HAM_H_ @@ -10,7 +10,6 @@ //////////////////////////////////////////////////////////// #include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/inv.h b/include/cpp-sort/probes/inv.h index cf521125..eca5d89e 100644 --- a/include/cpp-sort/probes/inv.h +++ b/include/cpp-sort/probes/inv.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_INV_H_ @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -45,7 +44,7 @@ namespace probe auto buffer = std::make_unique(size); auto store = iterators.get(); - for (ForwardIterator it = first ; it != last ; ++it) { + for (auto it = first; it != last; ++it) { *store++ = it; } diff --git a/include/cpp-sort/probes/max.h b/include/cpp-sort/probes/max.h index 94dbf57d..e336ce3d 100644 --- a/include/cpp-sort/probes/max.h +++ b/include/cpp-sort/probes/max.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_MAX_H_ @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -66,7 +65,7 @@ namespace probe difference_type max_dist = 0; difference_type it_pos = 0; - for (auto it = first ; it != last ; ++it) { + for (auto it = first; it != last; ++it) { // Find the range where *first belongs once sorted auto rng = cppsort::detail::equal_range( iterators.begin(), iterators.end(), proj(*it), diff --git a/include/cpp-sort/probes/mono.h b/include/cpp-sort/probes/mono.h index ae311b70..1cee61c0 100644 --- a/include/cpp-sort/probes/mono.h +++ b/include/cpp-sort/probes/mono.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 Morwenn + * Copyright (c) 2018-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_MONO_H_ @@ -10,7 +10,6 @@ //////////////////////////////////////////////////////////// #include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index b527b355..83b0a913 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_OSC_H_ @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/rem.h b/include/cpp-sort/probes/rem.h index cdd20d6f..85bbf2a8 100644 --- a/include/cpp-sort/probes/rem.h +++ b/include/cpp-sort/probes/rem.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_REM_H_ @@ -10,7 +10,6 @@ //////////////////////////////////////////////////////////// #include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/runs.h b/include/cpp-sort/probes/runs.h index 6677f36f..5106b340 100644 --- a/include/cpp-sort/probes/runs.h +++ b/include/cpp-sort/probes/runs.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_RUNS_H_ @@ -10,7 +10,6 @@ //////////////////////////////////////////////////////////// #include #include -#include #include #include #include diff --git a/include/cpp-sort/probes/sus.h b/include/cpp-sort/probes/sus.h index 6702705e..d76d3545 100644 --- a/include/cpp-sort/probes/sus.h +++ b/include/cpp-sort/probes/sus.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Morwenn + * Copyright (c) 2021-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_SUS_H_ @@ -9,14 +9,11 @@ // Headers //////////////////////////////////////////////////////////// #include -#include -#include #include #include #include #include #include -#include #include #include "../detail/longest_non_descending_subsequence.h" #include "../detail/type_traits.h" From cb04cdaacc754016323c04a782ae192801a66f36 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 4 Nov 2022 22:57:26 +0100 Subject: [PATCH 15/53] Minor documentation fixes --- docs/Sorter-facade.md | 4 ++-- docs/Sorter-traits.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index afa27911..e448f0eb 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -127,7 +127,7 @@ constexpr auto operator()(Iterable&& iterable, Compare compare, Projection proje -> /* implementation-defined */; ``` -These overloads will generally forward the parameters to the corresponding `operator()` overloads in the wrapped *sorter implementation* if they exist, or try to call an equivalent `operator()` taking a pair of iterators in the wrapped sorter by using `utility::begin` and `utility::end` on the iterable to sort. It also does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in `sorter` and to complete the call with instances of [`std::less<>`][std-less-void] and/or [`utility::identity`][utility-identity] when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. +These overloads will generally forward the parameters to the corresponding `operator()` overloads in the wrapped *sorter implementation* if they exist, or try to call an equivalent `operator()` taking a pair of iterators in the wrapped sorter by using `std::begin` and `std::end` on the iterable to sort. It also does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in `sorter` and to complete the call with instances of [`std::less<>`][std-less-void] and/or [`utility::identity`][utility-identity] when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. It will always call the most suitable iterable `operator()` overload in the wrapped *sorter implementation* if there is one, and dispatch the call to an overload taking a pair of iterators when it cannot do otherwise. @@ -137,7 +137,7 @@ It will always call the most suitable iterable `operator()` overload in the wrap ### Projection support for comparison-only sorters -Some *sorter implementations* are able to handle custom comparison functions but don't have any dedicated support for projections. If such an implementation is wrapped by `sorter_facade` and is given a projection function, `sorter_facade` will bake the projection into the comparison function and give the result to the *sorter implementation* as a comparison function. Basically it means that a *sorter implementation* with a single `operator()` taking a pair of iterators and a comparison function can take any iterable, pair of iterators, comparison and/or projection function once it wrapped into `sorter_facade`. +Some *sorter implementations* are able to handle custom comparison functions but don't have any dedicated support for projections. If such an implementation is wrapped by `sorter_facade` and is given a projection function, `sorter_facade` will bake the projection into the comparison function and give the result to the *sorter implementation* as a comparison function. Basically it means that a *sorter implementation* with a single `operator()` taking a pair of iterators and a comparison function can take any iterable, pair of iterators, comparison and/or projection function once it is wrapped into `sorter_facade`. The reverse operation (baking a comparison function into a projection function) is not doable and simply does not make sense most of the time, so `sorter_facade` does not provide it for projection-only *sorter implementations*. diff --git a/docs/Sorter-traits.md b/docs/Sorter-traits.md index b2d124e6..01ee1df4 100644 --- a/docs/Sorter-traits.md +++ b/docs/Sorter-traits.md @@ -4,7 +4,7 @@ ### `is_projection` and `is_projection_iterator` -The goal is these type traits is to check whether a projection function can be applied on the `value_type` of an iterable or an iterator. An additional template parameter `Compare` may be specified, in which case the traits will also check whether the given binary comparison function can be called with two projected values. +The goal of these type traits is to check whether a projection function can be applied on the `reference_type` of an iterable or an iterator. An additional template parameter `Compare` may be specified, in which case the traits will also check whether the given binary comparison function can be called with two projected values. ```cpp template< From a24b1005bd16c978ba05b24d0d34fa088fe7ef93 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 14:29:06 +0100 Subject: [PATCH 16/53] Fix potential move-after-free bug in quick_merge_sort --- include/cpp-sort/sorters/quick_merge_sorter.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/cpp-sort/sorters/quick_merge_sorter.h b/include/cpp-sort/sorters/quick_merge_sorter.h index 0d1dd3db..6d7acbb6 100644 --- a/include/cpp-sort/sorters/quick_merge_sorter.h +++ b/include/cpp-sort/sorters/quick_merge_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 Morwenn + * Copyright (c) 2018-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_QUICK_MERGE_SORTER_H_ @@ -76,8 +76,8 @@ namespace cppsort ); using std::distance; // Hack for sized_iterator - quick_merge_sort(std::move(first), std::move(last), - distance(first, last), + auto dist = distance(first, last); + quick_merge_sort(std::move(first), std::move(last), dist, std::move(compare), std::move(projection)); } From 48c6841a394bc21943abe6f1887932d668b4b93e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 15:17:13 +0100 Subject: [PATCH 17/53] New sorter: d_ary_heap_sorter --- NOTICE.txt | 10 ++ README.md | 3 + docs/Sorters.md | 23 ++- include/cpp-sort/detail/d_ary_heapsort.h | 170 ++++++++++++++++++ include/cpp-sort/fwd.h | 2 + include/cpp-sort/sorters.h | 1 + include/cpp-sort/sorters/d_ary_heap_sorter.h | 80 +++++++++ .../drop_merge_adapter_every_sorter.cpp | 1 + .../indirect_adapter_every_sorter.cpp | 1 + .../schwartz_adapter_every_sorter.cpp | 1 + ...schwartz_adapter_every_sorter_reversed.cpp | 1 + tests/adapters/split_adapter_every_sorter.cpp | 1 + .../adapters/stable_adapter_every_sorter.cpp | 1 + tests/adapters/verge_adapter_every_sorter.cpp | 1 + tests/distributions/all_equal.cpp | 1 + tests/distributions/alternating.cpp | 1 + tests/distributions/ascending.cpp | 1 + tests/distributions/ascending_sawtooth.cpp | 1 + tests/distributions/descending.cpp | 1 + tests/distributions/descending_sawtooth.cpp | 1 + tests/distributions/median_of_3_killer.cpp | 1 + tests/distributions/pipe_organ.cpp | 1 + tests/distributions/push_front.cpp | 1 + tests/distributions/push_middle.cpp | 1 + tests/distributions/shuffled.cpp | 1 + tests/distributions/shuffled_16_values.cpp | 1 + tests/sorters/every_instantiated_sorter.cpp | 6 + tests/sorters/every_sorter.cpp | 8 + .../every_sorter_heap_memory_exhaustion.cpp | 1 + .../sorters/every_sorter_internal_compare.cpp | 1 + tests/sorters/every_sorter_long_string.cpp | 1 + .../every_sorter_move_compare_projection.cpp | 1 + tests/sorters/every_sorter_move_only.cpp | 1 + .../sorters/every_sorter_no_post_iterator.cpp | 1 + .../every_sorter_non_const_compare.cpp | 1 + .../every_sorter_rvalue_projection.cpp | 1 + .../every_sorter_small_collections.cpp | 1 + tests/sorters/every_sorter_span.cpp | 1 + tests/sorters/every_sorter_throwing_moves.cpp | 1 + .../every_sorter_tricky_difference_type.cpp | 1 + 40 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 include/cpp-sort/detail/d_ary_heapsort.h create mode 100644 include/cpp-sort/sorters/d_ary_heap_sorter.h diff --git a/NOTICE.txt b/NOTICE.txt index 7ea96e0b..4658142b 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -75,6 +75,16 @@ In addition, certain files include the notices provided below. ---------------------- +// boost heap: d-ary heap as container adaptor +// +// Copyright (C) 2010 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +---------------------- + //---------------------------------------------------------------------------- /// @file merge.hpp /// @brief low level merge functions diff --git a/README.md b/README.md index dedd83d9..0ca455b7 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,9 @@ of a Timsort](https://github.com/gfx/cpp-TimSort). * The three algorithms used by `spread_sorter` come from Steven Ross [Boost.Sort module](https://www.boost.org/doc/libs/1_71_0/libs/sort/doc/html/index.html). +* The algorithm used by `d_ary_spread_sorter` comes from Tim Blechmann's +[Boost.Heap module](https://www.boost.org/doc/libs/1_80_0/doc/html/heap.html). + * [`utility::as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function), [`utility::static_const`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#static_const), and several projection-enhanced helper algorithms come from Eric Niebler's [Range diff --git a/docs/Sorters.md b/docs/Sorters.md index 1241e9e6..c727d139 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -54,6 +54,22 @@ Implements a [Cartesian tree sort][cartesian-tree-sort], a rather slow but highl *Changed in version 1.11.0:* `cartesian_tree_sorter` now works with forward iterators. It used to only work with random-access iterators. +### `d_ary_heap_sorter` + +```cpp +#include +``` + +Implements a heapsort algorithm based on a [*d*-ary heap][d-ary-heap]: the value of *D* is an integer template parameter that can be passed to either `d_ary_heap_sort` or `d_ary_heap_sorter`. That value cannot be smaller than 2. + +| Best | Average | Worst | Memory | Stable | Iterators | +| ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | +| n log n | n log n | n log n | 1 | No | Random-access | + +*d*-ary heapsorts are more complicated than their binary counterparts, but storing more elements in each node can improve locality of reference and improve the execution speed. This sorter, unlike [`heap_sorter`][heap-sorter], does not implement a [bottom-up node searching strategy][bottom-up-heapsort]. + +*New in version 1.14.0* + ### `default_sorter` ```cpp @@ -137,7 +153,7 @@ Whether this sorter works with types that are not default-constructible depends #include ``` -Implements a bottom-up [heapsort][heapsort]. +Implements a [bottom-up heapsort][bottom-up-heapsort]. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -251,7 +267,7 @@ This sorter can't throw `std::bad_alloc`. #include ``` -Implements a *poplar sort*, which is a heapsort derivate described by Coenraad Bron and Wim H. Hesselink in *Smoothsort revisited*. It builds a forest of perfect max heaps whose roots are stored on the right, then unheaps the elements to sort the collection. +Implements a *poplar sort*, which is a [heapsort][heapsort] derivate described by Coenraad Bron and Wim H. Hesselink in *Smoothsort revisited*. It builds a forest of perfect max heaps whose roots are stored on the right, then unheaps the elements to sort the collection. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -572,15 +588,18 @@ struct spread_sorter: [adaptive-shivers-sort]: https://arxiv.org/abs/1809.08411 [bitmap-allocator]: https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html [block-sort]: https://en.wikipedia.org/wiki/Block_sort + [bottom-up-heapsort]: https://en.wikipedia.org/wiki/Heapsort#Bottom-up_heapsort [branchless-traits]: Miscellaneous-utilities.md#branchless-traits [cartesian-tree-sort]: https://en.wikipedia.org/wiki/Cartesian_tree#Application_in_sorting [container-aware-adapter]: Sorter-adapters.md#container_aware_adapter [counting-sort]: https://en.wikipedia.org/wiki/Counting_sort [cppsort-sort]: Sorting-functions.md#cppsortsort + [d-ary-heap]: https://en.wikipedia.org/wiki/D-ary_heap [default-sorter]: Sorters.md#default_sorter [drop-merge-sort]: https://github.com/emilk/drop-merge-sort [grailsort]: https://github.com/Mrrl/GrailSort [heapsort]: https://en.wikipedia.org/wiki/Heapsort + [heap-sorter]: Sorters.md#heap_sorter [insertion-sort]: https://en.wikipedia.org/wiki/Insertion_sort [introselect]: https://en.wikipedia.org/wiki/Introselect [issue-168]: https://github.com/Morwenn/cpp-sort/issues/168 diff --git a/include/cpp-sort/detail/d_ary_heapsort.h b/include/cpp-sort/detail/d_ary_heapsort.h new file mode 100644 index 00000000..b617af80 --- /dev/null +++ b/include/cpp-sort/detail/d_ary_heapsort.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2016-2022 Morwenn + * SPDX-License-Identifier: MIT + */ +// boost heap: d-ary heap as container adaptor +// +// Copyright (C) 2010 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef CPPSORT_DETAIL_D_ARY_HEAPSORT_H_ +#define CPPSORT_DETAIL_D_ARY_HEAPSORT_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include "min_element.h" + +namespace cppsort +{ +namespace detail +{ + template + auto parent_it(RandomAccessIterator first, RandomAccessIterator it) + -> RandomAccessIterator + { + auto index = it - first; + return first + (index - 1) / D; + } + + template + auto first_child_it(RandomAccessIterator first, RandomAccessIterator it) + -> RandomAccessIterator + { + auto index = it - first; + return first + index * D + 1; + } + + template + auto top_child_it(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIterator it, + Compare compare, Projection projection) + -> RandomAccessIterator + { + // invariant: it is not a leaf, so the iterator range is not empty + + auto first_child = first_child_it(first, it); + auto max_elements = last - first_child; + auto last_child = (max_elements > D) ? first_child + D : last; + + return detail::min_element( + first_child, last_child, + cppsort::flip(std::move(compare)), + std::move(projection) + ); + } + + template + auto not_leaf(RandomAccessIterator first, RandomAccessIterator last, + RandomAccessIterator it) + -> bool + { + return (it - first) * (D - 1) < last - it - 1; + } + + template + auto siftdown(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIterator it, + Compare compare, Projection projection) + -> void + { + using utility::iter_swap; + + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + while (not_leaf(first, last, it)) { + auto max_child = top_child_it(first, last, it, compare, projection); + if (not comp(proj(*max_child), proj(*it))) { + iter_swap(max_child, it); + it = max_child; + } else { + return; + } + } + } + + template + auto siftup(RandomAccessIterator first, RandomAccessIterator it, + Compare compare, Projection projection) + -> void + { + using utility::iter_swap; + + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + while (it != first) { + auto parent = parent_it(first, it); + + if (comp(proj(*parent), proj(*it))) { + iter_swap(parent, it); + it = parent; + } else { + return; + } + } + } + + template + auto push_d_ary_heap(RandomAccessIterator first, RandomAccessIterator last, + Compare compare, Projection projection) + -> void + { + siftup(first, std::prev(last), std::move(compare), std::move(projection)); + } + + template + auto pop_d_ary_heap(RandomAccessIterator first, RandomAccessIterator last, + Compare compare, Projection projection) + -> void + { + using utility::iter_swap; + iter_swap(first, --last); + if (first == last) return; + + siftdown(first, last, first, std::move(compare), std::move(projection)); + } + + template + auto make_d_ary_heap(RandomAccessIterator first, RandomAccessIterator last, + Compare compare, Projection projection) + -> void + { + if (first == last) return; + + for (auto it = std::next(first); it != last; ++it) { + push_d_ary_heap(first, it, compare, projection); + } + // Take the last element into consideration + push_d_ary_heap(first, last, std::move(compare), std::move(projection)); + } + + template + auto sort_d_ary_heap(RandomAccessIterator first, RandomAccessIterator last, + Compare compare, Projection projection) + -> void + { + for (auto it = last; it != first; --it) { + pop_d_ary_heap(first, it, compare, projection); + } + } + + template + auto d_ary_heapsort(RandomAccessIterator first, RandomAccessIterator last, + Compare compare, Projection projection) + -> void + { + make_d_ary_heap(first, last, compare, projection); + sort_d_ary_heap(std::move(first), std::move(last), + std::move(compare), std::move(projection)); + } +}} + +#endif // CPPSORT_DETAIL_D_ARY_HEAPSORT_H_ diff --git a/include/cpp-sort/fwd.h b/include/cpp-sort/fwd.h index 112ce182..ee35465a 100644 --- a/include/cpp-sort/fwd.h +++ b/include/cpp-sort/fwd.h @@ -29,6 +29,8 @@ namespace cppsort struct block_sorter; struct cartesian_tree_sorter; struct counting_sorter; + template + struct d_ary_heap_sorter; struct default_sorter; struct drop_merge_sorter; struct float_spread_sorter; diff --git a/include/cpp-sort/sorters.h b/include/cpp-sort/sorters.h index 82428feb..74cdab18 100644 --- a/include/cpp-sort/sorters.h +++ b/include/cpp-sort/sorters.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/include/cpp-sort/sorters/d_ary_heap_sorter.h b/include/cpp-sort/sorters/d_ary_heap_sorter.h new file mode 100644 index 00000000..f010a867 --- /dev/null +++ b/include/cpp-sort/sorters/d_ary_heap_sorter.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_SORTERS_D_ARY_HEAP_SORTER_H_ +#define CPPSORT_SORTERS_D_ARY_HEAP_SORTER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/d_ary_heapsort.h" +#include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // Sorter + + namespace detail + { + template + struct d_ary_heap_sorter_impl + { + template< + typename RandomAccessIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = detail::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + static_assert( + std::is_base_of< + iterator_category, + iterator_category_t + >::value, + "d_ary_heap_sorter requires at least random-access iterators" + ); + + d_ary_heapsort(std::move(first), std::move(last), + std::move(compare), std::move(projection)); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::random_access_iterator_tag; + using is_always_stable = std::false_type; + }; + } + + template + struct d_ary_heap_sorter: + sorter_facade> + { + static_assert(D >= 2, "d_ary_heap_sorter must be instantiated with D >= 2"); + }; + + namespace + { + template + constexpr auto&& d_ary_heap_sort + = utility::static_const>::value; + } +} + +#endif // CPPSORT_SORTERS_D_ARY_HEAP_SORTER_H_ diff --git a/tests/adapters/drop_merge_adapter_every_sorter.cpp b/tests/adapters/drop_merge_adapter_every_sorter.cpp index 8c3b2a46..e8936e0e 100644 --- a/tests/adapters/drop_merge_adapter_every_sorter.cpp +++ b/tests/adapters/drop_merge_adapter_every_sorter.cpp @@ -15,6 +15,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with drop_merge_adapter", "[drop_merge_adapter]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<6>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/adapters/indirect_adapter_every_sorter.cpp b/tests/adapters/indirect_adapter_every_sorter.cpp index 4449cbc0..9ef662df 100644 --- a/tests/adapters/indirect_adapter_every_sorter.cpp +++ b/tests/adapters/indirect_adapter_every_sorter.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with indirect adapter", "[indirect_adapter]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<7>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/adapters/schwartz_adapter_every_sorter.cpp b/tests/adapters/schwartz_adapter_every_sorter.cpp index 6a88e56d..16df64d0 100644 --- a/tests/adapters/schwartz_adapter_every_sorter.cpp +++ b/tests/adapters/schwartz_adapter_every_sorter.cpp @@ -26,6 +26,7 @@ using wrapper = generic_wrapper; TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapter", "[schwartz_adapter]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<9>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/adapters/schwartz_adapter_every_sorter_reversed.cpp b/tests/adapters/schwartz_adapter_every_sorter_reversed.cpp index 33678413..d44e2c84 100644 --- a/tests/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/tests/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -26,6 +26,7 @@ TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse "[schwartz_adapter][reverse_iterator]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<8>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/adapters/split_adapter_every_sorter.cpp b/tests/adapters/split_adapter_every_sorter.cpp index 9d927607..2c719fa5 100644 --- a/tests/adapters/split_adapter_every_sorter.cpp +++ b/tests/adapters/split_adapter_every_sorter.cpp @@ -15,6 +15,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with split_adapter", "[split_adapter]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<2>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/adapters/stable_adapter_every_sorter.cpp b/tests/adapters/stable_adapter_every_sorter.cpp index a7fb5411..7a172d42 100644 --- a/tests/adapters/stable_adapter_every_sorter.cpp +++ b/tests/adapters/stable_adapter_every_sorter.cpp @@ -29,6 +29,7 @@ using wrapper = generic_stable_wrapper; TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_adapter]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<3>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/adapters/verge_adapter_every_sorter.cpp b/tests/adapters/verge_adapter_every_sorter.cpp index 5088a7ac..62a265c3 100644 --- a/tests/adapters/verge_adapter_every_sorter.cpp +++ b/tests/adapters/verge_adapter_every_sorter.cpp @@ -17,6 +17,7 @@ TEMPLATE_TEST_CASE( "every sorter with verge_adapter", "[verge_adapter]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<4>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/distributions/all_equal.cpp b/tests/distributions/all_equal.cpp index 507c29f3..ed43740c 100644 --- a/tests/distributions/all_equal.cpp +++ b/tests/distributions/all_equal.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test random-access sorters with all_equal distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<2>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/alternating.cpp b/tests/distributions/alternating.cpp index d31f73ee..e460e1e6 100644 --- a/tests/distributions/alternating.cpp +++ b/tests/distributions/alternating.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test sorter with alternating distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<3>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/ascending.cpp b/tests/distributions/ascending.cpp index 76f93acc..661757d4 100644 --- a/tests/distributions/ascending.cpp +++ b/tests/distributions/ascending.cpp @@ -19,6 +19,7 @@ TEMPLATE_TEST_CASE( "test sorter with ascending distribution", "[distributions]" // that could specifically appear with an ascending distribution, // so here is the dedicated test (see issue #103) cppsort::counting_sorter, + cppsort::d_ary_heap_sorter<5>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/ascending_sawtooth.cpp b/tests/distributions/ascending_sawtooth.cpp index 7eed81a1..363d60ec 100644 --- a/tests/distributions/ascending_sawtooth.cpp +++ b/tests/distributions/ascending_sawtooth.cpp @@ -15,6 +15,7 @@ TEMPLATE_TEST_CASE( "test random-access sorters with ascending_sawtooth distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<4>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/descending.cpp b/tests/distributions/descending.cpp index e4d2dfc9..0506ca64 100644 --- a/tests/distributions/descending.cpp +++ b/tests/distributions/descending.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test sorter with descending distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<7>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/descending_sawtooth.cpp b/tests/distributions/descending_sawtooth.cpp index 00d48185..6ee08f6c 100644 --- a/tests/distributions/descending_sawtooth.cpp +++ b/tests/distributions/descending_sawtooth.cpp @@ -15,6 +15,7 @@ TEMPLATE_TEST_CASE( "test random-access sorters with descending_sawtooth distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<6>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/median_of_3_killer.cpp b/tests/distributions/median_of_3_killer.cpp index 67bc4bff..2913e970 100644 --- a/tests/distributions/median_of_3_killer.cpp +++ b/tests/distributions/median_of_3_killer.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test random-access sorters with median_of_3_killer distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<8>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/pipe_organ.cpp b/tests/distributions/pipe_organ.cpp index bd2d6398..896411de 100644 --- a/tests/distributions/pipe_organ.cpp +++ b/tests/distributions/pipe_organ.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test sorter with pipe_organ distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<9>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/push_front.cpp b/tests/distributions/push_front.cpp index 9260511f..422226ae 100644 --- a/tests/distributions/push_front.cpp +++ b/tests/distributions/push_front.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test sorter with push_front distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<2>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/push_middle.cpp b/tests/distributions/push_middle.cpp index a0fd01a2..b0c27557 100644 --- a/tests/distributions/push_middle.cpp +++ b/tests/distributions/push_middle.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test sorter with push_middle distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<3>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/shuffled.cpp b/tests/distributions/shuffled.cpp index 039c8f63..3375d92a 100644 --- a/tests/distributions/shuffled.cpp +++ b/tests/distributions/shuffled.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test sorter with shuffled distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<5>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/distributions/shuffled_16_values.cpp b/tests/distributions/shuffled_16_values.cpp index 911cc069..d260b083 100644 --- a/tests/distributions/shuffled_16_values.cpp +++ b/tests/distributions/shuffled_16_values.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test sorter with shuffled_16_values distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<4>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/sorters/every_instantiated_sorter.cpp b/tests/sorters/every_instantiated_sorter.cpp index f638d0a9..dece397f 100644 --- a/tests/sorters/every_instantiated_sorter.cpp +++ b/tests/sorters/every_instantiated_sorter.cpp @@ -46,6 +46,12 @@ TEST_CASE( "test every instantiated sorter", "[sorters]" ) CHECK( std::is_sorted(collection.begin(), collection.end()) ); } + SECTION( "d_ary_heap_sort" ) + { + cppsort::d_ary_heap_sort<5>(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + SECTION( "drop_merge_sort" ) { cppsort::drop_merge_sort(collection); diff --git a/tests/sorters/every_sorter.cpp b/tests/sorters/every_sorter.cpp index d8a07c82..fe1646a2 100644 --- a/tests/sorters/every_sorter.cpp +++ b/tests/sorters/every_sorter.cpp @@ -18,6 +18,14 @@ TEMPLATE_TEST_CASE( "test every random-access sorter", "[sorters]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, cppsort::counting_sorter, + cppsort::d_ary_heap_sorter<2>, + cppsort::d_ary_heap_sorter<3>, + cppsort::d_ary_heap_sorter<4>, + cppsort::d_ary_heap_sorter<5>, + cppsort::d_ary_heap_sorter<6>, + cppsort::d_ary_heap_sorter<7>, + cppsort::d_ary_heap_sorter<8>, + cppsort::d_ary_heap_sorter<9>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/sorters/every_sorter_heap_memory_exhaustion.cpp b/tests/sorters/every_sorter_heap_memory_exhaustion.cpp index 66ffb4dc..291d0eb2 100644 --- a/tests/sorters/every_sorter_heap_memory_exhaustion.cpp +++ b/tests/sorters/every_sorter_heap_memory_exhaustion.cpp @@ -20,6 +20,7 @@ // TEMPLATE_TEST_CASE( "heap exhaustion for random-access sorters", "[sorters][heap_exhaustion]", + cppsort::d_ary_heap_sorter<7>, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, diff --git a/tests/sorters/every_sorter_internal_compare.cpp b/tests/sorters/every_sorter_internal_compare.cpp index afe05794..d446217a 100644 --- a/tests/sorters/every_sorter_internal_compare.cpp +++ b/tests/sorters/every_sorter_internal_compare.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test every sorter with a pointer to member function compari "[sorters][as_function]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<4>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, diff --git a/tests/sorters/every_sorter_long_string.cpp b/tests/sorters/every_sorter_long_string.cpp index 8189cbff..81b3891e 100644 --- a/tests/sorters/every_sorter_long_string.cpp +++ b/tests/sorters/every_sorter_long_string.cpp @@ -40,6 +40,7 @@ namespace TEMPLATE_TEST_CASE( "test every sorter with long std::string", "[sorters]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<6>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/sorters/every_sorter_move_compare_projection.cpp b/tests/sorters/every_sorter_move_compare_projection.cpp index 7fd971bd..9ce7c4bf 100644 --- a/tests/sorters/every_sorter_move_compare_projection.cpp +++ b/tests/sorters/every_sorter_move_compare_projection.cpp @@ -15,6 +15,7 @@ TEMPLATE_TEST_CASE( "every sorter with comparison function altered by move", "[sorters]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<2>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/sorters/every_sorter_move_only.cpp b/tests/sorters/every_sorter_move_only.cpp index 5f28aa48..6a08d7ab 100644 --- a/tests/sorters/every_sorter_move_only.cpp +++ b/tests/sorters/every_sorter_move_only.cpp @@ -14,6 +14,7 @@ TEMPLATE_TEST_CASE( "test every sorter with move-only types", "[sorters]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<5>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/sorters/every_sorter_no_post_iterator.cpp b/tests/sorters/every_sorter_no_post_iterator.cpp index 32149889..625976aa 100644 --- a/tests/sorters/every_sorter_no_post_iterator.cpp +++ b/tests/sorters/every_sorter_no_post_iterator.cpp @@ -17,6 +17,7 @@ TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, cppsort::counting_sorter, + cppsort::d_ary_heap_sorter<6>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/sorters/every_sorter_non_const_compare.cpp b/tests/sorters/every_sorter_non_const_compare.cpp index 02a25b41..4d3c72e6 100644 --- a/tests/sorters/every_sorter_non_const_compare.cpp +++ b/tests/sorters/every_sorter_non_const_compare.cpp @@ -13,6 +13,7 @@ TEMPLATE_TEST_CASE( "test extended compatibility with LWG 3031", "[sorters]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<7>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/sorters/every_sorter_rvalue_projection.cpp b/tests/sorters/every_sorter_rvalue_projection.cpp index 7e243f3b..d285d921 100644 --- a/tests/sorters/every_sorter_rvalue_projection.cpp +++ b/tests/sorters/every_sorter_rvalue_projection.cpp @@ -16,6 +16,7 @@ TEMPLATE_TEST_CASE( "random-access sorters with a projection returning an rvalue", "[sorters][projection]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<8>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, diff --git a/tests/sorters/every_sorter_small_collections.cpp b/tests/sorters/every_sorter_small_collections.cpp index 517df11d..a6ef08e0 100644 --- a/tests/sorters/every_sorter_small_collections.cpp +++ b/tests/sorters/every_sorter_small_collections.cpp @@ -11,6 +11,7 @@ TEMPLATE_TEST_CASE( "test every sorter with small collections", "[sorters]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, cppsort::counting_sorter, + cppsort::d_ary_heap_sorter<9>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, diff --git a/tests/sorters/every_sorter_span.cpp b/tests/sorters/every_sorter_span.cpp index f67d6e51..628ed4dd 100644 --- a/tests/sorters/every_sorter_span.cpp +++ b/tests/sorters/every_sorter_span.cpp @@ -16,6 +16,7 @@ TEMPLATE_TEST_CASE( "test every sorter with temporary span", "[sorters][span]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, cppsort::counting_sorter, + cppsort::d_ary_heap_sorter<2>, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, diff --git a/tests/sorters/every_sorter_throwing_moves.cpp b/tests/sorters/every_sorter_throwing_moves.cpp index fe0fd328..2baac0c5 100644 --- a/tests/sorters/every_sorter_throwing_moves.cpp +++ b/tests/sorters/every_sorter_throwing_moves.cpp @@ -79,6 +79,7 @@ namespace TEMPLATE_TEST_CASE( "random-access sorters against throwing move operations", "[sorters][throwing_moves]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::d_ary_heap_sorter<3>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< diff --git a/tests/sorters/every_sorter_tricky_difference_type.cpp b/tests/sorters/every_sorter_tricky_difference_type.cpp index f03c1800..e8d3248e 100644 --- a/tests/sorters/every_sorter_tricky_difference_type.cpp +++ b/tests/sorters/every_sorter_tricky_difference_type.cpp @@ -16,6 +16,7 @@ TEMPLATE_TEST_CASE( "test every sorter with an int8_t difference_type", "[sorter cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, cppsort::counting_sorter, + cppsort::d_ary_heap_sorter<4>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< From d7264fd902cc503dc416c38c918ac255ef7bf04d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 15:20:31 +0100 Subject: [PATCH 18/53] Mention spinsort author in README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0ca455b7..b8bd5e2f 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,10 @@ module](https://www.boost.org/doc/libs/1_71_0/libs/sort/doc/html/index.html). * The algorithm used by `d_ary_spread_sorter` comes from Tim Blechmann's [Boost.Heap module](https://www.boost.org/doc/libs/1_80_0/doc/html/heap.html). +* The algorithm used by `spin_sorter` comes from the eponymous algorithm implemented +in [Boost.Sort](https://www.boost.org/doc/libs/1_80_0/libs/sort/doc/html/index.html). +by Francisco Jose Tapia. + * [`utility::as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function), [`utility::static_const`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#static_const), and several projection-enhanced helper algorithms come from Eric Niebler's [Range From ccdb1d56e610e565c10b868ee60001f772ae8d4c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 15:22:24 +0100 Subject: [PATCH 19/53] Update links to Boost documentation --- README.md | 2 +- docs/Sorters.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b8bd5e2f..0c47f08b 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ parts of the benchmarks come from there as well. of a Timsort](https://github.com/gfx/cpp-TimSort). * The three algorithms used by `spread_sorter` come from Steven Ross [Boost.Sort -module](https://www.boost.org/doc/libs/1_71_0/libs/sort/doc/html/index.html). +module](https://www.boost.org/doc/libs/1_80_0/libs/sort/doc/html/index.html). * The algorithm used by `d_ary_spread_sorter` comes from Tim Blechmann's [Boost.Heap module](https://www.boost.org/doc/libs/1_80_0/doc/html/heap.html). diff --git a/docs/Sorters.md b/docs/Sorters.md index c727d139..86b36d68 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -617,7 +617,7 @@ struct spread_sorter: [smoothsort]: https://en.wikipedia.org/wiki/Smoothsort [sorter-adapters]: Sorter-adapters.md [sorting-functions]: Sorting-functions.md - [spinsort]: https://www.boost.org/doc/libs/1_78_0/libs/sort/doc/html/sort/single_thread/spinsort.html + [spinsort]: https://www.boost.org/doc/libs/1_80_0/libs/sort/doc/html/sort/single_thread/spinsort.html [spreadsort]: https://en.wikipedia.org/wiki/Spreadsort [stable-adapter]: Sorter-adapters.md#stable_adapter-make_stable-and-stable_t [std-greater-void]: https://en.cppreference.com/w/cpp/utility/functional/greater_void From 0410eb1680a1387ae3adb10b59cec55ce87f6a16 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 15:37:59 +0100 Subject: [PATCH 20/53] More Conan v2 forward compatibility --- conanfile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conanfile.py b/conanfile.py index d9e7f44f..0654f0e0 100644 --- a/conanfile.py +++ b/conanfile.py @@ -6,9 +6,9 @@ import os.path from conan import ConanFile +from conan.tools.build import check_min_cppstd from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout from conan.tools.files import copy -from conans import tools required_conan_version = ">=1.50.0" @@ -17,7 +17,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" version = "1.13.2" description = "Additional sorting algorithms & related tools" - topics = "conan", "cpp-sort", "sorting", "algorithms" + topics = "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" homepage = url license = "MIT" @@ -34,8 +34,8 @@ class CppSortConan(ConanFile): settings = "os", "compiler", "build_type", "arch" def validate(self): - if self.info.settings.get_safe("compiler.cppstd"): - tools.check_min_cppstd(self, 14) + if self.settings.get_safe("compiler.cppstd"): + check_min_cppstd(self, 14) def layout(self): cmake_layout(self) From 2c1bfa653886ea91bce76ea3aa182122b06f2fe3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 16:34:18 +0100 Subject: [PATCH 21/53] drop_merge_adapter: fix misleading template parameter name --- include/cpp-sort/adapters/drop_merge_adapter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/cpp-sort/adapters/drop_merge_adapter.h b/include/cpp-sort/adapters/drop_merge_adapter.h index e220a86e..b19a20d8 100644 --- a/include/cpp-sort/adapters/drop_merge_adapter.h +++ b/include/cpp-sort/adapters/drop_merge_adapter.h @@ -37,21 +37,21 @@ namespace cppsort {} template< - typename ForwardIterator, + typename BidirectionalIterator, typename Compare = std::less<>, typename Projection = utility::identity, typename = detail::enable_if_t< - is_projection_iterator_v + is_projection_iterator_v > > - auto operator()(ForwardIterator first, ForwardIterator last, + auto operator()(BidirectionalIterator first, BidirectionalIterator last, Compare compare={}, Projection projection={}) const -> void { static_assert( std::is_base_of< iterator_category, - iterator_category_t + iterator_category_t >::value, "drop_merge_adapter requires at least bidirectional iterators" ); From a14c1c4abde32ba9496445941466a9ee653ae4e0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 16:57:33 +0100 Subject: [PATCH 22/53] Remove uneeded include --- tests/adapters/hybrid_adapter_many_sorters.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/adapters/hybrid_adapter_many_sorters.cpp b/tests/adapters/hybrid_adapter_many_sorters.cpp index 24be7d2c..e3e46302 100644 --- a/tests/adapters/hybrid_adapter_many_sorters.cpp +++ b/tests/adapters/hybrid_adapter_many_sorters.cpp @@ -8,7 +8,6 @@ #include #include #include -#include namespace { From b07f03415f7fec711b5db0c841a392573525b202 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 16:58:14 +0100 Subject: [PATCH 23/53] Fix rendering of the logo The SVG for the library's logo used text as-is but it referenced a font that's not available on most computers. It happened to work on mine but looked weird anymore else. The new SVG uses shape objects directly instead of rendered text, and as such doesn't rely on the availability on the font anymore. I also took the opportunity to reduce it to the minimal required components for it to display correctly, roughly halving its size. --- docs/images/cpp-sort-logo.svg | 336 +++------------------------------- 1 file changed, 26 insertions(+), 310 deletions(-) diff --git a/docs/images/cpp-sort-logo.svg b/docs/images/cpp-sort-logo.svg index 47bb4d67..f0e8558f 100644 --- a/docs/images/cpp-sort-logo.svg +++ b/docs/images/cpp-sort-logo.svg @@ -1,312 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - cpp-sort - - - - - - - - - - - - - - + + + + + + + + + + + + + + From 116a35bfd4219a37849161979a1a9231f04cc235 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 19:20:40 +0100 Subject: [PATCH 24/53] Merge every_sorter.cpp into distributions/shuffled.cpp Those tests roughly tested the same things, leading to needless duplication. This commit merges them both in distributions/shuffled.cpp, keeping enough to cover what both tests used to cover. One notable exception is default_sorter: the sorter has been deprecated for a while and mostly influences overload resolution but doesn't lead to algorithmic code paths that aren't otherwise tested. As such, it has no place in distributions tests and thus wasn't included. --- tests/CMakeLists.txt | 1 - tests/distributions/shuffled.cpp | 76 ++++++++++++++++++- tests/sorters/every_sorter.cpp | 123 ------------------------------- 3 files changed, 72 insertions(+), 128 deletions(-) delete mode 100644 tests/sorters/every_sorter.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 165d495d..e5ea07e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -195,7 +195,6 @@ add_executable(main-tests $<$>:sorters/default_sorter_fptr.cpp> sorters/default_sorter_projection.cpp sorters/every_instantiated_sorter.cpp - sorters/every_sorter.cpp sorters/every_sorter_internal_compare.cpp sorters/every_sorter_long_string.cpp sorters/every_sorter_move_compare_projection.cpp diff --git a/tests/distributions/shuffled.cpp b/tests/distributions/shuffled.cpp index 3375d92a..cc2b6702 100644 --- a/tests/distributions/shuffled.cpp +++ b/tests/distributions/shuffled.cpp @@ -3,7 +3,10 @@ * SPDX-License-Identifier: MIT */ #include +#include +#include #include +#include #include #include #include @@ -11,22 +14,33 @@ #include #include -TEMPLATE_TEST_CASE( "test sorter with shuffled distribution", "[distributions]", +TEMPLATE_TEST_CASE( "test random-access sorters with shuffled distribution", "[distributions]", cppsort::adaptive_shivers_sorter, cppsort::cartesian_tree_sorter, + cppsort::counting_sorter, + cppsort::d_ary_heap_sorter<2>, + cppsort::d_ary_heap_sorter<3>, + cppsort::d_ary_heap_sorter<4>, cppsort::d_ary_heap_sorter<5>, + cppsort::d_ary_heap_sorter<6>, + cppsort::d_ary_heap_sorter<7>, + cppsort::d_ary_heap_sorter<8>, + cppsort::d_ary_heap_sorter<9>, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::insertion_sorter, cppsort::mel_sorter, cppsort::merge_sorter, + cppsort::merge_insertion_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, + cppsort::selection_sorter, cppsort::ska_sorter, cppsort::slab_sorter, cppsort::smooth_sorter, @@ -41,10 +55,64 @@ TEMPLATE_TEST_CASE( "test sorter with shuffled distribution", "[distributions]", cppsort::utility::dynamic_buffer > ) { - std::vector collection; - collection.reserve(10'000); + SECTION( "with std::vector" ) + { + std::vector collection; + collection.reserve(10'000); + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 10'000, -2500); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "with std::deque" ) + { + std::deque collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 10'000, -2500); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } +} + +TEMPLATE_TEST_CASE( "test bidirectional sorters with shuffled distribution", "[distributions]", + cppsort::cartesian_tree_sorter, + cppsort::counting_sorter, + cppsort::drop_merge_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter, + cppsort::verge_sorter ) +{ + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 2500, -1000); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} + +TEMPLATE_TEST_CASE( "test forward sorters with shuffled distribution", "[distributions]", + cppsort::cartesian_tree_sorter, + cppsort::counting_sorter, + cppsort::mel_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter ) +{ + std::forward_list collection; auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 10'000); + distribution(std::front_inserter(collection), 2500, -1000); TestType sorter; sorter(collection); diff --git a/tests/sorters/every_sorter.cpp b/tests/sorters/every_sorter.cpp deleted file mode 100644 index fe1646a2..00000000 --- a/tests/sorters/every_sorter.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2016-2022 Morwenn - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -TEMPLATE_TEST_CASE( "test every random-access sorter", "[sorters]", - cppsort::adaptive_shivers_sorter, - cppsort::cartesian_tree_sorter, - cppsort::counting_sorter, - cppsort::d_ary_heap_sorter<2>, - cppsort::d_ary_heap_sorter<3>, - cppsort::d_ary_heap_sorter<4>, - cppsort::d_ary_heap_sorter<5>, - cppsort::d_ary_heap_sorter<6>, - cppsort::d_ary_heap_sorter<7>, - cppsort::d_ary_heap_sorter<8>, - cppsort::d_ary_heap_sorter<9>, - cppsort::drop_merge_sorter, - cppsort::grail_sorter<>, - cppsort::grail_sorter< - cppsort::utility::dynamic_buffer - >, - cppsort::heap_sorter, - cppsort::insertion_sorter, - cppsort::mel_sorter, - cppsort::merge_insertion_sorter, - cppsort::merge_sorter, - cppsort::pdq_sorter, - cppsort::poplar_sorter, - cppsort::quick_merge_sorter, - cppsort::quick_sorter, - cppsort::selection_sorter, - cppsort::ska_sorter, - cppsort::slab_sorter, - cppsort::smooth_sorter, - cppsort::spin_sorter, - cppsort::split_sorter, - cppsort::std_sorter, - cppsort::tim_sorter, - cppsort::verge_sorter, - cppsort::wiki_sorter<>, - cppsort::wiki_sorter< - cppsort::utility::dynamic_buffer - > ) -{ - // General test to make sure that every sorter compiles fine - // and is able to sort a vector of numbers. spread_sorter is - // already tested in-depth somewhere else and needs specific - // tests, so it's not included here. - - auto distribution = dist::shuffled{}; - - SECTION( "with std::vector" ) - { - std::vector collection; collection.reserve(491); - distribution(std::back_inserter(collection), 491, -125); - - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); - } - - SECTION( "with std::deque" ) - { - std::deque collection; - distribution(std::back_inserter(collection), 491, -125); - - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); - } -} - -TEMPLATE_TEST_CASE( "test every bidirectional sorter", "[sorters]", - cppsort::cartesian_tree_sorter, - cppsort::counting_sorter, - cppsort::drop_merge_sorter, - cppsort::insertion_sorter, - cppsort::mel_sorter, - cppsort::merge_sorter, - cppsort::quick_merge_sorter, - cppsort::quick_sorter, - cppsort::selection_sorter, - cppsort::slab_sorter, - cppsort::verge_sorter ) -{ - std::list collection; - auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 491, -125); - - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); -} - -TEMPLATE_TEST_CASE( "test every forward sorter", "[sorters]", - cppsort::cartesian_tree_sorter, - cppsort::counting_sorter, - cppsort::mel_sorter, - cppsort::merge_sorter, - cppsort::quick_merge_sorter, - cppsort::quick_sorter, - cppsort::selection_sorter ) -{ - std::forward_list collection; - auto distribution = dist::shuffled{}; - distribution(std::front_inserter(collection), 491, -125); - - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); -} From 5e9c74bc55023ee467378e8a3657cce7d8d389fd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 5 Nov 2022 23:32:08 +0100 Subject: [PATCH 25/53] New component: utility::sorted_indices (#200) --- docs/Miscellaneous-utilities.md | 24 +++++ include/cpp-sort/utility/sorted_indices.h | 110 ++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/utility/sorted_indices.cpp | 72 ++++++++++++++ 4 files changed, 207 insertions(+) create mode 100644 include/cpp-sort/utility/sorted_indices.h create mode 100644 tests/utility/sorted_indices.cpp diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 57b166cf..f4035edc 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -257,6 +257,27 @@ using make_index_range = make_integer_range; *Changed in version 1.12.1:* `utility::size()` now also works for collections that only provide non-`const` `begin()` and `end()`. +### `sorted_indices` + +```cpp +#include +``` + +`utility::sorted_indices` is a a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]). + +```cpp +std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; +auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; +auto indices = get_sorted_indices_for(vec); +// indices == [6, 3, 2, 9, 1, 8, 0, 5, 4, 7] +``` + +Concretely `sorted_indices` is designed like a [sorter adapter][sorter-adapters] and therefore supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade]. The main reason it does not sit with sorter adapters is that the returned function object is not a sorter per se since it doesn't sort the passed collection directly. + +When the collection contains several elements, the order of their indices in the results depend on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of elements that compare equal appear in a stable order in the result. + +*New in version 1.14.0* + ### Sorting network tools ```cpp @@ -335,10 +356,13 @@ You can read more about this instantiation pattern in [this article][eric-nieble [eric-niebler-static-const]: https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ [fixed-size-sorters]: Fixed-size-sorters.md [inline-variables]: https://en.cppreference.com/w/cpp/language/inline + [is-stable]: Sorter-traits.md#is_stable + [numpy-argsort]: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html [p0022]: https://wg21.link/P0022 [pdq-sorter]: Sorters.md#pdq_sorter [range-v3]: https://github.com/ericniebler/range-v3 [sorter-adapters]: Sorter-adapters.md + [sorter-facade]: Sorter-facade.md [sorters]: Sorters.md [sorting-network]: https://en.wikipedia.org/wiki/Sorting_network [std-array]: https://en.cppreference.com/w/cpp/container/array diff --git a/include/cpp-sort/utility/sorted_indices.h b/include/cpp-sort/utility/sorted_indices.h new file mode 100644 index 00000000..7b041828 --- /dev/null +++ b/include/cpp-sort/utility/sorted_indices.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_UTILITY_SORTED_INDICES_H_ +#define CPPSORT_UTILITY_SORTED_INDICES_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/checkers.h" +#include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ +namespace utility +{ + namespace detail + { + template + struct sorted_indices_impl: + utility::adapter_storage, + cppsort::detail::check_is_always_stable + { + sorted_indices_impl() = default; + + constexpr explicit sorted_indices_impl(Sorter&& sorter): + utility::adapter_storage(std::move(sorter)) + {} + + template< + typename RandomAccessIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = cppsort::detail::enable_if_t> + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + Compare compare={}, Projection projection={}) const + -> std::vector> + { + static_assert( + std::is_base_of< + iterator_category, + cppsort::detail::iterator_category_t + >::value, + "sorted_indices requires at least random-access iterators" + ); + + using difference_type = cppsort::detail::difference_type_t; + auto&& proj = utility::as_function(projection); + + // Create a vector of indices + std::vector indices(last - first, 0); + std::iota(indices.begin(), indices.end(), 0); + + // Reorder the vector thanks to the passed sorter + this->get()(indices, std::move(compare), + [&first, &proj](difference_type index) -> auto& { + return proj(first[index]); + }); + + // Return the indices that would sort the array + return indices; + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::random_access_iterator_tag; + }; + } + + template + struct sorted_indices: + sorter_facade> + { + sorted_indices() = default; + + constexpr explicit sorted_indices(Sorter sorter): + sorter_facade>(std::move(sorter)) + {} + }; +}} + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // is_stable specialization + + template + struct is_stable(Args...)>: + is_stable + {}; +} + +#endif // CPPSORT_UTILITY_SORTED_INDICES_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e5ea07e9..86ac7fa0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -227,6 +227,7 @@ add_executable(main-tests utility/buffer.cpp utility/chainable_projections.cpp utility/iter_swap.cpp + utility/sorted_indices.cpp utility/sorting_networks.cpp ) configure_tests(main-tests) diff --git a/tests/utility/sorted_indices.cpp b/tests/utility/sorted_indices.cpp new file mode 100644 index 00000000..6f0ce390 --- /dev/null +++ b/tests/utility/sorted_indices.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "basic sorted_indices test", "[utility][sorted_indices]" ) +{ + SECTION( "simple case" ) + { + auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; + const std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; + auto indices = get_sorted_indices_for(vec); + + std::vector expected = { 6, 3, 2, 9, 1, 8, 0, 5, 4, 7 }; + CHECK( indices == expected ); + } + + SECTION( "empty collection" ) + { + auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; + const std::vector vec = {}; + auto indices = get_sorted_indices_for(vec); + + std::vector expected = {}; + CHECK( indices == expected ); + + } + + SECTION( "all_equal" ) + { + // Use a stable algorithm to get deterministic results for equivalent elements + auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; + std::vector vec; + auto distribution = dist::all_equal{}; + distribution(std::back_inserter(vec), 10); + auto indices = get_sorted_indices_for(vec); + + std::vector expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + CHECK( indices == expected ); + } + + SECTION( "ascending" ) + { + auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; + std::vector vec; + auto distribution = dist::ascending{}; + distribution(std::back_inserter(vec), 10); + auto indices = get_sorted_indices_for(vec); + + std::vector expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + CHECK( indices == expected ); + } + + SECTION( "descending" ) + { + auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; + std::vector vec; + auto distribution = dist::descending{}; + distribution(std::back_inserter(vec), 10); + auto indices = get_sorted_indices_for(vec); + + std::vector expected = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + CHECK( indices == expected ); + } +} From 8bd558cf9a6d993bf9aa51d3cd64f263c821f817 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 6 Nov 2022 17:54:18 +0100 Subject: [PATCH 26/53] New component: sorted_iterators (#200) --- docs/Miscellaneous-utilities.md | 29 +++- include/cpp-sort/utility/sorted_iterators.h | 144 ++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/utility/sorted_indices.cpp | 1 + tests/utility/sorted_iterators.cpp | 92 +++++++++++++ 5 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 include/cpp-sort/utility/sorted_iterators.h create mode 100644 tests/utility/sorted_iterators.cpp diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index f4035edc..4fcbd2a5 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -263,7 +263,7 @@ using make_index_range = make_integer_range; #include ``` -`utility::sorted_indices` is a a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]). +`utility::sorted_indices` is a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]). ```cpp std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; @@ -274,7 +274,32 @@ auto indices = get_sorted_indices_for(vec); Concretely `sorted_indices` is designed like a [sorter adapter][sorter-adapters] and therefore supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade]. The main reason it does not sit with sorter adapters is that the returned function object is not a sorter per se since it doesn't sort the passed collection directly. -When the collection contains several elements, the order of their indices in the results depend on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of elements that compare equal appear in a stable order in the result. +When the collection contains several elements that compare equivalent, the order of their indices in the result depends on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of elements that compare equivalent appear in a stable order in the result. + +*New in version 1.14.0* + +### `sorted_iterators` + +```cpp +#include +``` + +`utility::sorted_iterators` is a function object that takes a sorter and returns a new function object. This new function object accepts a collection and returns an `std::vector` containing iterators to the passed collection in a sorted order. It is designed like a [sorter adapter][sorter-adapters] and as such supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade]. + +```cpp +std::list li = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; +auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; +const auto iterators = get_sorted_iterators_for(li); + +// Displays 0 1 2 3 4 5 6 7 8 9 +for (auto it: iterators) { + std::cout << *it << ' '; +} +``` + +It can be thought of as a kind of sorted view of the passed collection - as long as said collection does not change. It can be useful when the order of the original collection must be preserved, but operations have to be performed on the sorted collection. + +When the collection contains several elements that compare equivalent, the order of the corresponding iterators in the result depends on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_iterators` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the iterators to elements that compare equivalent appear in a stable order in the result. *New in version 1.14.0* diff --git a/include/cpp-sort/utility/sorted_iterators.h b/include/cpp-sort/utility/sorted_iterators.h new file mode 100644 index 00000000..1fda6a7d --- /dev/null +++ b/include/cpp-sort/utility/sorted_iterators.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_UTILITY_SORTED_ITERATORS_H_ +#define CPPSORT_UTILITY_SORTED_ITERATORS_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/checkers.h" +#include "../detail/functional.h" +#include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ +namespace utility +{ + namespace detail + { + template< + typename Sorter, + typename Iterator, + typename Compare, + typename Projection + > + auto compute_sorted_iterators(Sorter&& sorter, Iterator first, Iterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> std::vector + { + // Copy the iterators in a vector + std::vector iterators; + iterators.reserve(size); + for (auto it = first; it != last; ++it) { + iterators.emplace_back(it); + } + + // Sort the iterators on pointed values + std::forward(sorter)(iterators.begin(), iterators.end(), + std::move(compare), + cppsort::detail::indirect(std::move(projection))); + + return iterators; + } + + template + struct sorted_iterators_impl: + utility::adapter_storage, + cppsort::detail::check_is_always_stable + { + sorted_iterators_impl() = default; + + constexpr explicit sorted_iterators_impl(Sorter&& sorter): + utility::adapter_storage(std::move(sorter)) + {} + + template< + typename ForwardIterable, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = cppsort::detail::enable_if_t< + is_projection_v + > + > + auto operator()(ForwardIterable&& iterable, Compare compare={}, Projection projection={}) const + -> std::vector> + { + using category = cppsort::detail::iterator_category_t; + static_assert( + std::is_base_of::value, + "sorted_iterators requires at least forward iterators" + ); + + auto dist = cppsort::utility::size(iterable); + return compute_sorted_iterators(this->get(), std::begin(iterable), std::end(iterable), + dist, std::move(compare), std::move(projection)); + } + + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = cppsort::detail::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> std::vector + { + using category = cppsort::detail::iterator_category_t; + static_assert( + std::is_base_of::value, + "sorted_iterators requires at least forward iterators" + ); + + auto dist = std::distance(first, last); + return compute_sorted_iterators(this->get(), std::move(first), std::move(last), + dist, std::move(compare), std::move(projection)); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::forward_iterator_tag; + }; + } + + template + struct sorted_iterators: + sorter_facade> + { + sorted_iterators() = default; + + constexpr explicit sorted_iterators(Sorter sorter): + sorter_facade>(std::move(sorter)) + {} + }; +}} + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // is_stable specialization + + template + struct is_stable(Args...)>: + is_stable + {}; +} + +#endif // CPPSORT_UTILITY_SORTED_ITERATORS_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 86ac7fa0..da0aa673 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -228,6 +228,7 @@ add_executable(main-tests utility/chainable_projections.cpp utility/iter_swap.cpp utility/sorted_indices.cpp + utility/sorted_iterators.cpp utility/sorting_networks.cpp ) configure_tests(main-tests) diff --git a/tests/utility/sorted_indices.cpp b/tests/utility/sorted_indices.cpp index 6f0ce390..235095e6 100644 --- a/tests/utility/sorted_indices.cpp +++ b/tests/utility/sorted_indices.cpp @@ -3,6 +3,7 @@ * SPDX-License-Identifier: MIT */ #include +#include #include #include #include diff --git a/tests/utility/sorted_iterators.cpp b/tests/utility/sorted_iterators.cpp new file mode 100644 index 00000000..e3037550 --- /dev/null +++ b/tests/utility/sorted_iterators.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) +{ + SECTION( "simple case" ) + { + auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; + const std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; + auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + + std::vector expected = { + vec.data() + 6, vec.data() + 3, vec.data() + 2, + vec.data() + 9, vec.data() + 1, vec.data() + 8, + vec.data() + 0, vec.data() + 5, vec.data() + 4, + vec.data() + 7 + }; + CHECK( indices == expected ); + } + + SECTION( "empty collection" ) + { + auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; + const std::vector vec = {}; + auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + + std::vector expected = {}; + CHECK( indices == expected ); + + } + + SECTION( "all_equal" ) + { + // Use a stable algorithm to get deterministic results for equivalent elements + auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; + std::vector vec; + auto distribution = dist::all_equal{}; + distribution(std::back_inserter(vec), 10); + auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + + std::vector expected = { + vec.data() + 0, vec.data() + 1, vec.data() + 2, + vec.data() + 3, vec.data() + 4, vec.data() + 5, + vec.data() + 6, vec.data() + 7, vec.data() + 8, + vec.data() + 9 + }; + CHECK( indices == expected ); + } + + SECTION( "ascending" ) + { + auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; + std::vector vec; + auto distribution = dist::ascending{}; + distribution(std::back_inserter(vec), 10); + auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + + std::vector expected = { + vec.data() + 0, vec.data() + 1, vec.data() + 2, + vec.data() + 3, vec.data() + 4, vec.data() + 5, + vec.data() + 6, vec.data() + 7, vec.data() + 8, + vec.data() + 9 + }; + CHECK( indices == expected ); + } + + SECTION( "descending" ) + { + auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; + std::vector vec; + auto distribution = dist::descending{}; + distribution(std::back_inserter(vec), 10); + auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + + std::vector expected = { + vec.data() + 9, vec.data() + 8, vec.data() + 7, + vec.data() + 6, vec.data() + 5, vec.data() + 4, + vec.data() + 3, vec.data() + 2, vec.data() + 1, + vec.data() + 0 + }; + CHECK( indices == expected ); + } +} From c03d80f00eb885410e6f4c20858226c422b9a08c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 7 Nov 2022 18:32:57 +0100 Subject: [PATCH 27/53] Make indirect[_t] public (#200) --- docs/Miscellaneous-utilities.md | 37 ++++++++++- include/cpp-sort/adapters/indirect_adapter.h | 9 ++- include/cpp-sort/detail/functional.h | 64 ------------------- include/cpp-sort/detail/indiesort.h | 9 +-- .../longest_non_descending_subsequence.h | 4 +- .../cpp-sort/detail/merge_insertion_sort.h | 8 +-- include/cpp-sort/detail/slabsort.h | 4 +- include/cpp-sort/probes/block.h | 3 +- include/cpp-sort/probes/enc.h | 5 +- include/cpp-sort/probes/exc.h | 5 +- include/cpp-sort/probes/ham.h | 5 +- include/cpp-sort/probes/inv.h | 3 +- include/cpp-sort/probes/max.h | 7 +- include/cpp-sort/probes/osc.h | 11 ++-- include/cpp-sort/utility/functional.h | 46 +++++++++++++ include/cpp-sort/utility/sorted_iterators.h | 3 +- 16 files changed, 116 insertions(+), 107 deletions(-) delete mode 100644 include/cpp-sort/detail/functional.h diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 4fcbd2a5..e10a66ed 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -154,12 +154,45 @@ struct identity: It is equivalent to the C++20 [`std::identity`][std-identity]. Wherever the documentation mentions special handling of `utility::identity`, the same support is provided for `std::identity` when it is available. +Another simple yet very handy projection available in the header is `indirect_t`, along with its construction function `indirect()`. It takes a projection, and is meant to be called on a dereferenceable value `it`, in which case it will return the result of `proj(*it)`. It is meant to be used as a projection with standard algorithms when iterating over a collection of iterators. + +```cpp +template +class indirect_t: + projection_base +{ +private: + Projection proj_; + +public: + indirect_t() = default; + indirect_t(const indirect_t&) = default; + indirect_t(indirect_t&&) = default; + indirect_t& operator=(const indirect_t&) = default; + indirect_t& operator=(indirect_t&&) = default; + + constexpr explicit indirect_t(Projection proj); + + template + constexpr auto operator()(T&& indirect_value) + -> decltype(utility::as_function(proj_)(*indirect_value)); + + template + constexpr auto operator()(T&& indirect_value) const + -> decltype(utility::as_function(proj_)(*indirect_value)); +}; + +template +auto indirect(Projection&& proj) + -> indirect_t>; +``` + This header also provides additional function objects implementing basic unary operations. These functions objects are designed to be used as *size policies* with `dynamic_buffer` and similar classes. The following function objects are available: * `half`: returns the passed value divided by 2. * `log`: returns the base 10 logarithm of the passed value. * `sqrt`: returns the square root of the passed value. -All of those function objects inherit from `projection_base` and are [*transparent function objects*][transparent-func]. +All of those function objects inherit from `projection_base` and are [*transparent function objects*][transparent-func]. Since C++17, the following utility is also available when some level of micro-optimization is needed: @@ -192,6 +225,8 @@ This utility is modeled after [`std::integral_constant`][std-integral-constant], *Changed in version 1.13.0:* `half`, `log` and `sqrt` are now [*transparent function objects*][transparent-func]. +*New in version 1.14.0:* `indirect` and `indirect_t`. + ### `iter_move` and `iter_swap` ```cpp diff --git a/include/cpp-sort/adapters/indirect_adapter.h b/include/cpp-sort/adapters/indirect_adapter.h index c7714e6c..67f49cef 100644 --- a/include/cpp-sort/adapters/indirect_adapter.h +++ b/include/cpp-sort/adapters/indirect_adapter.h @@ -20,7 +20,6 @@ #include #include #include "../detail/checkers.h" -#include "../detail/functional.h" #include "../detail/immovable_vector.h" #include "../detail/indiesort.h" #include "../detail/iterator_traits.h" @@ -56,7 +55,7 @@ namespace cppsort #ifdef __cpp_lib_uncaught_exceptions -> decltype(std::forward(sorter)( (RandomAccessIterator*)0, (RandomAccessIterator*)0, - std::move(compare), indirect(projection) + std::move(compare), utility::indirect(projection) )) #else -> std::enable_if_t< @@ -64,7 +63,7 @@ namespace cppsort Sorter, RandomAccessIterator*, Compare, - indirect_t + utility::indirect_t >::value > #endif @@ -83,7 +82,7 @@ namespace cppsort // Sort the iterators on pointed values std::forward(sorter)( iterators.begin(), iterators.end(), - std::move(compare), indirect(projection) + std::move(compare), utility::indirect(projection) ); #else // Work around the sorters that return void @@ -132,7 +131,7 @@ namespace cppsort return std::forward(sorter)( iterators.begin(), iterators.end(), - std::move(compare), indirect(projection) + std::move(compare), utility::indirect(projection) ); #endif } diff --git a/include/cpp-sort/detail/functional.h b/include/cpp-sort/detail/functional.h deleted file mode 100644 index 7f4204d4..00000000 --- a/include/cpp-sort/detail/functional.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2021-2022 Morwenn - * SPDX-License-Identifier: MIT - */ -#ifndef CPPSORT_DETAIL_FUNCTIONAL_H_ -#define CPPSORT_DETAIL_FUNCTIONAL_H_ - -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include - -namespace cppsort -{ -namespace detail -{ - //////////////////////////////////////////////////////////// - // indirect - - template - class indirect_t: - utility::projection_base - { - private: - - Projection projection; - - public: - - indirect_t() = delete; - - explicit indirect_t(Projection projection): - projection(std::move(projection)) - {} - - template - auto operator()(T&& indirect_value) - -> decltype(utility::as_function(projection)(*indirect_value)) - { - auto&& proj = utility::as_function(projection); - return proj(*indirect_value); - } - - template - auto operator()(T&& indirect_value) const - -> decltype(utility::as_function(projection)(*indirect_value)) - { - auto&& proj = utility::as_function(projection); - return proj(*indirect_value); - } - }; - - template - auto indirect(Projection&& proj) - -> indirect_t> - { - return indirect_t>(std::forward(proj)); - } -}} - -#endif // CPPSORT_DETAIL_FUNCTIONAL_H_ diff --git a/include/cpp-sort/detail/indiesort.h b/include/cpp-sort/detail/indiesort.h index 2bc98dde..ad2c4959 100644 --- a/include/cpp-sort/detail/indiesort.h +++ b/include/cpp-sort/detail/indiesort.h @@ -30,6 +30,7 @@ //////////////////////////////////////////////////////////// #include #include +#include #include #include "../detail/immovable_vector.h" #include "iterator_traits.h" @@ -61,7 +62,7 @@ namespace detail -> decltype(std::forward(sorter)( (it_and_index*)0, (it_and_index*)0, std::move(compare), - &it_and_index::original_location | indirect(projection) + &it_and_index::original_location | utility::indirect(projection) )) #else -> std::enable_if_t< @@ -69,7 +70,7 @@ namespace detail Sorter, it_and_index*, Compare, - decltype(&it_and_index::original_location | indirect(projection)) + decltype(&it_and_index::original_location | utility::indirect(projection)) >::value > #endif @@ -90,7 +91,7 @@ namespace detail // Sort the iterators on pointed values std::forward(sorter)( storage.begin(), storage.end(), std::move(compare), - &item_index_tuple::original_location | indirect(projection) + &item_index_tuple::original_location | utility::indirect(projection) ); #else // Work around the sorters that return void @@ -127,7 +128,7 @@ namespace detail // Sort the iterators on pointed values return std::forward(sorter)( storage.begin(), storage.end(), std::move(compare), - &item_index_tuple::original_location | indirect(projection) + &item_index_tuple::original_location | utility::indirect(projection) ); #endif } diff --git a/include/cpp-sort/detail/longest_non_descending_subsequence.h b/include/cpp-sort/detail/longest_non_descending_subsequence.h index 4cd64ba7..1c97da2e 100644 --- a/include/cpp-sort/detail/longest_non_descending_subsequence.h +++ b/include/cpp-sort/detail/longest_non_descending_subsequence.h @@ -13,7 +13,7 @@ #include #include #include -#include "functional.h" +#include #include "iterator_traits.h" #include "upper_bound.h" @@ -64,7 +64,7 @@ namespace detail while (first != last) { auto it = detail::upper_bound( stack_tops.begin(), stack_tops.end(), - proj(*first), compare, indirect(projection)); + proj(*first), compare, utility::indirect(projection)); if (it == stack_tops.end()) { // The element is bigger than everything else, diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index 7b448781..ea824c74 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_MERGE_INSERTION_SORT_H_ @@ -11,10 +11,10 @@ #include #include #include +#include #include #include "attributes.h" #include "fixed_size_list.h" -#include "functional.h" #include "immovable_vector.h" #include "iterator_traits.h" #include "move.h" @@ -377,7 +377,7 @@ namespace detail auto insertion_point = detail::upper_bound( chain.begin(), *pe, proj(*it), - comp, indirect(proj) + comp, utility::indirect(proj) ); chain.insert(insertion_point, it); @@ -396,7 +396,7 @@ namespace detail current_it += 2; auto insertion_point = detail::upper_bound( chain.begin(), *current_pend, proj(*current_it), - comp, indirect(proj) + comp, utility::indirect(proj) ); chain.insert(insertion_point, current_it); ++current_pend; diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h index 6917678d..03e28a6a 100644 --- a/include/cpp-sort/detail/slabsort.h +++ b/include/cpp-sort/detail/slabsort.h @@ -14,11 +14,11 @@ #include #include #include +#include #include #include "bitops.h" #include "config.h" #include "fixed_size_list.h" -#include "functional.h" #include "immovable_vector.h" #include "insertion_sort.h" #include "iterator_traits.h" @@ -85,7 +85,7 @@ namespace detail return *nth_element( iterators_buffer.begin(), iterators_buffer.end(), size / 2, size, - std::move(compare), indirect(std::move(projection)) + std::move(compare), utility::indirect(std::move(projection)) ); } diff --git a/include/cpp-sort/probes/block.h b/include/cpp-sort/probes/block.h index 7314edb4..0deef014 100644 --- a/include/cpp-sort/probes/block.h +++ b/include/cpp-sort/probes/block.h @@ -17,7 +17,6 @@ #include #include #include -#include "../detail/functional.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -55,7 +54,7 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( iterators.begin(), iterators.end(), compare, - cppsort::detail::indirect(projection) + utility::indirect(projection) ); //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/probes/enc.h b/include/cpp-sort/probes/enc.h index 4a174cdd..89c2542a 100644 --- a/include/cpp-sort/probes/enc.h +++ b/include/cpp-sort/probes/enc.h @@ -19,7 +19,6 @@ #include #include #include -#include "../detail/functional.h" #include "../detail/iterator_traits.h" #include "../detail/lower_bound.h" #include "../detail/type_traits.h" @@ -46,13 +45,13 @@ namespace probe return cppsort::detail::lower_monobound_n( lists.begin(), lists.size() - 1, value, std::move(compare), - accessor | cppsort::detail::indirect(projection) + accessor | utility::indirect(projection) ); } else { return cppsort::detail::lower_bound_n( lists.begin(), lists.size() - 1, value, std::move(compare), - accessor | cppsort::detail::indirect(projection) + accessor | utility::indirect(projection) ); } } diff --git a/include/cpp-sort/probes/exc.h b/include/cpp-sort/probes/exc.h index 9add8441..26fc3e48 100644 --- a/include/cpp-sort/probes/exc.h +++ b/include/cpp-sort/probes/exc.h @@ -18,7 +18,6 @@ #include #include #include -#include "../detail/functional.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -55,8 +54,8 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( - iterators.begin(), iterators.end(), compare, - cppsort::detail::indirect(projection) + iterators.begin(), iterators.end(), + compare, utility::indirect(projection) ); //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/probes/ham.h b/include/cpp-sort/probes/ham.h index 047f99ed..b93b809c 100644 --- a/include/cpp-sort/probes/ham.h +++ b/include/cpp-sort/probes/ham.h @@ -17,7 +17,6 @@ #include #include #include -#include "../detail/functional.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -54,8 +53,8 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( - iterators.begin(), iterators.end(), compare, - cppsort::detail::indirect(projection) + iterators.begin(), iterators.end(), + compare, utility::indirect(projection) ); //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/probes/inv.h b/include/cpp-sort/probes/inv.h index eca5d89e..ceb5cdf8 100644 --- a/include/cpp-sort/probes/inv.h +++ b/include/cpp-sort/probes/inv.h @@ -18,7 +18,6 @@ #include #include #include "../detail/count_inversions.h" -#include "../detail/functional.h" #include "../detail/iterator_traits.h" #include "../detail/type_traits.h" @@ -51,7 +50,7 @@ namespace probe return cppsort::detail::count_inversions( iterators.get(), iterators.get() + size, buffer.get(), std::move(compare), - cppsort::detail::indirect(std::move(projection)) + utility::indirect(std::move(projection)) ); } diff --git a/include/cpp-sort/probes/max.h b/include/cpp-sort/probes/max.h index e336ce3d..186c2990 100644 --- a/include/cpp-sort/probes/max.h +++ b/include/cpp-sort/probes/max.h @@ -19,7 +19,6 @@ #include #include #include "../detail/equal_range.h" -#include "../detail/functional.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -55,8 +54,8 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( - iterators.begin(), iterators.end(), compare, - cppsort::detail::indirect(projection) + iterators.begin(), iterators.end(), + compare, utility::indirect(projection) ); //////////////////////////////////////////////////////////// @@ -69,7 +68,7 @@ namespace probe // Find the range where *first belongs once sorted auto rng = cppsort::detail::equal_range( iterators.begin(), iterators.end(), proj(*it), - compare, cppsort::detail::indirect(projection) + compare, utility::indirect(projection) ); auto pos_min = std::distance(iterators.begin(), rng.first); auto pos_max = std::distance(iterators.begin(), rng.second); diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index 83b0a913..30a5a0e6 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -22,7 +22,6 @@ #include #include #include "../detail/equal_range.h" -#include "../detail/functional.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -94,8 +93,8 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( - iterators.begin(), iterators.end(), compare, - cppsort::detail::indirect(projection) + iterators.begin(), iterators.end(), + compare, utility::indirect(projection) ); //////////////////////////////////////////////////////////// @@ -121,7 +120,7 @@ namespace probe auto prev_bounds = cppsort::detail::equal_range( iterators.begin(), iterators.end(), proj(*first), - compare, cppsort::detail::indirect(projection) + compare, utility::indirect(projection) ); for (auto prev = first, current = std::next(prev); current != last; ++prev, (void)++current) { @@ -130,7 +129,7 @@ namespace probe auto current_bounds = cppsort::detail::equal_range( //prev_bounds.second, std::prev(iterators.end()), proj(*current), iterators.begin(), iterators.end(), proj(*current), - compare, cppsort::detail::indirect(projection) + compare, utility::indirect(projection) ); min_idx = prev_bounds.second - iterators.begin(); max_idx = current_bounds.first - iterators.begin(); @@ -139,7 +138,7 @@ namespace probe auto current_bounds = cppsort::detail::equal_range( //iterators.begin(), prev_bounds.first, proj(*current), iterators.begin(), iterators.end(), proj(*current), - compare, cppsort::detail::indirect(projection) + compare, utility::indirect(projection) ); min_idx = current_bounds.second - iterators.begin(); max_idx = prev_bounds.first - iterators.begin(); diff --git a/include/cpp-sort/utility/functional.h b/include/cpp-sort/utility/functional.h index 1d62d690..d3473683 100644 --- a/include/cpp-sort/utility/functional.h +++ b/include/cpp-sort/utility/functional.h @@ -100,6 +100,52 @@ namespace utility std::true_type {}; + //////////////////////////////////////////////////////////// + // indirect + + template + class indirect_t: + projection_base + { + private: + + Projection projection; + + public: + + indirect_t() = default; + indirect_t(const indirect_t&) = default; + indirect_t(indirect_t&&) = default; + indirect_t& operator=(const indirect_t&) = default; + indirect_t& operator=(indirect_t&&) = default; + + constexpr explicit indirect_t(Projection projection): + projection(std::move(projection)) + {} + + template + constexpr auto operator()(T&& indirect_value) + -> decltype(utility::as_function(projection)(*indirect_value)) + { + auto&& proj = utility::as_function(projection); + return proj(*indirect_value); + } + + template + constexpr auto operator()(T&& indirect_value) const + -> decltype(utility::as_function(projection)(*indirect_value)) + { + auto&& proj = utility::as_function(projection); + return proj(*indirect_value); + } + }; + + template + auto indirect(Projection&& proj) + -> indirect_t> + { + return indirect_t>(std::forward(proj)); + } //////////////////////////////////////////////////////////// // Transform overload in unary or binary function diff --git a/include/cpp-sort/utility/sorted_iterators.h b/include/cpp-sort/utility/sorted_iterators.h index 1fda6a7d..b603d609 100644 --- a/include/cpp-sort/utility/sorted_iterators.h +++ b/include/cpp-sort/utility/sorted_iterators.h @@ -19,7 +19,6 @@ #include #include #include "../detail/checkers.h" -#include "../detail/functional.h" #include "../detail/iterator_traits.h" #include "../detail/type_traits.h" @@ -50,7 +49,7 @@ namespace utility // Sort the iterators on pointed values std::forward(sorter)(iterators.begin(), iterators.end(), std::move(compare), - cppsort::detail::indirect(std::move(projection))); + utility::indirect(std::move(projection))); return iterators; } From 2c2240e6680d6d31587e69d29a5a94162374e901 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 8 Nov 2022 00:20:00 +0100 Subject: [PATCH 28/53] Remove unneeded std::distance calls in probe::max I thought those calls were made on possibly non-random-access iterators while they are actually always made on contiguous iterators. I decided to drop the std::distance calls and replace them with a simple subtraction to make the possible complexity difference more obvious. I consequently removed the note in the documentation about probe::max being potentially super slower when used on forward or bidirectional iterators, which wasn't actually true. --- docs/Measures-of-presortedness.md | 4 +--- include/cpp-sort/probes/max.h | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index e33cafd1..61016d2b 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -198,8 +198,6 @@ Computes the maximum distance an element in *X* must travel to find its sorted p `max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. -*Warning: this algorithm might be noticeably slower when the passed iterable is not random-access.* - ### *Mono* ```cpp @@ -307,7 +305,7 @@ Some additional measures of presortedness how been described in the literature b The following definition is also given to determine whether a sequence is *p*-sorted: -> *X* is *p*-sorted iff for all *i*, *j* ∈ {1, 2, ..., |*X*|}, *i* - *j* > *p* implies *Xj* ≤ *Xi*. +> *X* is *p*-sorted iff for all *i*, *j* ∈ {1, 2, ..., |*X*|}, *i* - *j* > *p* implies *Xj* ≤ *Xi*. *Right invariant metrics and measures of presortedness* by V. Estivill-Castro, H. Mannila and D. Wood mentions that: diff --git a/include/cpp-sort/probes/max.h b/include/cpp-sort/probes/max.h index 186c2990..2e6faf22 100644 --- a/include/cpp-sort/probes/max.h +++ b/include/cpp-sort/probes/max.h @@ -70,8 +70,8 @@ namespace probe iterators.begin(), iterators.end(), proj(*it), compare, utility::indirect(projection) ); - auto pos_min = std::distance(iterators.begin(), rng.first); - auto pos_max = std::distance(iterators.begin(), rng.second); + auto pos_min = rng.first - iterators.begin(); + auto pos_max = rng.second - iterators.begin(); // If *first isn't into one of its sorted positions, computed the closest if (it_pos < pos_min) { @@ -116,7 +116,8 @@ namespace probe Compare compare={}, Projection projection={}) const -> decltype(auto) { - return max_probe_algo(first, last, std::distance(first, last), + auto dist = std::distance(first, last); + return max_probe_algo(std::move(first), std::move(last), dist, std::move(compare), std::move(projection)); } From 5832a752f331a6827e012ee310c5da12def58483 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 9 Nov 2022 00:23:19 +0100 Subject: [PATCH 29/53] Make utility::indirect default to utility::identity Also introduce spcializations of indirect_t for std::identity when available and utility::identity that simply return the dereferenced indirect value passed to operator(). --- docs/Miscellaneous-utilities.md | 8 ++-- include/cpp-sort/utility/functional.h | 69 +++++++++++++++++++++++++-- tests/utility/sorted_iterators.cpp | 9 ++++ 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index e10a66ed..67183428 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -157,8 +157,8 @@ It is equivalent to the C++20 [`std::identity`][std-identity]. Wherever the docu Another simple yet very handy projection available in the header is `indirect_t`, along with its construction function `indirect()`. It takes a projection, and is meant to be called on a dereferenceable value `it`, in which case it will return the result of `proj(*it)`. It is meant to be used as a projection with standard algorithms when iterating over a collection of iterators. ```cpp -template -class indirect_t: +template +struct indirect_t: projection_base { private: @@ -182,8 +182,8 @@ public: -> decltype(utility::as_function(proj_)(*indirect_value)); }; -template -auto indirect(Projection&& proj) +template +auto indirect(Projection&& proj={}) -> indirect_t>; ``` diff --git a/include/cpp-sort/utility/functional.h b/include/cpp-sort/utility/functional.h index d3473683..381825f4 100644 --- a/include/cpp-sort/utility/functional.h +++ b/include/cpp-sort/utility/functional.h @@ -13,9 +13,14 @@ #include #include #include +#include "../detail/config.h" #include "../detail/raw_checkers.h" #include "../detail/type_traits.h" +#if CPPSORT_STD_IDENTITY_AVAILABLE +# include +#endif + namespace cppsort { namespace utility @@ -103,8 +108,8 @@ namespace utility //////////////////////////////////////////////////////////// // indirect - template - class indirect_t: + template + struct indirect_t: projection_base { private: @@ -140,8 +145,64 @@ namespace utility } }; - template - auto indirect(Projection&& proj) + template<> + struct indirect_t: + projection_base + { + indirect_t() = default; + indirect_t(const indirect_t&) = default; + indirect_t(indirect_t&&) = default; + indirect_t& operator=(const indirect_t&) = default; + indirect_t& operator=(indirect_t&&) = default; + + constexpr explicit indirect_t(identity) noexcept {} + + template + constexpr auto operator()(T&& indirect_value) + -> decltype(*indirect_value) + { + return *indirect_value; + } + + template + constexpr auto operator()(T&& indirect_value) const + -> decltype(*indirect_value) + { + return *indirect_value; + } + }; + +#if CPPSORT_STD_IDENTITY_AVAILABLE + template<> + struct indirect_t: + projection_base + { + indirect_t() = default; + indirect_t(const indirect_t&) = default; + indirect_t(indirect_t&&) = default; + indirect_t& operator=(const indirect_t&) = default; + indirect_t& operator=(indirect_t&&) = default; + + constexpr explicit indirect_t(std::identity) noexcept {} + + template + constexpr auto operator()(T&& indirect_value) + -> decltype(*indirect_value) + { + return *indirect_value; + } + + template + constexpr auto operator()(T&& indirect_value) const + -> decltype(*indirect_value) + { + return *indirect_value; + } + }; +#endif + + template + auto indirect(Projection&& proj={}) -> indirect_t> { return indirect_t>(std::forward(proj)); diff --git a/tests/utility/sorted_iterators.cpp b/tests/utility/sorted_iterators.cpp index e3037550..36fffafc 100644 --- a/tests/utility/sorted_iterators.cpp +++ b/tests/utility/sorted_iterators.cpp @@ -7,16 +7,21 @@ #include #include #include +#include #include +#include #include TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) { + using cppsort::utility::indirect; + SECTION( "simple case" ) { auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; const std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); std::vector expected = { vec.data() + 6, vec.data() + 3, vec.data() + 2, @@ -32,6 +37,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; const std::vector vec = {}; auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); std::vector expected = {}; CHECK( indices == expected ); @@ -46,6 +52,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto distribution = dist::all_equal{}; distribution(std::back_inserter(vec), 10); auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); std::vector expected = { vec.data() + 0, vec.data() + 1, vec.data() + 2, @@ -63,6 +70,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto distribution = dist::ascending{}; distribution(std::back_inserter(vec), 10); auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); std::vector expected = { vec.data() + 0, vec.data() + 1, vec.data() + 2, @@ -80,6 +88,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto distribution = dist::descending{}; distribution(std::back_inserter(vec), 10); auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); std::vector expected = { vec.data() + 9, vec.data() + 8, vec.data() + 7, From b1596201dd88d3dc520598c91b7fcea8732aa7b0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 9 Nov 2022 00:45:02 +0100 Subject: [PATCH 30/53] Optimize operator| for identity --- include/cpp-sort/utility/functional.h | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/include/cpp-sort/utility/functional.h b/include/cpp-sort/utility/functional.h index 381825f4..d95ffca8 100644 --- a/include/cpp-sort/utility/functional.h +++ b/include/cpp-sort/utility/functional.h @@ -84,6 +84,32 @@ namespace utility ); } +#if CPPSORT_STD_IDENTITY_AVAILABLE + template< + typename T, + typename = cppsort::detail::enable_if_t< + std::is_base_of>::value + > + > + constexpr auto operator|(T&& lhs, std::identity) + -> decltype(auto) + { + return as_function(std::forward(lhs)); + } + + template< + typename T, + typename = cppsort::detail::enable_if_t< + std::is_base_of>::value + > + > + constexpr auto operator|(std::identity, T&& rhs) + -> decltype(auto) + { + return as_function(std::forward(rhs)); + } +#endif + //////////////////////////////////////////////////////////// // Identity (mostly useful for projections) @@ -105,6 +131,30 @@ namespace utility std::true_type {}; + template< + typename T, + typename = cppsort::detail::enable_if_t< + std::is_base_of>::value + > + > + constexpr auto operator|(T&& lhs, utility::identity) + -> decltype(auto) + { + return as_function(std::forward(lhs)); + } + + template< + typename T, + typename = cppsort::detail::enable_if_t< + std::is_base_of>::value + > + > + constexpr auto operator|(utility::identity, T&& rhs) + -> decltype(auto) + { + return as_function(std::forward(rhs)); + } + //////////////////////////////////////////////////////////// // indirect From b4b9810a5e92501e78dea13d5acbcd4a1ed20168 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 9 Nov 2022 23:49:52 +0100 Subject: [PATCH 31/53] Simplify indirect() design --- docs/Miscellaneous-utilities.md | 25 +---- include/cpp-sort/adapters/indirect_adapter.h | 10 +- include/cpp-sort/detail/indiesort.h | 8 +- .../longest_non_descending_subsequence.h | 2 +- .../cpp-sort/detail/merge_insertion_sort.h | 4 +- include/cpp-sort/detail/slabsort.h | 2 +- include/cpp-sort/probes/block.h | 4 +- include/cpp-sort/probes/enc.h | 4 +- include/cpp-sort/probes/exc.h | 2 +- include/cpp-sort/probes/ham.h | 2 +- include/cpp-sort/probes/inv.h | 2 +- include/cpp-sort/probes/max.h | 4 +- include/cpp-sort/probes/osc.h | 8 +- include/cpp-sort/utility/functional.h | 92 +------------------ include/cpp-sort/utility/sorted_iterators.h | 2 +- tests/utility/sorted_iterators.cpp | 10 +- 16 files changed, 42 insertions(+), 139 deletions(-) diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 67183428..3fd7e699 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -154,37 +154,20 @@ struct identity: It is equivalent to the C++20 [`std::identity`][std-identity]. Wherever the documentation mentions special handling of `utility::identity`, the same support is provided for `std::identity` when it is available. -Another simple yet very handy projection available in the header is `indirect_t`, along with its construction function `indirect()`. It takes a projection, and is meant to be called on a dereferenceable value `it`, in which case it will return the result of `proj(*it)`. It is meant to be used as a projection with standard algorithms when iterating over a collection of iterators. +Another simple yet very handy projection available in the header is `indirect`: its instances can be called on any dereferenceable value `it`, in which case it returns `*it`. It is meant to be used as a projection with standard algorithms when iterating over a collection of iterators. ```cpp -template -struct indirect_t: +struct indirect: projection_base { -private: - Projection proj_; - -public: - indirect_t() = default; - indirect_t(const indirect_t&) = default; - indirect_t(indirect_t&&) = default; - indirect_t& operator=(const indirect_t&) = default; - indirect_t& operator=(indirect_t&&) = default; - - constexpr explicit indirect_t(Projection proj); - template constexpr auto operator()(T&& indirect_value) - -> decltype(utility::as_function(proj_)(*indirect_value)); + -> decltype(*std::forward(indirect_value)); template constexpr auto operator()(T&& indirect_value) const - -> decltype(utility::as_function(proj_)(*indirect_value)); + -> decltype(*std::forward(indirect_value)); }; - -template -auto indirect(Projection&& proj={}) - -> indirect_t>; ``` This header also provides additional function objects implementing basic unary operations. These functions objects are designed to be used as *size policies* with `dynamic_buffer` and similar classes. The following function objects are available: diff --git a/include/cpp-sort/adapters/indirect_adapter.h b/include/cpp-sort/adapters/indirect_adapter.h index 67f49cef..a08fd0e1 100644 --- a/include/cpp-sort/adapters/indirect_adapter.h +++ b/include/cpp-sort/adapters/indirect_adapter.h @@ -55,7 +55,7 @@ namespace cppsort #ifdef __cpp_lib_uncaught_exceptions -> decltype(std::forward(sorter)( (RandomAccessIterator*)0, (RandomAccessIterator*)0, - std::move(compare), utility::indirect(projection) + std::move(compare), utility::indirect{} | std::move(projection) )) #else -> std::enable_if_t< @@ -63,7 +63,7 @@ namespace cppsort Sorter, RandomAccessIterator*, Compare, - utility::indirect_t + decltype(utility::indirect{} | std::move(projection)) >::value > #endif @@ -82,7 +82,8 @@ namespace cppsort // Sort the iterators on pointed values std::forward(sorter)( iterators.begin(), iterators.end(), - std::move(compare), utility::indirect(projection) + std::move(compare), + utility::indirect{} | std::move(projection) ); #else // Work around the sorters that return void @@ -131,7 +132,8 @@ namespace cppsort return std::forward(sorter)( iterators.begin(), iterators.end(), - std::move(compare), utility::indirect(projection) + std::move(compare), + utility::indirect{} | std::move(projection) ); #endif } diff --git a/include/cpp-sort/detail/indiesort.h b/include/cpp-sort/detail/indiesort.h index ad2c4959..d56f2f15 100644 --- a/include/cpp-sort/detail/indiesort.h +++ b/include/cpp-sort/detail/indiesort.h @@ -62,7 +62,7 @@ namespace detail -> decltype(std::forward(sorter)( (it_and_index*)0, (it_and_index*)0, std::move(compare), - &it_and_index::original_location | utility::indirect(projection) + &it_and_index::original_location | utility::indirect{} | std::move(projection) )) #else -> std::enable_if_t< @@ -70,7 +70,7 @@ namespace detail Sorter, it_and_index*, Compare, - decltype(&it_and_index::original_location | utility::indirect(projection)) + decltype(&it_and_index::original_location | utility::indirect{} | std::move(projection)) >::value > #endif @@ -91,7 +91,7 @@ namespace detail // Sort the iterators on pointed values std::forward(sorter)( storage.begin(), storage.end(), std::move(compare), - &item_index_tuple::original_location | utility::indirect(projection) + &item_index_tuple::original_location | utility::indirect{} | std::move(projection) ); #else // Work around the sorters that return void @@ -128,7 +128,7 @@ namespace detail // Sort the iterators on pointed values return std::forward(sorter)( storage.begin(), storage.end(), std::move(compare), - &item_index_tuple::original_location | utility::indirect(projection) + &item_index_tuple::original_location | utility::indirect{} | std::move(projection) ); #endif } diff --git a/include/cpp-sort/detail/longest_non_descending_subsequence.h b/include/cpp-sort/detail/longest_non_descending_subsequence.h index 1c97da2e..b8d6617a 100644 --- a/include/cpp-sort/detail/longest_non_descending_subsequence.h +++ b/include/cpp-sort/detail/longest_non_descending_subsequence.h @@ -64,7 +64,7 @@ namespace detail while (first != last) { auto it = detail::upper_bound( stack_tops.begin(), stack_tops.end(), - proj(*first), compare, utility::indirect(projection)); + proj(*first), compare, utility::indirect{} | projection); if (it == stack_tops.end()) { // The element is bigger than everything else, diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index ea824c74..b586a903 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -377,7 +377,7 @@ namespace detail auto insertion_point = detail::upper_bound( chain.begin(), *pe, proj(*it), - comp, utility::indirect(proj) + compare, utility::indirect{} | projection ); chain.insert(insertion_point, it); @@ -396,7 +396,7 @@ namespace detail current_it += 2; auto insertion_point = detail::upper_bound( chain.begin(), *current_pend, proj(*current_it), - comp, utility::indirect(proj) + compare, utility::indirect{} | projection ); chain.insert(insertion_point, current_it); ++current_pend; diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h index 03e28a6a..a6475358 100644 --- a/include/cpp-sort/detail/slabsort.h +++ b/include/cpp-sort/detail/slabsort.h @@ -85,7 +85,7 @@ namespace detail return *nth_element( iterators_buffer.begin(), iterators_buffer.end(), size / 2, size, - std::move(compare), utility::indirect(std::move(projection)) + std::move(compare), utility::indirect{} | std::move(projection) ); } diff --git a/include/cpp-sort/probes/block.h b/include/cpp-sort/probes/block.h index 0deef014..e83fb263 100644 --- a/include/cpp-sort/probes/block.h +++ b/include/cpp-sort/probes/block.h @@ -53,8 +53,8 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( - iterators.begin(), iterators.end(), compare, - utility::indirect(projection) + iterators.begin(), iterators.end(), + compare, utility::indirect{} | projection ); //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/probes/enc.h b/include/cpp-sort/probes/enc.h index 89c2542a..2f8add68 100644 --- a/include/cpp-sort/probes/enc.h +++ b/include/cpp-sort/probes/enc.h @@ -45,13 +45,13 @@ namespace probe return cppsort::detail::lower_monobound_n( lists.begin(), lists.size() - 1, value, std::move(compare), - accessor | utility::indirect(projection) + accessor | utility::indirect{} | std::move(projection) ); } else { return cppsort::detail::lower_bound_n( lists.begin(), lists.size() - 1, value, std::move(compare), - accessor | utility::indirect(projection) + accessor | utility::indirect{} | std::move(projection) ); } } diff --git a/include/cpp-sort/probes/exc.h b/include/cpp-sort/probes/exc.h index 26fc3e48..79301592 100644 --- a/include/cpp-sort/probes/exc.h +++ b/include/cpp-sort/probes/exc.h @@ -55,7 +55,7 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( iterators.begin(), iterators.end(), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/probes/ham.h b/include/cpp-sort/probes/ham.h index b93b809c..fde0c2a6 100644 --- a/include/cpp-sort/probes/ham.h +++ b/include/cpp-sort/probes/ham.h @@ -54,7 +54,7 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( iterators.begin(), iterators.end(), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/probes/inv.h b/include/cpp-sort/probes/inv.h index ceb5cdf8..8936a947 100644 --- a/include/cpp-sort/probes/inv.h +++ b/include/cpp-sort/probes/inv.h @@ -50,7 +50,7 @@ namespace probe return cppsort::detail::count_inversions( iterators.get(), iterators.get() + size, buffer.get(), std::move(compare), - utility::indirect(std::move(projection)) + utility::indirect{} | std::move(projection) ); } diff --git a/include/cpp-sort/probes/max.h b/include/cpp-sort/probes/max.h index 2e6faf22..dba36055 100644 --- a/include/cpp-sort/probes/max.h +++ b/include/cpp-sort/probes/max.h @@ -55,7 +55,7 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( iterators.begin(), iterators.end(), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); //////////////////////////////////////////////////////////// @@ -68,7 +68,7 @@ namespace probe // Find the range where *first belongs once sorted auto rng = cppsort::detail::equal_range( iterators.begin(), iterators.end(), proj(*it), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); auto pos_min = rng.first - iterators.begin(); auto pos_max = rng.second - iterators.begin(); diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index 30a5a0e6..afa170b3 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -94,7 +94,7 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( iterators.begin(), iterators.end(), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); //////////////////////////////////////////////////////////// @@ -120,7 +120,7 @@ namespace probe auto prev_bounds = cppsort::detail::equal_range( iterators.begin(), iterators.end(), proj(*first), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); for (auto prev = first, current = std::next(prev); current != last; ++prev, (void)++current) { @@ -129,7 +129,7 @@ namespace probe auto current_bounds = cppsort::detail::equal_range( //prev_bounds.second, std::prev(iterators.end()), proj(*current), iterators.begin(), iterators.end(), proj(*current), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); min_idx = prev_bounds.second - iterators.begin(); max_idx = current_bounds.first - iterators.begin(); @@ -138,7 +138,7 @@ namespace probe auto current_bounds = cppsort::detail::equal_range( //iterators.begin(), prev_bounds.first, proj(*current), iterators.begin(), iterators.end(), proj(*current), - compare, utility::indirect(projection) + compare, utility::indirect{} | projection ); min_idx = current_bounds.second - iterators.begin(); max_idx = prev_bounds.first - iterators.begin(); diff --git a/include/cpp-sort/utility/functional.h b/include/cpp-sort/utility/functional.h index d95ffca8..9987123a 100644 --- a/include/cpp-sort/utility/functional.h +++ b/include/cpp-sort/utility/functional.h @@ -158,106 +158,24 @@ namespace utility //////////////////////////////////////////////////////////// // indirect - template - struct indirect_t: + struct indirect: projection_base { - private: - - Projection projection; - - public: - - indirect_t() = default; - indirect_t(const indirect_t&) = default; - indirect_t(indirect_t&&) = default; - indirect_t& operator=(const indirect_t&) = default; - indirect_t& operator=(indirect_t&&) = default; - - constexpr explicit indirect_t(Projection projection): - projection(std::move(projection)) - {} - - template - constexpr auto operator()(T&& indirect_value) - -> decltype(utility::as_function(projection)(*indirect_value)) - { - auto&& proj = utility::as_function(projection); - return proj(*indirect_value); - } - - template - constexpr auto operator()(T&& indirect_value) const - -> decltype(utility::as_function(projection)(*indirect_value)) - { - auto&& proj = utility::as_function(projection); - return proj(*indirect_value); - } - }; - - template<> - struct indirect_t: - projection_base - { - indirect_t() = default; - indirect_t(const indirect_t&) = default; - indirect_t(indirect_t&&) = default; - indirect_t& operator=(const indirect_t&) = default; - indirect_t& operator=(indirect_t&&) = default; - - constexpr explicit indirect_t(identity) noexcept {} - template constexpr auto operator()(T&& indirect_value) - -> decltype(*indirect_value) + -> decltype(*std::forward(indirect_value)) { - return *indirect_value; + return *std::forward(indirect_value); } template constexpr auto operator()(T&& indirect_value) const - -> decltype(*indirect_value) + -> decltype(*std::forward(indirect_value)) { - return *indirect_value; + return *std::forward(indirect_value); } }; -#if CPPSORT_STD_IDENTITY_AVAILABLE - template<> - struct indirect_t: - projection_base - { - indirect_t() = default; - indirect_t(const indirect_t&) = default; - indirect_t(indirect_t&&) = default; - indirect_t& operator=(const indirect_t&) = default; - indirect_t& operator=(indirect_t&&) = default; - - constexpr explicit indirect_t(std::identity) noexcept {} - - template - constexpr auto operator()(T&& indirect_value) - -> decltype(*indirect_value) - { - return *indirect_value; - } - - template - constexpr auto operator()(T&& indirect_value) const - -> decltype(*indirect_value) - { - return *indirect_value; - } - }; -#endif - - template - auto indirect(Projection&& proj={}) - -> indirect_t> - { - return indirect_t>(std::forward(proj)); - } - //////////////////////////////////////////////////////////// // Transform overload in unary or binary function diff --git a/include/cpp-sort/utility/sorted_iterators.h b/include/cpp-sort/utility/sorted_iterators.h index b603d609..abed1205 100644 --- a/include/cpp-sort/utility/sorted_iterators.h +++ b/include/cpp-sort/utility/sorted_iterators.h @@ -49,7 +49,7 @@ namespace utility // Sort the iterators on pointed values std::forward(sorter)(iterators.begin(), iterators.end(), std::move(compare), - utility::indirect(std::move(projection))); + utility::indirect{} | std::move(projection)); return iterators; } diff --git a/tests/utility/sorted_iterators.cpp b/tests/utility/sorted_iterators.cpp index 36fffafc..7c0cafb9 100644 --- a/tests/utility/sorted_iterators.cpp +++ b/tests/utility/sorted_iterators.cpp @@ -21,7 +21,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; const std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); - CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect{}) ); std::vector expected = { vec.data() + 6, vec.data() + 3, vec.data() + 2, @@ -37,7 +37,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto get_sorted_iterators_for = cppsort::utility::sorted_iterators{}; const std::vector vec = {}; auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); - CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect{}) ); std::vector expected = {}; CHECK( indices == expected ); @@ -52,7 +52,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto distribution = dist::all_equal{}; distribution(std::back_inserter(vec), 10); auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); - CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect{}) ); std::vector expected = { vec.data() + 0, vec.data() + 1, vec.data() + 2, @@ -70,7 +70,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto distribution = dist::ascending{}; distribution(std::back_inserter(vec), 10); auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); - CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect{}) ); std::vector expected = { vec.data() + 0, vec.data() + 1, vec.data() + 2, @@ -88,7 +88,7 @@ TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" ) auto distribution = dist::descending{}; distribution(std::back_inserter(vec), 10); auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size()); - CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect()) ); + CHECK( helpers::is_sorted(indices.begin(), indices.end(), {}, indirect{}) ); std::vector expected = { vec.data() + 9, vec.data() + 8, vec.data() + 7, From c7e3427e29cb552b6b6e2645d21dacaff69a965b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Nov 2022 00:03:28 +0100 Subject: [PATCH 32/53] merge_insertion_sort: remove unused variable --- include/cpp-sort/detail/merge_insertion_sort.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index b586a903..9ed83487 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -276,7 +276,6 @@ namespace detail auto size = last - first; if (size < 2) return; - auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); // Whether there is a stray element not in a pair From f00b258b432986ba6ba12359c3e1fc84d2f1e7be Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Nov 2022 00:07:12 +0100 Subject: [PATCH 33/53] New feature: apply_permutation (#200) --- docs/Miscellaneous-utilities.md | 26 +++++++- include/cpp-sort/utility/apply_permutation.h | 56 +++++++++++++++++ tests/CMakeLists.txt | 1 + tests/utility/apply_permutation.cpp | 63 ++++++++++++++++++++ tests/utility/sorted_indices.cpp | 1 - 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 include/cpp-sort/utility/apply_permutation.h create mode 100644 tests/utility/apply_permutation.cpp diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 3fd7e699..9a9ff0ed 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -20,6 +20,29 @@ The usual way to use it when implementing a *sorter adapter* is to make said ada *New in version 1.5.0* +### `apply_permutation` + +```cpp +#include +``` + +`apply_permutation` is a function template accepting a random-access range of elements and a random-access range of [0, N) indices of the same size. The indices in the second range represent the positions of the elements in the first range that should be moved in the indices positions to bring the collection in sorted order. + +The algorithm requires both the elements range and the indices range to be mutable and modifies both. + +```cpp +template +auto apply_permutation(RandomAccessIterator1 first, RandomAccessIterator1 last, + RandomAccessIterator2 indices_first, RandomAccessIterator2 indices_last) + -> void; + +template +auto apply_permutation(RandomAccessIterable1&& iterable, RandomAccessIterable2&& indices) + -> void; +``` + +*New in version 1.14.0* + ### `as_comparison` and `as_projection` ```cpp @@ -281,7 +304,7 @@ using make_index_range = make_integer_range; #include ``` -`utility::sorted_indices` is a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]). +`utility::sorted_indices` is a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]). The resulting indices can be passed [`apply_permutation`][apply-permutation] to sort the original collection. ```cpp std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; @@ -393,6 +416,7 @@ namespace You can read more about this instantiation pattern in [this article][eric-niebler-static-const] by Eric Niebler. + [apply-permutation]: Miscellaneous-utilities.md#apply_permutation [chainable-projections]: Chainable-projections.md [callable]: https://en.cppreference.com/w/cpp/named_req/Callable [ebo]: https://en.cppreference.com/w/cpp/language/ebo diff --git a/include/cpp-sort/utility/apply_permutation.h b/include/cpp-sort/utility/apply_permutation.h new file mode 100644 index 00000000..ae735aa3 --- /dev/null +++ b/include/cpp-sort/utility/apply_permutation.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_UTILITY_APPLY_PERMUTATION_H_ +#define CPPSORT_UTILITY_APPLY_PERMUTATION_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include "../detail/config.h" +#include "../detail/iterator_traits.h" + +namespace cppsort +{ +namespace utility +{ + template + auto apply_permutation(RandomAccessIterator1 first, RandomAccessIterator1 last, + RandomAccessIterator2 indices_first, RandomAccessIterator2 indices_last) + -> void + { + using difference_type = cppsort::detail::difference_type_t; + using utility::iter_move; + CPPSORT_ASSERT( (last - first) == (indices_last - indices_first) ); + + auto size = indices_last - indices_first; + for (difference_type idx = 0; idx < size; ++idx) { + if (idx != indices_first[idx]) { + auto current_idx = idx; + auto tmp = iter_move(first + current_idx); + do { + auto next_idx = indices_first[current_idx]; + using std::iter_swap; + first[current_idx] = iter_move(first + next_idx); + indices_first[current_idx] = current_idx; + current_idx = next_idx; + } while (idx != indices_first[current_idx]); + indices_first[current_idx] = current_idx; + first[current_idx] = std::move(tmp); + } + } + } + + template + auto apply_permutation(RandomAccessIterable1&& iterable, RandomAccessIterable2&& indices) + -> void + { + apply_permutation(std::begin(iterable), std::end(iterable), + std::begin(indices), std::end(indices)); + } +}} + +#endif // CPPSORT_UTILITY_APPLY_PERMUTATION_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index da0aa673..61be32af 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -220,6 +220,7 @@ add_executable(main-tests # Utilities tests utility/adapter_storage.cpp + utility/apply_permutation.cpp utility/as_comparison.cpp utility/as_projection.cpp utility/as_projection_iterable.cpp diff --git a/tests/utility/apply_permutation.cpp b/tests/utility/apply_permutation.cpp new file mode 100644 index 00000000..6426aa5e --- /dev/null +++ b/tests/utility/apply_permutation.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "apply_permutation test", "[utility][apply_permutation]" ) +{ + SECTION( "simple case" ) + { + std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; + std::vector indices = { 6, 3, 2, 9, 1, 8, 0, 5, 4, 7 }; + cppsort::utility::apply_permutation(vec, indices); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } + + SECTION( "empty collection" ) + { + std::vector vec = {}; + std::vector indices = {}; + cppsort::utility::apply_permutation(vec, indices); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } + + SECTION( "ascending" ) + { + std::vector vec; + auto distribution = dist::ascending{}; + distribution(std::back_inserter(vec), 10); + std::vector indices = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + cppsort::utility::apply_permutation(vec, indices); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } + + SECTION( "descending" ) + { + std::vector vec; + auto distribution = dist::descending{}; + distribution(std::back_inserter(vec), 10); + std::vector indices = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + cppsort::utility::apply_permutation(vec, indices); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } + + SECTION( "big collection" ) + { + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 1'000); + auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; + std::vector indices = get_sorted_indices_for(vec); + cppsort::utility::apply_permutation(vec, indices); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } +} diff --git a/tests/utility/sorted_indices.cpp b/tests/utility/sorted_indices.cpp index 235095e6..f49743e6 100644 --- a/tests/utility/sorted_indices.cpp +++ b/tests/utility/sorted_indices.cpp @@ -31,7 +31,6 @@ TEST_CASE( "basic sorted_indices test", "[utility][sorted_indices]" ) std::vector expected = {}; CHECK( indices == expected ); - } SECTION( "all_equal" ) From 04342005d8a2e5d83c7fc2a3d88dddac1324dbc2 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Nov 2022 14:30:07 +0100 Subject: [PATCH 34/53] More tweaks to operator| for projections Improve documentation, allow to have utility::identity on both sides, don't needlessly use as_function on operands. --- docs/Chainable-projections.md | 6 +++-- docs/Miscellaneous-utilities.md | 2 +- include/cpp-sort/utility/functional.h | 36 +++++++++++-------------- tests/utility/chainable_projections.cpp | 7 +++++ 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/docs/Chainable-projections.md b/docs/Chainable-projections.md index 2b2be55e..94050bc9 100644 --- a/docs/Chainable-projections.md +++ b/docs/Chainable-projections.md @@ -1,6 +1,6 @@ *New in version 1.7.0* -Sometimes one needs to apply several transformations to the elements of a collection before comparing them. To support this use case, some projection functions in **cpp-sort** can be composed with `operator|` +Sometimes one needs to apply several transformations to the elements of a collection before comparing them. To support this use case, some projection functions in **cpp-sort** can be composed with `operator|`. ```cpp struct my_negate: @@ -25,7 +25,7 @@ my_negate projection; cppsort::poplar_sort(vec, &wrapper::value | projection); ``` -The object returned by the utility function [`utility::as_projection`][as_projection] also inherits from `utility::projection_base`, making `as_projection` the proper function to turn any suitable projection into a projection composable with `operator|`. +The object returned by the utility function [`utility::as_projection`][as_projection] also inherits from `utility::projection_base`, making `as_projection` the proper function to turn any suitable projection into a projection composable with `operator|`. If one of the arguments to `operator|` arguments is either [`utility::identity`][utility-identity] or [`std::identity`][std-identity], then the other argument is returned directly. If both of the projections composed with `operator|` are [*transparent*][transparent-func], then the returned object is also a *transparent* projection. @@ -34,4 +34,6 @@ If both of the projections composed with `operator|` are [*transparent*][transpa [as_projection]: Miscellaneous-utilities.md#as_comparison-and-as_projection [callable]: https://en.cppreference.com/w/cpp/named_req/Callable + [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity [transparent-func]: Comparators-and-projections.md#Transparent-function-objects + [utility-identity]: Miscellaneous-utilities.md#miscellaneous-function-objects diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 9a9ff0ed..3ae4d68c 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -231,7 +231,7 @@ This utility is modeled after [`std::integral_constant`][std-integral-constant], *Changed in version 1.13.0:* `half`, `log` and `sqrt` are now [*transparent function objects*][transparent-func]. -*New in version 1.14.0:* `indirect` and `indirect_t`. +*New in version 1.14.0:* `indirect`. ### `iter_move` and `iter_swap` diff --git a/include/cpp-sort/utility/functional.h b/include/cpp-sort/utility/functional.h index 9987123a..cc50947d 100644 --- a/include/cpp-sort/utility/functional.h +++ b/include/cpp-sort/utility/functional.h @@ -92,9 +92,9 @@ namespace utility > > constexpr auto operator|(T&& lhs, std::identity) - -> decltype(auto) + -> decltype(std::forward(lhs)) { - return as_function(std::forward(lhs)); + return std::forward(lhs); } template< @@ -104,9 +104,9 @@ namespace utility > > constexpr auto operator|(std::identity, T&& rhs) - -> decltype(auto) + -> decltype(std::forward(rhs)) { - return as_function(std::forward(rhs)); + return std::forward(rhs); } #endif @@ -131,28 +131,24 @@ namespace utility std::true_type {}; - template< - typename T, - typename = cppsort::detail::enable_if_t< - std::is_base_of>::value - > - > + template constexpr auto operator|(T&& lhs, utility::identity) - -> decltype(auto) + -> decltype(std::forward(lhs)) { - return as_function(std::forward(lhs)); + return std::forward(lhs); } - template< - typename T, - typename = cppsort::detail::enable_if_t< - std::is_base_of>::value - > - > + template constexpr auto operator|(utility::identity, T&& rhs) - -> decltype(auto) + -> decltype(std::forward(rhs)) + { + return std::forward(rhs); + } + + constexpr auto operator|(utility::identity, utility::identity) + -> utility::identity { - return as_function(std::forward(rhs)); + return {}; } //////////////////////////////////////////////////////////// diff --git a/tests/utility/chainable_projections.cpp b/tests/utility/chainable_projections.cpp index 86807ea0..48bf716f 100644 --- a/tests/utility/chainable_projections.cpp +++ b/tests/utility/chainable_projections.cpp @@ -105,3 +105,10 @@ TEST_CASE( "Pipe a projection with as_projection", CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } +TEST_CASE( "Pipe utility::identity with utility::identity", + "[utility][projection_base]" ) +{ + std::vector vec(15, 42); + cppsort::spin_sort(vec, cppsort::utility::identity{} | cppsort::utility::identity{}); + CHECK( true ); +} From 0f30e4dc655b6027ec38de47684af646a0c20d8f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Dec 2022 13:15:06 +0100 Subject: [PATCH 35/53] Backport associated cleanups from 2.x.y --- .../cpp-sort/adapters/out_of_place_adapter.h | 4 +-- .../cpp-sort/adapters/small_array_adapter.h | 27 ++++-------------- include/cpp-sort/adapters/verge_adapter.h | 28 +++++++++---------- .../comparators/case_insensitive_less.h | 4 +-- .../cpp-sort/detail/adaptive_quickselect.h | 1 + include/cpp-sort/detail/associate_iterator.h | 20 ++----------- .../cpp-sort/detail/boost_common/util/merge.h | 6 +--- .../cpp-sort/detail/merge_insertion_sort.h | 16 ----------- include/cpp-sort/detail/rotate_left.h | 4 +-- include/cpp-sort/detail/rotate_right.h | 4 +-- .../cpp-sort/detail/schwartz_small_array.h | 27 ++++-------------- .../detail/spreadsort/detail/common.h | 4 --- include/cpp-sort/detail/swap_ranges.h | 1 + include/cpp-sort/utility/apply_permutation.h | 2 +- 14 files changed, 37 insertions(+), 111 deletions(-) diff --git a/include/cpp-sort/adapters/out_of_place_adapter.h b/include/cpp-sort/adapters/out_of_place_adapter.h index 3236c11c..b09d755e 100644 --- a/include/cpp-sort/adapters/out_of_place_adapter.h +++ b/include/cpp-sort/adapters/out_of_place_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 Morwenn + * Copyright (c) 2018-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_OUT_OF_PLACE_ADAPTER_H_ @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "../detail/checkers.h" #include "../detail/immovable_vector.h" @@ -35,7 +34,6 @@ namespace cppsort Size size, const Sorter& sorter, Args&&... args) -> decltype(auto) { - using utility::iter_move; using rvalue_type = rvalue_type_t; // Copy the collection into contiguous memory buffer diff --git a/include/cpp-sort/adapters/small_array_adapter.h b/include/cpp-sort/adapters/small_array_adapter.h index 942a4ebe..ccebe046 100644 --- a/include/cpp-sort/adapters/small_array_adapter.h +++ b/include/cpp-sort/adapters/small_array_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_SMALL_ARRAY_ADAPTER_H_ @@ -65,11 +65,7 @@ namespace cppsort detail::all(std::is_empty>::value...) > { - template< - typename T, - std::size_t N, - typename... Args - > + template auto operator()(std::array& array, Args&&... args) const -> detail::enable_if_t< detail::is_in_pack, @@ -79,12 +75,7 @@ namespace cppsort return FixedSizeSorter{}(array, std::forward(args)...); } - template< - typename T, - std::size_t N, - typename... Args, - typename = detail::enable_if_t> - > + template auto operator()(T (&array)[N], Args&&... args) const -> detail::enable_if_t< detail::is_in_pack, @@ -103,22 +94,14 @@ namespace cppsort true // TODO: how can we do better? > { - template< - typename T, - std::size_t N, - typename... Args - > + template auto operator()(std::array& array, Args&&... args) const -> decltype(FixedSizeSorter{}(array, std::forward(args)...)) { return FixedSizeSorter{}(array, std::forward(args)...); } - template< - typename T, - std::size_t N, - typename... Args - > + template auto operator()(T (&array)[N], Args&&... args) const -> decltype(FixedSizeSorter{}(array, std::forward(args)...)) { diff --git a/include/cpp-sort/adapters/verge_adapter.h b/include/cpp-sort/adapters/verge_adapter.h index 434394db..4714c9e2 100644 --- a/include/cpp-sort/adapters/verge_adapter.h +++ b/include/cpp-sort/adapters/verge_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 Morwenn + * Copyright (c) 2017-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_VERGE_ADAPTER_H_ @@ -27,14 +27,14 @@ namespace cppsort namespace detail { - template + template struct verge_adapter_impl: - utility::adapter_storage + utility::adapter_storage { verge_adapter_impl() = default; - constexpr explicit verge_adapter_impl(FallbackSorter&& sorter): - utility::adapter_storage(std::move(sorter)) + constexpr explicit verge_adapter_impl(Sorter&& sorter): + utility::adapter_storage(std::move(sorter)) {} template< @@ -70,25 +70,25 @@ namespace cppsort }; } - template + template struct verge_adapter: - sorter_facade> + sorter_facade> { verge_adapter() = default; - constexpr explicit verge_adapter(FallbackSorter sorter): - sorter_facade>(std::move(sorter)) + constexpr explicit verge_adapter(Sorter sorter): + sorter_facade>(std::move(sorter)) {} }; - template - struct stable_adapter>: - sorter_facade> + template + struct stable_adapter>: + sorter_facade> { stable_adapter() = default; - constexpr explicit stable_adapter(verge_adapter sorter): - sorter_facade>(std::move(sorter).get()) + constexpr explicit stable_adapter(verge_adapter sorter): + sorter_facade>(std::move(sorter).get()) {} }; } diff --git a/include/cpp-sort/comparators/case_insensitive_less.h b/include/cpp-sort/comparators/case_insensitive_less.h index ff9d6add..3db89670 100644 --- a/include/cpp-sort/comparators/case_insensitive_less.h +++ b/include/cpp-sort/comparators/case_insensitive_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_CASE_INSENSITIVE_LESS_H_ @@ -225,7 +225,7 @@ namespace cppsort template auto operator()(const T& lhs, const T& rhs) const -> detail::enable_if_t< - negation>::value, + not is_invocable_r_v, decltype(case_insensitive_less(lhs, rhs)) > { diff --git a/include/cpp-sort/detail/adaptive_quickselect.h b/include/cpp-sort/detail/adaptive_quickselect.h index 8c562f86..86684991 100644 --- a/include/cpp-sort/detail/adaptive_quickselect.h +++ b/include/cpp-sort/detail/adaptive_quickselect.h @@ -23,6 +23,7 @@ #include #include #include "config.h" +#include "iterator_traits.h" #include "partition.h" #include "type_traits.h" diff --git a/include/cpp-sort/detail/associate_iterator.h b/include/cpp-sort/detail/associate_iterator.h index 71d1de77..2d70e74e 100644 --- a/include/cpp-sort/detail/associate_iterator.h +++ b/include/cpp-sort/detail/associate_iterator.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_ASSOCIATE_ITERATOR_H_ @@ -8,7 +8,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include +#include #include #include #include "attributes.h" @@ -216,14 +216,6 @@ namespace detail return *this; } - auto operator++(int) - -> associate_iterator - { - auto tmp = *this; - operator++(); - return tmp; - } - auto operator--() -> associate_iterator& { @@ -231,14 +223,6 @@ namespace detail return *this; } - auto operator--(int) - -> associate_iterator - { - auto tmp = *this; - operator--(); - return tmp; - } - auto operator+=(difference_type increment) -> associate_iterator& { diff --git a/include/cpp-sort/detail/boost_common/util/merge.h b/include/cpp-sort/detail/boost_common/util/merge.h index 51a95a9b..ce26079a 100644 --- a/include/cpp-sort/detail/boost_common/util/merge.h +++ b/include/cpp-sort/detail/boost_common/util/merge.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 Morwenn + * Copyright (c) 2019-2022 Morwenn * SPDX-License-Identifier: MIT */ @@ -25,7 +25,6 @@ #include #include #include -#include #include "../../buffered_inplace_merge.h" #include "../../config.h" #include "../../iterator_traits.h" @@ -61,8 +60,6 @@ namespace util Compare compare, Projection projection) -> RandomAccessIterator3 { - using utility::iter_move; - constexpr std::size_t min_size = 1024; auto&& comp = utility::as_function(compare); @@ -111,7 +108,6 @@ namespace util Compare compare, Projection projection) -> RandomAccessIterator2 { - using utility::iter_move; CPPSORT_ASSERT(buf2 - buf_out == end_buf1 - buf1); constexpr std::size_t min_size = 1024; diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index 9ed83487..14e43e7a 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -99,14 +99,6 @@ namespace detail return *this; } - auto operator++(int) - -> group_iterator - { - auto tmp = *this; - operator++(); - return tmp; - } - auto operator--() -> group_iterator& { @@ -114,14 +106,6 @@ namespace detail return *this; } - auto operator--(int) - -> group_iterator - { - auto tmp = *this; - operator--(); - return tmp; - } - auto operator+=(difference_type increment) -> group_iterator& { diff --git a/include/cpp-sort/detail/rotate_left.h b/include/cpp-sort/detail/rotate_left.h index 68ee82d2..0bcafa1a 100644 --- a/include/cpp-sort/detail/rotate_left.h +++ b/include/cpp-sort/detail/rotate_left.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_ROTATE_LEFT_H_ @@ -42,7 +42,7 @@ namespace detail struct rotate_left_n<0u> { template - auto operator()(RandomAccessIterator) const + auto operator()(RandomAccessIterator) const noexcept -> void {} }; diff --git a/include/cpp-sort/detail/rotate_right.h b/include/cpp-sort/detail/rotate_right.h index ca548eed..b9761727 100644 --- a/include/cpp-sort/detail/rotate_right.h +++ b/include/cpp-sort/detail/rotate_right.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_ROTATE_RIGHT_H_ @@ -40,7 +40,7 @@ namespace detail struct rotate_right_n<0u> { template - auto operator()(RandomAccessIterator) const + auto operator()(RandomAccessIterator) const noexcept -> void {} }; diff --git a/include/cpp-sort/detail/schwartz_small_array.h b/include/cpp-sort/detail/schwartz_small_array.h index 4465a870..34cb424f 100644 --- a/include/cpp-sort/detail/schwartz_small_array.h +++ b/include/cpp-sort/detail/schwartz_small_array.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2022 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_SCHWARTZ_SMALL_ARRAY_H_ @@ -32,11 +32,7 @@ namespace cppsort detail::all(std::is_empty>::value...) > { - template< - typename T, - std::size_t N, - typename... Args - > + template auto operator()(std::array& array, Args&&... args) const -> detail::enable_if_t< detail::is_in_pack, @@ -47,12 +43,7 @@ namespace cppsort return sorter{}(array, std::forward(args)...); } - template< - typename T, - std::size_t N, - typename... Args, - typename = detail::enable_if_t> - > + template auto operator()(T (&array)[N], Args&&... args) const -> detail::enable_if_t< detail::is_in_pack, @@ -72,11 +63,7 @@ namespace cppsort true // TODO: not sure how to specify that one > { - template< - typename T, - std::size_t N, - typename... Args - > + template auto operator()(std::array& array, Args&&... args) const -> decltype(schwartz_adapter>{}(array, std::forward(args)...)) { @@ -84,11 +71,7 @@ namespace cppsort return sorter{}(array, std::forward(args)...); } - template< - typename T, - std::size_t N, - typename... Args - > + template auto operator()(T (&array)[N], Args&&... args) const -> decltype(schwartz_adapter>{}(array, std::forward(args)...)) { diff --git a/include/cpp-sort/detail/spreadsort/detail/common.h b/include/cpp-sort/detail/spreadsort/detail/common.h index d45d81cf..862bb322 100644 --- a/include/cpp-sort/detail/spreadsort/detail/common.h +++ b/include/cpp-sort/detail/spreadsort/detail/common.h @@ -36,10 +36,6 @@ namespace spreadsort { namespace detail { - //Well, we're not using Boost in the end - template - using disable_if_t = cppsort::detail::enable_if_t; - //This only works on unsigned data types template constexpr auto rough_log_2_size(const T& input) diff --git a/include/cpp-sort/detail/swap_ranges.h b/include/cpp-sort/detail/swap_ranges.h index 547dc1cb..9a61dff3 100644 --- a/include/cpp-sort/detail/swap_ranges.h +++ b/include/cpp-sort/detail/swap_ranges.h @@ -14,6 +14,7 @@ #include #include #include "config.h" +#include "iterator_traits.h" #include "move.h" #include "type_traits.h" diff --git a/include/cpp-sort/utility/apply_permutation.h b/include/cpp-sort/utility/apply_permutation.h index ae735aa3..b520a982 100644 --- a/include/cpp-sort/utility/apply_permutation.h +++ b/include/cpp-sort/utility/apply_permutation.h @@ -8,6 +8,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include #include #include #include "../detail/config.h" @@ -33,7 +34,6 @@ namespace utility auto tmp = iter_move(first + current_idx); do { auto next_idx = indices_first[current_idx]; - using std::iter_swap; first[current_idx] = iter_move(first + next_idx); indices_first[current_idx] = current_idx; current_idx = next_idx; From 05c65d6173a59e4f633bc8ee15c7a557cbb1aba7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Dec 2022 13:31:56 +0100 Subject: [PATCH 36/53] minmax_element: remove useless comparison --- include/cpp-sort/detail/minmax_element.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/include/cpp-sort/detail/minmax_element.h b/include/cpp-sort/detail/minmax_element.h index 0fa75efa..7aced4fb 100644 --- a/include/cpp-sort/detail/minmax_element.h +++ b/include/cpp-sort/detail/minmax_element.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ @@ -46,12 +46,6 @@ namespace detail auto&& proj = utility::as_function(projection); std::pair result{begin, begin}; - if (comp(proj(*begin), proj(*result.first))) { - result.first = begin; - } else { - result.second = begin; - } - while (++begin != end) { ForwardIterator tmp = begin; if (++begin == end) { From 297904042bdcd386c47ddcdece7b0487d2e99e09 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Dec 2022 13:51:34 +0100 Subject: [PATCH 37/53] string_spread_sort: add using utility::iter_swap Currently the iter_swap calls in string_spread_sort were all unqualified and I fear they only worked by virtue of always being called either on standard iterator types, or on iterators templated on std::string, and therefore looking for iter_swap in the std:: namespace too. I tried to create a regression test but didn't manage to trigger an error. Nonetheless, adding these using declarations makes the code more robust. --- include/cpp-sort/detail/spreadsort/detail/string_sort.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/cpp-sort/detail/spreadsort/detail/string_sort.h b/include/cpp-sort/detail/spreadsort/detail/string_sort.h index cb3ecda9..2b4869b1 100644 --- a/include/cpp-sort/detail/spreadsort/detail/string_sort.h +++ b/include/cpp-sort/detail/spreadsort/detail/string_sort.h @@ -32,6 +32,7 @@ Phil Endecott and Frank Gennari #include #include #include +#include #include "common.h" #include "constants.h" #include "../../pdqsort.h" @@ -213,6 +214,7 @@ namespace spreadsort //empties belong in this bin while (proj(*current).size() > char_offset) { target_bin = bins + static_cast(proj(*current)[char_offset]); + using utility::iter_swap; iter_swap(current, *target_bin); ++(*target_bin); } @@ -234,6 +236,7 @@ namespace spreadsort (proj(*current)[char_offset]); target_bin != local_bin; target_bin = bins + static_cast (proj(*current)[char_offset])) { + using utility::iter_swap; iter_swap(current, *target_bin); ++(*target_bin); } @@ -325,6 +328,7 @@ namespace spreadsort //empties belong in this bin while (proj(*current).size() > char_offset) { target_bin = end_bin - static_cast(proj(*current)[char_offset]); + using utility::iter_swap; iter_swap(current, *target_bin); ++(*target_bin); } @@ -348,6 +352,7 @@ namespace spreadsort target_bin != local_bin; target_bin = end_bin - static_cast(proj(*current)[char_offset])) { + using utility::iter_swap; iter_swap(current, *target_bin); ++(*target_bin); } From d22490d0c0bf9fcd317d5f94bc16d1cc4516a9e1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Dec 2022 15:20:59 +0100 Subject: [PATCH 38/53] New documentation power words: equivalent elements --- docs/Comparators.md | 2 +- docs/Library-nomenclature.md | 7 +++++-- docs/Measures-of-presortedness.md | 2 +- docs/Miscellaneous-utilities.md | 4 ++-- docs/Sorter-adapters.md | 4 ++-- docs/Writing-a-randomizing_adapter.md | 6 +++--- docs/Writing-a-sorter.md | 6 +++--- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/Comparators.md b/docs/Comparators.md index 246c1266..ddc7cd1f 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -23,7 +23,7 @@ The comparators `total_less` and `total_order` are [customization points][custom * negative signaling NaNs * negative quiet NaNs -That said, the comparators are currently unable to discriminate between quiet and signaling NaNs, so they compare equivalent. When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). +That said, the comparators are currently unable to discriminate between quiet and signaling NaNs, so they are considered to be *equivalent*. When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). Total order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_integral`][std-is-integral]. diff --git a/docs/Library-nomenclature.md b/docs/Library-nomenclature.md index 49cc28b0..9949664b 100644 --- a/docs/Library-nomenclature.md +++ b/docs/Library-nomenclature.md @@ -1,4 +1,4 @@ -**cpp-sort** deals with many concepts related to sorting and algorithms in general. This section tries to briefly explain the many things that you may encounter while using it, in alphabetical order: +**cpp-sort** deals with many concepts related to sorting and algorithms in general. This section tries to briefly explain the many things that you may encounter while using it. When a term or an expression appears in *italics* in the rest of the documentation, it is generally a reference to one of the following entries: * *Buffered sorter*: some sorting algorithms optionally use a buffer where they store elements to improve the performance of the sort. Some of them, such as block sort, will manage to sort the collection regardless of the actual size of the buffer, which will only have on influence on the performance of the sort. A buffered sorter is a sorter that takes a *buffer provider* template parameter that tells how the temporary buffer should be allocated, and uses this provider to create the buffer. A *buffer provider* is a class that has a nested `buffer` class which implements a set of basic operations (construction with a size, `begin`, `end` and `size`). Implementing a buffer provider is a bit tricky, but using them should be easy enough: @@ -14,6 +14,8 @@ The library provides a set of additional [comparators][comparators] generally corresponding to common ways to compare common types. +* *Equivalent elements*: this notion appears in the context of comparing elements with a predicate. Two elements `a` and `b` are equivalent with regard to a predicate `comp` when `not comp(a, b) && not comp(b, a)`. Predicates in comparison sorts only require to model a [weak order][weak-order], so elements satifying the previous expressions do not have to be strictly equal - we call them *equivalent elements* in the rest of the documentation. + * *Fixed-size sorter*: [fixed-size sorters][fixed-size-sorters] are a special breed of sorters designed to sort a fixed number of values. While they try their best to be full-fledge sorters, they are definitely not full-fledge sorters and probably don't blend as well as one would like into the library. Their main advantage is that they can be more performant than regular sorters in some specific scenarios. * *Iterator category*: the C++ standard defines [several categories of iterators][iterator-categories] such as forward iterators, bidirectional iterators or random-access iterators. The standard library uses [iterator tags][iterator-tags] to document the category of an iterator. These categories are important since algorithms are designed to work with some categories of iterators and not with other categories, and those in this library are not different: in-place sorting needs at least forward iterators. You can use the [`iterator_category`][iterator-category] sorter trait to get the least constrained iterator category associated with a sorter. @@ -43,7 +45,7 @@ * *Sorter adapter*: [sorter adapters][sorter-adapters] are class templates that take one or several sorters and produce a new sorter from the parameters. What a sorter adapter can do is not constrained, but they are generally expected to behave like sorters themselves. For example, **cpp-sort** contains adapters to count the number of comparisons performed by a sorting algorithms or to aggregate several sorters together. The best way to learn more about them is still to read the dedicated section in the documentation. -* *Stability*: a sorting algorithm is *stable* if it preserves the relative order of equivalent elements. While it does not matter when the equivalence relationship is also an equality relationship, it may have its importance in other situations. It is possible to query whether a sorter is guaranteed to always use a stable sorting algorithm with the [`is_always_stable`][is-always-stable] sorter trait. +* *Stability*: a sorting algorithm is *stable* if it preserves the relative order of *equivalent elements*. While it does not matter when the equivalence relationship also happens to be an equality relationship, it may have its importance in other situations. It is possible to query whether a sorter is guaranteed to always use a stable sorting algorithm with the [`is_always_stable`][is-always-stable] sorter trait. using stability = cppsort::is_stable; @@ -81,3 +83,4 @@ [std-ranges]: https://en.cppreference.com/w/cpp/algorithm/ranges [stlab]: https://stlab.adobe.com/ [utility-iter-move]: Miscellaneous-utilities.md#iter_move-and-iter_swap + [weak-order]: https://en.wikipedia.org/wiki/Weak_ordering diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 61016d2b..a6556feb 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -95,7 +95,7 @@ Computes the number of elements in a sequence that aren't followed by the same e Our implementation is slightly different from the original description in *Sublinear merging and natural mergesort* by S. Carlsson, C. Levcopoulos and O. Petersson: * It doesn't add 1 to the general result, thus returning 0 when *X* is sorted - therefore respecting the Mannila definition of a MOP. -* It explicitly handles elements that compare equivalent, while the original formal definition makes it difficult. +* It explicitly handles *equivalent elements*, while the original formal definition makes it difficult. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 3ae4d68c..1c8b4992 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -315,7 +315,7 @@ auto indices = get_sorted_indices_for(vec); Concretely `sorted_indices` is designed like a [sorter adapter][sorter-adapters] and therefore supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade]. The main reason it does not sit with sorter adapters is that the returned function object is not a sorter per se since it doesn't sort the passed collection directly. -When the collection contains several elements that compare equivalent, the order of their indices in the result depends on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of elements that compare equivalent appear in a stable order in the result. +When the collection contains several *equivalent elements*, the order of their indices in the result depends on the sorter being used. However that order should be consistent across all stable sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of *equivalent elements* appear in a stable order in the result. *New in version 1.14.0* @@ -340,7 +340,7 @@ for (auto it: iterators) { It can be thought of as a kind of sorted view of the passed collection - as long as said collection does not change. It can be useful when the order of the original collection must be preserved, but operations have to be performed on the sorted collection. -When the collection contains several elements that compare equivalent, the order of the corresponding iterators in the result depends on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_iterators` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the iterators to elements that compare equivalent appear in a stable order in the result. +When the collection contains several *equivalent elements*, the order of the corresponding iterators in the result depends on the sorter being used. However that order should be consistent across all stable sorters. `sorted_iterators` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the iterators to *equivalent elements* appear in a stable order in the result. *New in version 1.14.0* diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index 953180d8..d196d3de 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -18,7 +18,7 @@ constexpr auto sort = indirect_adapter(quick_sort); ``` Most of the library's *sorter adapters* can store the passed *sorters* in their internals, allowing them to use adapt *stateful sorters*. Unless explicitly mentioned otherwise in an adapter's description, it is safe to assume that the *sorter adapters* in the library have the following properties: -* The *sorter adapter* stores a copy of every passed sorters in its internals and uses those copy when needed. If every *original sorter* is empty and default-constructible, then the *sorter adapter* is also empty and default-constructible. +* The *sorter adapter* stores a copy of every passed sorters in its internals and uses those copy when needed. If every *original sorter* is empty and default-constructible, then the *sorter adapter* is also empty and default-constructible. * If the *sorter adapter* adapts a single *sorter*, then it has a member function called `get()` which returns a reference to the internal *sorter* whose reference and `const` qualifications match those of the *sorter adapter* instance. If the *sorter adapter* is empty and default-constructible, then a default-constructed instance of the type of the *original sorter* is returned instead. * If the *sorter adapter* is empty and default-constructible, then it can be converted to any function pointer whose signature matches that of its `operator()`. @@ -278,7 +278,7 @@ template struct make_stable; ``` -`make_stable` takes a sorter and artificially alters its behavior to produce a stable sorter. It does so by associating every element of the collection to sort to its starting position and then uses the *adapted sorter* to sort the collection with a special comparator: whenever two elements compare equivalent, it compares the starting positions of the elements to ensure that their relative starting positions are preserved. Storing the starting positions requires O(n) additional space. +`make_stable` takes a sorter and artificially alters its behavior to produce a stable sorter. It does so by associating every element of the collection to sort to its starting position and then uses the *adapted sorter* to sort the collection with a special comparator: whenever two elements are *equivalent*, it compares the starting positions of the elements to ensure that their relative starting positions are preserved. Storing the starting positions requires O(n) additional space. ```cpp template diff --git a/docs/Writing-a-randomizing_adapter.md b/docs/Writing-a-randomizing_adapter.md index 7638cc77..096cf8cb 100644 --- a/docs/Writing-a-randomizing_adapter.md +++ b/docs/Writing-a-randomizing_adapter.md @@ -29,9 +29,9 @@ An arguably more useful use for a `randomizing_adapter` would be to avoid becomi > all observable behaviors of your system > will be depended on by somebody. -Danila Kutenin rightfully mentions that [changing `std::sort` is harder than meets the eye][changing-std-sort], the main reason being that pieces of code accidentally rely on the observable yet not guaranteed properties of [`std::sort`][std-sort], namely the order of elements that compare equivalent. The article gives [golden tests][golden-tests] as an example of things that might break when changing a sorting algorithm. +Danila Kutenin rightfully mentions that [changing `std::sort` is harder than meets the eye][changing-std-sort], the main reason being that pieces of code accidentally rely on the observable yet not guaranteed properties of [`std::sort`][std-sort], namely the order of *equivalent elements*. The article gives [golden tests][golden-tests] as an example of things that might break when changing a sorting algorithm. -In order to make it less likely for users to rely on the order of elements that compare equivalent, the author proposes to shuffle the collection prior to sorting it debug mode. This makes the order of equivalent elements non deterministic, which in turns can purposefuly break code accidentally relying on this order. +In order to make it less likely for users to rely on the order of *equivalent elements*, the author proposes to shuffle the collection prior to sorting it debug mode. This makes the order of *equivalent elements* non deterministic, which in turns can purposefuly break code accidentally relying on this order. It might seem at first that **cpp-sort**'s algorithms are not vulnerable to such changes since the name of the algorithm is part of sorter's name, but the truth is that their implementation still changes, and a user of the library might still want to swap a sorter for another one and suffer the same fate. @@ -105,7 +105,7 @@ struct randomizing_adapter: constexpr explicit randomizing_adapter(Sorter sorter): cppsort::utility::adapter_storage(std::move(sorter)) {} - + template auto operator()(RandomAccessIterator begin, RandomAccessIterator end, Args&&... args) const -> decltype(this->get()(begin, end, std::forward(args)...)) diff --git a/docs/Writing-a-sorter.md b/docs/Writing-a-sorter.md index 1eea40b8..8784eca5 100644 --- a/docs/Writing-a-sorter.md +++ b/docs/Writing-a-sorter.md @@ -28,7 +28,7 @@ auto selection_sort(ForwardIterator first, ForwardIterator last) for (auto it = first ; it != last ; ++it) { auto selection = std::min_element(it, last); using std::iter_swap; - iter_swap(selection, it); + iter_swap(selection, it); } } ``` @@ -172,7 +172,7 @@ The rules for *comparison sorters* are but an extension to the rules defined for ## Handling projections -C++20 introduces the notion of callable *projections*, borrowed from the [Adobe Source Libraries][stlab]. A projection is a callable object that can be passed to an algorithm so that it "views" the values to be compared differently. For example, [`std::negate<>`][std-negate-void] could be used to sort a collection of integers in descending order. Let's assume that our `selection_sort` algorithm from a while has been given a fourth parameter to handle projections; here is the corresponding *sorter implementation*: +C++20 introduces the notion of callable *projections*, borrowed from the [Adobe Source Libraries][stlab]. A projection is a callable object that can be passed to an algorithm so that it "views" the values to be compared differently. For example, [`std::negate<>`][std-negate-void] could be used to sort a collection of integers in descending order. Let's assume that our `selection_sort` algorithm from a while has been given a fourth parameter to handle projections; here is the corresponding *sorter implementation*: ```cpp struct selection_sorter_impl @@ -368,7 +368,7 @@ While type-specific sorters are, by their very nature, unable to generically han ## Stability -A sorting algorithm is said to be [stable][stability] if it preserves the relative order of equivalent elements. **cpp-sort** documents the stability of every sorter by giving them an `is_always_stable` type aliasing a boolean specialization of `std::integer_constant`. This information should be accessed via [`sorter_traits`][sorter-traits] or via the more specific [`is_always_stable`][is-always-stable] trait. The stability of a sorter is always either [`std::true_type` or `std::false_type`][std-integral-constant]. +A sorting algorithm is said to be [stable][stability] if it preserves the relative order of *equivalent elements*. **cpp-sort** documents the stability of every sorter by giving them an `is_always_stable` type aliasing a boolean specialization of `std::integer_constant`. This information should be accessed via [`sorter_traits`][sorter-traits] or via the more specific [`is_always_stable`][is-always-stable] trait. The stability of a sorter is always either [`std::true_type` or `std::false_type`][std-integral-constant]. ```cpp using stability = cppsort::is_always_stable; From b05e9e10a616307adb6acbe9299235b39f8ca231 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Dec 2022 15:22:11 +0100 Subject: [PATCH 39/53] typo --- docs/Chainable-projections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Chainable-projections.md b/docs/Chainable-projections.md index 94050bc9..6caed588 100644 --- a/docs/Chainable-projections.md +++ b/docs/Chainable-projections.md @@ -25,7 +25,7 @@ my_negate projection; cppsort::poplar_sort(vec, &wrapper::value | projection); ``` -The object returned by the utility function [`utility::as_projection`][as_projection] also inherits from `utility::projection_base`, making `as_projection` the proper function to turn any suitable projection into a projection composable with `operator|`. If one of the arguments to `operator|` arguments is either [`utility::identity`][utility-identity] or [`std::identity`][std-identity], then the other argument is returned directly. +The object returned by the utility function [`utility::as_projection`][as_projection] also inherits from `utility::projection_base`, making `as_projection` the proper function to turn any suitable projection into a projection composable with `operator|`. If one of the arguments to `operator|` is either [`utility::identity`][utility-identity] or [`std::identity`][std-identity], then the other argument is returned directly. If both of the projections composed with `operator|` are [*transparent*][transparent-func], then the returned object is also a *transparent* projection. From 611e8c6aebaee4ae4154b01f0a4a8c24f92b1393 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Dec 2022 16:04:38 +0100 Subject: [PATCH 40/53] Bump downloaded Catch2 version to v3.2.0 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 61be32af..c52c6aab 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,7 +27,7 @@ else() message(STATUS "Catch2 not found") download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v3.1.0 + GIT_TAG v3.2.0 UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) From 0fbf8f7f057d7a0e1c73616d4c396b70ea1273f3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 4 Dec 2022 13:21:04 +0100 Subject: [PATCH 41/53] Library nomenclature: unified sorting interface --- docs/Library-nomenclature.md | 7 +++++-- docs/Measures-of-presortedness.md | 4 ++-- docs/Miscellaneous-utilities.md | 11 ++++------- docs/Sorter-adapters.md | 4 ++-- docs/Sorters.md | 8 ++++---- include/cpp-sort/adapters/hybrid_adapter.h | 8 ++++---- include/cpp-sort/detail/type_traits.h | 8 ++++++++ 7 files changed, 29 insertions(+), 21 deletions(-) diff --git a/docs/Library-nomenclature.md b/docs/Library-nomenclature.md index 9949664b..2aabeb96 100644 --- a/docs/Library-nomenclature.md +++ b/docs/Library-nomenclature.md @@ -38,12 +38,12 @@ * *Proxy iterator*: sometimes `std::move` and `std::swap` are not enough to correctly move values around, and we need to know more about the iterators in order to perform the appropriate operation. It's typically the case with proxy iterators: iterators whose `reference` type is not actually a reference type (*e.g.* `std::vector::reference`). Traditional algorithms don't play well with these types, however there are [standard proposals][p0022] to solve the problem by introducing a function named `iter_move` and making it as well as `iter_swap` customization points. No proposal has been accepted yet, so standard libraries don't handle proxy iterators; however every sorter in **cpp-sort** can actually handle such iterators (except `std_sorter` and `std_stable_sorter`). The library exposes the functions [`utility::iter_move` and `utility::iter_swap`][utility-iter-move] in case you also need to make your own algorithms handle proxy iterators. -* *Sorter*: [sorters][sorters] are the protagonists in this library. They are function objects implementing specific sorting algorithms. Their `operator()` is overloaded so that it can handle iterables or pairs of iterators, and conditionally overloaded so that it can handle user-provided comparison and/or projection functions. +* *Sorter*: [sorters][sorters] are the protagonists in this library. They are function objects implementing specific sorting algorithms. Their `operator()` is overloaded so that it can handle iterables or pairs of iterators, and conditionally overloaded so that it can handle user-provided comparison and/or projection functions (see *unified sorting interface*). cppsort::pdq_sorter{}(std::begin(collection), std::end(collection), std::greater<>{}, &wrapper::value); -* *Sorter adapter*: [sorter adapters][sorter-adapters] are class templates that take one or several sorters and produce a new sorter from the parameters. What a sorter adapter can do is not constrained, but they are generally expected to behave like sorters themselves. For example, **cpp-sort** contains adapters to count the number of comparisons performed by a sorting algorithms or to aggregate several sorters together. The best way to learn more about them is still to read the dedicated section in the documentation. +* *Sorter adapter*: [sorter adapters][sorter-adapters] are class templates that take one or several sorters and produce a new sorter from the parameters. What a sorter adapter can do is not constrained, but they are generally expected to behave like sorters themselves. For example, **cpp-sort** contains adapters to count the number of comparisons performed by a sorting algorithms or to aggregate several sorters together. The best way to learn more about them is still to read the dedicated section of the documentation. * *Stability*: a sorting algorithm is *stable* if it preserves the relative order of *equivalent elements*. While it does not matter when the equivalence relationship also happens to be an equality relationship, it may have its importance in other situations. It is possible to query whether a sorter is guaranteed to always use a stable sorting algorithm with the [`is_always_stable`][is-always-stable] sorter trait. @@ -63,6 +63,8 @@ * *Type-specific sorter*: some non-comparison sorters such as the [`spread_sorter`][spread-sorter] implement specific sorting algorithms which only work with some specific types (for example integers or strings). +* *Unified sorting interface*: *sorters*, *sorter adapters*, *measures of presortedness* and a few other components of the library accept an iterable or a pair of iterators, and optionally a comparison function and/or a comparison function. Those components typically rely on the library's [`sorter_facade`][sorter-facade] which handles the dispatching to the component's implementation and to handle a number of special cases. For simplicity, what is accepted by the `operator()` of such components is referred to as the *unified sorting interface* in the rest of the library. + [comparators]: Comparators.md [fixed-size-sorters]: Fixed-size-sorters.md @@ -75,6 +77,7 @@ [p0022]: https://wg21.link/P0022 [radix-sort]: https://en.wikipedia.org/wiki/Radix_sort [sorter-adapters]: Sorter-adapters.md + [sorter-facade]: Sorter-facade.md [sorters]: Sorters.md [spread-sorter]: Sorters.md#spread_sorter [stable-adapter]: Sorter-adapters.md#stable_adapter-make_stable-and-stable_t diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index a6556feb..da03ca89 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -37,7 +37,7 @@ The measures of presortedness in bold in the graph are available in **cpp-sort** ## Measures of presortedness in cpp-sort -In **cpp-sort**, measures of presortedness are implemented as instances of some specific function objects. They take an iterable or a pair of iterators and return how much disorder there is in the sequence according to the measure. Just like sorters, measures of presortedness can handle custom comparison and projection functions, and with the same degree of freedom when it comes to how they can be called: +In **cpp-sort**, measures of presortedness are implemented as instances of some specific function objects. They take an iterable or a pair of iterators and return how much disorder there is in the sequence according to the measure. Measures of presortedness follow the *unified sorting interface*, allowing a certain degree a freedom in the parameters they accept: ```cpp using namespace cppsort; @@ -47,7 +47,7 @@ auto c = probe::ham(li, std::greater<>{}); auto d = probe::runs(integers, std::negate<>{}); ``` -Note however that these algorithms can be expensive. Using them before an actual sorting algorithm has no interest at all; they are meant to be profiling tools: when sorting is a critical part of your application, you can use these measures on typical data and check whether it is mostly sorted according to one measure or another, then you may be able to find a sorting algorithm known to be optimal with regard to this specific measure. +Note however that these algorithms can be expensive. Using them before an actual sorting algorithm little interest if any. They are instead meant to be profiling tools: when sorting is a critical part of your application, you can use these measures on typical data and check whether it is mostly sorted according to one measure or another, then you may be able to find a sorting algorithm known to be optimal with regard to this specific measure. Measures of presortedness can be used with the *sorter adapters* from the library. Even though most of the adapters are meaningless with measures of presortedness, some of them can still be used to mitigate space and time: diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 1c8b4992..07749c98 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -304,7 +304,7 @@ using make_index_range = make_integer_range; #include ``` -`utility::sorted_indices` is a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]). The resulting indices can be passed [`apply_permutation`][apply-permutation] to sort the original collection. +`utility::sorted_indices` is a function object that takes a sorter and returns a new function object that follows the *unified sorting interface*. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]). The resulting indices can be passed [`apply_permutation`][apply-permutation] to sort the original collection. ```cpp std::vector vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; @@ -313,9 +313,7 @@ auto indices = get_sorted_indices_for(vec); // indices == [6, 3, 2, 9, 1, 8, 0, 5, 4, 7] ``` -Concretely `sorted_indices` is designed like a [sorter adapter][sorter-adapters] and therefore supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade]. The main reason it does not sit with sorter adapters is that the returned function object is not a sorter per se since it doesn't sort the passed collection directly. - -When the collection contains several *equivalent elements*, the order of their indices in the result depends on the sorter being used. However that order should be consistent across all stable sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of *equivalent elements* appear in a stable order in the result. +When the collection contains *equivalent elements*, the order of their indices in the result depends on the sorter being used. However that order should be consistent across all stable sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of *equivalent elements* appear in a stable order in the result. *New in version 1.14.0* @@ -325,7 +323,7 @@ When the collection contains several *equivalent elements*, the order of their i #include ``` -`utility::sorted_iterators` is a function object that takes a sorter and returns a new function object. This new function object accepts a collection and returns an `std::vector` containing iterators to the passed collection in a sorted order. It is designed like a [sorter adapter][sorter-adapters] and as such supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade]. +`utility::sorted_iterators` is a function object that takes a sorter and returns a new function object that follows the *unified sorting interface*. This new function object accepts a collection and returns an `std::vector` containing iterators to the passed collection in a sorted order. ```cpp std::list li = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 }; @@ -340,7 +338,7 @@ for (auto it: iterators) { It can be thought of as a kind of sorted view of the passed collection - as long as said collection does not change. It can be useful when the order of the original collection must be preserved, but operations have to be performed on the sorted collection. -When the collection contains several *equivalent elements*, the order of the corresponding iterators in the result depends on the sorter being used. However that order should be consistent across all stable sorters. `sorted_iterators` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the iterators to *equivalent elements* appear in a stable order in the result. +When the collection contains *equivalent elements*, the order of the corresponding iterators in the result depends on the sorter being used. However that order should be consistent across all stable sorters. `sorted_iterators` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the iterators to *equivalent elements* appear in a stable order in the result. *New in version 1.14.0* @@ -429,7 +427,6 @@ You can read more about this instantiation pattern in [this article][eric-nieble [pdq-sorter]: Sorters.md#pdq_sorter [range-v3]: https://github.com/ericniebler/range-v3 [sorter-adapters]: Sorter-adapters.md - [sorter-facade]: Sorter-facade.md [sorters]: Sorters.md [sorting-network]: https://en.wikipedia.org/wiki/Sorting_network [std-array]: https://en.cppreference.com/w/cpp/container/array diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index d196d3de..c836d4b8 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -1,10 +1,10 @@ -Sorter adapters are the main reason for using sorter function objects instead of regular functions. A *sorter adapter* is a class template that takes another `Sorter` template parameter and alters its behavior. The resulting class can be used as a regular sorter, and be adapted in turn. Note that some of the adapters are actually *[fixed-size sorter][fixed-size-sorters] adapters* instead of regular *sorter adapters*. It is possible to include all of the available adapters at once with the following directive: +Sorter adapters are the main reason for using sorter function objects instead of regular functions. A *sorter adapter* is a class template that takes a `Sorter` template parameter and alters its behavior. The resulting class can be used as a regular sorter, and be adapted in turn. Some of the adapters below are actually *[fixed-size sorter][fixed-size-sorters] adapters* instead of regular *sorter adapters*. It is possible to include all of the available adapters at once with the following directive: ```cpp #include ``` -In this documentation, we will call *adapted sorters* the sorters passed to the adapters and *resulting sorter* the sorter class that results from the adaption of a sorter by an adapter. If not specified, the stability and the iterator category of the *resulting sorter* is that of the *adapted sorter* provided there is a single *adapted sorter*. +In this documentation, we call *adapted sorters* the sorters passed to the adapters and *resulting sorter* the sorter class that results from the adaption of a sorter by an adapter. If not specified, the stability and the iterator category of the *resulting sorter* is that of the *adapted sorter* provided there is a single *adapted sorter*. The *resulting sorter* is expected to follow the *unified sorting interface*. In C++17, *sorter adapters* can be used in a function-like fashion thanks to `explicit` constructors (taking one or several sorters) by taking advantage of implicit [deduction guides][ctad]. The following example illustrates how it simplifies their use: diff --git a/docs/Sorters.md b/docs/Sorters.md index 86b36d68..3c098212 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -1,18 +1,18 @@ -**cpp-sort** uses function objects called *sorters* instead of regular function templates in order to implement sorting algorithms. The library provides two categories of sorters: generic sorters that will sort a collection with any given comparison function, and type-specific sorters which will be optimized to sort collections of a given type, and generally don't allow to use custom comparison functions due to the way they work. Every comparison sorter as well as some type-specific sorters may also take an additional projection parameter, allowing the algorithm to "view" the data to sort through an on-the-fly transformation. +**cpp-sort** uses function objects called *sorters* instead of regular function templates in order to implement sorting algorithms. The library provides two categories of sorters: comparison sorters, that sort a collection with using a weak order comparator, and type-specific sorters that use other strategies to sort collections, generally restricting their domain to specific types. Every comparison sorter as well as some type-specific sorters may also take an additional projection parameter, allowing the algorithm to "view" the data to sort through an on-the-fly transformation. (see *unified sorting interface*) -While these function objects offer little more than regular sorting functions by themselves, they can be used together with *[sorter adapters][sorter-adapters]* to craft more elaborate sorters effortlessly. Every sorter is available in its own file. However, all the available sorters can be included at once with the following line: +While these function objects offer little more than regular sorting functions by themselves, they can be used together with *[sorter adapters][sorter-adapters]* to craft more elaborate sorters. Every sorter is available in its own file. However, all the available sorters can be included at once with the following line: ```cpp #include ``` -Note that for every `foobar_sorter` described in this page, there is a corresponding `foobar_sort` global instance that allows not to care about the sorter abstraction as long as it is not needed (the instances are usable as regular function templates). The only sorter without a corresponding global instance is [`default_sorter`][default-sorter] since it mainly exists as a fallback sorter for the functions [`cppsort::sort` and `cppsort::stable_sort`][sorting-functions] when they are called without an explicit sorter. +For every `foobar_sorter` described in this page, there is a corresponding `foobar_sort` global instance that allows not to care about the sorter abstraction as long as it is not needed (the instances are usable as regular function templates). The only sorter without a corresponding global instance is [`default_sorter`][default-sorter] since it mainly exists as a fallback sorter for the functions [`cppsort::sort` and `cppsort::stable_sort`][sorting-functions] when they are called without an explicit sorter. If you want to read more about sorters and/or write your own one, then you should have a look at [the dedicated page][writing-a-sorter] or at [a specific example][writing-a-bubble-sorter]. ## Comparison sorters -The following sorters are available and will work with any type for which `std::less` works and should accept any well-formed comparison function: +The following sorters are available and should work with any type for which `std::less` works and should accept any weak order comparison function: ### `adaptive_shivers_sorter` diff --git a/include/cpp-sort/adapters/hybrid_adapter.h b/include/cpp-sort/adapters/hybrid_adapter.h index b5d76b78..0a88d908 100644 --- a/include/cpp-sort/adapters/hybrid_adapter.h +++ b/include/cpp-sort/adapters/hybrid_adapter.h @@ -165,7 +165,7 @@ namespace cppsort constexpr auto get() & -> decltype(auto) { - using sorter_t = std::tuple_element_t>; + using sorter_t = pack_element; return hybrid_adapter_storage_leaf< sizeof...(Sorters) * iterator_category_value> @@ -178,7 +178,7 @@ namespace cppsort constexpr auto get() const& -> decltype(auto) { - using sorter_t = std::tuple_element_t>; + using sorter_t = pack_element; return hybrid_adapter_storage_leaf< sizeof...(Sorters) * iterator_category_value> @@ -191,7 +191,7 @@ namespace cppsort constexpr auto get() && -> decltype(auto) { - using sorter_t = std::tuple_element_t>; + using sorter_t = pack_element; return hybrid_adapter_storage_leaf< sizeof...(Sorters) * iterator_category_value> @@ -204,7 +204,7 @@ namespace cppsort constexpr auto get() const&& -> decltype(auto) { - using sorter_t = std::tuple_element_t>; + using sorter_t = pack_element; return hybrid_adapter_storage_leaf< sizeof...(Sorters) * iterator_category_value> diff --git a/include/cpp-sort/detail/type_traits.h b/include/cpp-sort/detail/type_traits.h index 18e6d3d7..2ecac240 100644 --- a/include/cpp-sort/detail/type_traits.h +++ b/include/cpp-sort/detail/type_traits.h @@ -9,6 +9,7 @@ // Headers //////////////////////////////////////////////////////////// #include +#include #include namespace cppsort @@ -327,6 +328,13 @@ namespace detail template class Template> constexpr bool is_specialization_of_v = is_specialization_of::value; + //////////////////////////////////////////////////////////// + // pack_element: retrieve the nth elements of a parameter + // pack + + template + using pack_element = std::tuple_element_t>; + //////////////////////////////////////////////////////////// // is_in_pack: check whether a given std::size_t value // appears in a std::size_t... parameter pack From 03ec3a27f1fe1efdb945c505d427892e6a36bf13 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 7 Dec 2022 00:17:18 +0100 Subject: [PATCH 42/53] Mention unified sorting interface in sorter_facade doc --- docs/Sorter-facade.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index e448f0eb..e8404881 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -1,9 +1,9 @@ -To write a full-fledged sorter, implementers have to implement a variety of `operator()` overloads with a rather high redundancy factor. To make the task simpler, **cpp-sort** provides a wrapper class which generates most of the boilerplate for the required operations in the simplest cases. To benefit from it, one needs to create a *sorter implementation* and to wrap it into `sorter_facade`: +To write a full-fledged sorter, implementers have to implement what we call the *unified sorting interface*: a variety of `operator()` overloads with a rather high redundancy factor. To make the task simpler, **cpp-sort** provides `sorter_facade`, a class template which wraps a user-provided *sorter implementation* with a minimal interface and generates most of the boilerplate for the required operations: ```cpp struct frob_sorter_impl { - // Regular sorter code + // Minimal algorithm interface here }; struct frob_sorter: From 3d9917499b09e91066ee36d598bee05a2ae2a04f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 7 Dec 2022 01:49:24 +0100 Subject: [PATCH 43/53] doc: add a Quickstart page (#203) --- docs/Home.md | 5 +- docs/Quickstart.md | 232 +++++++++++++++++++++++++++++++++++++++++++++ docs/_Sidebar.md | 1 + 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 docs/Quickstart.md diff --git a/docs/Home.md b/docs/Home.md index b2b5f37f..97830e85 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -2,7 +2,9 @@ Welcome to the **cpp-sort 1.13.2** documentation! -You probably read the introduction in the README, so I won't repeat it here. This wiki contains documentation about the library: basic documentation about the many sorting tools and how to use them, documentation about the additional utilities provided by the library and even some detailed tutorials if you ever want to write your own sorters or sorter adapters. This main page explains a few general things that didn't quite fit in other parts of the documentation. +This wiki contains documentation about the library: basic documentation about the many sorting tools and how to use them, documentation about the additional utilities provided by the library, as well as a few tutorials about writing your own sorters or sorter adapters. This main page explains a few general things that didn't quite fit in other parts of the documentation. + +For the simpler use cases, a good place to start is the [quickstart page][quickstart]. If you find a library feature which isn't documented, don't hesitate to open an issue. It generally means that we either need to hide it in a subnamespace or to let it available and document it. @@ -89,4 +91,5 @@ Hope you have fun! [benchmarks]: Benchmarks.md [original-research]: Original-research.md + [quickstart]: Quickstart.md [swappable]: https://en.cppreference.com/w/cpp/concepts/swappable diff --git a/docs/Quickstart.md b/docs/Quickstart.md new file mode 100644 index 00000000..fed10e9c --- /dev/null +++ b/docs/Quickstart.md @@ -0,0 +1,232 @@ +The core idea behind **cpp-sort** is rather simple: providing a collection of sorting algorithms, along with a few tools allowing to make the most of them. This page describes the most important parts of the library and how to effectively use them. + +Before jumping in, you might want to install the library: you can use [CMake][cmake] and/or [Conan][conan] for the task, as described in the [tooling page][tooling] of this documentation. + +## Sorting algorithms + +Arguably the most basic task one might want to perform is to sort a collection of elements with a sorting algorithm of their choice. This can be achieved as follows: + +```cpp +#include +#include + +int main() +{ + std::vector collection = { /* ... */ }; + cppsort::pdq_sort(collection); +} +``` + +The library provides dozens of sorting algorithms described in [this page][sorters]. Their space and time complexities are all documented there, as well as their stability and the kind of iterators they work on. The [`pdq_sort`][pdq-sorter] algorithm used above only handles collections that provide random-access iterators, but other algorithms such as [`drop_merge_sort`][drop-merge-sorter] or [`merge_sort`][merge-sorter] work with bidirectional iterators and forward iterators. + +The algorithms are designed after [`std::sort`][std-sort] with extensions borrowed from later standards, and as such accept the following arguments: +* A collection, which is represented by either a range or a pair of iterators. +* A comparison function (optional). +* A projection function (optional). + +The following example demonstrates how a sorter can be used to sort a collection in a variety of ways: + +```cpp +#include +#include +#include +#include +#include +#include + +struct Person { + std::string name; + int age; + + friend bool operator<(Person const& lhs, Person const& rhs) { + return std::tie(lhs.name, lhs.age) < std::tie(rhs.name, rhs.age); + } +} + +int main() +{ + Person[100] persons = { /* ... */ }; + using cppsort::quick_sort; + + // Sort using operator<(Person, Person) + quick_sort(persons); + // Sort in reverse order usinig operator<(Person, Person) + quick_sort(persons, cppsort::flip(std::less<>{})); + // Sort the first half of the collection from youngest to oldest + quick_sort(std::begin(persons), persons+50, &Person::age); + // Sort the second half of the collection from older to youngest + quick_sort(persons+50, std::end(persons), std::greater<>{}, &Person::age); + + std::list li(std::begin(persons), std::end(persons)); + // quick_sort also works on bidirectional (and forward) iterators + quick_sort(li.begin(), li.end()); + // Though passing the container directly allows to use the size + // information directly, allowing to perform less work + quick_sort(li); +} +``` + +Most of the provided sorting algorithms are comparison sorts, but a few others such as [`ska_sort`][ska-sorter] do not support comparisons at all. Almost all sorters support projections. All the variations of the collection/comparison/projection triplet supported by the sorters are known as the *unified sorting interface*. + +## Sorters + +Every sorting algorithm in the library is actually an instance of a *sorter* object. For example `cppsort::quick_sort` is an instance of `cppsort::quick_sorter`. Using function objects for algorithms has a few advantages: it makes it easier to pass complete overload sets around, and also allows to separate sorter-specific arguments from the *unified sorting interface*. This design is philosophically similar to that of the [standard library searchers][std-searchers] and ensures that all sorters, once constructed, can be called in a similar fashion, in turn allowing to write functions that generically manipulate sorter objects. + +For example, a function to count the number of comparisons performed by a sorter while sorting a collection can be written as follows: + +```cpp +template +int sort_count_comparisons(Sorter& sorter, Iterator first, Iterator last) +{ + int count = 0; + auto cmp = [&count](auto const& lhs, auto const& rhs) { + ++count; + return lhs < rhs; + }; + sorter(first, last, cmp); + return count; +} +``` + +All sorters described in the list linked in the previous section are actually stateless and default-constructible, but nothing prevents a custom sorter from taking parameters during construction and holding a state. Stateless sorters, however, do have an advantage over stateful ones: they can be converted to any function pointer corresponding to one of its `operator()` overload: + +```cpp +void(*sort)(std::forward_list&, std::negate<>) = cppsort::mel_sort; +``` + +Sorters are generally implemented using the library's [`sorter_facade`][sorter-facade], which generates most of the boilerplate needed for a sorter to implement the *unified sorting interface*. Its documentation is useful to understand all the combinations of parameters accepted by the library's sorters, but it isn't a component that you should use explicitly unless you are building your own *sorters* or *sorter adapters*. + +## Sorters adapters + +Building on the ideas of *sorters*, [*sorter adapters*][sorter-adapters] are function objects provided by the library that are constructed with a *sorter* object and are themselves a *sorter*. This pattern can be used to solve a variety of issues, for example: + +* Sort a list by moving its elements to a contiguous memory buffer and sorting them there before moving them back, allowing to use any sorter that accepts random-access iterators: + ```cpp + #include + #include + #include + + int main() + { + auto sorter = cppsort::out_of_place_adapter{}; + std::list collection = { /* ... */ }; + sorter(collection); + } + ``` + +* Reduce the cost of expensive projections with a [Schwartzian transform][schwartzian-transform]: + ```cpp + #include + #include + #include + + int main() + { + auto sorter = cppsort::schwartz_adapter{}; + std::vector collection = { /* ... */ }; + sorter(collection, expensive_projection); + } + ``` + +* Minimze the number of element moves by sorting a collection of iterators, and moving all the elemrnts directly to their sorted position: + ```cpp + #include + #include + #include + + int main() + { + auto sorter = cppsort::indirect_adapter{}; + std::vector collection = { /* ... */ }; + sorter(collection); + } + ``` + +* Make any sorter [*Rem*-adaptive][probe-rem] with an additional O(n) pass and a merge: + ```cpp + #include + #include + #include + + int main() + { + auto sorter = cppsort::split_adapter{}; + std::vector collection = { /* ... */ }; + sorter(collection); + } + ``` + +Almost any sorter can be passed to any adapter, with a few exceptions: +* The iterator categories have to match. +* Some adapters require the *adapted sorter* to handle comparisons. +* Some adapters requires *adapted sorter* to handle projections. +* Fixed-size sorter adapters only adapt fixed-size sorters. + +The specific restrictions are all documented in the adapters descriptions. + +## Two-step sorting + +Sometimes the information is not represented as simple collection of class instances, but as [parallel arrays][parallel-arrays] (also known as structure of arrays). To sort those, **cpp-sort** provides components for two-step sorting of random-access collections: +1. Extract the sorted indices of a collection with [`utility::sorted_indices`][utility-sorter-indices] (similar to [`numpy.argsort`][numpy-argsort]). +2. Use the sorted indices to permute the arrays with [`utility::apply_permutation`][utility-apply-permutation]. + +```cpp +// Names & ages of persons, where the identity of the person is the +// same index in both collections +std::vector names = { /* ... */ }; +std::vector ages = { /* ... */ }; + +auto get_sorted_indices_for = cppsort::utility::sorted_indices{}; +auto indices = get_sorted_indices_for(names); // Get sorted indices to sort by name +// Bring persons in sorted order +cppsort::utility::apply_permutation(names, auto(indices)); +cppsort::utility::apply_permutation(ages, indices); +``` + +This method allows to call a O(n log n) sorting agorithm once, and to call the O(n) `apply_permutation` once per array to bring into sorted order. Do note however that `apply_permutation` also alters the indices collection, which has to be copied for each array to bring into sorted order. + +## Measures of presortedness + +**cpp-sort** also provides a collection of [measures of presortedness][mops]: also known as *measures of disorder*, they are algorithms that evaluate the amount of disorder present in a collection - the exact algorithms used are described in the corresponding documentation page, and mostly come from the literature around adaptive sorting. These measures are provided as function objects implementing the *unified sorting interface* and returning an integer type. The exact value returned depends on the measure used, but all of them return 0 for a sorted collection. + +```cpp +#include +#include +#include + +int main() +{ + std::vector collection = { /* ... */ }; + std::cout << "Number of inversions: " << cppsort::probe::inv(collection) << '\n' + << "Number of runs: " << cppsort::probe::runs(collection) << '\n' + << "Number of encroaching lists: " << cppsort::probe::enc(collection) << std::endl; +} +``` + +For a measure of presortedness *M*, some algorithms are said to be *M*-adaptive when they naturally take advantage of the particular kind of presortedness described by *M* to perform proportionally less work. Analyzing the presortedness patterns found in real data can therefore be useful to pick an adaptive sorting algorithm tailored for the job. + +## Going further + +The previous sections describe some of the main tools provided by **cpp-sort** but that list is far from being exhaustive: the library contains more sorting-adjacent utilities, as well as tools that are meant to help you build your own sorters or sorter adapters. This documentation tries to cover most of the useful concepts and to describe the public-facing library components, so don't hesitate to open an issue if you feel that something is missing or unclear. + + + [cmake]: https://cmake.org/ + [conan]: https://conan.io/ + [drop-merge-sorter]: Sorters.md#drop_merge_sorter + [merge-sorter]: Sorters.md#merge_sorter + [mops]: Measures-of-presortedness.md + [numpy-argsort]: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html + [parallel-arrays]: https://en.wikipedia.org/wiki/Parallel_array + [pdq-sorter]: Sorters.md#pdq_sorter + [probe-rem]: Measures-of-presortedness.md#rem + [schwartzian-transform]: https://en.wikipedia.org/wiki/Schwartzian_transform + [ska-sorter]: Sorters.md#ska_sorter + [sorter-adapters]: Sorter-adapters.md + [sorter-facade]: Sorter-facade.md + [sorter-traits]: Sorter-traits.md + [sorters]: Sorters.md + [std-searchers]: https://en.cppreference.com/w/cpp/utility/functional#Searchers + [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort + [tooling]: Tooling.md + [utility-apply-permutation]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#apply_permutation + [utility-sorted-indices]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#sorted_indices diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 6bff272c..8c7458b9 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -1,4 +1,5 @@ * [Home](Home.md) +* [Quickstart](Quickstart.md) * Sorting library * [Library nomenclature](Library-nomenclature.md) * [Sorting functions](Sorting-functions.md) From 95ea9ff05d8809649c0c7c5616297b5dc782c5a2 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 7 Dec 2022 23:11:11 +0100 Subject: [PATCH 44/53] doc: add warnings for components removed in 2.0.0 --- docs/Miscellaneous-utilities.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 07749c98..74a3fc42 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -239,6 +239,8 @@ This utility is modeled after [`std::integral_constant`][std-integral-constant], #include ``` +***WARNING:** this header is removed in version 2.0.0, use `mstd::iter_move` and `mstd::iter_swap` instead.* + The functions `iter_move` and `iter_swap` are equivalent to the same functions as proposed by [P0022][p0022]: utility functions intended to be used with ADL to handle proxy iterators among other things. An algorithm can use them instead of `std::move` and possibly ADL-found `swap` to handle tricky classes such as `std::vector`. The default implementation of `iter_move` simply move-returns the dereferenced iterator. `iter_swap` is a bit more tricky: if the iterators to be swapped have a custom `iter_move`, then `iter_swap` will use it, otherwise it will call an ADL-found `swap` or `std::swap` on the dereferenced iterators. @@ -294,6 +296,8 @@ using make_index_range = make_integer_range; #include ``` +***WARNING:** this header is removed in version 2.0.0, use `mstd::distance` instead.* + `size` is a function that can be used to get the size of an iterable. It is equivalent to the C++17 function [`std::size`][std-size] but has an additional tweak so that, if the iterable is not a fixed-size C array and doesn't have a `size` method, it calls `std::distance(std::begin(iter), std::end(iter))` on the iterable. Therefore, this function can also be used for `std::forward_list` as well as some implementations of ranges. *Changed in version 1.12.1:* `utility::size()` now also works for collections that only provide non-`const` `begin()` and `end()`. From 52e1c76cf9bfe0ab46e17eed67b0f21b38c2aa84 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 12 Dec 2022 12:59:44 +0100 Subject: [PATCH 45/53] Assorted small improvements to inversions benchmark --- benchmarks/inversions/inv-bench.cpp | 34 ++++++++++++++++++++--------- benchmarks/inversions/plot.py | 27 ++++++++++++++--------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/benchmarks/inversions/inv-bench.cpp b/benchmarks/inversions/inv-bench.cpp index eb45e5f7..6ced3046 100644 --- a/benchmarks/inversions/inv-bench.cpp +++ b/benchmarks/inversions/inv-bench.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 Morwenn + * Copyright (c) 2020-2022 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -17,7 +17,6 @@ #include "../benchmarking-tools/distributions.h" #include "../benchmarking-tools/filesystem.h" #include "../benchmarking-tools/rdtsc.h" -#include "../benchmarking-tools/statistics.h" using namespace std::chrono_literals; @@ -34,14 +33,14 @@ using sort_f = void (*)(collection_t&); std::pair sorts[] = { { "drop_merge_sort", cppsort::drop_merge_sort }, { "pdq_sort", cppsort::pdq_sort }, - { "split_sort", cppsort::split_sort } + { "split_sort", cppsort::split_sort }, }; // Size of the collections to sort constexpr std::size_t size = 1'000'000; // Maximum time to let the benchmark run for a given size before giving up -auto max_run_time = 3s; +auto max_run_time = 5s; // Maximum number of benchmark runs per size std::size_t max_runs_per_size = 25; @@ -68,10 +67,16 @@ int main(int argc, char* argv[]) std::uint_fast32_t seed = std::time(nullptr); std::cout << "SEED: " << seed << '\n'; + int sort_number = 0; for (auto& sort: sorts) { // Create a file to store the results - std::string output_filename = output_directory + '/' + safe_file_name(sort.first) + ".csv"; - std::ofstream output_file(output_filename); + auto sort_number_str = std::to_string(sort_number); + auto output_filename = + std::string(3 - sort_number_str.size(), '0') + + std::move(sort_number_str) + + '-' + safe_file_name(sort.first) + ".csv"; + std::string output_path = output_directory + '/' + output_filename; + std::ofstream output_file(output_path); output_file << sort.first << '\n'; std::cout << sort.first << '\n'; @@ -79,7 +84,7 @@ int main(int argc, char* argv[]) // sort the same collections when there is randomness distributions_prng.seed(seed); - for (int idx = 0 ; idx <= 100 ; ++idx) { + for (int idx = 0; idx <= 100; ++idx) { double factor = 0.01 * idx; auto distribution = dist::inversions(factor); @@ -100,9 +105,18 @@ int main(int argc, char* argv[]) } // Compute and display stats & numbers - double avg = average(cycles); - output_file << idx << ", " << avg << '\n'; - std::cout << idx << ", " << avg << std::endl; + output_file << idx << ","; + std::cout << idx << ","; + auto it = cycles.begin(); + output_file << *it; + std::cout << *it; + while (++it != cycles.end()) { + output_file << "," << *it; + std::cout << "," << *it; + } + output_file << '\n'; + std::cout << std::endl; } + ++sort_number; } } diff --git a/benchmarks/inversions/plot.py b/benchmarks/inversions/plot.py index 6c0d985e..8ff866e2 100644 --- a/benchmarks/inversions/plot.py +++ b/benchmarks/inversions/plot.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020-2021 Morwenn +# Copyright (c) 2020-2022 Morwenn # SPDX-License-Identifier: MIT import argparse import pathlib +import sys import numpy from matplotlib import pyplot @@ -15,7 +16,7 @@ def fetch_results(fresults): results.pop() return [float(elem) for elem in results] - + if __name__ == '__main__': parser = argparse.ArgumentParser(description="Plot the results of the errorbar-plot benchmark.") parser.add_argument('root', help="directory with the result files to plot") @@ -26,6 +27,7 @@ def fetch_results(fresults): root = pathlib.Path(args.root) result_files = list(root.glob('*.csv')) + result_files.sort() if len(result_files) == 0: print(f"There are no files to plot in {root}") sys.exit(1) @@ -42,22 +44,27 @@ def fetch_results(fresults): colors = iter(palette) for result_file in result_files: + percent_inversions = [] + averages = [] with result_file.open() as fd: # Read the first line algo_name = fd.readline().strip() # Read the rest of the file - data = numpy.genfromtxt(fd, delimiter=',').transpose() - percent_inversions, avg = data + for line in fd: + pct, *data = line.strip().split(',') + data = list(map(int, data)) + percent_inversions.append(pct) + averages.append(numpy.average(data)) # Plot the results - pyplot.plot(percent_inversions, - avg, + pyplot.plot(list(map(int, percent_inversions)), + averages, label=algo_name, color=next(colors)) # Add a legend - pyplot.legend(loc='best') - pyplot.title('Sorting std::vector with $10^6$ elements') - pyplot.xlabel('Percentage of inversions') - pyplot.ylabel('Cycles (lower is better)') + pyplot.legend() + pyplot.title("Sorting std::vector with $10^6$ elements") + pyplot.xlabel("Percentage of inversions") + pyplot.ylabel("Cycles (lower is better)") pyplot.show() From ac7e465b610c5e12b32b9c1e1c4d501a4f7c4c6e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 12 Dec 2022 15:42:48 +0100 Subject: [PATCH 46/53] Bump downloaded Catch2 version to v3.2.1 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c52c6aab..4b0cdc25 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,7 +27,7 @@ else() message(STATUS "Catch2 not found") download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v3.2.0 + GIT_TAG v3.2.1 UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) From 3dbd49141adee0daa3265c2085d015cd09fd9fd0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 12 Dec 2022 17:51:36 +0100 Subject: [PATCH 47/53] Assorted improvements to small-array benchmark --- benchmarks/small-array/benchmark.cpp | 16 +++++------ benchmarks/small-array/plot.py | 41 ++++++++++++---------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/benchmarks/small-array/benchmark.cpp b/benchmarks/small-array/benchmark.cpp index 712ae60e..58e5bc0a 100644 --- a/benchmarks/small-array/benchmark.cpp +++ b/benchmarks/small-array/benchmark.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 Morwenn + * Copyright (c) 2015-2022 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -69,12 +69,12 @@ auto time_it(Sorter sorter, DistributionFunction distribution) total_end = clock_type::now(); } - // Return the average number of cycles it took to sort the arrays - std::uint64_t avg = 0; + // Return the average number of cycles it took to sort an array + std::uint64_t total = 0; for (auto value: cycles) { - avg += value; + total += value; } - return avg / double(cycles.size()); + return total / double(cycles.size()); } template< @@ -109,9 +109,9 @@ auto time_distribution(std::index_sequence) // Output the results to their respective files std::ofstream output(Distribution::output); for (auto&& sort_result: results) { - output << std::get<0>(sort_result) << ' '; + output << std::get<0>(sort_result) << ','; for (auto&& nb_cycles: std::get<1>(sort_result)) { - output << nb_cycles << ' '; + output << nb_cycles << ','; } output << '\n'; } @@ -138,7 +138,7 @@ int main() { std::cout << "SEED: " << seed << '\n'; - time_distributions') - plt.xlabel('Number of elements to sort') - plt.ylabel('Cycles (lower is better)') - plt.show() + pyplot.legend(values, names) + pyplot.title('Sorting std::array') + pyplot.xlabel('Number of elements to sort') + pyplot.ylabel('Cycles per element (lower is better)') + pyplot.show() From 65e738b0147825b85e86f396bcd4a6e6dcddbf34 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 15 Dec 2022 21:32:49 +0100 Subject: [PATCH 48/53] Fix unused parameter warning in apply_permutation --- include/cpp-sort/utility/apply_permutation.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/cpp-sort/utility/apply_permutation.h b/include/cpp-sort/utility/apply_permutation.h index b520a982..01ce24b2 100644 --- a/include/cpp-sort/utility/apply_permutation.h +++ b/include/cpp-sort/utility/apply_permutation.h @@ -26,6 +26,7 @@ namespace utility using difference_type = cppsort::detail::difference_type_t; using utility::iter_move; CPPSORT_ASSERT( (last - first) == (indices_last - indices_first) ); + (void)last; auto size = indices_last - indices_first; for (difference_type idx = 0; idx < size; ++idx) { From 76c090ccb90de59961fd30b6a1ba2aae49b6c6c5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 15 Dec 2022 23:27:26 +0100 Subject: [PATCH 49/53] doc: add note about odd_even_merge_sorter only accepting power of 2 sizes --- docs/Fixed-size-sorters.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Fixed-size-sorters.md b/docs/Fixed-size-sorters.md index f14f7d05..45e1e9e9 100644 --- a/docs/Fixed-size-sorters.md +++ b/docs/Fixed-size-sorters.md @@ -122,6 +122,8 @@ template struct odd_even_merge_network_sorter; ``` +`odd_even_merge_network_sorter` only accepts power of 2 for `N`. + All specializations of `odd_even_merge_network_sorter` provide a `index_pairs() static` function template which returns an [`std::array`][std-array] of [`utility::index_pair`][utility-sorting-networks]. Those pairs represent the indices used by the CE operations of the network and can be passed manipulated and passed to dedicated [sorting network tools][utility-sorting-networks] from the library's utility module. The function is templated of the index/difference type, which must be constructible from `int`. ```cpp From d48cd0eee1decd1c53f1a92960b41b132eb9e2b3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 15 Dec 2022 23:29:20 +0100 Subject: [PATCH 50/53] doc: update small array benchmarks --- docs/Benchmarks.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index 685c79e9..acbc7917 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -1,6 +1,7 @@ *Note: this page only benchmarks sorting algorithms under specific conditions. It can be used as a quick guide but if you really need a fast algorithm for a specific use case, you better run your own benchmarks.* *Last meaningful updates:* +* *1.14.0 for small array sorts* * *1.13.1 for unstable random-access sorts, slow O(n log n) sorts, forward sorts, and the expensive move/cheap comparison benchmark* * *1.12.0 for measures of presortedness* * *1.9.0 otherwise* @@ -69,7 +70,7 @@ I decided to include a dedicated category for slow O(n log n) sorts, because I f ![Benchmark slow O(n log n) sorts over different patterns for std::deque](https://i.imgur.com/Z9O4I6p.png) The analysis is pretty simple here: -* Most of the algorithms in this category are slow, but exhibit a good adaptiveness with most kinds of patterns. It isn't all that surprising since I specifically found them in literature about adaptive sorting. +* Most of the algorithms in this category are slow, but exhibit a good adaptiveness with most kinds of patterns. It isn't all that surprising since I specifically found them in literature about adaptive sorting. * `poplar_sort` is a bit slower for `std::vector` than for `std::deque`, which makes me suspect a weird issue somewhere. * As a result `smooth_sort` and `poplar_sort` beat each other depending on the type of the collection to sort. * Slabsort has an unusual graph: even for shuffled data it might end up beating `heap_sort` when the collection becomes big enough. @@ -157,16 +158,16 @@ Here `merge_sort` still loses the battle, but it also displays an impressive eno ## Small array sorters -Some sorting algorithms are particularly suited to sort very small collections: the ones provided by ``, but also the very simple ones such as `insertion_sort` or `selection_sort`. Most other sorting algorithms fallback to one of these when sorting a small collection. +Some sorting algorithms are particularly suited to sort very small collections: [*fixed-size sorters*][fixed-size-sorters] of course, but also very simple regular sorters such as [`insertion_sorter`][insertion-sorter] or [`selection_sorter`][selection-sorter]. Most other sorting algorithms fallback to one of these when sorting a small collection. -![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/dOa3vyl.png) -![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/4WRtPYP.png) +![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/ABfEmJe.png) +![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/wqz1q3R.png) -As far as only speed matters, sorting networks tend to win in these artificial benchmarks, but in a real world scenario the cost of loading the network code for a specific size again and again tends to make them slower. A sorting network can be fast when it is used over and over again. - -The spikes in the otherwise smooth sorting networks curve when sorting arrays of integers are weird: they don't exist for the `long double` benchmark but are consistent across runs for the `int` scenario. Interestingly enough those spikes seem to follow the `insertion_sort` curve. - -`low_moves_sorter` uses a modified selection sort above a small threshold, which might explain why the artefacts in the two curves have similar shapes. +We can see several trends in these benchmarks, rather consistant across `int` and `long double`: +* As far as only speed matters, the size-optimal hand-unrolled sorting networks of [`sorting_network_sorter`][sorting-network-sorter] tend to win in these artificial microbenchmarks, but in a real world scenario the cost of loading the network code for a specific size again and again tends to make them slower. A sorting network can be fast when it is used over and over again. +* [`insertion_sorter`][insertion-sorter] and [`merge_exchange_network_sorter`][merge-exchange-network-sorter] are both good enough with a similar profile, despite having very different implementations. +* [`low_comparisons_sorter`][low-comparisons-sorter] is second-best here but has a very limited range. +* [`selection_sorter`][selection-sorter] and [`low_moves_sorter`][low-moves-sorter] are the worst contenders here. They are both different flavours of selection sorts, and as a results are pretty similar. # Measures of presortedness @@ -180,6 +181,13 @@ It makes rather easy to see the different groups of complexities: * All of the other measures of presortedness run in O(n log n) time. + [fixed-size-sorters]: Fixed-size-sorters.md + [insertion-sorter]: Sorters.md#insertion_sorter + [low-comparisons-sorter]: Fixed-size-sorters.md#low_comparisons_sorter + [low-moves-sorter]: Fixed-size-sorters.md#low_moves_sorter [measures-of-presortedness]: Measures-of-presortedness.md + [merge-exchange-network-sorter]: Fixed-size-sorters.md#merge_exchange_network_sorter + [selection-sorter]: Sorters.md#selection_sorter + [sorting-network-sorter]: Fixed-size-sorters.md#sorting_network_sorter [std-forward-list-sort]: https://en.cppreference.com/w/cpp/container/list/sort [std-list-sort]: https://en.cppreference.com/w/cpp/container/list/sort From 27d29be37a8791e6d96c0165775c3c8e20f84075 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 15 Dec 2022 23:30:23 +0100 Subject: [PATCH 51/53] More tweaks to the small arrays benchmark The follow notable changes are included: * Plot the median of cycles instead of the mean * Generate results for merge_exchange_network_sorter * Don't generate results for size == 0 --- benchmarks/small-array/benchmark.cpp | 46 +++++++++++++++------------- benchmarks/small-array/plot.py | 15 ++++----- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/benchmarks/small-array/benchmark.cpp b/benchmarks/small-array/benchmark.cpp index 58e5bc0a..7101d22a 100644 --- a/benchmarks/small-array/benchmark.cpp +++ b/benchmarks/small-array/benchmark.cpp @@ -46,8 +46,10 @@ template< typename DistributionFunction > auto time_it(Sorter sorter, DistributionFunction distribution) - -> double + -> std::uint64_t { + static_assert(N > 0, "this benchmark does not support zero-sized arrays"); + // Seed the distribution manually to ensure that all algorithms // sort the same collections when there is randomness distributions_prng.seed(seed); @@ -65,45 +67,45 @@ auto time_it(Sorter sorter, DistributionFunction distribution) sorter(arr); std::uint64_t end = rdtsc(); assert(std::is_sorted(arr.begin(), arr.end())); - cycles.push_back(end - start); + cycles.push_back(double(end - start) / N); total_end = clock_type::now(); } - // Return the average number of cycles it took to sort an array - std::uint64_t total = 0; - for (auto value: cycles) { - total += value; - } - return total / double(cycles.size()); + // Return the median number of cycles per element + auto cycles_median = cycles.begin() + cycles.size() / 2; + std::nth_element(cycles.begin(), cycles_median, cycles.end()); + return *cycles_median; } template< typename T, - typename Distribution, + typename Dist, std::size_t... Ind > auto time_distribution(std::index_sequence) -> void { - using sorting_network_sorter = cppsort::small_array_adapter< - cppsort::sorting_network_sorter - >; - using low_comparisons_sorter = cppsort::small_array_adapter< cppsort::low_comparisons_sorter >; - using low_moves_sorter = cppsort::small_array_adapter< cppsort::low_moves_sorter >; + using merge_exchange_network_sorter = cppsort::small_array_adapter< + cppsort::merge_exchange_network_sorter + >; + using sorting_network_sorter = cppsort::small_array_adapter< + cppsort::sorting_network_sorter + >; // Compute results for the different sorting algorithms - std::pair> results[] = { - { "insertion_sorter", { time_it(cppsort::insertion_sort, Distribution{})... } }, - { "selection_sorter", { time_it(cppsort::selection_sort, Distribution{})... } }, - { "low_moves_sorter", { time_it(low_moves_sorter{}, Distribution{})... } }, - { "low_comparisons_sorter", { time_it(low_comparisons_sorter{}, Distribution{})... } }, - { "sorting_network_sorter", { time_it(sorting_network_sorter{}, Distribution{})... } }, + std::pair> results[] = { + { "insertion_sorter", { time_it(cppsort::insertion_sort, Dist{})... } }, + { "selection_sorter", { time_it(cppsort::selection_sort, Dist{})... } }, + { "low_comparisons_sorter", { time_it(low_comparisons_sorter{}, Dist{})... } }, + { "low_moves_sorter", { time_it(low_moves_sorter{}, Dist{})... } }, + { "merge_exchange_network_sorter", { time_it(merge_exchange_network_sorter{}, Dist{})... } }, + { "sorting_network_sorter", { time_it(sorting_network_sorter{}, Dist{})... } }, }; // Output the results to their respective files @@ -125,7 +127,7 @@ template< auto time_distributions() -> void { - using indices = std::make_index_sequence; + using indices = std::make_index_sequence; // Variadic dispatch only works with expressions int dummy[] = { @@ -138,7 +140,7 @@ int main() { std::cout << "SEED: " << seed << '\n'; - time_distributions') - pyplot.xlabel('Number of elements to sort') - pyplot.ylabel('Cycles per element (lower is better)') + pyplot.title("Sorting std::array") + pyplot.xlabel("Number of elements to sort") + pyplot.ylabel("Cycles per element (lower is better)") pyplot.show() From 47e810910c7ea25d6bf9ed3034ef710e0ec6c98f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 15 Dec 2022 23:48:50 +0100 Subject: [PATCH 52/53] Update the Benchmarks section of the README --- README.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0c47f08b..e8fc3a4f 100644 --- a/README.md +++ b/README.md @@ -98,16 +98,22 @@ and extending **cpp-sort** in [the wiki](https://github.com/Morwenn/cpp-sort/wik # Benchmarks The following graph has been generated with a script found in the benchmarks -directory. It shows the time needed for a sorting algorithm to sort one million -shuffled `std::array` of sizes 0 to 32. It compares the sorters generally -used to sort small arrays: +directory. It shows the time needed for [`heap_sort`][heap-sorter] to sort one +million elements without being adapted, then when it is adapted with either +[`drop_merge_adapter`][drop-merge-adapter] or [`split_adapter`][split-adapter]. -![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/dOa3vyl.png) +![Graph showing the speed difference between heap_sort raw, then adapted with +split_adapter and drop_merge_adapter, when the number of inversions in the +std::vector to sort increases](https://i.imgur.com/IcjUkYF.png) -These results were generated with MinGW-w64 g++ 10.1 with the compiler options -`-std=c++2a -O3 -march=native`. That benchmark is merely an example to make this -introduction look good. You can find more commented benchmarks in the [dedicated -wiki page](https://github.com/Morwenn/cpp-sort/wiki/Benchmarks). +As can be seen above, wrapping `heap_sort` with either of the adapters makes it +[*adaptive*][adaptive-sort] to the number of inversions in a non-intrusive +manner. The algorithms used to adapt it have different pros and cons, it is up +to you to use either. + +This benchmark is mostly there to show the possibilities offered by the +library. You can find more such commented benchmarks in the [dedicated wiki +page][benchmarks]. # Compiler support & tooling @@ -234,3 +240,10 @@ and [Crascit/DownloadProject](https://github.com/Crascit/DownloadProject). * Some of the benchmarks use a [colorblind-friendly palette](https://gist.github.com/thriveth/8560036) developed by Thøger Rivera-Thorsen. + + + [adaptive-sort]: https://en.wikipedia.org/wiki/Adaptive_sort + [benchmarks]: https://github.com/Morwenn/cpp-sort/wiki/Benchmarks + [drop-merge-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#drop_merge_adapter + [heap-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#heap_sorter + [split-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#split_adapter From cbad910f8fe7199eb8dcbda70f691db2464fb06d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 16 Dec 2022 14:03:50 +0100 Subject: [PATCH 53/53] Preparing release 1.14.0 --- CMakeLists.txt | 2 +- README.md | 4 ++-- conanfile.py | 2 +- docs/Home.md | 2 +- docs/Tooling.md | 4 ++-- include/cpp-sort/version.h | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5b6fb8d..a061b9be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 1.13.2 LANGUAGES CXX) +project(cpp-sort VERSION 1.14.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index e8fc3a4f..5dc54996 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![cpp-sort logo](docs/images/cpp-sort-logo.svg) -[![Latest Release](https://img.shields.io/badge/release-1.13.2-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.13.2) -[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.13.2-blue.svg)](https://conan.io/center/cpp-sort?version=1.13.2) +[![Latest Release](https://img.shields.io/badge/release-1.14.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.14.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.14.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.14.0) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/develop/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) [![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork) diff --git a/conanfile.py b/conanfile.py index 0654f0e0..677e397a 100644 --- a/conanfile.py +++ b/conanfile.py @@ -15,7 +15,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.13.2" + version = "1.14.0" description = "Additional sorting algorithms & related tools" topics = "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" diff --git a/docs/Home.md b/docs/Home.md index 97830e85..d394530f 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,6 +1,6 @@ ![cpp-sort logo](images/cpp-sort-logo.svg) -Welcome to the **cpp-sort 1.13.2** documentation! +Welcome to the **cpp-sort 1.14.0** documentation! This wiki contains documentation about the library: basic documentation about the many sorting tools and how to use them, documentation about the additional utilities provided by the library, as well as a few tutorials about writing your own sorters or sorter adapters. This main page explains a few general things that didn't quite fit in other parts of the documentation. diff --git a/docs/Tooling.md b/docs/Tooling.md index c53c52cf..83b329bc 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -56,10 +56,10 @@ Some of those options also exist without the `CPPSORT_` prefix, but they are dep conan search cpp-sort --remote=conan-center ``` -And then install any version to your local cache as follows (here with version 1.13.2): +And then install any version to your local cache as follows (here with version 1.14.0): ```sh -conan install cpp-sort/1.13.2 +conan install cpp-sort/1.14.0 ``` The packages downloaded from conan-center are minimal and only contain the files required to use **cpp-sort** as a library: the headers, CMake files and licensing information. If you need anything else you have to build your own package with the `conanfile.py` available in this repository. diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index 5689d63a..582f0176 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 1 -#define CPPSORT_VERSION_MINOR 13 -#define CPPSORT_VERSION_PATCH 2 +#define CPPSORT_VERSION_MINOR 14 +#define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_