From 6bb1b5a5012a2c96d788d03bdc9c6fb7fad83ff5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 21 Sep 2025 22:30:09 +0200 Subject: [PATCH 01/40] Allow to run the action to deploy the wiki manually --- .github/workflows/deploy-to-wiki.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-to-wiki.yml b/.github/workflows/deploy-to-wiki.yml index b37eaba7..f2b1b964 100644 --- a/.github/workflows/deploy-to-wiki.yml +++ b/.github/workflows/deploy-to-wiki.yml @@ -9,6 +9,7 @@ on: - 2.x.y-stable paths: - 'docs/**' + workflow_dispatch: jobs: sync-wiki-files: From 6cf368a37db138f41e94f02236ba88bb5db0e395 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 30 Sep 2025 00:21:05 +0200 Subject: [PATCH 02/40] Fix overflow issue with signed subtraction in float_sort Cherry-pick commit 82ef138385399bb3de323885e71f3c7a23d13313 from upstream repository boostorg/sort. --- include/cpp-sort/detail/spreadsort/detail/float_sort.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/cpp-sort/detail/spreadsort/detail/float_sort.h b/include/cpp-sort/detail/spreadsort/detail/float_sort.h index aa6c4a48..2dd613c9 100644 --- a/include/cpp-sort/detail/spreadsort/detail/float_sort.h +++ b/include/cpp-sort/detail/spreadsort/detail/float_sort.h @@ -137,7 +137,7 @@ namespace cppsort::detail::spreadsort::detail auto&& proj = utility::as_function(projection); unsigned log_divisor = get_log_divisor( - last - first, rough_log_2_size(Size_type(max - min))); + last - first, rough_log_2_size(Size_type(max/2 - min/2)) + 1); Div_type div_min = min >> log_divisor; Div_type div_max = max >> log_divisor; unsigned bin_count = unsigned(div_max - div_min) + 1; @@ -203,7 +203,7 @@ namespace cppsort::detail::spreadsort::detail auto&& proj = utility::as_function(projection); unsigned log_divisor = get_log_divisor( - last - first, rough_log_2_size(Size_type(max - min))); + last - first, rough_log_2_size(Size_type(max/2 - min/2)) + 1); Div_type div_min = min >> log_divisor; Div_type div_max = max >> log_divisor; unsigned bin_count = unsigned(div_max - div_min) + 1; @@ -268,7 +268,7 @@ namespace cppsort::detail::spreadsort::detail auto&& proj = utility::as_function(projection); unsigned log_divisor = get_log_divisor( - last - first, rough_log_2_size(Size_type(max - min))); + last - first, rough_log_2_size(Size_type(max/2 - min/2)) + 1); Div_type div_min = min >> log_divisor; Div_type div_max = max >> log_divisor; unsigned bin_count = unsigned(div_max - div_min) + 1; From 25e3bdc49c2fa48c83d7fbf4bb975658eda4b8f3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 30 Sep 2025 00:23:31 +0200 Subject: [PATCH 03/40] Wrap std::min/std::max in parentheses --- include/cpp-sort/detail/grail_sort.h | 2 +- include/cpp-sort/probes/dis.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index 0cf517c7..bf1e0639 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -467,7 +467,7 @@ namespace cppsort::detail::grail auto&& proj = utility::as_function(projection); auto size = last - first; - auto kbuf = std::min(K, LExtBuf); + auto kbuf = (std::min)(K, LExtBuf); while (kbuf & (kbuf - 1)) { kbuf &= kbuf - 1; // max power or 2 - just in case } diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index d5ca5528..a9d7a819 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -92,7 +92,7 @@ namespace cppsort::probe 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 - res = std::max(res, i - j); + res = (std::max)(res, i - j); // Compute the next value of RL if (--i <= res) { return res; From ebd2b07dfc6009e2177878f998809a79bc7f197a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 30 Sep 2025 23:29:41 +0200 Subject: [PATCH 04/40] Upgrade downloaded Catch2 version to v3.11.0 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e03906c8..db02ab33 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,7 +25,7 @@ else() FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG 25319fd3047c6bdcf3c0170e76fa526c77f99ca9 # v3.10.0 + GIT_TAG b3fb4b9feafcd8d91c5cb510a4775143fdbef02f # v3.11.0 ) FetchContent_GetProperties(Catch2) if (NOT Catch2_POPULATED) From 701a916d5589b369f97c032f7274b745616f56dd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 2 Oct 2025 23:45:06 +0200 Subject: [PATCH 05/40] Mark internal container functions with lifetimebound attribute --- include/cpp-sort/detail/config.h | 15 +++++++++++++++ include/cpp-sort/detail/fixed_size_list.h | 10 ++++++++++ include/cpp-sort/detail/immovable_vector.h | 6 ++++++ 3 files changed, 31 insertions(+) diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 219f3d58..40675a2c 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -142,4 +142,19 @@ # endif #endif +//////////////////////////////////////////////////////////// +// CPPSORT_LIFETIME_BOUND + +#ifdef __has_cpp_attribute +# if __has_cpp_attribute(clang::lifetimebound) +# define CPPSORT_LIFETIME_BOUND [[clang::lifetimebound]] +# elif __has_cpp_attribute(msvc::lifetimebound) +# define CPPSORT_LIFETIME_BOUND [[msvc::lifetimebound]] +# else +# define CPPSORT_LIFETIME_BOUND +# endif +#else +# define CPPSORT_LIFETIME_BOUND +#endif + #endif // CPPSORT_DETAIL_CONFIG_H_ diff --git a/include/cpp-sort/detail/fixed_size_list.h b/include/cpp-sort/detail/fixed_size_list.h index e6a95087..79da97cf 100644 --- a/include/cpp-sort/detail/fixed_size_list.h +++ b/include/cpp-sort/detail/fixed_size_list.h @@ -175,6 +175,7 @@ namespace cppsort::detail [[nodiscard]] auto next_free_node() noexcept + CPPSORT_LIFETIME_BOUND -> node_type* { // Retrieve next free node @@ -487,6 +488,7 @@ namespace cppsort::detail [[nodiscard]] auto front() noexcept + CPPSORT_LIFETIME_BOUND -> reference { return static_cast(sentinel_node_.next)->value; @@ -494,6 +496,7 @@ namespace cppsort::detail [[nodiscard]] auto back() noexcept + CPPSORT_LIFETIME_BOUND -> reference { return static_cast(sentinel_node_.prev)->value; @@ -511,6 +514,7 @@ namespace cppsort::detail [[nodiscard]] auto begin() noexcept + CPPSORT_LIFETIME_BOUND -> iterator { return iterator(sentinel_node_.next); @@ -518,6 +522,7 @@ namespace cppsort::detail [[nodiscard]] auto end() noexcept + CPPSORT_LIFETIME_BOUND -> iterator { return iterator(&sentinel_node_); @@ -537,12 +542,14 @@ namespace cppsort::detail // Modifiers auto insert(iterator pos, const value_type& value) + CPPSORT_LIFETIME_BOUND -> iterator { return iterator(insert_node_(pos.base(), value)); } auto insert(iterator pos, value_type&& value) + CPPSORT_LIFETIME_BOUND -> iterator { return iterator(insert_node_(pos.base(), std::move(value))); @@ -781,6 +788,7 @@ namespace cppsort::detail // Helper functions auto insert_node_(list_node_base* pos, const value_type& value) + CPPSORT_LIFETIME_BOUND -> node_type* { node_type* new_node = node_pool_->next_free_node(); @@ -790,6 +798,7 @@ namespace cppsort::detail } auto insert_node_(list_node_base* pos, value_type&& value) + CPPSORT_LIFETIME_BOUND -> node_type* { node_type* new_node = node_pool_->next_free_node(); @@ -800,6 +809,7 @@ namespace cppsort::detail template auto insert_node_(list_node_base* pos, Callable setter) + CPPSORT_LIFETIME_BOUND -> node_type* { node_type* new_node = node_pool_->next_free_node(); diff --git a/include/cpp-sort/detail/immovable_vector.h b/include/cpp-sort/detail/immovable_vector.h index c3bebd2c..29614c3b 100644 --- a/include/cpp-sort/detail/immovable_vector.h +++ b/include/cpp-sort/detail/immovable_vector.h @@ -71,6 +71,7 @@ namespace cppsort::detail // Element access auto operator[](std::ptrdiff_t pos) noexcept + CPPSORT_LIFETIME_BOUND -> T& { CPPSORT_ASSERT(pos <= end_ - memory_); @@ -78,6 +79,7 @@ namespace cppsort::detail } auto front() noexcept + CPPSORT_LIFETIME_BOUND -> T& { CPPSORT_ASSERT(memory_ != end_); @@ -85,6 +87,7 @@ namespace cppsort::detail } auto back() noexcept + CPPSORT_LIFETIME_BOUND -> T& { CPPSORT_ASSERT(end_ - memory_ > 0); @@ -95,12 +98,14 @@ namespace cppsort::detail // Iterators auto begin() noexcept + CPPSORT_LIFETIME_BOUND -> T* { return memory_; } auto end() noexcept + CPPSORT_LIFETIME_BOUND -> T* { return end_; @@ -121,6 +126,7 @@ namespace cppsort::detail template auto emplace_back(Args&&... args) + CPPSORT_LIFETIME_BOUND -> T* { CPPSORT_ASSERT(end_ - memory_ < capacity_); From 4f8c43c75460e587996c359401665885a339306c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 3 Oct 2025 22:22:38 +0200 Subject: [PATCH 06/40] Don't needlessly use decltype in buffers --- include/cpp-sort/utility/buffer.h | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/include/cpp-sort/utility/buffer.h b/include/cpp-sort/utility/buffer.h index 5260752d..c5ed74d5 100644 --- a/include/cpp-sort/utility/buffer.h +++ b/include/cpp-sort/utility/buffer.h @@ -39,49 +39,49 @@ namespace cppsort::utility } constexpr auto operator[](std::size_t pos) - -> decltype(_memory[pos]) + -> typename std::array::reference { return _memory[pos]; } constexpr auto operator[](std::size_t pos) const - -> decltype(_memory[pos]) + -> typename std::array::const_reference { return _memory[pos]; } constexpr auto begin() - -> decltype(_memory.data()) + -> T* { return _memory.data(); } constexpr auto begin() const - -> decltype(_memory.data()) + -> const T* { return _memory.data(); } constexpr auto cbegin() const - -> decltype(_memory.data()) + -> const T* { return _memory.data(); } constexpr auto end() - -> decltype(_memory.data() + _memory.size()) + -> T* { return _memory.data() + _memory.size(); } constexpr auto end() const - -> decltype(_memory.data() + _memory.size()) + -> const T* { return _memory.data() + _memory.size(); } constexpr auto cend() const - -> decltype(_memory.data() + _memory.size()) + -> const T* { return _memory.data() + _memory.size(); } @@ -193,49 +193,49 @@ namespace cppsort::utility } auto operator[](std::size_t pos) - -> decltype(_memory[pos]) + -> T& { return _memory[pos]; } auto operator[](std::size_t pos) const - -> decltype(_memory[pos]) + -> const T& { return _memory[pos]; } auto begin() - -> decltype(_memory.get()) + -> T* { return _memory.get(); } auto begin() const - -> decltype(_memory.get()) + -> const T* { return _memory.get(); } auto cbegin() const - -> decltype(_memory.get()) + -> const T* { return _memory.get(); } auto end() - -> decltype(_memory.get() + size()) + -> T* { return _memory.get() + size(); } auto end() const - -> decltype(_memory.get() + size()) + -> const T* { return _memory.get() + size(); } auto cend() const - -> decltype(_memory.get() + size()) + -> const T* { return _memory.get() + size(); } From 23a625adf9141a7f1748d2bf93ff1a396afeacd6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 3 Oct 2025 22:23:31 +0200 Subject: [PATCH 07/40] Add lifetime annotations to cppsort::utility::metrics --- include/cpp-sort/utility/metrics_tools.h | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/include/cpp-sort/utility/metrics_tools.h b/include/cpp-sort/utility/metrics_tools.h index 0cc7bbc5..7086b177 100644 --- a/include/cpp-sort/utility/metrics_tools.h +++ b/include/cpp-sort/utility/metrics_tools.h @@ -12,6 +12,7 @@ #include #include #include +#include "../detail/config.h" #include "../detail/type_traits.h" namespace cppsort::utility @@ -70,6 +71,7 @@ namespace cppsort::utility constexpr auto operator=(const T& other) noexcept(std::is_nothrow_copy_assignable_v) + CPPSORT_LIFETIME_BOUND -> metric& { _value = other; @@ -78,6 +80,7 @@ namespace cppsort::utility constexpr auto operator=(T&& other) noexcept(std::is_nothrow_move_assignable_v) + CPPSORT_LIFETIME_BOUND -> metric& { _value = std::move(other); @@ -87,6 +90,7 @@ namespace cppsort::utility template constexpr auto operator=(const metric& other) noexcept(std::is_nothrow_assignable_v) + CPPSORT_LIFETIME_BOUND -> metric& { _value = other._value; @@ -96,6 +100,7 @@ namespace cppsort::utility template constexpr auto operator=(metric&& other) noexcept(std::is_nothrow_assignable_v) + CPPSORT_LIFETIME_BOUND -> metric& { _value = std::move(other._value); @@ -271,28 +276,28 @@ namespace cppsort::utility // Index-based get() template - friend constexpr auto get(metrics& mm) + friend constexpr auto get(metrics& mm CPPSORT_LIFETIME_BOUND) -> std::tuple_element_t...>>& { return std::get(mm.metrics_); } template - friend constexpr auto get(const metrics& mm) + friend constexpr auto get(const metrics& mm CPPSORT_LIFETIME_BOUND) -> const std::tuple_element_t...>>& { return std::get(mm.metrics_); } template - friend constexpr auto get(metrics&& mm) + friend constexpr auto get(metrics&& mm CPPSORT_LIFETIME_BOUND) -> std::tuple_element_t...>>&& { return std::get(std::move(mm).metrics_); } template - friend constexpr auto get(const metrics&& mm) + friend constexpr auto get(const metrics&& mm CPPSORT_LIFETIME_BOUND) -> const std::tuple_element_t...>>&& { return std::get(std::move(mm).metrics_); @@ -302,28 +307,28 @@ namespace cppsort::utility // Tag-based get() template> - friend constexpr auto get(metrics& mm) + friend constexpr auto get(metrics& mm CPPSORT_LIFETIME_BOUND) -> std::tuple_element_t...>>& { return std::get(mm.metrics_); } template> - friend constexpr auto get(const metrics& mm) + friend constexpr auto get(const metrics& mm CPPSORT_LIFETIME_BOUND) -> const std::tuple_element_t...>>& { return std::get(mm.metrics_); } template> - friend constexpr auto get(metrics&& mm) + friend constexpr auto get(metrics&& mm CPPSORT_LIFETIME_BOUND) -> std::tuple_element_t...>>&& { return std::get(std::move(mm).metrics_); } template> - friend constexpr auto get(const metrics&& mm) + friend constexpr auto get(const metrics&& mm CPPSORT_LIFETIME_BOUND) -> const std::tuple_element_t...>>&& { return std::get(std::move(mm).metrics_); From 7e6b9c202505f4134f5bfef460b34db086df72df Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 3 Oct 2025 22:44:13 +0200 Subject: [PATCH 08/40] Add lifetime annotations to adapter_storage --- include/cpp-sort/utility/adapter_storage.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/cpp-sort/utility/adapter_storage.h b/include/cpp-sort/utility/adapter_storage.h index 3aa2f5ea..c65615da 100644 --- a/include/cpp-sort/utility/adapter_storage.h +++ b/include/cpp-sort/utility/adapter_storage.h @@ -10,6 +10,7 @@ //////////////////////////////////////////////////////////// #include #include +#include "../detail/config.h" namespace cppsort::utility { @@ -87,24 +88,28 @@ namespace cppsort::utility } constexpr auto get() & noexcept + CPPSORT_LIFETIME_BOUND -> Sorter& { return static_cast(sorter); } constexpr auto get() const& noexcept + CPPSORT_LIFETIME_BOUND -> const Sorter& { return static_cast(sorter); } constexpr auto get() && noexcept + CPPSORT_LIFETIME_BOUND -> Sorter&& { return static_cast(sorter); } constexpr auto get() const&& noexcept + CPPSORT_LIFETIME_BOUND -> const Sorter&& { return static_cast(sorter); From c64a07036e98bed17ddc7702a5772c57b04d71fc Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 3 Oct 2025 23:17:55 +0200 Subject: [PATCH 09/40] Add lifetime annotations to buffer types --- include/cpp-sort/utility/buffer.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/cpp-sort/utility/buffer.h b/include/cpp-sort/utility/buffer.h index c5ed74d5..8cb811cf 100644 --- a/include/cpp-sort/utility/buffer.h +++ b/include/cpp-sort/utility/buffer.h @@ -11,6 +11,7 @@ #include #include #include +#include "../detail/config.h" namespace cppsort::utility { @@ -39,48 +40,56 @@ namespace cppsort::utility } constexpr auto operator[](std::size_t pos) + CPPSORT_LIFETIME_BOUND -> typename std::array::reference { return _memory[pos]; } constexpr auto operator[](std::size_t pos) const + CPPSORT_LIFETIME_BOUND -> typename std::array::const_reference { return _memory[pos]; } constexpr auto begin() + CPPSORT_LIFETIME_BOUND -> T* { return _memory.data(); } constexpr auto begin() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.data(); } constexpr auto cbegin() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.data(); } constexpr auto end() + CPPSORT_LIFETIME_BOUND -> T* { return _memory.data() + _memory.size(); } constexpr auto end() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.data() + _memory.size(); } constexpr auto cend() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.data() + _memory.size(); @@ -193,48 +202,56 @@ namespace cppsort::utility } auto operator[](std::size_t pos) + CPPSORT_LIFETIME_BOUND -> T& { return _memory[pos]; } auto operator[](std::size_t pos) const + CPPSORT_LIFETIME_BOUND -> const T& { return _memory[pos]; } auto begin() + CPPSORT_LIFETIME_BOUND -> T* { return _memory.get(); } auto begin() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.get(); } auto cbegin() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.get(); } auto end() + CPPSORT_LIFETIME_BOUND -> T* { return _memory.get() + size(); } auto end() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.get() + size(); } auto cend() const + CPPSORT_LIFETIME_BOUND -> const T* { return _memory.get() + size(); From eb88584a325053c2fc8620dd060dfce7d58b5c38 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 4 Oct 2025 11:16:50 +0200 Subject: [PATCH 10/40] Add bug fixes category to the release template --- tools/release_template.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/release_template.md b/tools/release_template.md index d4da7daa..570aa302 100644 --- a/tools/release_template.md +++ b/tools/release_template.md @@ -14,6 +14,11 @@ TODO 1 TODO 2 +### Bug fixes + +* TODO: bug fix 1 +* TODO: bug fix 2 + ### Improvements Algorithmic & speed improvements: From 393ac87c2c01f692ec4d2261c64bd0471d81478c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 4 Oct 2025 11:17:28 +0200 Subject: [PATCH 11/40] Add more lifetime annotations --- include/cpp-sort/detail/associate_iterator.h | 10 ++++++++++ include/cpp-sort/detail/fake_category_iterator.h | 5 +++++ include/cpp-sort/detail/fixed_size_list.h | 2 ++ include/cpp-sort/detail/memory.h | 2 ++ include/cpp-sort/detail/merge_insertion_sort.h | 5 +++++ include/cpp-sort/detail/ska_sort.h | 3 ++- include/cpp-sort/metrics/moves.h | 2 ++ include/cpp-sort/utility/functional.h | 2 +- include/cpp-sort/utility/metrics_tools.h | 2 +- 9 files changed, 30 insertions(+), 3 deletions(-) diff --git a/include/cpp-sort/detail/associate_iterator.h b/include/cpp-sort/detail/associate_iterator.h index 176bad78..d6629366 100644 --- a/include/cpp-sort/detail/associate_iterator.h +++ b/include/cpp-sort/detail/associate_iterator.h @@ -11,6 +11,7 @@ #include #include #include +#include "config.h" #include "iterator_traits.h" namespace cppsort::detail @@ -62,6 +63,7 @@ namespace cppsort::detail {} auto operator=(association&& other) noexcept + CPPSORT_LIFETIME_BOUND -> association& { *it = std::move(*other.it); @@ -70,6 +72,7 @@ namespace cppsort::detail } auto operator=(associated_value, Data>&& other) + CPPSORT_LIFETIME_BOUND -> association& { *it = std::move(other.value); @@ -124,6 +127,7 @@ namespace cppsort::detail {} auto operator=(associated_value&& other) + CPPSORT_LIFETIME_BOUND -> associated_value& { value = std::move(other.value); @@ -133,6 +137,7 @@ namespace cppsort::detail [[nodiscard]] auto get() + CPPSORT_LIFETIME_BOUND -> Value& { return value; @@ -140,6 +145,7 @@ namespace cppsort::detail [[nodiscard]] auto get() const + CPPSORT_LIFETIME_BOUND -> const Value& { return value; @@ -207,6 +213,7 @@ namespace cppsort::detail // Increment/decrement operators auto operator++() + CPPSORT_LIFETIME_BOUND -> associate_iterator& { ++_it; @@ -214,6 +221,7 @@ namespace cppsort::detail } auto operator--() + CPPSORT_LIFETIME_BOUND -> associate_iterator& { --_it; @@ -221,6 +229,7 @@ namespace cppsort::detail } auto operator+=(difference_type increment) + CPPSORT_LIFETIME_BOUND -> associate_iterator& { _it += increment; @@ -228,6 +237,7 @@ namespace cppsort::detail } auto operator-=(difference_type increment) + CPPSORT_LIFETIME_BOUND -> associate_iterator& { _it -= increment; diff --git a/include/cpp-sort/detail/fake_category_iterator.h b/include/cpp-sort/detail/fake_category_iterator.h index 462405a2..e22bef39 100644 --- a/include/cpp-sort/detail/fake_category_iterator.h +++ b/include/cpp-sort/detail/fake_category_iterator.h @@ -12,6 +12,7 @@ #include #include #include +#include "config.h" #include "iterator_traits.h" #include "type_traits.h" @@ -84,6 +85,7 @@ namespace cppsort::detail // Increment/decrement operators auto operator++() + CPPSORT_LIFETIME_BOUND -> fake_category_iterator& { ++_it; @@ -99,6 +101,7 @@ namespace cppsort::detail } auto operator--() + CPPSORT_LIFETIME_BOUND -> fake_category_iterator& { --_it; @@ -114,6 +117,7 @@ namespace cppsort::detail } auto operator+=(difference_type increment) + CPPSORT_LIFETIME_BOUND -> fake_category_iterator& { _it += increment; @@ -121,6 +125,7 @@ namespace cppsort::detail } auto operator-=(difference_type increment) + CPPSORT_LIFETIME_BOUND -> fake_category_iterator& { _it -= increment; diff --git a/include/cpp-sort/detail/fixed_size_list.h b/include/cpp-sort/detail/fixed_size_list.h index 79da97cf..a416fc41 100644 --- a/include/cpp-sort/detail/fixed_size_list.h +++ b/include/cpp-sort/detail/fixed_size_list.h @@ -308,6 +308,7 @@ namespace cppsort::detail // Increment/decrement operators auto operator++() noexcept + CPPSORT_LIFETIME_BOUND -> fixed_size_list_iterator& { ptr_ = ptr_->next; @@ -323,6 +324,7 @@ namespace cppsort::detail } auto operator--() noexcept + CPPSORT_LIFETIME_BOUND -> fixed_size_list_iterator& { ptr_ = ptr_->prev; diff --git a/include/cpp-sort/detail/memory.h b/include/cpp-sort/detail/memory.h index 3e056d4b..159e764f 100644 --- a/include/cpp-sort/detail/memory.h +++ b/include/cpp-sort/detail/memory.h @@ -23,6 +23,7 @@ #include #include #include +#include "config.h" #include "type_traits.h" namespace cppsort::detail @@ -201,6 +202,7 @@ namespace cppsort::detail temporary_buffer& operator=(const temporary_buffer&) = delete; auto operator=(temporary_buffer&& other) noexcept + CPPSORT_LIFETIME_BOUND -> temporary_buffer& { using std::swap; diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index c6d1904a..fceb6d18 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -13,6 +13,7 @@ #include #include #include +#include "config.h" #include "fixed_size_list.h" #include "immovable_vector.h" #include "iterator_traits.h" @@ -90,6 +91,7 @@ namespace cppsort::detail // Increment/decrement operators auto operator++() + CPPSORT_LIFETIME_BOUND -> group_iterator& { std::advance(_it, _size); @@ -97,6 +99,7 @@ namespace cppsort::detail } auto operator--() + CPPSORT_LIFETIME_BOUND -> group_iterator& { std::advance(_it, -_size); @@ -104,6 +107,7 @@ namespace cppsort::detail } auto operator+=(difference_type increment) + CPPSORT_LIFETIME_BOUND -> group_iterator& { _it += _size * increment; @@ -111,6 +115,7 @@ namespace cppsort::detail } auto operator-=(difference_type increment) + CPPSORT_LIFETIME_BOUND -> group_iterator& { _it -= _size * increment; diff --git a/include/cpp-sort/detail/ska_sort.h b/include/cpp-sort/detail/ska_sort.h index 6cc9fbe8..5b30bccd 100644 --- a/include/cpp-sort/detail/ska_sort.h +++ b/include/cpp-sort/detail/ska_sort.h @@ -23,6 +23,7 @@ #include #include #include +#include "config.h" #include "iterator_traits.h" // projected_t #include "memcpy_cast.h" #include "partition.h" @@ -460,7 +461,7 @@ namespace cppsort::detail using next = SubKey; using sub_key_type = T; - static auto sub_key(const T& value, void*) + static auto sub_key(const T& value CPPSORT_LIFETIME_BOUND, void*) -> const T& { return value; diff --git a/include/cpp-sort/metrics/moves.h b/include/cpp-sort/metrics/moves.h index eb179081..2d7e672e 100644 --- a/include/cpp-sort/metrics/moves.h +++ b/include/cpp-sort/metrics/moves.h @@ -18,6 +18,7 @@ #include #include #include "../detail/checkers.h" +#include "../detail/config.h" #include "../detail/fake_category_iterator.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" @@ -50,6 +51,7 @@ namespace cppsort::metrics } auto operator=(move_counting_wrapper&& other) + CPPSORT_LIFETIME_BOUND -> move_counting_wrapper& { value = std::move(other.value); diff --git a/include/cpp-sort/utility/functional.h b/include/cpp-sort/utility/functional.h index 9bd04b33..f39f153a 100644 --- a/include/cpp-sort/utility/functional.h +++ b/include/cpp-sort/utility/functional.h @@ -137,7 +137,7 @@ namespace cppsort::utility projection_base { template - constexpr auto operator()(T&& value) const noexcept + constexpr auto operator()(T&& value CPPSORT_LIFETIME_BOUND) const noexcept -> T&& { return std::forward(value); diff --git a/include/cpp-sort/utility/metrics_tools.h b/include/cpp-sort/utility/metrics_tools.h index 7086b177..d66b2148 100644 --- a/include/cpp-sort/utility/metrics_tools.h +++ b/include/cpp-sort/utility/metrics_tools.h @@ -231,7 +231,7 @@ namespace cppsort::utility // Stream operators template - friend auto operator<<(std::ostream& stream, const metric& met) + friend auto operator<<(std::ostream& stream CPPSORT_LIFETIME_BOUND, const metric& met) -> decltype(stream << std::declval()) { stream << met.value(); From 3883324e4b118eef2d42d185717e4be03ef447ac Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 9 Oct 2025 22:18:59 +0200 Subject: [PATCH 12/40] Add a quicksort_adversary utility (#178) --- README.md | 3 + docs/Miscellaneous-utilities.md | 27 ++++++++ .../cpp-sort/utility/quicksort_adversary.h | 50 ++++++++++++++ tests/CMakeLists.txt | 1 + tests/utility/quicksort_adversary.cpp | 69 +++++++++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 include/cpp-sort/utility/quicksort_adversary.h create mode 100644 tests/utility/quicksort_adversary.cpp diff --git a/README.md b/README.md index 59890131..d9552910 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,9 @@ discussion](https://stackoverflow.com/q/2786899/1364752) on StackOverflow and ar backed by the article [*Applying Sorting Networks to Synthesize Optimized Sorting Libraries*](https://arxiv.org/abs/1505.01962). +* The algorithm behind `utility::quicksort_adversary` is a fairly straightforward adaptation of the +one provided by M. D. McIlroy in [*A Killer Adversary for Quicksort*](https://www.cs.dartmouth.edu/~doug/mdmspe.pdf). + * The test suite reimplements random number algorithms originally found in the following places: - [xoshiro256\*\*](https://prng.di.unimi.it/) - [*Optimal Discrete Uniform Generation from Coin Flips, and Applications*](https://arxiv.org/abs/1304.1916) diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index c59ea876..543d47a8 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -367,6 +367,31 @@ auto m = get(mm); `utility::metrics` is still mostly experimental and unused in the rest of the library. As such this documentation is voluntarily thin. +### `quicksort_adversary` + +```cpp +#include +``` + +`utility::quicksort_adversary` is a function template that implements an algorithm described by M. D. McIlroy in [*A Killer Adversary for Quicksort*][quicksort-adversary], which attempts to trigger the quadratic case of many quicksort implementations by trying to guess the pivot and forcing the testing comparison to perform a certain set of comparisons. + +```cpp +template +auto quicksort_adversary(Sorter&& sorter, Integer size); +``` + +The function accepts a sorter to test, and a parameter corresponding to the size of the input for which we wish to test the sorter. It then instantiates a collection of `size` elements of `Integer` type that it passes to `sorter`, and returns the result of the operation. It additionally passes a custom comparison function to `sorter`, which means that it only works with *comparison sorters*. + +It can be used together with [`metrics::comparisons`][metrics-comparisons] or some other metrics to analyze the number of operations performed, and attempt to detect quadratic behavior in quicksort-like sorters: + +```cpp +auto sorter = cppsort::metrics::comparisons(cppsort::quick_sort); +auto comps = cppsort::utility::quicksort_adversary(sorter, 1000); +std::print("Comparisons: {}", comps.value()); +``` + +*New in version 2.1.0* + ### `size` ```cpp @@ -474,9 +499,11 @@ auto swap_index_pairs_force_unroll(RandomAccessIterator first, [fixed-size-sorters]: Fixed-size-sorters.md [is-stable]: Sorter-traits.md#is_stable [metrics]: Metrics.md + [metrics-comparisons]: Metrics.md#comparisons [numpy-argsort]: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html [p0022]: https://wg21.link/P0022 [pdq-sorter]: Sorters.md#pdq_sorter + [quicksort-adversary]: https://www.cs.dartmouth.edu/~doug/mdmspe.pdf [range-v3]: https://github.com/ericniebler/range-v3 [sorter-adapters]: Sorter-adapters.md [sorters]: Sorters.md diff --git a/include/cpp-sort/utility/quicksort_adversary.h b/include/cpp-sort/utility/quicksort_adversary.h new file mode 100644 index 00000000..b4978a8a --- /dev/null +++ b/include/cpp-sort/utility/quicksort_adversary.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_QUICKSORT_ADVERSARY_H_ +#define CPPSORT_QUICKSORT_ADVERSARY_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +namespace cppsort::utility +{ + // Implementation of a quicksort adversary as described by M. D. McIlroy + // in *A Killer Adversary for Quicksort* + + template + auto quicksort_adversary(Sorter&& sorter, Integer size) + { + Integer solid = 0; + auto gas = size - 1; + std::vector elements(size, gas); + + std::vector values(size, 0); + std::iota(values.begin(), values.end(), 0); + + int pivot_candidate = size; // Too big to match any + return sorter(values, [&, gas](Integer lhs_idx, Integer rhs_idx) { + int& lhs = elements[lhs_idx]; + int& rhs = elements[rhs_idx]; + if (lhs == gas && rhs == gas) { + if (lhs_idx == pivot_candidate) { + lhs = solid++; + } else { + rhs = solid++; + } + } + if (lhs == gas) { + pivot_candidate = lhs_idx; + } else if (rhs == gas) { + pivot_candidate = rhs_idx; + } + return lhs < rhs; + }); + } +} + +#endif // CPPSORT_QUICKSORT_ADVERSARY_H_ \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index db02ab33..f6302897 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -266,6 +266,7 @@ add_executable(main-tests utility/chainable_projections.cpp utility/iter_swap.cpp utility/metric_tools.cpp + utility/quicksort_adversary.cpp utility/sorted_indices.cpp utility/sorted_iterators.cpp utility/sorting_networks.cpp diff --git a/tests/utility/quicksort_adversary.cpp b/tests/utility/quicksort_adversary.cpp new file mode 100644 index 00000000..912075f4 --- /dev/null +++ b/tests/utility/quicksort_adversary.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "test quicksort-based sorters with quicksort_adversary", "[utility][quicksort_adversary]", + cppsort::pdq_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::std_sorter ) +{ + cppsort::metrics::comparisons sorter; + auto comps = cppsort::utility::quicksort_adversary(sorter, 1000); + CHECK( comps < 100'000 ); // Guesstimate between n and n² +} + +namespace +{ + // Median-of-three quicksort + template + auto median_of_3_quicksort(Iterator first, Iterator last, Compare comp) + -> void + { + auto size = last - first; + if (size < 2) return; + + auto middle = first + size / 2; + auto pivot_pos = cppsort::detail::iter_sort3( + first, middle, last - 1, + comp, cppsort::utility::identity{} + ); + + iter_swap(pivot_pos, last - 1); + auto middle1 = std::partition( + first, last - 1, + [&](int& value) { return comp(value, *(last - 1)); } + ); + + iter_swap(middle1, last - 1); + auto middle2 = std::partition( + std::next(middle1), last, + [&](int& value) { return not comp(*middle1, value); } + ); + + median_of_3_quicksort(first, middle1, comp); + median_of_3_quicksort(middle2, last, comp); + } +} + + +TEST_CASE( "quicksort adversary over a simple quicksort", + "[utility][quicksort_adversary]" ) +{ + auto do_sort = [](std::vector& vec, auto comp) { + return median_of_3_quicksort(vec.begin(), vec.end(), comp); + }; + auto sorter = cppsort::metrics::comparisons(do_sort); + auto comps = cppsort::utility::quicksort_adversary(sorter, 100); + CHECK( comps > 5000 ); // Guesstimate over n² +} From 363ba03a41d9686f19bf5a138f792d201ad270c4 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 15 Oct 2025 20:07:51 +0200 Subject: [PATCH 13/40] Base LDS and LNDS on longest increasing subsequence --- ...nce.h => longest_increasing_subsequence.h} | 30 +++++++++---------- include/cpp-sort/probes/rem.h | 13 ++++---- include/cpp-sort/probes/sus.h | 8 ++--- 3 files changed, 27 insertions(+), 24 deletions(-) rename include/cpp-sort/detail/{longest_non_descending_subsequence.h => longest_increasing_subsequence.h} (71%) diff --git a/include/cpp-sort/detail/longest_non_descending_subsequence.h b/include/cpp-sort/detail/longest_increasing_subsequence.h similarity index 71% rename from include/cpp-sort/detail/longest_non_descending_subsequence.h rename to include/cpp-sort/detail/longest_increasing_subsequence.h index 90155e52..dd51193b 100644 --- a/include/cpp-sort/detail/longest_non_descending_subsequence.h +++ b/include/cpp-sort/detail/longest_increasing_subsequence.h @@ -2,8 +2,8 @@ * Copyright (c) 2021-2025 Morwenn * SPDX-License-Identifier: MIT */ -#ifndef CPPSORT_DETAIL_LONGEST_NON_DESCENDING_SUBSEQUENCE_H_ -#define CPPSORT_DETAIL_LONGEST_NON_DESCENDING_SUBSEQUENCE_H_ +#ifndef CPPSORT_DETAIL_LONGEST_INCREASING_SUBSEQUENCE_H_ +#define CPPSORT_DETAIL_LONGEST_INCREASING_SUBSEQUENCE_H_ //////////////////////////////////////////////////////////// // Headers @@ -15,13 +15,13 @@ #include #include #include "iterator_traits.h" -#include "upper_bound.h" +#include "lower_bound.h" namespace cppsort::detail { - // Longest non-decreasing subsequence, computed with an altered + // Longest increasing subsequence, computed with an altered // patience sorting algorithm - returns a pair containing the - // size of the LNDS and the size of the collection + // size of the LIS and the size of the collection template< bool RecomputeSize, @@ -29,9 +29,9 @@ namespace cppsort::detail typename Compare, typename Projection > - auto longest_non_descending_subsequence(ForwardIterator first, ForwardIterator last, - difference_type_t size, - Compare compare, Projection projection) + auto longest_increasing_subsequence(ForwardIterator first, ForwardIterator last, + difference_type_t size, + Compare compare, Projection projection) -> std::pair, difference_type_t> { constexpr bool is_random_access = std::is_base_of_v< @@ -59,18 +59,18 @@ namespace cppsort::detail // Top (smaller) elements in patience sorting stacks std::vector stack_tops; - while (first != last) { - auto it = detail::upper_bound( + do { + auto it = detail::lower_bound( stack_tops.begin(), stack_tops.end(), proj(*first), compare, utility::indirect{} | projection); if (it == stack_tops.end()) { - // The element is bigger than everything else, + // The element is strictly bigger than everything else, // create a new "stack" to put it stack_tops.emplace_back(first); } else { - // The element is strictly smaller than the top - // of a given stack, replace the stack top + // The element is strictly smaller than or equal to + // the top of a given stack, replace the stack top *it = first; } ++first; @@ -79,10 +79,10 @@ namespace cppsort::detail // Compute the size as-we-go if iterators are not random-access ++size; } - } + } while (first != last); return { stack_tops.size(), size }; } } -#endif // CPPSORT_DETAIL_LONGEST_NON_DESCENDING_SUBSEQUENCE_H_ +#endif // CPPSORT_DETAIL_LONGEST_INCREASING_SUBSEQUENCE_H_ diff --git a/include/cpp-sort/probes/rem.h b/include/cpp-sort/probes/rem.h index dd2bc2aa..6763d42a 100644 --- a/include/cpp-sort/probes/rem.h +++ b/include/cpp-sort/probes/rem.h @@ -11,11 +11,13 @@ #include #include #include +#include +#include #include #include #include #include -#include "../detail/longest_non_descending_subsequence.h" +#include "../detail/longest_increasing_subsequence.h" #include "../detail/type_traits.h" namespace cppsort::probe @@ -49,10 +51,10 @@ namespace cppsort::probe // with the assumption that it's better than O(n) - which is at least // consistent as far as the standard library is concerned. We also // handle C arrays whose size is known and part of the type. - auto res = cppsort::detail::longest_non_descending_subsequence( + auto res = cppsort::detail::longest_increasing_subsequence( std::begin(range), std::end(range), utility::size(range), - std::move(compare), std::move(projection) + cppsort::not_fn(cppsort::flip(compare)), std::move(projection) ); auto lnds_size = res.second - res.first; return lnds_size >= 0 ? lnds_size : 0; @@ -73,8 +75,9 @@ namespace cppsort::probe // We give 0 as a "dummy" value since it will be recomputed, but it // is also used by the non-random-access iterators version as the // initial value used for the size count - auto res = cppsort::detail::longest_non_descending_subsequence( - first, last, 0, std::move(compare), std::move(projection) + auto res = cppsort::detail::longest_increasing_subsequence( + first, last, 0, + cppsort::not_fn(cppsort::flip(compare)), std::move(projection) ); auto lnds_size = res.second - res.first; return lnds_size >= 0 ? lnds_size : 0; diff --git a/include/cpp-sort/probes/sus.h b/include/cpp-sort/probes/sus.h index 633f0d96..2ce971c7 100644 --- a/include/cpp-sort/probes/sus.h +++ b/include/cpp-sort/probes/sus.h @@ -10,11 +10,11 @@ //////////////////////////////////////////////////////////// #include #include -#include +#include #include #include #include -#include "../detail/longest_non_descending_subsequence.h" +#include "../detail/longest_increasing_subsequence.h" #include "../detail/type_traits.h" namespace cppsort::probe @@ -37,10 +37,10 @@ namespace cppsort::probe { // We don't need the size information, so we can avoid // computing it altogether - auto res = cppsort::detail::longest_non_descending_subsequence( + auto res = cppsort::detail::longest_increasing_subsequence( first, last, 0, // Dummy value, not useful here - cppsort::not_fn(compare), std::move(projection) + cppsort::flip(compare), std::move(projection) ); return res.first > 0 ? res.first - 1 : 0; } From 856163e487b639107f790404457e32f7e95fb1d7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 15 Oct 2025 23:01:15 +0200 Subject: [PATCH 14/40] Improve rendering of documentation with Gollum --- docs/Benchmarks.md | 12 +++++++ docs/Comparator-adapters.md | 2 +- docs/Library-nomenclature.md | 32 +++++++++--------- docs/Measures-of-disorder.md | 60 ++++++++++++++++----------------- docs/Miscellaneous-utilities.md | 2 +- docs/Original-research.md | 10 +++--- docs/Tooling.md | 2 -- 7 files changed, 65 insertions(+), 55 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index e43ebd2c..194568d1 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -24,6 +24,7 @@ Most sorting algorithms are designed to work with random-access iterators, so th Sorting a random-access collection with an unstable sort is probably one of the most common things to want, and not only are those sorts among the fastest comparison sorts, but type-specific sorters can also be used to sort a variety of types. If you don't know what algorithm you want and don't have specific needs, then you probably want one of these. ![Benchmark speed of unstable sorts with increasing size for std::vector](https://i.imgur.com/Q3IEeci.png) + ![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/oRW5kFr.png) The plots above show a few general tendencies: @@ -34,6 +35,7 @@ The plots above show a few general tendencies: The quicksort derivatives and the hybrid radix sorts are generally the fastest of the lot, yet `drop_merge_sort` seems to offer interesting speedups for `std::deque` despite not being designed to be the fastest on truly shuffled data. Part of the explanation is that it uses `pdq_sort` in a contiguous memory buffer underneath, which might be faster for `std::deque` than sorting completely in-place. ![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/WZ4s6Xt.png) + ![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/UAaObUW.png) A few random takeways: @@ -50,11 +52,13 @@ A few random takeways: Pretty much all stable sorts in the library are different flavours of merge sort with sligthly different properties. Most of them allocate additional merge memory, and a good number of those also have a fallback algorithm that makes them run in O(n log²n) instead of O(n log n) when no extra heap memory is available. ![Benchmark speed of stable sorts with increasing size for std::vector](https://i.imgur.com/vRW1zcs.png) + ![Benchmark speed of stable sorts with increasing size for std::deque](https://i.imgur.com/CQePcBh.png) `insertion_sort` being O(n²) it's not surprising that it doesn't perform well in such a benchmark. All the other sorting algorithms display roughly equivalent and rather tight curves. ![Benchmark stable sorts over different patterns for std::vector](https://i.imgur.com/bRQ5cu5.png) + ![Benchmark stable sorts over different patterns for std::deque](https://i.imgur.com/fHIZB5L.png) These plots highlight a few important things: @@ -67,7 +71,9 @@ These plots highlight a few important things: I decided to include a dedicated category for slow O(n log n) sorts, because I find this class of algorithms interesting. This category contains experimental algorithms, often taken from rather old research papers. `heap_sort` is used as the "fast" algorithm in this category, despite it being consistently the slowest in the previous category. ![Benchmark speed of slow O(n log n) sorts with increasing size for std::vector](https://i.imgur.com/SUbyqKV.png) + ![Benchmark slow O(n log n) sorts over different patterns for std::vector](https://i.imgur.com/Dli1xrp.png) + ![Benchmark slow O(n log n) sorts over different patterns for std::deque](https://i.imgur.com/WxBmipj.png) The analysis is pretty simple here: @@ -97,6 +103,7 @@ For elements as small as `double`, there are two clear winners here: `drop_merge Even fewer sorters can handle forward iterators. `out_of_place_adapter(pdq_sort)` was not included in the patterns benchmark, because it adapts to patterns the same way `pdq_sort` does. ![Benchmark speed of sorts with increasing size for std::forward_list](https://i.imgur.com/if15kX1.png) + ![Benchmark sorts over different patterns for std::forward_list](https://i.imgur.com/uF0UzLm.png) The results are roughly the same than with bidirectional collections: @@ -114,6 +121,7 @@ This category will highlight the advantages of some sorters in sorting scenarios Integer sorting is a rather specific scenario for which many solutions exist: counting sorts, radix sorts, algorithms optimized to take advantage of branchless comparisons, etc. ![Benchmark speed of integer sorts with increasing size for std::vector](https://i.imgur.com/zuCAkIf.png) + ![Benchmark integer sorts over different patterns for std::vector](https://i.imgur.com/20uDwTM.png) `counting_sort` appears as a clear winner here but with a catch: its speed depends on the difference between the smaller and the greater integers in the collection to sort. In the benchmarks above the integer values scale with the size of the collection, but if a collection contains just a few elements with a big difference of the minimum and maximum values, `counting_sort` won't be a good solution. @@ -147,12 +155,15 @@ The improvements are not always as clear as in this benchmark, but it shows that Only a few algorithms allow to sort a collection stably without using extra heap memory: `grail_sort` and `wiki_sort` can accept a fixed-size buffer (possibly of size 0) while `merge_sort` has a fallback algorithm when no heap memory is available. ![Benchmark speed of stable sorts with no heap memory with increasing size for std::vector](https://i.imgur.com/1a64irX.png) + ![Benchmark speed of stable sorts with no heap memory with increasing size for std::deque](https://i.imgur.com/U5uD8Er.png) + ![Detail of the previous benchmark](https://i.imgur.com/owUictQ.png) `merge_sort` is definitely losing this benchmark. Interestingly enough `wiki_sort` is way better with a fixed buffer of 512 elements while it hardly affects `grail_sort` at all. For `std::deque`, `grail_sort` is almost always the fastest no matter what. ![Benchmark stable sorts with no heap memory over different patterns for std::vector](https://i.imgur.com/74YxCLI.png) + ![Benchmark stable sorts with no heap memory over different patterns for std::deque](https://i.imgur.com/jqek5Ii.png) Here `merge_sort` still loses the battle, but it also displays an impressive enough adaptiveness to presortedness and patterns. @@ -162,6 +173,7 @@ Here `merge_sort` still loses the battle, but it also displays an impressive eno 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/ABfEmJe.png) + ![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/wqz1q3R.png) We can see several trends in these benchmarks, rather consistant across `int` and `long double`: diff --git a/docs/Comparator-adapters.md b/docs/Comparator-adapters.md index 411ebc9d..b3faba1a 100644 --- a/docs/Comparator-adapters.md +++ b/docs/Comparator-adapters.md @@ -4,7 +4,7 @@ All adapters below are composed of two elements: * A class template that wraps a comparator and is itself a comparator (ex: `not_fn_t`, `flip_t`). * A function template that simplifies the construction and sometimes implements optimizations (ex: `not_fn`, `flip`). -The optimizations performed by the function templates are of the "unwrapping" kind, with a goal to reduce the nesting of templates in the library and to eventually reduce the overall number of instantiated templates. +The optimizations performed by the function templates are of the "unwrapping" kind, with a goal to reduce the nesting of templates in the library and to eventually reduce the overall number of template instantiations. ```cpp auto cmp = std::less{}; diff --git a/docs/Library-nomenclature.md b/docs/Library-nomenclature.md index 4d8af1c8..188c1203 100644 --- a/docs/Library-nomenclature.md +++ b/docs/Library-nomenclature.md @@ -1,12 +1,12 @@ **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: +* **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: using sorter = cppsort::grail_sorter< cppsort::utility::fixed_buffer<512> >; -* *Comparison function*: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`][std-invoke]. The default comparison function used by the sorting algorithms is [`std::less<>`][std-less-void]. Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order. +* **Comparison function**: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`][std-invoke]. The default comparison function used by the sorting algorithms is [`std::less<>`][std-less-void]. Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order. cppsort::heap_sort(collection, std::greater{}); @@ -14,25 +14,25 @@ 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. +* **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. +* **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. +* **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. using category = cppsort::iterator_category; Note that the *sorters* (and virtually bery algorithm) in **cpp-sort** accept iterators that do not implement post-increment and post-decrement operations. The iterator categories accepted by the library are thus less restrictive than the ones mandated for the standard library. -* *Measure of disorder*: a function used to estimate the amount of disorder in a sequence. There are many different to do that, such as counting the number of inversions in the sequence, or the number of elements to remove to get a sorted subsequence. **cpp-sort** provides a number of [measures of disorder][Measures-of-disorder] in the namespace `cppsort::probe`. +* **Measure of disorder**: a function used to estimate the amount of disorder in a sequence. There are many different to do that, such as counting the number of inversions in the sequence, or the number of elements to remove to get a sorted subsequence. **cpp-sort** provides a number of [measures of disorder][Measures-of-disorder] in the namespace `cppsort::probe`. auto max_inversions = cppsort::probe::dis(collection); -* *Measure of presortedness*: a special kind of *measure of disorder* that satisfies a specific set of additional properties (see the page on *measures of disorder*). The overarching goal of those measures is to be able to estimate and reason about the number of steps required to sort a sequence of elements. Most notably, they allow to formally reason about *adaptive sorting algorithms*: given a measure of presortedness $M$, an $M$-adaptive (or $M$-optimal) sorting algorithm is an algorithm that can sort a sequence with a number of steps that is without a constant bound of the estimated minimal number of steps for the estimated disorder. +* **Measure of presortedness**: a special kind of *measure of disorder* that satisfies a specific set of additional properties (see the page on *measures of disorder*). The overarching goal of those measures is to be able to estimate and reason about the number of steps required to sort a sequence of elements. Most notably, they allow to formally reason about *adaptive sorting algorithms*: given a measure of presortedness $M$, an $M$-adaptive (or $M$-optimal) sorting algorithm is an algorithm that can sort a sequence with a number of steps that is without a constant bound of the estimated minimal number of steps for the estimated disorder. -* *Metric*: as special kind of *sorter adapter* that returns information about sorted collections. See [the corresponding page][metrics] for additional information. +* **Metric**: as special kind of *sorter adapter* that returns information about sorted collections. See [the corresponding page][metrics] for additional information. -* *Projection*: some sorters accept a projection as an additional parameter. A projection is a unary function that allows to "view" the values of a collection differently. For example it may allow to sort a collection of values on a specific field. The available sorting algorithms transform projections on the fly so that pointers to member data can also be used as projections. Projections were pioneered by the [Adobe Source Libraries][stlab] and appear in the C++20 [constrained algorithms][std-ranges]. +* **Projection**: some sorters accept a projection as an additional parameter. A projection is a unary function that allows to "view" the values of a collection differently. For example it may allow to sort a collection of values on a specific field. The available sorting algorithms transform projections on the fly so that pointers to member data can also be used as projections. Projections were pioneered by the [Adobe Source Libraries][stlab] and appear in the C++20 [constrained algorithms][std-ranges]. struct wrapper { int value; }; std::vector collection = { /* ... */ }; @@ -40,16 +40,16 @@ Every *comparison sorter* is also a *projection sorter*, but there are also projection-only sorters, such as [`spread_sorter`][spread-sorter]. -* *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. +* **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 ranges or pairs of iterators, and conditionally overloaded so that it can handle user-provided comparison and/or projection functions (see *unified sorting interface*). +* **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 ranges 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 of 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. +* **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; @@ -61,13 +61,13 @@ The library also provides the adapter [`stable_adapter`][stable-adapter] to obtain a stable sorter corresponding to the passed sorter. When calling the adapted sorter yields an unstable sorting algorithm, the utility adapter `make_stable` is used to transform it into a stable sorting algorithm, providing the underlying algorithm handles proxy iterators. -* *Stateful/Stateless sorter*: a sorter either carries a state or not; when it does so it is called a *stateful* sorter, otherwise it is called a *stateless* sorter. Most of the *sorters* in the library are stateless sorters. +* **Stateful/Stateless sorter**: a sorter either carries a state or not; when it does so it is called a *stateful* sorter, otherwise it is called a *stateless* sorter. Most of the *sorters* in the library are stateless sorters. Stateless sorters are generally empty default-constructible types. If they indeed satisfy these guarantees, then several components of the library will provide space optimizations, and some wrapping components will also provide overloaded operators to be turned into several kinds of function pointers. Therefore, authors of *stateless sorters* are encouraged to also make them empty and default-constructible to benefit from the full powers of the library. -* *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). +* **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 disorder* and a few other components of the library accept a range 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. +* **Unified sorting interface**: *sorters*, *sorter adapters*, *measures of disorder* and a few other components of the library accept a range 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 diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index b9b9cbe5..09a3cca1 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -1,10 +1,10 @@ *Measures of disorder* are functions used to measure how much a sequence differs from its sorted permutation. Several loose definitions of measures of disorder exist in the literature; in this documentation, we use the formal definition provided by Vladimir Estivill-Castro in *Sorting and Measures of Disorder*. That is, a *measure of disorder* $M$ is a non-negative real function that accepts a sequence $X$ and satifies the following properties: -1. When $X$ is sorted, $M(X) = \min_{|Y|=|X|}\{M(Y)\}$. In other words, a measure of disorder *grows* with the amount of disorder in $X$, and reaches its minimum when $X$ is sorted. +1. When $X$ is sorted, $M(X) = \min_{\lvert Y \rvert=\lvert X \rvert}\{M(Y)\}$. In other words, a measure of disorder *grows* with the amount of disorder in $X$, and reaches its minimum when $X$ is sorted. 2. Order isomorphism: if the relative order of elements in two sequences $X$ and $Y$ is the same, then $M(X) = M(Y)$. In the rest of this document, we also use the following notation: * Sequences are ordered, and use angle brackets as delimiter, ex: $\langle 1, 3, 2, 4, 10 \rangle$. -* $|X|$ corresponds to the number of elements in the sequence $X$ (its size). +* $\lvert X \rvert$ corresponds to the number of elements in the sequence $X$ (its size). * Given two sequences $X$ and $Y$, $X \lt Y$ means that every of $X$ compares less than every element in $Y$ (assume similar meaning for other ordering operators). * Given two sequences $X$ and $Y$, $XY$ corresponds to their concatenation. Similarly $\langle e \rangle X$ is the concatenation of the sequence made of the single element $e$ and of the sequence $X$. * The expression "subsequence of $X$" refers to a sequence obtained by removing any number of possibly non-adjacent elements from $X$, unless specified otherwise. @@ -19,7 +19,7 @@ In the rest of this document, we also use the following notation: > 2. If $X$ and $Y$ are order isomorphic, then $M(X) = M(Y)$ > 3. If $X$ is a subsequence of $Y$, then $M(X) ≤ M(Y)$ > 4. If $X \le Y$, then $M(XY) ≤ M(X) + M(Y)$ -> 5. $M(⟨e⟩X) ≤ |X| + M(X)$ for every element $e$ of the domain +> 5. $M(⟨e⟩X) ≤ \lvert X \rvert + M(X)$ for every element $e$ of the domain Mannila's goal was to define strong properties allowing to reason about the minimum amount of work required for an adaptive sorting algorithm to sort a sequence with little disorder. Namely: * Criterion 1 above tries to formally represent the intuitive notion that no work is needed to sort a sequence that is already sorted. @@ -34,7 +34,7 @@ Some authors found that definition to be overly strict for their application, an > 2. If $X$ and $Y$ are order isomorphic, then $M(X) = M(Y)$ > 3. If $X$ is a subsequence of $Y$, then $M(X) ≤ M(Y)$ > 4. If $X \le Y$, then $M(XY) ≤ M(X) + M(Y) + b$ -> 5. $M(⟨e⟩X) ≤ |X| + M(X) + c$ for every element $e$ of the domain +> 5. $M(⟨e⟩X) ≤ \lvert X \rvert + M(X) + c$ for every element $e$ of the domain That loosened definition however is arguably less suited to estimate the amount of work need to order a sequence. We include it here for the sake of exposition and to highlight that vocabulary in the domain has historically been debated, but in the rest of this document the expression *measure of presortedness* refers to any measure of disorder that satisfies Mannila's five criteria. @@ -50,13 +50,13 @@ The *monotonicity* property implies the *prefix monotonicity* one. A measure of Let $X$ be a sequence of elements, and let $S_X$ be set of all permutations of that sequence: -$$below_M(X) = \{ \pi | \pi \in S_X \text{ and } M(\pi) \le M(X) \}$$ +$$below_M(X) = \{ \pi \vert \pi \in S_X \text{ and } M(\pi) \le M(X) \}$$ Let $T_S(X)$ be the number of steps needed for an algorithm $S$ to sort $X$. A sorting algorithm is said to be $M$-optimal if and only if, for some constant $c$, we have for all $X$: -$$T_S(X) \le c \cdot max\{|X|, \log{} |below_M(X)|\}$$ +$$T_S(X) \le c \cdot max\{\lvert X \rvert, \log{} |below_M(X)|\}$$ -In other words, a sorting algorithm is considered $M$-optimal if it takes a number of steps that is within a constant factor of the lower bound of $M$ to sort a sequence. For example a $Rem$-optimal algorithm should be able to sort any sequence in $O(|X| \log{} Rem(X))$ steps. +In other words, a sorting algorithm is considered $M$-optimal if it takes a number of steps that is within a constant factor of the lower bound of $M$ to sort a sequence. For example a $Rem$-optimal algorithm should be able to sort any sequence in $O(\lvert X \rvert \log{} Rem(X))$ steps. ### Partial ordering of measures of disorder @@ -71,7 +71,7 @@ While useful to understand what we want from a partial order on measures of diso > Let $M_1$ and $M_2$ be two measures of disorder: > -> * $M_1$ is superior to $M_2$ (denoted $M_1 \preceq M_2$) if and only if there exists a constant $c$ such as $|below_{M_1}(X)| \le c \cdot |below_{M_2}(X)|$ for any sequence $X$. +> * $M_1$ is superior to $M_2$ (denoted $M_1 \preceq M_2$) if and only if there exists a constant $c$ such as $\lvert below_{M_1}(X) \rvert \le c \cdot \lvert below_{M_2}(X) \rvert$ for any sequence $X$. > * $M_1$ and $M_2$ are equivalent (denoted $M_1 \equiv M_2$) if and only if $M_1 \preceq M_2$ and $M_2 \preceq M_1$. That definition seems to match the one proposed much earlier by Alistair Moffat and Ola Petersson in *A Framework for Adaptive Sorting*, though the authors use the symbol $\supseteq$ instead of $\preceq$. @@ -141,7 +141,7 @@ Measures of disorder are pretty formalized, so the names of the functions in the #include ``` -Computes the number of elements in a sequence that aren't followed by the same element in the sorted sequence. +Computes the number of elements in $X$ that aren't followed by the same element in the sorted permutation. 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 and respecting Mannila's first criterion for what makes a measure of presortedness (though this change might be responsible for the breakage of criterion 4). @@ -151,7 +151,7 @@ Our implementation is slightly different from the original description in *Subli | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | No | -`max_for_size`: $|X| - 1$ when $X$ is sorted in reverse order. +`max_for_size`: $\lvert X \rvert - 1$ when $X$ is sorted in reverse order. **Note:** *Block* does not seem to respect Mannila's criterion 3 in the presence of *equivalent elements*. @@ -172,7 +172,7 @@ Computes the maximum distance determined by an inversion. When enough memory is available `probe::dis` runs in O(n) using an algorithm described by T. Altman and Y. Igarashi in *Roughly Sorting: Sequential and Parallel Approach*, otherwise it falls back to an O(n log n) algorithm that does not require extra memory. If forward iterators are passed, the O(n log n) algorithm is always used. -`max_for_size`: $|X| - 1$ when the last element of $X$ is smaller than the first one. +`max_for_size`: $\lvert X \rvert - 1$ when the last element of $X$ is smaller than the first one. ### *Enc* @@ -203,7 +203,7 @@ $$ | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | No | -`max_for_size`: $\frac{|X|}{2}$ when all values extracted from $X$ are within the bounds of already extracted encroaching lists (for example the sequence $\langle 10, 0, 9, 1, 8, 2, 7, 3, 6, 4, 5 \rangle$ triggers the worst case). +`max_for_size`: $\frac{\lvert X \rvert}{2}$ when all values extracted from $X$ are within the bounds of already extracted encroaching lists (for example the sequence $\langle 10, 0, 9, 1, 8, 2, 7, 3, 6, 4, 5 \rangle$ triggers the worst case). ### *Exc* @@ -211,7 +211,7 @@ $$ #include ``` -Computes the minimum number of exchanges required to sort $X$, which corresponds to $|X|$ minus the number of cycles in the sequence. A cycle corresponds to a number of elements in a sequence that need to be rotated to be in their sorted position; for example, let $\langle 2, 4, 0, 6, 3, 1, 5 \rangle$ be a sequence, the cycles are $\langle 0, 2 \rangle$ and $\langle 1, 3, 4, 5, 6 \rangle$ so $Exc(X) = |X| - 2 = 5$. +Computes the minimum number of exchanges required to sort $X$, which corresponds to $\lvert X \rvert$ minus the number of cycles in the sequence. A cycle corresponds to a number of elements in a sequence that need to be rotated to be in their sorted position; for example, let $\langle 2, 4, 0, 6, 3, 1, 5 \rangle$ be a sequence, the cycles are $\langle 0, 2 \rangle$ and $\langle 1, 3, 4, 5, 6 \rangle$ so $Exc(X) = \lvert X \rvert - 2 = 5$. **Warning:** `probe::exc` generally returns a result higher than the minimum number of exchanges required to sort $X$ when it contains *equivalent elements*. This is because extending $Exc$ to *equivalent elements* is a NP-hard problem (see *On the Cost of Interchange Rearrangement in Strings* by Amir et al). The function does handle such elements in some simple cases, but not in the general case. @@ -219,7 +219,7 @@ Computes the minimum number of exchanges required to sort $X$, which corresponds | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $|X| - 1$ when every element in $X$ is one element away from its sorted position. +`max_for_size`: $\lvert X \rvert - 1$ when every element in $X$ is one element away from its sorted position. **Note:** *Exc* does not respect Mannila's criterion 3 (a subsequence contains no more disorder than the whole sequence): $Exc(\langle 3, 1, 2, 0 \rangle) = 1$, but $Exc(\langle 3, 1, 2 \rangle) = 2$. @@ -237,11 +237,11 @@ Computes the number of elements in $X$ that are not in their sorted position, wh | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $|X|$ when every element in $X$ is one element away from its sorted position. +`max_for_size`: $\lvert X \rvert$ when every element in $X$ is one element away from its sorted position. **Note:** *Ham* does not respect Mannila's criterion 3 (a subsequence contains no more disorder than the whole sequence): $Ham(\langle 3, 1, 2, 0 \rangle) = 2$, but $Ham(\langle 3, 1, 2 \rangle) = 3$. -**Note²:** *Ham* does not respect Mannila's criterion 5: $Ham(\langle 4, 1, 2, 3 \rangle) \not \le |\langle 1, 2, 3 \rangle| + Ham(\langle 1, 2, 3 \rangle)$. +**Note²:** *Ham* does not respect Mannila's criterion 5: $Ham(\langle 4, 1, 2, 3 \rangle) \not \le \lvert \langle 1, 2, 3 \rangle \rvert + Ham(\langle 1, 2, 3 \rangle)$. ### *Inv* @@ -255,7 +255,7 @@ Computes the number of inversions in $X$, where an inversion corresponds to a pa | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $\frac{|X|(|X| - 1)}{2}$ when $X$ is sorted in reverse order. +`max_for_size`: $\frac{\lvert X \rvert(\lvert X \rvert - 1)}{2}$ when $X$ is sorted in reverse order. ### *Max* @@ -269,7 +269,7 @@ Computes the maximum distance an element in $X$ must travel to find its sorted p | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $|X| - 1$ when $X$ is sorted in reverse order. +`max_for_size`: $\lvert X \rvert - 1$ when $X$ is sorted in reverse order. ### *Mono* @@ -277,7 +277,7 @@ Computes the maximum distance an element in $X$ must travel to find its sorted p #include ``` -Computes the number of non-increasing and non-decreasing consecutive runs of adjacent elements that need to be removed from $X$ to make it sorted +Computes the number of non-increasing and non-decreasing consecutive runs of adjacent elements that need to be removed from $X$ to make it sorted. The measure of disorder is slightly different from its original description in [*Sort Race*][sort-race] by H. Zhang, B. Meng and Y. Liang: * It subtracts 1 from the number of runs, thus returning 0 when $X$ is sorted. @@ -287,7 +287,7 @@ The measure of disorder is slightly different from its original description in [ | ----------- | ----------- | ------------- | --------- | | n | 1 | Forward | No | -`max_for_size`: $\frac{|X| + 1}{2} - 1$ when $X$ is a sequence of elements that are alternatively greater then lesser than their previous neighbour. +`max_for_size`: $\frac{\lvert X \rvert + 1}{2} - 1$ when $X$ is a sequence of elements that are alternatively greater then lesser than their previous neighbour. **Note:** `probe::mono` does not respect Mannila's criterion 4: $Mono(\langle 1, 2, 3, 4, 5 \rangle) = 0$ and $Mono(\langle 10, 9, 8, 7, 6 \rangle) = 0$, but $Mono(\langle 1, 2, 3, 4, 5, 10, 9, 8, 7, 6 \rangle) = 1$. @@ -303,13 +303,13 @@ Computes the *Oscillation* measure described by C. Levcopoulos and O. Petersson | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | No | -`max_for_size`: it is reached when the values in $X$ are strongly oscillating, and equals $\frac{|X|(|X| - 2)}{2}$ when $|X|$ is even, and $\frac{|X|(|X| - 2) - 1}{2}$ when $|X|$ is odd. +`max_for_size`: it is reached when the values in $X$ are strongly oscillating, and equals $\frac{\lvert X \rvert(\lvert X \rvert - 2)}{2}$ when $\lvert X \rvert$ is even, and $\frac{\lvert X \rvert(\lvert X \rvert - 2) - 1}{2}$ when $\lvert X \rvert$ is odd. **Note:** *Osc* does not seem to respect Mannila's criterion 3 in the presence of *equivalent elements*. **Note²:** *Osc* does not respect Mannila's criterion 4: $Osc(\langle 0 \rangle) = 0$ and $Osc(\langle 3, 2, 1 \rangle) = 0$, but $Osc(\langle 0, 3, 2, 1 \rangle) = 2$. -**Note³:** *Osc* does not respect Mannila's criterion 5: $Osc(\langle 3, 0, 4, 2, 5, 1 \rangle) \not \le |\langle 0, 4, 2, 5, 1 \rangle| + Osc(\langle 0, 4, 2, 5, 1 \rangle)$, simplified: $11 \not \le 5 + 5$. +**Note³:** *Osc* does not respect Mannila's criterion 5: $Osc(\langle 3, 0, 4, 2, 5, 1 \rangle) \not \le \lvert \langle 0, 4, 2, 5, 1 \rangle \rvert + Osc(\langle 0, 4, 2, 5, 1 \rangle)$, simplified: $11 \not \le 5 + 5$. ### *Rem* @@ -317,13 +317,13 @@ Computes the *Oscillation* measure described by C. Levcopoulos and O. Petersson #include ``` -Computes the minimum number of elements that must be removed from $X$ to obtain a sorted subsequence, which corresponds to $|X|$ minus the size of the [longest non-decreasing subsequence][longest-increasing-subsequence] of $X$. +Computes the minimum number of elements that must be removed from $X$ to obtain a sorted subsequence, which corresponds to $\lvert X \rvert$ minus the size of the [longest non-decreasing subsequence][longest-increasing-subsequence] of $X$. | Complexity | Memory | Iterators | Monotonic | | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $|X| - 1$ when $X$ is sorted in reverse order. +`max_for_size`: $\lvert X \rvert - 1$ when $X$ is sorted in reverse order. ### *Runs* @@ -337,7 +337,7 @@ Computes the number of non-decreasing runs in $X$ minus one. | ----------- | ----------- | ------------- | --------- | | n | 1 | Forward | Yes | -`max_for_size`: $|X| - 1$ when $X$ is sorted in reverse order. +`max_for_size`: $\lvert X \rvert - 1$ when $X$ is sorted in reverse order. ### *Spear* @@ -351,9 +351,9 @@ Spearman's footrule distance: sum of distances between the position of individua | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $\frac{|X|²}{2}$ when $X$ is sorted in reverse order. +`max_for_size`: $\frac{\lvert X \rvert²}{2}$ when $X$ is sorted in reverse order. -**Note:** *Spear* does not respect Mannila's criterion 5: $Spear(\langle 4, 1, 2, 3 \rangle) \not \le |\langle 1, 2, 3 \rangle| + Spear(\langle 1, 2, 3 \rangle)$. +**Note:** *Spear* does not respect Mannila's criterion 5: $Spear(\langle 4, 1, 2, 3 \rangle) \not \le \lvert \langle 1, 2, 3 \rangle \rvert + Spear(\langle 1, 2, 3 \rangle)$. ### *SUS* @@ -369,7 +369,7 @@ Computes the minimum number of non-decreasing subsequences (of possibly not adja | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $|X| - 1$ when $X$ is sorted in reverse order. +`max_for_size`: $\lvert X \rvert - 1$ when $X$ is sorted in reverse order. ## Other measures of disorder @@ -387,11 +387,11 @@ In other domains, that value is called *F* (for *Footrule*). It is no more helpf *Par* is described by V. Estivill-Castro and D. Wood in *A New Measure of Presortedness* as follows: -> *Par(X)* = min { *p* | $X$ is *p*-sorted } +> *Par(X)* = min { *p* \vert $X$ is *p*-sorted } 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, ..., $\lvert X \rvert$}, *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/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 543d47a8..c6fe88ae 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -489,7 +489,7 @@ auto swap_index_pairs_force_unroll(RandomAccessIterator first, -> void; ``` -`swap_index_pairs` loops over the index pairs in the simplest fashion and calls the compare-exchange operations in the simplest possible way. `swap_index_pairs_force_unroll` is a best effort function trying to achieve the same job by unrolling the loop over indices the best it can - a perfect unrolling is thus attempted, but never guaranteed, which might or might result in faster runtime and/or increased binary size. +`swap_index_pairs` loops over the index pairs in the simplest fashion and calls the compare-exchange operations in the simplest possible way. `swap_index_pairs_force_unroll` is a best effort function trying to achieve the same job by unrolling the loop over indices the best it can - a perfect unrolling is thus attempted, but never guaranteed, which might or might not result in faster runtime and/or increased binary size. [apply-permutation]: Miscellaneous-utilities.md#apply_permutation diff --git a/docs/Original-research.md b/docs/Original-research.md index bc1d1173..3a8f0340 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -109,7 +109,7 @@ I tried to apply the same technique to create a 40-sorter, but the resulting 20- ### Sorting network for 29 inputs -_Note: the following has since been improved upon: [SorterHunter][sorter-hunter] found a network that sorts 29 inputs with 164 *compare-exchange* operations._ +_Note: the following has since been improved upon: [SorterHunter][sorter-hunter] found a network that sorts 29 inputs with 164 compare-exchange operations._ The following sorting network for 29 inputs has 165 *compare-exchange* operations (CEs), which is one less that the most size-optimal 29-input sorting networks that I could find in the literature. Here is how I generated it: first it sorts the first 16 inputs and the last 13 inputs independently. Then it merges the two sorted subarrays using a size 32 Batcher odd-even merge network (the version that does not need the inputs to be interleaved), where all compare-exchange operations working on indexes greater than 28 have been dropped. Dropping comparators in such a way is ok: consider that the values at the indexes [29, 32) are greater than every other value in the array to sort, and it will become intuitive that dropping them generates a correct merging network of a smaller size. @@ -221,11 +221,11 @@ Regardless, it is interesting to find how it fits in the existing partial orderi - $SMS \preceq Mono$: this one seems intuitive too: $SMS$ which detects the minimum number of subsequences of non-adjacent elements should be at least as good as $Mono$ which only detects subsequences of adjacent elements. - $Enc \preceq Mono$: when making encroaching lists, $Enc$ is guaranteed to create no more than one such new list per non-increasing or non-decreasing run found in $X$, so the result will be at most as big as that of $Mono$. However $Enc$ can also find presortedness in patterns such as $\langle 5, 6, 4, 7, 3, 8, 2, 9, 1, 10 \rangle$ where $Mono$ finds maximum disorder. Therefore $Enc(X)$ should always be at most as big as $Mono(X)$. - $Mono \not \equiv SUS$: - - There is no constant $c$ such as $c \cdot SUS(X) \le Mono(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $Mono(X) = 1$ (a single decreasing run), but $SUS(X) = |X|$ (each element is decreasing, and as such constitues a new single-element ascending subsequence). - - There is no constant $c$ such as $c \cdot Mono(X) \le SUS(X)$: a sequence $X$ like $\langle 0, \frac{n}{2}, 1, \frac{n}{2} + 1, 2, \frac{n}{2} + 2, ..., \frac{n}{2} - 2, n - 1, \frac{n}{2} - 1, n \rangle$ always has $SUS(X) = 2$ (an ascending subsequence of even indices, another one of odd indices), but $Mono(X) = \frac{|X|}{2}$ (every pair of elements is a new descending run). + - There is no constant $c$ such as $c \cdot SUS(X) \le Mono(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $Mono(X) = 1$ (a single decreasing run), but $SUS(X) = \lvert X \rvert$ (each element is decreasing, and as such constitues a new single-element ascending subsequence). + - There is no constant $c$ such as $c \cdot Mono(X) \le SUS(X)$: a sequence $X$ like $\langle 0, \frac{n}{2}, 1, \frac{n}{2} + 1, 2, \frac{n}{2} + 2, ..., \frac{n}{2} - 2, n - 1, \frac{n}{2} - 1, n \rangle$ always has $SUS(X) = 2$ (an ascending subsequence of even indices, another one of odd indices), but $Mono(X) = \frac{\lvert X \rvert}{2}$ (every pair of elements is a new descending run). - $Mono \not \equiv Max$: - - There is no constant $c$ such as $c \cdot Max(X) \le Mono(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $Mono(X) = 1$ (a single decreasing run), but $Max(X) = |X| - 1$ (the distance between the smallest and greatest elements is maximal). - - There is no constant $c$ such as $c \cdot Mono(X) \le Max(X)$: a sequence $X$ like $\langle 1, 0, 3, 2, ..., n , n - 1 \rangle$ always has $Max(X) = 1$ (all inversions are with a neighbour, hence they all equal $1$), but $Mono(X) = \frac{|X|}{2}$ (every pair of elements is a new descending run). + - There is no constant $c$ such as $c \cdot Max(X) \le Mono(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $Mono(X) = 1$ (a single decreasing run), but $Max(X) = \lvert X \rvert - 1$ (the distance between the smallest and greatest elements is maximal). + - There is no constant $c$ such as $c \cdot Mono(X) \le Max(X)$: a sequence $X$ like $\langle 1, 0, 3, 2, ..., n , n - 1 \rangle$ always has $Max(X) = 1$ (all inversions are with a neighbour, hence they all equal $1$), but $Mono(X) = \frac{\lvert X \rvert}{2}$ (every pair of elements is a new descending run). The following relations can be transitively deduced from the results presented in *A framework for adaptive sorting*: - $Mono \not \preceq Exc$: we know that $SMS \preceq Mono$ and $SMS \not \preceq Exc$. diff --git a/docs/Tooling.md b/docs/Tooling.md index ede97c1d..ab8e60d1 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -63,8 +63,6 @@ The packages downloaded from conan-center are minimal and only contain the files This can notably used to browse old versions of the documentation. It seems however that `--ref` doesn't understand Git tags, so you have to create a proper branch from the version tag you want to browse beforehand. -Due to slight markup differences, some pages might not fully render correctly but it should nonetheless be a better experience than navigaitng the Markdown files by hand. - [assertions-and-audits]: Home.md#assertions--audits [catch2]: https://github.com/catchorg/Catch2 From c85c634852aebb8eb9f974e193e4b80f452830f8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 16 Oct 2025 23:59:22 +0200 Subject: [PATCH 15/40] Fix rendering of benchmarks with Gollum --- docs/Benchmarks.md | 64 +++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index 194568d1..f4b0dbdb 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -23,9 +23,8 @@ Most sorting algorithms are designed to work with random-access iterators, so th Sorting a random-access collection with an unstable sort is probably one of the most common things to want, and not only are those sorts among the fastest comparison sorts, but type-specific sorters can also be used to sort a variety of types. If you don't know what algorithm you want and don't have specific needs, then you probably want one of these. -![Benchmark speed of unstable sorts with increasing size for std::vector](https://i.imgur.com/Q3IEeci.png) - -![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/oRW5kFr.png) +![Benchmark speed of unstable sorts with increasing size for std::vector<double>](https://i.imgur.com/Q3IEeci.png) +![Benchmark speed of unstable sorts with increasing size for std::deque<double>](https://i.imgur.com/oRW5kFr.png) The plots above show a few general tendencies: * `selection_sort` is O(n²) and doesn't scale. @@ -34,9 +33,8 @@ The plots above show a few general tendencies: The quicksort derivatives and the hybrid radix sorts are generally the fastest of the lot, yet `drop_merge_sort` seems to offer interesting speedups for `std::deque` despite not being designed to be the fastest on truly shuffled data. Part of the explanation is that it uses `pdq_sort` in a contiguous memory buffer underneath, which might be faster for `std::deque` than sorting completely in-place. -![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/WZ4s6Xt.png) - -![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/UAaObUW.png) +![Benchmark unstable sorts over different patterns for std::vector<double>](https://i.imgur.com/WZ4s6Xt.png) +![Benchmark unstable sorts over different patterns for std::deque<double>](https://i.imgur.com/UAaObUW.png) A few random takeways: * All the algorithms are more or less adaptive, not always for the same patterns. @@ -51,15 +49,13 @@ A few random takeways: Pretty much all stable sorts in the library are different flavours of merge sort with sligthly different properties. Most of them allocate additional merge memory, and a good number of those also have a fallback algorithm that makes them run in O(n log²n) instead of O(n log n) when no extra heap memory is available. -![Benchmark speed of stable sorts with increasing size for std::vector](https://i.imgur.com/vRW1zcs.png) - -![Benchmark speed of stable sorts with increasing size for std::deque](https://i.imgur.com/CQePcBh.png) +![Benchmark speed of stable sorts with increasing size for std::vector<double>](https://i.imgur.com/vRW1zcs.png) +![Benchmark speed of stable sorts with increasing size for std::deque<double>](https://i.imgur.com/CQePcBh.png) `insertion_sort` being O(n²) it's not surprising that it doesn't perform well in such a benchmark. All the other sorting algorithms display roughly equivalent and rather tight curves. -![Benchmark stable sorts over different patterns for std::vector](https://i.imgur.com/bRQ5cu5.png) - -![Benchmark stable sorts over different patterns for std::deque](https://i.imgur.com/fHIZB5L.png) +![Benchmark stable sorts over different patterns for std::vector<double>](https://i.imgur.com/bRQ5cu5.png) +![Benchmark stable sorts over different patterns for std::deque<double>](https://i.imgur.com/fHIZB5L.png) These plots highlight a few important things: * `spin_sort` consistently beats pretty much anything else. @@ -70,11 +66,9 @@ These plots highlight a few important things: I decided to include a dedicated category for slow O(n log n) sorts, because I find this class of algorithms interesting. This category contains experimental algorithms, often taken from rather old research papers. `heap_sort` is used as the "fast" algorithm in this category, despite it being consistently the slowest in the previous category. -![Benchmark speed of slow O(n log n) sorts with increasing size for std::vector](https://i.imgur.com/SUbyqKV.png) - -![Benchmark slow O(n log n) sorts over different patterns for std::vector](https://i.imgur.com/Dli1xrp.png) - -![Benchmark slow O(n log n) sorts over different patterns for std::deque](https://i.imgur.com/WxBmipj.png) +![Benchmark speed of slow O(n log n) sorts with increasing size for std::vector<double>](https://i.imgur.com/SUbyqKV.png) +![Benchmark slow O(n log n) sorts over different patterns for std::vector<double>](https://i.imgur.com/Dli1xrp.png) +![Benchmark slow O(n log n) sorts over different patterns for std::deque<double>](https://i.imgur.com/WxBmipj.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. @@ -86,11 +80,11 @@ The analysis is pretty simple here: Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`][std-list-sort] which are aware of the data structure they are sorting. -![Benchmark speed of sorts with increasing size for std::list](https://i.imgur.com/yNQG8kk.png) +![Benchmark speed of sorts with increasing size for std::list<double>](https://i.imgur.com/yNQG8kk.png) For elements as small as `double`, there are two clear winners here: `drop_merge_sort` and `out_of_place_adapter(pdq_sort)`. Both have in common the fact that they move a part of the collection (or the whole collection) to a contiguous memory buffer and sort it there using `pdq_sort`. The only difference is that `drop_merge_sort` does that "accidentally" while `out_of_place_adapter` was specifically introduced to sort into a contiguous memory buffer and move back for speed. -![Benchmark sorts over different patterns for std::list](https://i.imgur.com/zlHzRLd.png) +![Benchmark sorts over different patterns for std::list<double>](https://i.imgur.com/zlHzRLd.png) `out_of_place_adapter(pdq_sort)` was not included in this benchmark, because it adapts to patterns the same way `pdq_sort` does. Comments can be added for these results: * `std::list::sort` would require more expensive to move elements for node relinking to be faster than move-based algorithms. @@ -102,9 +96,8 @@ For elements as small as `double`, there are two clear winners here: `drop_merge Even fewer sorters can handle forward iterators. `out_of_place_adapter(pdq_sort)` was not included in the patterns benchmark, because it adapts to patterns the same way `pdq_sort` does. -![Benchmark speed of sorts with increasing size for std::forward_list](https://i.imgur.com/if15kX1.png) - -![Benchmark sorts over different patterns for std::forward_list](https://i.imgur.com/uF0UzLm.png) +![Benchmark speed of sorts with increasing size for std::forward_list<double>](https://i.imgur.com/if15kX1.png) +![Benchmark sorts over different patterns for std::forward_list<double>](https://i.imgur.com/uF0UzLm.png) The results are roughly the same than with bidirectional collections: * Sorting out-of-place is faster than anything else. @@ -120,9 +113,8 @@ This category will highlight the advantages of some sorters in sorting scenarios Integer sorting is a rather specific scenario for which many solutions exist: counting sorts, radix sorts, algorithms optimized to take advantage of branchless comparisons, etc. -![Benchmark speed of integer sorts with increasing size for std::vector](https://i.imgur.com/zuCAkIf.png) - -![Benchmark integer sorts over different patterns for std::vector](https://i.imgur.com/20uDwTM.png) +![Benchmark speed of integer sorts with increasing size for std::vector<int>](https://i.imgur.com/zuCAkIf.png) +![Benchmark integer sorts over different patterns for std::vector<int>](https://i.imgur.com/20uDwTM.png) `counting_sort` appears as a clear winner here but with a catch: its speed depends on the difference between the smaller and the greater integers in the collection to sort. In the benchmarks above the integer values scale with the size of the collection, but if a collection contains just a few elements with a big difference of the minimum and maximum values, `counting_sort` won't be a good solution. @@ -134,7 +126,7 @@ Some sorting algorithms are specifically designed to be fast when there are only The following plot shows how fast those algorithms are depending on the percentage of inversions in the collection to sort. They are benchmarked against `pdq_sort` because it is the algorithm they use internally to sort the remaining unsorted elements prior to the merge, which makes it easy to compare the gains and overheads of those algorithms compared to a raw `pdq_sort`. -![Benchmark speed of Inv-adaptive sorts with an increasing percentage of inversions for std::vector](https://i.imgur.com/MYRdAKc.png) +![Benchmark speed of Inv-adaptive sorts with an increasing percentage of inversions for std::vector<int>](https://i.imgur.com/MYRdAKc.png) As long as there are up to 30~40% of inversions in the collection, `drop_merge_sort` and `split_sort` offer an advantage over a raw `pdq_sort`. Interestingly `drop_merge_sort` is the best when there are few inversions but `split_sort` is more robust: it can handle more inversions than `drop_merge_sort` before being slower than `pdq_sort`, and has a lower overhead when the number of inversions is high. @@ -146,7 +138,7 @@ Sometimes one has to sort a collection whose elements are expensive to move arou The following example uses a collection of `std::array` whose first element is the only one compared during the sort. Albeit a bit artificial, it illustrates the point well enough. -![Benchmark heap_sort vs. indirect_adapter(heap_sort) for a collection of std::array](https://i.imgur.com/Okkahwf.png) +![Benchmark heap_sort vs. indirect_adapter(heap_sort) for a collection of std::array<double, 100>](https://i.imgur.com/Okkahwf.png) The improvements are not always as clear as in this benchmark, but it shows that `indirect_adapter` might be an interesting tool to have in your sorting toolbox in such a scenario. @@ -154,17 +146,14 @@ The improvements are not always as clear as in this benchmark, but it shows that Only a few algorithms allow to sort a collection stably without using extra heap memory: `grail_sort` and `wiki_sort` can accept a fixed-size buffer (possibly of size 0) while `merge_sort` has a fallback algorithm when no heap memory is available. -![Benchmark speed of stable sorts with no heap memory with increasing size for std::vector](https://i.imgur.com/1a64irX.png) - -![Benchmark speed of stable sorts with no heap memory with increasing size for std::deque](https://i.imgur.com/U5uD8Er.png) - +![Benchmark speed of stable sorts with no heap memory with increasing size for std::vector<double>](https://i.imgur.com/1a64irX.png) +![Benchmark speed of stable sorts with no heap memory with increasing size for std::deque<double>](https://i.imgur.com/U5uD8Er.png) ![Detail of the previous benchmark](https://i.imgur.com/owUictQ.png) `merge_sort` is definitely losing this benchmark. Interestingly enough `wiki_sort` is way better with a fixed buffer of 512 elements while it hardly affects `grail_sort` at all. For `std::deque`, `grail_sort` is almost always the fastest no matter what. -![Benchmark stable sorts with no heap memory over different patterns for std::vector](https://i.imgur.com/74YxCLI.png) - -![Benchmark stable sorts with no heap memory over different patterns for std::deque](https://i.imgur.com/jqek5Ii.png) +![Benchmark stable sorts with no heap memory over different patterns for std::vector<double>](https://i.imgur.com/74YxCLI.png) +![Benchmark stable sorts with no heap memory over different patterns for std::deque<double>](https://i.imgur.com/jqek5Ii.png) Here `merge_sort` still loses the battle, but it also displays an impressive enough adaptiveness to presortedness and patterns. @@ -172,9 +161,8 @@ Here `merge_sort` still loses the battle, but it also displays an impressive eno 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/ABfEmJe.png) - -![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/wqz1q3R.png) +![Benchmark speed of small sorts with increasing size for std::array<int>](https://i.imgur.com/ABfEmJe.png) +![Benchmark speed of small sorts with increasing size for std::array<long double>](https://i.imgur.com/wqz1q3R.png) 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. @@ -186,7 +174,7 @@ We can see several trends in these benchmarks, rather consistant across `int` an This benchmark for [measures of disorder][Measures-of-disorder] is small and only intends to show the cost that these tools might incur. It is not meant to be exhaustive in any way. -![Benchmark speed of measures of disorder for increasing size for std::vector](https://i.imgur.com/7QZqe0m.png) +![Benchmark speed of measures of disorder for increasing size for std::vector<int>](https://i.imgur.com/7QZqe0m.png) It makes rather easy to see the different groups of complexities: * *Runs(X)* and *Mono(X)* are obvious O(n) algorithms. From 3a571b3d4357cd4ddeb8fcf83654127d00e6af90 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 17 Oct 2025 00:57:42 +0200 Subject: [PATCH 16/40] Add a Gollum redirects file to the documentation --- docs/.redirects.gollum | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/.redirects.gollum diff --git a/docs/.redirects.gollum b/docs/.redirects.gollum new file mode 100644 index 00000000..632c00ba --- /dev/null +++ b/docs/.redirects.gollum @@ -0,0 +1,2 @@ +--- +Measures-of-presortedness.md: Measures-of-disorder.md From 7e34bc22c5e1d889da9bb4ba605347d098509ab9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 19 Oct 2025 21:47:58 +0200 Subject: [PATCH 17/40] Add action to mirror commits to codeberg --- .github/workflows/mirror-to-codeberg.yml | 25 ++++++++++++++++++++++++ docs/Miscellaneous-utilities.md | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/mirror-to-codeberg.yml diff --git a/.github/workflows/mirror-to-codeberg.yml b/.github/workflows/mirror-to-codeberg.yml new file mode 100644 index 00000000..d13a05b6 --- /dev/null +++ b/.github/workflows/mirror-to-codeberg.yml @@ -0,0 +1,25 @@ +# Copyright (c) 2025 Morwenn +# SPDX-License-Identifier: MIT + +name: Mirror Commits to Codeberg + +on: [push, workflow_dispatch] + +jobs: + mirror-to-codeberg: + name: Mirror to Codeberg + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Mirror + uses: yesolutions/mirror-action@v0.7.0 + with: + REMOTE: 'https://codeberg.org/Morwenn/cpp-sort.git' + GIT_USERNAME: Morwenn + GIT_PASSWORD: ${{ secrets.GIT_PASSWORD }} diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index c6fe88ae..dfe75184 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -373,7 +373,7 @@ auto m = get(mm); #include ``` -`utility::quicksort_adversary` is a function template that implements an algorithm described by M. D. McIlroy in [*A Killer Adversary for Quicksort*][quicksort-adversary], which attempts to trigger the quadratic case of many quicksort implementations by trying to guess the pivot and forcing the testing comparison to perform a certain set of comparisons. +`utility::quicksort_adversary` is a function template that implements an algorithm described by M. D. McIlroy in [*A Killer Adversary for Quicksort*][quicksort-adversary], which attempts to trigger the quadratic case of many quicksort implementations by trying to guess the pivot and forcing the tested algorithm to perform a certain set of comparisons. ```cpp template From 2e8c3addad3a342154a00e1a1279639863cf1c55 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 1 Nov 2025 00:00:26 +0100 Subject: [PATCH 18/40] CI: uprade MacOS image to macos-14 --- .github/workflows/build-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index db432bae..479bc063 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -23,7 +23,7 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-14 strategy: fail-fast: false From 7ed94c61ee3629d125b2da47b7fddc456fa803a8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 Nov 2025 13:31:51 +0100 Subject: [PATCH 19/40] New measure of disorder: Amp --- docs/Measures-of-disorder.md | 39 +++++++- docs/images/mops-partial-ordering.png | Bin 39523 -> 0 bytes docs/images/pairwise-order-shadow.png | Bin 0 -> 13538 bytes .../partial-ordering-measures-of-disorder.png | Bin 0 -> 41936 bytes include/cpp-sort/probes.h | 1 + include/cpp-sort/probes/amp.h | 87 ++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/probes/amp.cpp | 46 +++++++++ tests/probes/every_probe_common.cpp | 13 ++- .../every_probe_heap_memory_exhaustion.cpp | 3 + tests/probes/relations.cpp | 12 +++ ...partial-ordering-measures-of-disorder.tex} | 43 +++++---- 12 files changed, 221 insertions(+), 24 deletions(-) delete mode 100644 docs/images/mops-partial-ordering.png create mode 100644 docs/images/pairwise-order-shadow.png create mode 100644 docs/images/partial-ordering-measures-of-disorder.png create mode 100644 include/cpp-sort/probes/amp.h create mode 100644 tests/probes/amp.cpp rename tools/{mops-partial-ordering.tex => partial-ordering-measures-of-disorder.tex} (58%) diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 09a3cca1..81483eed 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -83,9 +83,9 @@ The graph below shows the partial ordering of several measures of disorder: - *m₀* is a measure of presortedness that always returns 0. - *m₀₁* is a measure of presortedness that returns 0 when $X$ is sorted and 1 otherwise. -![Partial ordering of measures of disorder](images/mops-partial-ordering.png) +![Partial ordering of measures of disorder](images/partial-ordering-measures-of-disorder.png) -This graph is a modified version of the one in *A framework for adaptive sorting*. The relations of *Mono* are empirically derived [original research][original-research] and incomplete (unknown relations with *Osc* and *Loc*). +This graph is a modified version of the one found in *A framework for adaptive sorting*. The relations of *Mono* and *Amp* with other measures of disorder are empirically derived [original research][original-research] and known to be incomplete (unknown relations with *Osc* and *Loc*). The measures of disorder in bold in the graph are available in **cpp-sort**, the others are not. @@ -133,7 +133,40 @@ It takes an integer `n` and returns the maximum value that the measure of disord ## Available measures of disorder -Measures of disorder are pretty formalized, so the names of the functions in the library are short and generally correspond to the ones used in the literature. +Measures of disorder are pretty formalized, so the names of the functions in the library are short and generally correspond to the ones used in the literature, with a few exceptions. A justification is given whenever a name does not exactly match the ones from the literature, or when the definition differs. + +### *Amp* + +```cpp +#include +``` + +Let's consider the following functions to compare two elements elements of a sequence: + +$$ +comp(x, y)= +\begin{cases} +1 & \text{ if } x \lt y\\ +-1 & \text{ if } x \gt y\\ +0 & \text{otherwise} +\end{cases} +$$ + +We define $\mathit{Amp}(X)$ as follows: + +$$\mathit{Amp}(X) = \lvert X \rvert - \mathit{PTP}(X) - N_{\mathit{eq}}(X) - 1$$ + +Where $N_{\mathit{eq}}(X)$ is the number of pairs of neighbors that compare equivalent in $X$, and $\mathit{PTP}(X)$ is the number of unique values in the prefix sum of the sequence obtained by applying $comp$ to every pair of adjacent elements in $X$. + +![Illustration of how comp is applied to pairs of neighbors up to the prefix sum](images/pairwise-order-shadow.png) + +| Complexity | Memory | Iterators | Monotonic | +| ----------- | ----------- | ------------- | --------- | +| n | 1 | Forward | No | + +`max_for_size`: $\lvert X \rvert - 2$ when the sign of $comp$ changes for every pair of neighbors. + +**Note:** *Amp* does not respect Mannila's criterion 4: $\mathit{Amp}(\langle 1, 2, 3 \rangle) = 0$ and $\mathit{Amp}(\langle 6, 5, 4 \rangle) = 0$, but $\mathit{Amp}(\langle 1, 2, 3, 6, 5, 4 \rangle) = 4$. ### *Block* diff --git a/docs/images/mops-partial-ordering.png b/docs/images/mops-partial-ordering.png deleted file mode 100644 index f2b3b183612fbf42d73727a87c4686e88af75fbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39523 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 diff --git a/docs/images/pairwise-order-shadow.png b/docs/images/pairwise-order-shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..bab57b54a65a1b5a50e5245d32cb85665ba36e07 GIT binary patch literal 13538 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 B`}_a^ literal 0 HcmV?d00001 diff --git a/docs/images/partial-ordering-measures-of-disorder.png b/docs/images/partial-ordering-measures-of-disorder.png new file mode 100644 index 0000000000000000000000000000000000000000..cebc34f6f39d6c9d1145fbdf0eb02faa59579596 GIT binary patch literal 41936 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 LVi{xMyzPGgl #include #include #include diff --git a/include/cpp-sort/probes/amp.h b/include/cpp-sort/probes/amp.h new file mode 100644 index 00000000..b2dee5e9 --- /dev/null +++ b/include/cpp-sort/probes/amp.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_PROBES_AMP_H_ +#define CPPSORT_PROBES_AMP_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ +namespace probe +{ + namespace detail + { + struct amp_impl + { + 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 + -> cppsort::detail::difference_type_t + { + using difference_type = cppsort::detail::difference_type_t; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (first == last || std::next(first) == last) { + return 0; + } + + difference_type size = 0, + shadow = 0, + min = 0, + max = 0; + + auto current = first; + auto next = std::next(current); + do { + ++size; + + if (comp(proj(*current), proj(*next))) { + max = (std::max)(max, ++shadow); + } else if (comp(proj(*next), proj(*current))) { + min = (std::min)(min, --shadow); + } else { + // Neighbours that compare equivalent don't contribute to the amplitude + --size; + } + + ++current; + ++next; + } while (next != last); + + return size - (max - min); + } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n <= 2 ? 0 : n - 2; + } + }; + } + + inline constexpr sorter_facade amp{}; +}} + +#endif // CPPSORT_PROBES_AMP_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f6302897..903a0a21 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -209,6 +209,7 @@ add_executable(main-tests distributions/shuffled_16_values.cpp # Probes tests + probes/amp.cpp probes/block.cpp probes/dis.cpp probes/enc.cpp diff --git a/tests/probes/amp.cpp b/tests/probes/amp.cpp new file mode 100644 index 00000000..4cebd6ca --- /dev/null +++ b/tests/probes/amp.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "measure of disorder: amp", "[probe][amp]" ) +{ + using cppsort::probe::amp; + + SECTION( "simple test" ) + { + std::forward_list li = { 4, 6, 5, 2, 9, 1, 3, 8, 0, 7 }; + CHECK( amp(li) == 7 ); + CHECK( amp(li.begin(), li.end()) == 7 ); + + std::vector> tricky(li.begin(), li.end()); + CHECK( amp(tricky, &internal_compare::compare_to) == 7 ); + } + + SECTION( "upper bound" ) + { + // The upper bound should correspond to a sequence that + // oscillates at every step + + std::forward_list li = { 0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9 }; + auto max_n = amp.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 9 ); + CHECK( amp(li) == max_n ); + CHECK( amp(li.begin(), li.end()) == max_n ); + } + + // https://morwenn.github.io/presortedness/2025/10/18/TSB005-symmetry-of-amp.html + rc::prop("Amp(Reversed(X)) = Amp(X)", [](std::vector sequence) { + auto amp_x = amp(sequence); + std::reverse(sequence.begin(), sequence.end()); + return amp(sequence) == amp_x; + }); +} diff --git a/tests/probes/every_probe_common.cpp b/tests/probes/every_probe_common.cpp index c0a519a3..6c90ff5b 100644 --- a/tests/probes/every_probe_common.cpp +++ b/tests/probes/every_probe_common.cpp @@ -18,6 +18,7 @@ // TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), @@ -43,6 +44,7 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", } TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), @@ -69,6 +71,7 @@ TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]", } TEMPLATE_TEST_CASE( "test every probe with a 0 or 1 element", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), @@ -107,6 +110,7 @@ TEMPLATE_TEST_CASE( "test every probe with a 0 or 1 element", "[probe]", } TEMPLATE_TEST_CASE( "test order isomorphism for every probe", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), @@ -166,6 +170,7 @@ namespace } TEMPLATE_TEST_CASE( "test M(subsequence(X)) <= M(X) for most probes M", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), decltype(cppsort::probe::inv), @@ -279,7 +284,7 @@ TEMPLATE_TEST_CASE( "test M(2, 1, 4, 3, 6, 5, ...) <= |X| * M(2, 1) / 2 for most { // From *Sorting and Measures of Disorder* by Estivill-Castro: // property derived from Mannila's criteria 2 & 4 - // The following probes don't satisfy it: Block, Mono, Osc + // The following probes don't satisfy it: Amp, Block, Mono, Osc int size = 1000; std::vector sequence(size, 0); @@ -295,6 +300,7 @@ TEMPLATE_TEST_CASE( "test M(2, 1, 4, 3, 6, 5, ...) <= |X| * M(2, 1) / 2 for most } TEMPLATE_TEST_CASE( "test M(aX) <= |X| + M(X) for most probes M", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), @@ -335,7 +341,7 @@ TEMPLATE_TEST_CASE( "test prefix monotonicity", "[probe]", decltype(cppsort::probe::sus) ) { // Property formalized by Estivill-Castro in *Sorting and Measures of Disorder* - // The following probes don't satisfy it: Block, Mono, Osc + // The following probes don't satisfy it: Amp, Block, Mono, Osc // Note: the original paper claims that Osc also satisfies this property, // but it fails for X=⟨3, 0⟩ Y=⟨⟩ Z=⟨4, 2⟩ @@ -383,7 +389,7 @@ TEMPLATE_TEST_CASE( "test monotonicity", "[probe]", decltype(cppsort::probe::sus) ) { // Property formalized by Estivill-Castro in *Sorting and Measures of Disorder* - // The following probes don't satisfy it: Block, Enc, Mono, Osc + // The following probes don't satisfy it: Amp, Block, Enc, Mono, Osc // Note: the original paper claims that MEnc[k,A,D] also satisfies this property, // but at the time of writing this comment I ahev no idea what that means @@ -424,6 +430,7 @@ TEMPLATE_TEST_CASE( "test monotonicity", "[probe]", } TEMPLATE_TEST_CASE( "test that probes never produce more disorder than their theoretical maximum", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), diff --git a/tests/probes/every_probe_heap_memory_exhaustion.cpp b/tests/probes/every_probe_heap_memory_exhaustion.cpp index 6d437a5c..436cff71 100644 --- a/tests/probes/every_probe_heap_memory_exhaustion.cpp +++ b/tests/probes/every_probe_heap_memory_exhaustion.cpp @@ -20,6 +20,7 @@ // TEMPLATE_TEST_CASE( "heap exhaustion for random-access probes", "[probe][heap_exhaustion]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::runs) ) @@ -38,6 +39,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for random-access probes", "[probe][heap_ex } TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional probes", "[probe][heap_exhaustion]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::runs) ) @@ -56,6 +58,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional probes", "[probe][heap_ex } TEMPLATE_TEST_CASE( "heap exhaustion for forward probes", "[probe][heap_exhaustion]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::runs) ) diff --git a/tests/probes/relations.cpp b/tests/probes/relations.cpp index 4b0feb1e..f4a14288 100644 --- a/tests/probes/relations.cpp +++ b/tests/probes/relations.cpp @@ -183,4 +183,16 @@ TEST_CASE( "relations between measures of disorder", "[probe]" ) rc::prop("Enc(X) ≤ Mono(X) + 1", [](const std::vector& sequence) { return enc(sequence) <= mono(sequence) + 1; }); + + // Original research about Amp + + // https://morwenn.github.io/presortedness/2025/11/09/TSB007-relationship-between-amp-and-runs.html + rc::prop("Amp(X) ≤ 2 Runs(X)", [](const std::vector& sequence) { + return amp(sequence) <= 2 * runs(sequence); + }); + + // Conjecture + rc::prop("Mono(X) ≤ Amp(X)", [](const std::vector& sequence) { + return mono(sequence) <= amp(sequence); + }); } diff --git a/tools/mops-partial-ordering.tex b/tools/partial-ordering-measures-of-disorder.tex similarity index 58% rename from tools/mops-partial-ordering.tex rename to tools/partial-ordering-measures-of-disorder.tex index 2f2894a9..7d36686c 100644 --- a/tools/mops-partial-ordering.tex +++ b/tools/partial-ordering-measures-of-disorder.tex @@ -1,4 +1,4 @@ -% Copyright (c) 2021 Morwenn +% Copyright (c) 2021-2025 Morwenn % SPDX-License-Identifier: MIT \documentclass{standalone} @@ -10,7 +10,8 @@ background rectangle/.style={fill=white}, show background rectangle, auto, - node distance = 1.3cm, + on grid, + node distance = 1.3cm and 3cm, semithick ] @@ -26,21 +27,23 @@ % Max=Dis equivalence comes from: % NeatSort - A practical adaptive algorithm % by M. La Rocca and D. Cantone - \node[state] (reg) {$Reg$}; - \node[state] (loc) [below of=reg] {$Loc$}; - \node[state] (hist) [left=2.2cm of loc] {$Hist$}; - \node[state] (sms) [right=2.2cm of loc] {$SMS$}; - \node[state] (block) [below of=hist] {$\bm{Block}$}; - \node[state] (osc) [below of=loc] {$\bm{Osc}$}; - \node[state] (enc) [below of=sms] {$\bm{Enc}$}; - \node[state] (rem) [below of=block] {$\bm{Rem}$}; - \node[state] (inv) [below of=osc] {$\bm{Inv}~$$\equiv$$~\bm{Spear}$}; - \node[state] (sus) [below of=enc] {$\bm{SUS}$}; - \node[state] (exc) [below of=rem] {$\bm{Exc}~$$\equiv$$~\bm{Ham}$}; - \node[state] (max) [below of=inv] {$\bm{Max}~$$\equiv$$~\bm{Dis}~$}; - \node[state] (runs) [below of=sus] {$\bm{Runs}$}; + \node[state] (reg) {$\mathit{Reg}$}; + \node[state] (loc) [below of=reg] {$\mathit{Loc}$}; + \node[state] (sms) [right=of loc] {$\mathit{SMS}$}; + \node[state] (osc) [below of=loc] {$\bm{\mathit{Osc}}$}; + \node[state] (hist) [left=of loc] {$\mathit{Hist}$}; + \node[state] (blank) [below=of osc] {}; + \node[state] (enc) [below of=sms] {$\bm{\mathit{Enc}}$}; + \node[state] (inv) [below of=blank] {$\bm{\mathit{Inv}}~$$\equiv$$~\bm{\mathit{Spear}}$}; + \node[state] (rem) [left=of inv] {$\bm{\mathit{Rem}}$}; + \node[state] (sus) [below of=enc] {$\bm{\mathit{SUS}}$}; + \node[state] (exc) [below of=rem] {$\bm{\mathit{Exc}}~$$\equiv$$~\bm{\mathit{Ham}}$}; + \node[state] (block) [above=of rem] {$\bm{\mathit{Block}}$}; + \node[state] (max) [below of=inv] {$\bm{\mathit{Max}}~$$\equiv$$~\bm{\mathit{Dis}}~$}; + \node[state] (runs) [right=of max] {$\bm{\mathit{Runs}}$}; \node[state] (m01) [below of=max] {$m_{01}$}; \node[state] (m0) [below of=m01] {$m_{0}$}; + \path[-] (reg) edge node {} (hist); \path[-] (reg) edge node {} (loc); \path[-] (reg) edge node {} (sms); @@ -64,11 +67,15 @@ % Sort Race % by H. Zhang, B. Meng and Y. Liang - \node[state] (mono) [right=1.4cm of sus] {$\bm{Mono}$}; - \path[-] (mono) edge node {} (runs); - + \node[state] (mono) [right=of sus] {$\bm{\mathit{Mono}}$}; % See the Original Research page of the docs \path[-] (enc) edge node {} (mono); + + % Original research: Amp + \node[state] (amp) [below=of mono] {$\bm{\mathit{Amp}}$}; + \path[-] (amp) edge node {} (runs); + % Conjecture + \path[-] (amp) edge node {} (mono); \end{tikzpicture} \end{document} From 4aadf9a8e2fa553fa255523048ab85cde5f162ab Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 Nov 2025 14:42:42 +0100 Subject: [PATCH 20/40] CI: upgrade MacOS GCC version to 13 --- .github/workflows/build-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 479bc063..d53467d8 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -30,7 +30,7 @@ jobs: matrix: config: # Release build - - cxx: g++-12 + - cxx: g++-13 build_type: Release - cxx: clang++ build_type: Release From 4244d1e733ee0ddd73a6ec8a8c9180c91745f388 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 28 Nov 2025 22:03:22 +0100 Subject: [PATCH 21/40] Doc: improve kerning of math expressions --- docs/Measures-of-disorder.md | 44 ++++++++++++++++++------------------ docs/Original-research.md | 38 +++++++++++++++---------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 81483eed..6e899eab 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -50,13 +50,13 @@ The *monotonicity* property implies the *prefix monotonicity* one. A measure of Let $X$ be a sequence of elements, and let $S_X$ be set of all permutations of that sequence: -$$below_M(X) = \{ \pi \vert \pi \in S_X \text{ and } M(\pi) \le M(X) \}$$ +$$\mathit{below}_M(X) = \{ \pi \vert \pi \in S_X \text{ and } M(\pi) \le M(X) \}$$ Let $T_S(X)$ be the number of steps needed for an algorithm $S$ to sort $X$. A sorting algorithm is said to be $M$-optimal if and only if, for some constant $c$, we have for all $X$: -$$T_S(X) \le c \cdot max\{\lvert X \rvert, \log{} |below_M(X)|\}$$ +$$T_S(X) \le c \cdot max\{\lvert X \rvert, \log{} |\mathit{below}_M(X)|\}$$ -In other words, a sorting algorithm is considered $M$-optimal if it takes a number of steps that is within a constant factor of the lower bound of $M$ to sort a sequence. For example a $Rem$-optimal algorithm should be able to sort any sequence in $O(\lvert X \rvert \log{} Rem(X))$ steps. +In other words, a sorting algorithm is considered $M$-optimal if it takes a number of steps that is within a constant factor of the lower bound of $M$ to sort a sequence. For example a $\mathit{Rem}$-optimal algorithm should be able to sort any sequence in $O(\lvert X \rvert \log{} \mathit{Rem}(X))$ steps. ### Partial ordering of measures of disorder @@ -64,19 +64,19 @@ Early on, authors have been wanting to prove that some measures of disorder were > Let $M_1$ and $M_2$ be two measures of disorder: > -> * $M_1$ is algorithmically finer than $M_2$ (denoted $M_1 \le_{alg} M_2$) if and only if any $M_1$-optimal sorting algorithm is also $M_2$-optimal. -> * $M_1$ and $M_2$ are algorithmically equivalent (denoted $M_1 =_{alg} M_2$) if and only if $M_1 \le_{alg} M_2$ and $M_2 \le_{alg} M_1$. +> * $M_1$ is algorithmically finer than $M_2$ (denoted $M_1 \le_\mathit{alg} M_2$) if and only if any $M_1$-optimal sorting algorithm is also $M_2$-optimal. +> * $M_1$ and $M_2$ are algorithmically equivalent (denoted $M_1 =_\mathit{alg} M_2$) if and only if $M_1 \le_\mathit{alg} M_2$ and $M_2 \le_\mathit{alg} M_1$. While useful to understand what we want from a partial order on measures of disorder, the definition above does not help a lot when it comes to actually proving that a measure is algorithmically finer than another. To better compare two measures of disorder, Jingsen Chen introduces the following operator in *Computing and ranking measures of presortedness*: > Let $M_1$ and $M_2$ be two measures of disorder: > -> * $M_1$ is superior to $M_2$ (denoted $M_1 \preceq M_2$) if and only if there exists a constant $c$ such as $\lvert below_{M_1}(X) \rvert \le c \cdot \lvert below_{M_2}(X) \rvert$ for any sequence $X$. +> * $M_1$ is superior to $M_2$ (denoted $M_1 \preceq M_2$) if and only if there exists a constant $c$ such as $\lvert \mathit{below}_{M_1}(X) \rvert \le c \cdot \lvert \mathit{below}_{M_2}(X) \rvert$ for any sequence $X$. > * $M_1$ and $M_2$ are equivalent (denoted $M_1 \equiv M_2$) if and only if $M_1 \preceq M_2$ and $M_2 \preceq M_1$. That definition seems to match the one proposed much earlier by Alistair Moffat and Ola Petersson in *A Framework for Adaptive Sorting*, though the authors use the symbol $\supseteq$ instead of $\preceq$. -To prove that two measures of disorder were equivalent, authors have used the simpler method of showing that there exists some non-0 constants $c$ and $d$ such as $M_1(X) \le c \cdot M_2(X) \le d \cdot M_1(X)$. For example, the result $Max \equiv Dis$ below was originally obtained by proving that $Max(X) \le Dis(X) \le 2 Max(X)$ for any sequence $X$. +To prove that two measures of disorder were equivalent, authors have used the simpler method of showing that there exists some non-0 constants $c$ and $d$ such as $M_1(X) \le c \cdot M_2(X) \le d \cdot M_1(X)$. For example, the result $\mathit{Max} \equiv \mathit{Dis}$ below was originally obtained by proving that $\mathit{Max}(X) \le \mathit{Dis}(X) \le 2 \mathit{Max}(X)$ for any sequence $X$. The graph below shows the partial ordering of several measures of disorder: - *Reg* is a measure of presortedness superior to all other ones in the graph. @@ -144,7 +144,7 @@ Measures of disorder are pretty formalized, so the names of the functions in the Let's consider the following functions to compare two elements elements of a sequence: $$ -comp(x, y)= +\mathit{comp}(x, y)= \begin{cases} 1 & \text{ if } x \lt y\\ -1 & \text{ if } x \gt y\\ @@ -188,7 +188,7 @@ Our implementation is slightly different from the original description in *Subli **Note:** *Block* does not seem to respect Mannila's criterion 3 in the presence of *equivalent elements*. -**Note²:** `probe::block` does not respect Mannila's criterion 4: $Block(\langle 1, 0 \rangle) = 1$ and $Block(\langle 2, 3 \rangle) = 0$, but $Block(\langle 1, 0, 2, 3 \rangle) = 2$. +**Note²:** `probe::block` does not respect Mannila's criterion 4: $\mathit{Block}(\langle 1, 0 \rangle) = 1$ and $\mathit{Block}(\langle 2, 3 \rangle) = 0$, but $\mathit{Block}(\langle 1, 0, 2, 3 \rangle) = 2$. ### *Dis* @@ -222,13 +222,13 @@ Computes an approximation of the number of encroaching lists that can be extract Those lists are called encroaching because the bounds of a given list "encroach" those of all lists on its right. -The number of encroaching lists does not satisfy the formal definition of a measure of presortedness because it returns $1$ for non-empty sorted sequences instead of $0$, which does not respect first Mannila's criterion. Using $Enc(X) - 1$ does not work either because it does not respect Mannila's fourth criterion. To circumvent these issues, `probe::enc` implements an equivalent measure of disorder $M_{Enc}$ proposed by V. Estivill-Castro in *Sorting and Measures of Disorder*, which satisfies all of Mannila's criteria for what makes a measure of presortedness: +The number of encroaching lists does not satisfy the formal definition of a measure of presortedness because it returns $1$ for non-empty sorted sequences instead of $0$, which does not respect first Mannila's criterion. Using $\mathit{Enc}(X) - 1$ does not work either because it does not respect Mannila's fourth criterion. To circumvent these issues, `probe::enc` implements an equivalent measure of disorder $M_\mathit{Enc}$ proposed by V. Estivill-Castro in *Sorting and Measures of Disorder*, which satisfies all of Mannila's criteria for what makes a measure of presortedness: $$ -M_{Enc}(X)= +M_\mathit{Enc}(X)= \begin{cases} 0 & \text{if } X \text{ is sorted,}\\ -Enc(X_{tail}) & \text{otherwise, where } X_{tail} \text{ is } X \text{ without its leading ascending run.} +\mathit{Enc}(X_\mathit{tail}) & \text{otherwise, where } X_\mathit{tail} \text{ is } X \text{ without its leading ascending run.} \end{cases} $$ @@ -244,9 +244,9 @@ $$ #include ``` -Computes the minimum number of exchanges required to sort $X$, which corresponds to $\lvert X \rvert$ minus the number of cycles in the sequence. A cycle corresponds to a number of elements in a sequence that need to be rotated to be in their sorted position; for example, let $\langle 2, 4, 0, 6, 3, 1, 5 \rangle$ be a sequence, the cycles are $\langle 0, 2 \rangle$ and $\langle 1, 3, 4, 5, 6 \rangle$ so $Exc(X) = \lvert X \rvert - 2 = 5$. +Computes the minimum number of exchanges required to sort $X$, which corresponds to $\lvert X \rvert$ minus the number of cycles in the sequence. A cycle corresponds to a number of elements in a sequence that need to be rotated to be in their sorted position; for example, let $\langle 2, 4, 0, 6, 3, 1, 5 \rangle$ be a sequence, the cycles are $\langle 0, 2 \rangle$ and $\langle 1, 3, 4, 5, 6 \rangle$ so $\mathit{Exc}(X) = \lvert X \rvert - 2 = 5$. -**Warning:** `probe::exc` generally returns a result higher than the minimum number of exchanges required to sort $X$ when it contains *equivalent elements*. This is because extending $Exc$ to *equivalent elements* is a NP-hard problem (see *On the Cost of Interchange Rearrangement in Strings* by Amir et al). The function does handle such elements in some simple cases, but not in the general case. +**Warning:** `probe::exc` generally returns a result higher than the minimum number of exchanges required to sort $X$ when it contains *equivalent elements*. This is because extending $\mathit{Exc}$ to *equivalent elements* is a NP-hard problem (see *On the Cost of Interchange Rearrangement in Strings* by Amir et al). The function does handle such elements in some simple cases, but not in the general case. | Complexity | Memory | Iterators | Monotonic | | ----------- | ----------- | ------------- | --------- | @@ -254,7 +254,7 @@ Computes the minimum number of exchanges required to sort $X$, which corresponds `max_for_size`: $\lvert X \rvert - 1$ when every element in $X$ is one element away from its sorted position. -**Note:** *Exc* does not respect Mannila's criterion 3 (a subsequence contains no more disorder than the whole sequence): $Exc(\langle 3, 1, 2, 0 \rangle) = 1$, but $Exc(\langle 3, 1, 2 \rangle) = 2$. +**Note:** *Exc* does not respect Mannila's criterion 3 (a subsequence contains no more disorder than the whole sequence): $\mathit{Exc}(\langle 3, 1, 2, 0 \rangle) = 1$, but $\mathit{Exc}(\langle 3, 1, 2 \rangle) = 2$. *Warning: this algorithm might be noticeably slower when the passed range is not random-access.* @@ -272,9 +272,9 @@ Computes the number of elements in $X$ that are not in their sorted position, wh `max_for_size`: $\lvert X \rvert$ when every element in $X$ is one element away from its sorted position. -**Note:** *Ham* does not respect Mannila's criterion 3 (a subsequence contains no more disorder than the whole sequence): $Ham(\langle 3, 1, 2, 0 \rangle) = 2$, but $Ham(\langle 3, 1, 2 \rangle) = 3$. +**Note:** *Ham* does not respect Mannila's criterion 3 (a subsequence contains no more disorder than the whole sequence): $\mathit{Ham}(\langle 3, 1, 2, 0 \rangle) = 2$, but $\mathit{Ham}(\langle 3, 1, 2 \rangle) = 3$. -**Note²:** *Ham* does not respect Mannila's criterion 5: $Ham(\langle 4, 1, 2, 3 \rangle) \not \le \lvert \langle 1, 2, 3 \rangle \rvert + Ham(\langle 1, 2, 3 \rangle)$. +**Note²:** *Ham* does not respect Mannila's criterion 5: $\mathit{Ham}(\langle 4, 1, 2, 3 \rangle) \not \le \lvert \langle 1, 2, 3 \rangle \rvert + \mathit{Ham}(\langle 1, 2, 3 \rangle)$. ### *Inv* @@ -322,7 +322,7 @@ The measure of disorder is slightly different from its original description in [ `max_for_size`: $\frac{\lvert X \rvert + 1}{2} - 1$ when $X$ is a sequence of elements that are alternatively greater then lesser than their previous neighbour. -**Note:** `probe::mono` does not respect Mannila's criterion 4: $Mono(\langle 1, 2, 3, 4, 5 \rangle) = 0$ and $Mono(\langle 10, 9, 8, 7, 6 \rangle) = 0$, but $Mono(\langle 1, 2, 3, 4, 5, 10, 9, 8, 7, 6 \rangle) = 1$. +**Note:** `probe::mono` does not respect Mannila's criterion 4: $\mathit{Mono}(\langle 1, 2, 3, 4, 5 \rangle) = 0$ and $\mathit{Mono}(\langle 10, 9, 8, 7, 6 \rangle) = 0$, but $\mathit{Mono}(\langle 1, 2, 3, 4, 5, 10, 9, 8, 7, 6 \rangle) = 1$. ### *Osc* @@ -340,9 +340,9 @@ Computes the *Oscillation* measure described by C. Levcopoulos and O. Petersson **Note:** *Osc* does not seem to respect Mannila's criterion 3 in the presence of *equivalent elements*. -**Note²:** *Osc* does not respect Mannila's criterion 4: $Osc(\langle 0 \rangle) = 0$ and $Osc(\langle 3, 2, 1 \rangle) = 0$, but $Osc(\langle 0, 3, 2, 1 \rangle) = 2$. +**Note²:** *Osc* does not respect Mannila's criterion 4: $\mathit{Osc}(\langle 0 \rangle) = 0$ and $\mathit{Osc}(\langle 3, 2, 1 \rangle) = 0$, but $\mathit{Osc}(\langle 0, 3, 2, 1 \rangle) = 2$. -**Note³:** *Osc* does not respect Mannila's criterion 5: $Osc(\langle 3, 0, 4, 2, 5, 1 \rangle) \not \le \lvert \langle 0, 4, 2, 5, 1 \rangle \rvert + Osc(\langle 0, 4, 2, 5, 1 \rangle)$, simplified: $11 \not \le 5 + 5$. +**Note³:** *Osc* does not respect Mannila's criterion 5: $\mathit{Osc}(\langle 3, 0, 4, 2, 5, 1 \rangle) \not \le \lvert \langle 0, 4, 2, 5, 1 \rangle \rvert + \mathit{Osc}(\langle 0, 4, 2, 5, 1 \rangle)$, simplified: $11 \not \le 5 + 5$. ### *Rem* @@ -386,7 +386,7 @@ Spearman's footrule distance: sum of distances between the position of individua `max_for_size`: $\frac{\lvert X \rvert²}{2}$ when $X$ is sorted in reverse order. -**Note:** *Spear* does not respect Mannila's criterion 5: $Spear(\langle 4, 1, 2, 3 \rangle) \not \le \lvert \langle 1, 2, 3 \rangle \rvert + Spear(\langle 1, 2, 3 \rangle)$. +**Note:** *Spear* does not respect Mannila's criterion 5: $\mathit{Spear}(\langle 4, 1, 2, 3 \rangle) \not \le \lvert \langle 1, 2, 3 \rangle \rvert + \mathit{Spear}(\langle 1, 2, 3 \rangle)$. ### *SUS* @@ -428,7 +428,7 @@ The following definition is also given to determine whether a sequence is *p*-so *Right invariant metrics and measures of presortedness* by V. Estivill-Castro, H. Mannila and D. Wood mentions that: -> In fact, *Par*($X$) = *Dis*($X$), for all $X$. +> In fact, $\mathit{Par}(X) = \mathit{Dis}(X)$, for all $X$. In their subsequent papers, those authors consistently use *Dis* instead of *Par*, often accompanied by a link to *A New Measure of Presortedness*. diff --git a/docs/Original-research.md b/docs/Original-research.md index 3a8f0340..6931e259 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -204,39 +204,39 @@ Somehow Edelkamp and Weiß eventually [published a paper][quick-merge-sort-arxiv The measure of disorder *Mono* is described in [*Sort Race*][sort-race] by H. Zhang, B. Meng and Y. Liang. They describe it as follows: -> Intuitively, if $Mono(X) = k$, then $X$ is the concatenation of $k$ monotonic lists (either sorted or reversely sorted). +> Intuitively, if $\mathit{Mono}(X) = k$, then $X$ is the concatenation of $k$ monotonic lists (either sorted or reversely sorted). It computes the number of ascending or descending runs in $X$. Technically the definition in the paper makes it return 1 when the $X$ is sorted, which goes against Mannila's original definition of a measure of presortedness, which starts with the following criterion: > If $X$ is sorted, then $M(X) = 0$ -As a result the library's [`probe::mono`][probe-mono] uses $Mono(X) - 1$ instead, which does satisfy this first criterion, albeit not the fourth one: +As a result the library's [`probe::mono`][probe-mono] uses $\mathit{Mono}(X) - 1$ instead, which does satisfy this first criterion, albeit not the fourth one: -> If $X \le Y$, then $M(XY) ≤ M(X) + M(Y)$ +> If $X \le Y$, then $M(XY) \le M(X) + M(Y)$ -Counterexample: $Mono(\langle 1, 2, 3, 4, 5 \rangle) = 0$ and $Mono(\langle 10, 9, 8, 7, 6 \rangle) = 0$, but $Mono(\langle 1, 2, 3, 4, 5, 10, 9, 8, 7, 6 \rangle) = 1$. As such, we still don't have a definition of $Mono$ that satisfies all the criterion for a measure of presortedness. +Counterexample: $\mathit{Mono}(\langle 1, 2, 3, 4, 5 \rangle) = 0$ and $\mathit{Mono}(\langle 10, 9, 8, 7, 6 \rangle) = 0$, but $\mathit{Mono}(\langle 1, 2, 3, 4, 5, 10, 9, 8, 7, 6 \rangle) = 1$. As such, we still don't have a definition of $\mathit{Mono}$ that satisfies all the criteria for a measure of presortedness. Regardless, it is interesting to find how it fits in the existing partial ordering of measures of disorder.: -- $Mono \preceq Runs$: this relation is already mentioned in *Sort Race* and rather intuitive: since $Mono$ detects both non-increasing and non-decreasing runs, it is as least as good as $Runs$ that only detects non-decreasing runs. -- $SMS \preceq Mono$: this one seems intuitive too: $SMS$ which detects the minimum number of subsequences of non-adjacent elements should be at least as good as $Mono$ which only detects subsequences of adjacent elements. -- $Enc \preceq Mono$: when making encroaching lists, $Enc$ is guaranteed to create no more than one such new list per non-increasing or non-decreasing run found in $X$, so the result will be at most as big as that of $Mono$. However $Enc$ can also find presortedness in patterns such as $\langle 5, 6, 4, 7, 3, 8, 2, 9, 1, 10 \rangle$ where $Mono$ finds maximum disorder. Therefore $Enc(X)$ should always be at most as big as $Mono(X)$. -- $Mono \not \equiv SUS$: - - There is no constant $c$ such as $c \cdot SUS(X) \le Mono(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $Mono(X) = 1$ (a single decreasing run), but $SUS(X) = \lvert X \rvert$ (each element is decreasing, and as such constitues a new single-element ascending subsequence). - - There is no constant $c$ such as $c \cdot Mono(X) \le SUS(X)$: a sequence $X$ like $\langle 0, \frac{n}{2}, 1, \frac{n}{2} + 1, 2, \frac{n}{2} + 2, ..., \frac{n}{2} - 2, n - 1, \frac{n}{2} - 1, n \rangle$ always has $SUS(X) = 2$ (an ascending subsequence of even indices, another one of odd indices), but $Mono(X) = \frac{\lvert X \rvert}{2}$ (every pair of elements is a new descending run). -- $Mono \not \equiv Max$: - - There is no constant $c$ such as $c \cdot Max(X) \le Mono(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $Mono(X) = 1$ (a single decreasing run), but $Max(X) = \lvert X \rvert - 1$ (the distance between the smallest and greatest elements is maximal). - - There is no constant $c$ such as $c \cdot Mono(X) \le Max(X)$: a sequence $X$ like $\langle 1, 0, 3, 2, ..., n , n - 1 \rangle$ always has $Max(X) = 1$ (all inversions are with a neighbour, hence they all equal $1$), but $Mono(X) = \frac{\lvert X \rvert}{2}$ (every pair of elements is a new descending run). +- $\mathit{Mono} \preceq \mathit{Runs}$: this relation is already mentioned in *Sort Race* and rather intuitive: since $\mathit{Mono}$ detects both non-increasing and non-decreasing runs, it is as least as good as $\mathit{Runs}$ that only detects non-decreasing runs. +- $\mathit{SMS} \preceq \mathit{Mono}$: this one seems intuitive too: $\mathit{SMS}$ which detects the minimum number of subsequences of non-adjacent elements should be at least as good as $\mathit{Mono}$ which only detects subsequences of adjacent elements. +- $\mathit{Enc} \preceq \mathit{Mono}$: when making encroaching lists, $\mathit{Enc}$ is guaranteed to create no more than one such new list per non-increasing or non-decreasing run found in $X$, so the result will be at most as big as that of $\mathit{Mono}$. However $\mathit{Enc}$ can also find presortedness in patterns such as $\langle 5, 6, 4, 7, 3, 8, 2, 9, 1, 10 \rangle$ where $\mathit{Mono}$ finds maximum disorder. Therefore $\mathit{Enc}(X)$ should always be at most as big as $\mathit{Mono}(X)$. +- $\mathit{Mono} \not \equiv \mathit{SUS}$: + - There is no constant $c$ such as $c \cdot \mathit{SUS}(X) \le \mathit{Mono}(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $\mathit{Mono}(X) = 1$ (a single decreasing run), but $\mathit{SUS}(X) = \lvert X \rvert$ (each element is decreasing, and as such constitues a new single-element ascending subsequence). + - There is no constant $c$ such as $c \cdot \mathit{Mono}(X) \le \mathit{SUS}(X)$: a sequence $X$ like $\langle 0, \frac{n}{2}, 1, \frac{n}{2} + 1, 2, \frac{n}{2} + 2, ..., \frac{n}{2} - 2, n - 1, \frac{n}{2} - 1, n \rangle$ always has $\mathit{SUS}(X) = 2$ (an ascending subsequence of even indices, another one of odd indices), but $\mathit{Mono}(X) = \frac{\lvert X \rvert}{2}$ (every pair of elements is a new descending run). +- $\mathit{Mono} \not \equiv \mathit{Max}$: + - There is no constant $c$ such as $c \cdot \mathit{Max}(X) \le \mathit{Mono}(X)$: a sequence $X$ like $\langle n - 1, ..., 2, 1, 0 \rangle$ always has $\mathit{Mono}(X) = 1$ (a single decreasing run), but $\mathit{Max}(X) = \lvert X \rvert - 1$ (the distance between the smallest and greatest elements is maximal). + - There is no constant $c$ such as $c \cdot \mathit{Mono}(X) \le \mathit{Max}(X)$: a sequence $X$ like $\langle 1, 0, 3, 2, ..., n , n - 1 \rangle$ always has $\mathit{Max}(X) = 1$ (all inversions are with a neighbour, hence they all equal $1$), but $\mathit{Mono}(X) = \frac{\lvert X \rvert}{2}$ (every pair of elements is a new descending run). The following relations can be transitively deduced from the results presented in *A framework for adaptive sorting*: -- $Mono \not \preceq Exc$: we know that $SMS \preceq Mono$ and $SMS \not \preceq Exc$. -- $Mono \not \preceq Inv$: we know that $SMS \preceq Mono$ and $SMS \not \preceq Inv$. -- $Hist \not \preceq Mono$: we know that $Mono \preceq Runs$ and $Hist \not \preceq Runs$. +- $\mathit{Mono} \not \preceq \mathit{Exc}$: we know that $\mathit{SMS} \preceq \mathit{Mono}$ and $\mathit{SMS} \not \preceq \mathit{Exc}$. +- $\mathit{Mono} \not \preceq \mathit{Inv}$: we know that $\mathit{SMS} \preceq \mathit{Mono}$ and $\mathit{SMS} \not \preceq \mathit{Inv}$. +- $\mathit{Hist} \not \preceq \mathit{Mono}$: we know that $\mathit{Mono} \preceq \mathit{Runs}$ and $\mathit{Hist} \not \preceq \mathit{Runs}$. The following relations have yet to be analyzed: -- $Osc \preceq Mono$ -- $Loc \preceq Mono$ +- $\mathit{Osc} \preceq \mathit{Mono}$ +- $\mathit{Loc} \preceq \mathit{Mono}$ -Another interesting property of $Mono$ is that it returns the same amount of disorder for a sequence $X$ and for a sequence corresponding to $X$ with the order of all elements reversed, a property that is only shared with $Osc$ in the library. +Another interesting property of $\mathit{Mono}$ is that it returns the same amount of disorder for a sequence $X$ and for a sequence corresponding to $X$ with the order of all elements reversed, a property that is only shared with $\mathit{Osc}$ in the library. [better-sorting-networks]: https://etd.ohiolink.edu/!etd.send_file?accession=kent1239814529 From 5f66f2b3d37fcb1a6a365d0045f5bd8ed639c063 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 18 Dec 2025 16:06:00 +0100 Subject: [PATCH 22/40] Don't pass --rng-seed in catch_discover_tests Somehow it took me *that* long to remember that catch_discover_tests is used to run one test per execution of the test executable with ctest, which makes seeding the PRNG for the test run order useless. --- tests/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 903a0a21..15c5ed50 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -305,8 +305,7 @@ endif() include(CTest) -string(RANDOM LENGTH 6 ALPHABET 123456789 RNG_SEED) -catch_discover_tests(main-tests EXTRA_ARGS --rng-seed ${RNG_SEED}) +catch_discover_tests(main-tests) if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") - catch_discover_tests(heap-memory-exhaustion-tests EXTRA_ARGS --rng-seed ${RNG_SEED}) + catch_discover_tests(heap-memory-exhaustion-tests) endif() From b1383a37b29c40f863643bb57aef661f508ab2cc Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 21 Dec 2025 23:43:49 +0100 Subject: [PATCH 23/40] Mark libassert as a SYSTEM library --- CMakeLists.txt | 2 ++ cmake/cpp-sort-utils.cmake | 16 +++++++++++++++- tests/CMakeLists.txt | 10 ++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55b3897d..3d9f3b98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) project(cpp-sort VERSION 2.0.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) +include(cpp-sort-utils) include(GNUInstallDirs) # Project options @@ -30,6 +31,7 @@ if (CPPSORT_USE_LIBASSERT) if (NOT libassert_POPULATED) FetchContent_Populate(libassert) add_subdirectory(${libassert_SOURCE_DIR} ${libassert_BINARY_DIR}) + mark_system_library(libassert-lib) endif() endif() diff --git a/cmake/cpp-sort-utils.cmake b/cmake/cpp-sort-utils.cmake index fb946ef1..f7150ae6 100644 --- a/cmake/cpp-sort-utils.cmake +++ b/cmake/cpp-sort-utils.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2023 Morwenn +# Copyright (c) 2019-2025 Morwenn # SPDX-License-Identifier: MIT # Add a selection of warnings to a target @@ -15,3 +15,17 @@ macro(cppsort_add_warnings target) ) endif() endmacro() + +# Mark a target as a SYSTEM library +function(mark_system_library target) + get_target_property( + TARGET_INCLUDE_DIR + ${target} + INTERFACE_INCLUDE_DIRECTORIES + ) + set_target_properties( + ${target} + PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${TARGET_INCLUDE_DIR}" + ) +endfunction() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 15c5ed50..fa81bc6c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,9 +32,8 @@ else() FetchContent_Populate(Catch2) add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR}) list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) - # Make Catch2::Catch2 a SYSTEM target - get_target_property(catch2_INCLUDE_DIRECTORY Catch2 INTERFACE_INCLUDE_DIRECTORIES) - set_target_properties(Catch2 PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${catch2_INCLUDE_DIRECTORY}") + mark_system_library(Catch2) + mark_system_library(Catch2WithMain) endif() endif() include(Catch) @@ -59,9 +58,8 @@ else() FetchContent_Populate(RapidCheck) set(RC_INSTALL_ALL_EXTRAS ON) add_subdirectory(${rapidcheck_SOURCE_DIR} ${rapidcheck_BINARY_DIR}) - # Make rapidcheck a SYSTEM target - get_target_property(rapidcheck_INCLUDE_DIRECTORY rapidcheck INTERFACE_INCLUDE_DIRECTORIES) - set_target_properties(rapidcheck PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${rapidcheck_INCLUDE_DIRECTORY}") + mark_system_library(rapidcheck) + mark_system_library(rapidcheck_catch) endif() endif() From c60447e609968a55d9c3c93fb7d86e00bbca683a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 29 Dec 2025 23:36:07 +0100 Subject: [PATCH 24/40] Upgrade downloaded Catch2 version to v3.12.0 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fa81bc6c..240f1888 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,7 +25,7 @@ else() FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG b3fb4b9feafcd8d91c5cb510a4775143fdbef02f # v3.11.0 + GIT_TAG 88abf9bf325c798c33f54f6b9220ef885b267f4f # v3.12.0 ) FetchContent_GetProperties(Catch2) if (NOT Catch2_POPULATED) From 496eadcbdde63a180b4e315bb3fd6d991efa6dc1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Jan 2026 18:50:25 +0100 Subject: [PATCH 25/40] Add Forgejo action to deploy docs to Codeberg Wiki --- .forgejo/workflows/deploy-to-wiki.yml | 55 +++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .forgejo/workflows/deploy-to-wiki.yml diff --git a/.forgejo/workflows/deploy-to-wiki.yml b/.forgejo/workflows/deploy-to-wiki.yml new file mode 100644 index 00000000..eda21ce6 --- /dev/null +++ b/.forgejo/workflows/deploy-to-wiki.yml @@ -0,0 +1,55 @@ +# Copyright (c) 2026 Morwenn +# SPDX-License-Identifier: MIT + +name: Wiki Deployment + +on: + push: + branches: + - trunk + paths: + - 'docs/**' + workflow_dispatch: + +jobs: + sync-wiki-files: + name: Sync Wiki Files + + runs-on: codeberg-tiny-lazy + + steps: + - name: Checkout /docs + uses: actions/checkout@v6 + with: + repository: ${{forgejo.repository}} + path: main + + - name: Checkout wiki + uses: actions/checkout@v4 + with: + repository: ${{forgejo.repository}}.wiki + path: wiki + + - name: Sync wiki files + run: | + apt-get update + apt-get install -y rsync + for docname in main/docs/*.md; do + old=$(basename "$docname"); + new=${old%.*}; + find main/docs -name "*.md" -exec sed -i "s/$old/$new/g" {} \; + done + rsync -avzr --delete --exclude='.git/' "main/docs/" "wiki/" + + - name: Commit changes + working-directory: wiki + run: | + git config --local user.email "action@forgejo.org" + git config --local user.name "Forgejo Action" + git add . + git commit -m "Synchronize wiki with docs/" + + - name: Push changes to wiki + working-directory: wiki + run: | + git push From 1faa5ce44fa8daad61141f56ce2156cad76b9f38 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Jan 2026 19:29:14 +0100 Subject: [PATCH 26/40] Embrase the dual GitHub/Codeberg nature of the project --- README.md | 47 +++++++++++++--------- conanfile.py | 2 +- docs/Changelog.md | 2 +- docs/Quickstart.md | 4 +- docs/Sorter-facade.md | 2 +- docs/Sorters.md | 1 - docs/Writing-a-bubble_sorter.md | 2 +- docs/Writing-a-randomizing_adapter.md | 3 +- include/cpp-sort/detail/quick_merge_sort.h | 4 +- tools/release_template.md | 2 +- 10 files changed, 37 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d9552910..fd4e058d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![cpp-sort logo](docs/images/cpp-sort-logo.svg) -[![Latest Release](https://img.shields.io/badge/release-2.0.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/v2.0.0) +[![Latest Release](https://img.shields.io/badge/release-2.0.0-blue.svg)](https://codeberg.org/Morwenn/cpp-sort/releases/tag/v2.0.0) [![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F2.0.0-blue.svg)](https://conan.io/center/recipes/cpp-sort?version=2.0.0) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/2.x.y-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) @@ -41,11 +41,11 @@ anything to be backported._ **cpp-sort** provides a full set of sorting-related features. Here are the main building blocks of the library: -* Every sorting algorithm exists as a function object called a [sorter](https://github.com/Morwenn/cpp-sort/wiki/Sorters) -* Sorters can be wrapped in [sorter adapters](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters) to augment their behaviour -* The library provides a [sorter facade](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) to easily build sorters -* [Fixed-size sorters](https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters) can be used to efficiently sort tiny fixed-size collections -* [Measures of disorder](https://github.com/Morwenn/cpp-sort/wiki/Measures-of-disorder) can be used to evaluate the disorder in a collection +* Every sorting algorithm exists as a function object called a [sorter][sorters] +* Sorters can be wrapped in [sorter adapters][sorter-adapters] to augment their behaviour +* The library provides a [sorter facade][sorter-facade] to easily build sorters +* [Fixed-size sorters][fixed-size-sorters] can be used to efficiently sort tiny fixed-size collections +* [Measures of disorder][measures-of-disorder] can be used to evaluate the disorder in a collection Here is a more complete example of what can be done with the library: @@ -97,7 +97,7 @@ some interesting guarantees (ideas often taken from the Ranges TS): * Sorters are function objects: they can directly be passed as "overload sets" to other functions You can read more about all the available tools and find some tutorials about using -and extending **cpp-sort** in [the wiki](https://github.com/Morwenn/cpp-sort/wiki). +and extending **cpp-sort** in [the wiki][cpp-sort-wiki]. # Benchmarks @@ -150,7 +150,7 @@ You can read more about those [in the wiki][tooling]. > piece.* > — Jarod Kintz, $3.33 -Even though some parts of the library are [original research](https://github.com/Morwenn/cpp-sort/wiki/Original-research) +Even though some parts of the library are [original research][original-research] and some others correspond to custom and rather naive implementations of standard sorting algorithms, **cpp-sort** also reuses a great deal of code and ideas from open-source projects, often altered to integrate seamlessly into the library. Here @@ -175,12 +175,11 @@ module](https://www.boost.org/doc/libs/1_80_0/libs/sort/doc/html/index.html). 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), -and several projection-enhanced helper algorithms come from Eric Niebler's [Range -v3](https://github.com/ericniebler/range-v3) library. Several ideas such as proxy -iterators, customization points and projections, as well as a few other utility -functions also come from that library or from the related articles and standard -C++ proposals. +* [`utility::as_function`][utility-as-function], and several projection-enhanced helper +algorithms come from Eric Niebler's [Range v3](https://github.com/ericniebler/range-v3) +library. Several ideas such as proxy iterators, customization points and projections, +as well as a few other utility functions also come from that library or from the related +articles and standard C++ proposals. * The algorithm used by `ska_sorter` comes from Malte Skarupke's [implementation](https://github.com/skarupke/ska_sort) of his own [ska_sort](https://probablydance.com/2016/12/27/i-wrote-a-faster-sorting-algorithm/) algorithm. @@ -248,9 +247,17 @@ developed by Thøger Rivera-Thorsen. [adaptive-sort]: https://en.wikipedia.org/wiki/Adaptive_sort - [benchmarks]: https://github.com/Morwenn/cpp-sort/wiki/Benchmarks - [changelog]: https://github.com/Morwenn/cpp-sort/wiki/Changelog - [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 - [tooling]: https://github.com/Morwenn/cpp-sort/wiki/Tooling + [benchmarks]: https://codeberg.org/Morwenn/cpp-sort/wiki/Benchmarks + [changelog]: https://codeberg.org/Morwenn/cpp-sort/wiki/Changelog + [cpp-sort-wiki]: https://codeberg.org/Morwenn/cpp-sort/wiki + [drop-merge-adapter]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorter-adapters#drop_merge_adapter + [fixed-size-sorters]: https://codeberg.org/Morwenn/cpp-sort/wiki/Fixed-size-sorters + [heap-sorter]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorters#heap_sorter + [measures-of-disorder]: https://codeberg.org/Morwenn/cpp-sort/wiki/Measures-of-disorder + [original-research]: https://codeberg.org/Morwenn/cpp-sort/wiki/Original-research + [sorter-adapters]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorter-adapters + [sorter-facade]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorter-facade + [sorters]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorters + [split-adapter]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorter-adapters#split_adapter + [tooling]: https://codeberg.org/Morwenn/cpp-sort/wiki/Tooling + [utility-as-function]: https://codeberg.org/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function diff --git a/conanfile.py b/conanfile.py index 49cfb2f6..88e17b00 100644 --- a/conanfile.py +++ b/conanfile.py @@ -19,7 +19,7 @@ class CppSortConan(ConanFile): version = "2.0.0" description = "Sorting algorithms & related tools" license = "MIT" - url = "https://github.com/Morwenn/cpp-sort" + url = "https://codeberg.org/Morwenn/cpp-sort" homepage = url topics = "cpp-sort", "sorting", "algorithms" author = "Morwenn " diff --git a/docs/Changelog.md b/docs/Changelog.md index 882f1b5e..f52a0847 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -40,7 +40,7 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe [branchless-traits]: Miscellaneous-utilities.md#branchless-traits [counting-sorter]: Sorters.md#counting_sorter [cpp-sort-function-objects]: Miscellaneous-utilities.md#miscellaneous-function-objects - [cpp-sort-releases]: https://github.com/Morwenn/cpp-sort/releases + [cpp-sort-releases]: https://codeberg.org/Morwenn/cpp-sort/releases [feature-test-macros]: https://wg21.link/SD6 [pdq-sorter]: Sorters.md#pdq_sorter [ska-sorter]: Sorters.md#ska_sorter diff --git a/docs/Quickstart.md b/docs/Quickstart.md index 6d332e3b..ba35c45d 100644 --- a/docs/Quickstart.md +++ b/docs/Quickstart.md @@ -228,5 +228,5 @@ The previous sections describe some of the main tools provided by **cpp-sort** b [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 + [utility-apply-permutation]: Miscellaneous-utilities.md#apply_permutation + [utility-sorted-indices]: Miscellaneous-utilities.md#sorted_indices diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index 1ad9310d..77b1e7f4 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -45,7 +45,7 @@ The return type `Ret` can either match that of the sorter, or be `void`, in whic Note that the function pointer conversion syntax above is made up, but it allows to clearly highlight what it does while hiding the `typedef`s needed for the syntax to be valid. In these signatures, `Ret` is the [`std::result_of_t`][std-result-of] of the sorter called with the parameters. The actual implementation is more verbose and redundant, but it allows to transform a sorter into a function pointer corresponding to any valid overload of `operator()`. -***WARNING:** conversion to function pointers does not work with MSVC ([issue #185][issue-185]).* +***WARNING:** conversion to function pointers does not work with MSVC ([issue github#185][issue-185]).* ## `operator()` diff --git a/docs/Sorters.md b/docs/Sorters.md index 820726d2..ca82711c 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -465,7 +465,6 @@ struct spread_sorter: [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 [measures-of-disorder]: Measures-of-disorder.md [median-of-medians]: https://en.wikipedia.org/wiki/Median_of_medians [merge-sort]: https://en.wikipedia.org/wiki/Merge_sort diff --git a/docs/Writing-a-bubble_sorter.md b/docs/Writing-a-bubble_sorter.md index 98bc4e91..2ba74f75 100644 --- a/docs/Writing-a-bubble_sorter.md +++ b/docs/Writing-a-bubble_sorter.md @@ -286,7 +286,7 @@ Generic agorithms are good, more generic algorithms are sometimes better. The cu C++20 ranges introduce the notion of ["proxy iterators"][proxy-iterators], which are basically iterators that can't yield a proper reference to the object they point to, but instead yield a proxy object acting as a reference. In order to handle such iterators, C++20 introduces the *customization point objects* [`std::ranges::iter_move`][std-iter-move] and [`std::ranges::iter_swap`][std-iter-swap] which should be used instead of `std::move(*it)` and `std::iter_swap(it1, it2)` in generic algorithms that aim to support proxy iterators. -**cpp-sort** being a C++17 library, it can't rely on these CPOs and provides the utility functions [`utility::iter_move` and `utility::iter_swap`][utility-iter-move] to replace them. They are a bit cruder than their standard equivalents: you have to import them into the current namespace and perform an unqualified call, *à la* `std::swap`. Moreover, they are currently not compatible with their C++20 counterparts yet for legacy reasons (see [issue 223][issue-223]). +**cpp-sort** being a C++17 library, it can't rely on these CPOs and provides the utility functions [`utility::iter_move` and `utility::iter_swap`][utility-iter-move] to replace them. They are a bit cruder than their standard equivalents: you have to import them into the current namespace and perform an unqualified call, *à la* `std::swap`. Moreover, they are currently not compatible with their C++20 counterparts yet for legacy reasons (see [issue github#223][issue-223]). ```cpp template diff --git a/docs/Writing-a-randomizing_adapter.md b/docs/Writing-a-randomizing_adapter.md index 2e3437c0..a29c274a 100644 --- a/docs/Writing-a-randomizing_adapter.md +++ b/docs/Writing-a-randomizing_adapter.md @@ -72,7 +72,7 @@ When possible, a proper *sorter adapter* is expected to be callable with the sam ## Returned value -There is currently no strict rule about what a *sorter adapter* should return (this is actually a [open design issue][issue-134]), though the general wisdom is that an adapter should transparently provide as many features as the sorter it adapts when it reasonably can. The idea is that replacing the sorter by its wrapped counterpart should be easy. +There is no strict rule about what a *sorter adapter* should return. The general wisdom is that an adapter should transparently provide as many features as the *sorter* it adapts when it reasonably can unless it has good reasons to to otherwise. The idea is that replacing the sorter by its wrapped counterpart should be easy. We don't have a specific use for the return channel of `randomizing_adapter` and it is simple to make it transitively return whatever the wrapped sorter returns - and even convenient -, so I decided to do just that. @@ -142,7 +142,6 @@ The full implementation can be found in the `examples` folder. [ctad]: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction [golden-tests]: https://en.wikipedia.org/wiki/Characterization_test [hyrums-law]: https://www.hyrumslaw.com/ - [issue-134]: https://github.com/Morwenn/cpp-sort/issues/134 [iterator-category]: https://en.cppreference.com/w/cpp/iterator [proxy-iterators]: https://wg21.link/P0022 [quick-sorter]: Sorters.md#quick_sorter diff --git a/include/cpp-sort/detail/quick_merge_sort.h b/include/cpp-sort/detail/quick_merge_sort.h index 38a0ea1d..46071856 100644 --- a/include/cpp-sort/detail/quick_merge_sort.h +++ b/include/cpp-sort/detail/quick_merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 Morwenn + * Copyright (c) 2018-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_QUICK_MERGE_SORT_H_ @@ -138,7 +138,7 @@ namespace cppsort::detail internal_mergesort(first, pivot, size_left, pivot, compare, projection); if (std::is_base_of_v>) { - // Avoid weird codegen bug with MinGW-w64 (see GitHub issue #151) + // Avoid weird codegen bug with MinGW-w64 (see issue github#151) std::advance(first, size_left); } else { first = pivot; diff --git a/tools/release_template.md b/tools/release_template.md index 570aa302..5988af65 100644 --- a/tools/release_template.md +++ b/tools/release_template.md @@ -46,5 +46,5 @@ Miscellaneous: I didn't manage to fix every bug I could find since the previous release, so you might want to check the [list of known bugs][known-bugs]. - [deprecation-warnings]: https://github.com/Morwenn/cpp-sort/wiki#deprecation-warnings + [deprecation-warnings]: https://codeberg.org/Morwenn/cpp-sort/wiki#deprecation-warnings [known-bugs]: https://github.com/Morwenn/cpp-sort/issues?q=is%3Aissue+is%3Aopen+label%3Abug From 315fe6223ae2fd08665e772f78e5d5cae3a9e7ee Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 3 Jan 2026 23:51:06 +0100 Subject: [PATCH 27/40] Documentation: fix a few links --- docs/Comparators.md | 4 ++-- docs/Original-research.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Comparators.md b/docs/Comparators.md index 28bdd16d..0fb46650 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -97,11 +97,11 @@ The two-parameter version of the customization point calls the three-parameter o *This comparator can be [refined][refining] for a specific type to provide better performance.* - [binary-predicate]: https://en.cppreference.com/w/cpp/concept/BinaryPredicate + [binary-predicate]: https://en.cppreference.com/w/cpp/named_req/BinaryPredicate [branchless-traits]: Miscellaneous-utilities.md#branchless-traits [callable]: https://en.cppreference.com/w/cpp/named_req/Callable [case-sensitivity]: https://en.wikipedia.org/wiki/Case_sensitivity - [cppcon2015-compare]: https://github.com/CppCon/CppCon2015/tree/master/Presentations/Comparison%20is%20not%20simple%2C%20but%20it%20can%20be%20simpler%20-%20Lawrence%20Crowl%20-%20CppCon%202015 + [cppcon2015-compare]: https://github.com/CppCon/CppCon2015/tree/master/Presentations/Comparison%20is%20not%20simple%2C%20but%20it%20can%20be%20simpler [custom-point]: https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ [natural-sort]: https://en.wikipedia.org/wiki/Natural_sort_order [P0100]: http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0100r1.html diff --git a/docs/Original-research.md b/docs/Original-research.md index 6931e259..8416583d 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -241,7 +241,7 @@ Another interesting property of $\mathit{Mono}$ is that it returns the same amou [better-sorting-networks]: https://etd.ohiolink.edu/!etd.send_file?accession=kent1239814529 [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort - [divide-sort-merge-strategy]: http://www.dtic.mil/dtic/tr/fulltext/u2/737270.pdf + [divide-sort-merge-strategy]: https://apps.dtic.mil/sti/tr/pdf/AD0737270.pdf [exact-sort]: https://www.geocities.ws/p356spt/ [indirect-adapter]: Sorter-adapters.md#indirect_adapter [morwenn-gist]: https://gist.github.com/Morwenn From 48225afa8fe6e183835c8284e80bcafcbc19bf8f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 11 Jan 2026 20:28:51 +0100 Subject: [PATCH 28/40] Add utility::check_strict_weak_ordering --- README.md | 3 + docs/Miscellaneous-utilities.md | 26 ++++ .../utility/check_strict_weak_ordering.h | 125 ++++++++++++++++++ tests/CMakeLists.txt | 3 +- tests/utility/check_strict_weak_ordering.cpp | 94 +++++++++++++ 5 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 include/cpp-sort/utility/check_strict_weak_ordering.h create mode 100644 tests/utility/check_strict_weak_ordering.cpp diff --git a/README.md b/README.md index fd4e058d..972a2fd5 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,8 @@ Libraries*](https://arxiv.org/abs/1505.01962). * The algorithm behind `utility::quicksort_adversary` is a fairly straightforward adaptation of the one provided by M. D. McIlroy in [*A Killer Adversary for Quicksort*](https://www.cs.dartmouth.edu/~doug/mdmspe.pdf). +* The algorithm used by [`utility::check_strict_weak_ordering`][utility-check-strict-weak-ordering] is a reimplementation of the one desribed in the README file of danlark1's [quadratic_strict_weak_ordering project](https://github.com/danlark1/quadratic_strict_weak_ordering). + * The test suite reimplements random number algorithms originally found in the following places: - [xoshiro256\*\*](https://prng.di.unimi.it/) - [*Optimal Discrete Uniform Generation from Coin Flips, and Applications*](https://arxiv.org/abs/1304.1916) @@ -261,3 +263,4 @@ developed by Thøger Rivera-Thorsen. [split-adapter]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorter-adapters#split_adapter [tooling]: https://codeberg.org/Morwenn/cpp-sort/wiki/Tooling [utility-as-function]: https://codeberg.org/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function + [utility-check-strict-weak-ordering]: https://codeberg.org/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#strict-weak-ordering-checker diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index dfe75184..dc81fc0f 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -491,6 +491,31 @@ auto swap_index_pairs_force_unroll(RandomAccessIterator first, `swap_index_pairs` loops over the index pairs in the simplest fashion and calls the compare-exchange operations in the simplest possible way. `swap_index_pairs_force_unroll` is a best effort function trying to achieve the same job by unrolling the loop over indices the best it can - a perfect unrolling is thus attempted, but never guaranteed, which might or might not result in faster runtime and/or increased binary size. +## Strict weak ordering checker + +```cpp +#include +``` + +Comparison sorting requires the comparison function to model a [strict weak ordering][strict-weak-ordering] over the values of the range to sort. Otherwise, the sorting algorithm might fail to sort the collection, or encounter even fail in hard-to-predict ways, potentially invoking undefined behavior. + +Checking whether a comparison function models such an ordering for a given is an expensive task, which means that it is remains an unchecked precondition of algorithms in the library. If you suspect that a bug with a comparison sort might be linked to a violation of the strict weak ordering by the comparison function, you can use `utility::check_strict_weak_ordering` to analyze it over a given range: + +```cpp +std::vector vec = { 1.0, 9.0, std::nan("1"), 11.5, 56.3, 2.8 }; +assert(not cppsort::utility::check_strict_weak_ordering(vec, std::less{});) +``` + +`check_strict_weak_ordering` is a function object that follows the *unified sorting interface*: it takes a range of elements and a comparison function (and optionally a projection function). When called, it returns `true` if the passed comparison function models a strict weak ordering over the values of the input range, and `false` otherwise. + +**WARNING: `check_strict_weak_ordering` alters the input range.** + +| Time | Memory | Iterators | +| ---- | ------ | ------------- | +| n² | 1 | Random-access | + +*New in version 2.1.0* + [apply-permutation]: Miscellaneous-utilities.md#apply_permutation [chainable-projections]: Chainable-projections.md @@ -524,4 +549,5 @@ auto swap_index_pairs_force_unroll(RandomAccessIterator first, [std-ranges-greater]: https://en.cppreference.com/w/cpp/utility/functional/ranges/greater [std-ranges-less]: https://en.cppreference.com/w/cpp/utility/functional/ranges/less [std-size]: https://en.cppreference.com/w/cpp/iterator/size + [strict-weak-ordering]: https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings [transparent-func]: Comparators-and-projections.md#Transparent-function-objects diff --git a/include/cpp-sort/utility/check_strict_weak_ordering.h b/include/cpp-sort/utility/check_strict_weak_ordering.h new file mode 100644 index 00000000..05382e69 --- /dev/null +++ b/include/cpp-sort/utility/check_strict_weak_ordering.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_UTILITY_CHECK_STRICT_WEAK_ORDERING_H_ +#define CPPSORT_UTILITY_CHECK_STRICT_WEAK_ORDERING_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include "../detail/heapsort.h" +#include "../detail/is_sorted_until.h" +#include "../detail/type_traits.h" + +namespace cppsort::utility +{ + //////////////////////////////////////////////////////////// + // Check whether a comparison function implements a strict + // weak ordering over a range of data, following an + // algorithm described by danlark1 here: + // https://github.com/danlark1/quadratic_strict_weak_ordering + + namespace detail + { + template + constexpr auto compare_equivalent(ForwardIterator it1, ForwardIterator it2, + Compare compare, Projection projection) + -> bool + { + return not compare(projection(*it1), projection(*it2)) + && not compare(projection(*it2), projection(*it1)); + } + + struct strict_weak_ordering_checker_impl + { + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = cppsort::detail::enable_if_t< + is_projection_iterator_v + > + > + constexpr auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> bool + { + // In the comments below, we use the following abbreviations: + // - R is the input range + // - C is the comparison function + // - P is the projeciton function + + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + while (first != last) { + // 1. Sort R + // + // Note: standard library implementations of heapsort supposedly do not + // crash when passed a comparison function that does not model a strict + // weak ordering, and ours happens to be copy-pasted from libc++ + cppsort::detail::heapsort(first, last, compare, projection); + + // 2. If the R is not sorted, then C does not model a strict weak ordering + if (not cppsort::detail::is_sorted(first, last, compare, projection)) { + return false; + } + + // 3. Find first it such as *first < *it + auto it = std::next(first); + while (it != last && not comp(proj(*first), proj(*it))) { + ++it; + } + + // 4. Check that all elements before it compare equivalent + for (auto it1 = first; it1 != it; ++it1) { + for (auto it2 = it1; it2 != it; ++it2) { + if (not compare_equivalent(it1, it2, comp, proj)) { + return false; + } + } + } + + // 5. Check that all elements separated by it follow transitivity + for (auto it1 = first; it1 != it; ++it1) { + for (auto it2 = it; it2 != last; ++it2) { + if (comp(proj(*it2), proj(*it1))) { + return false; + } + if (not comp(proj(*it1), proj(*it2))) { + return false; + } + } + } + + // Exclude leading elements that compare equivalent, + // start all over again with the rest of the elements + first = it; + } + + // All checks passed, C models a strict weak ordering over R + return true; + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::random_access_iterator_tag; + }; + } + + struct strict_weak_ordering_checker: + sorter_facade + {}; + + inline constexpr strict_weak_ordering_checker check_strict_weak_ordering{}; +} + +#endif // CPPSORT_UTILITY_CHECK_STRICT_WEAK_ORDERING_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 240f1888..1c61e4de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2025 Morwenn +# Copyright (c) 2015-2026 Morwenn # SPDX-License-Identifier: MIT include(cpp-sort-utils) @@ -263,6 +263,7 @@ add_executable(main-tests utility/branchless_traits.cpp utility/buffer.cpp utility/chainable_projections.cpp + utility/check_strict_weak_ordering.cpp utility/iter_swap.cpp utility/metric_tools.cpp utility/quicksort_adversary.cpp diff --git a/tests/utility/check_strict_weak_ordering.cpp b/tests/utility/check_strict_weak_ordering.cpp new file mode 100644 index 00000000..f9932628 --- /dev/null +++ b/tests/utility/check_strict_weak_ordering.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include + +TEST_CASE( "check_strict_weak_ordering test", "[utility][check_strict_weak_ordering]" ) +{ + using cppsort::utility::check_strict_weak_ordering; + + SECTION( "empty collection" ) + { + std::vector vec = {}; + CHECK( check_strict_weak_ordering(vec) ); + } + + SECTION( "one element" ) + { + std::vector vec = { 0 }; + CHECK( check_strict_weak_ordering(vec) ); + } + + SECTION( "one element, NaN" ) + { + std::vector vec = { std::nan("1") }; + CHECK( check_strict_weak_ordering(vec) ); + } + + SECTION( "empty collection" ) + { + std::vector vec = {}; + CHECK( check_strict_weak_ordering(vec) ); + } + + SECTION( "two elements" ) + { + std::vector vec1 = { 0, 0 }; + CHECK( check_strict_weak_ordering(vec1) ); + + std::vector vec2 = { 0, 5 }; + CHECK( check_strict_weak_ordering(vec2) ); + + std::vector vec3 = { 5, 0 }; + CHECK( check_strict_weak_ordering(vec3) ); + } + + SECTION( "two elements, NaN" ) + { + std::vector vec1 = { std::nan("1"), std::nan("1") }; + CHECK( check_strict_weak_ordering(vec1) ); + + std::vector vec2 = { std::nan("1"), 5 }; + CHECK( check_strict_weak_ordering(vec2) ); + + std::vector vec3 = { 5, std::nan("1") }; + CHECK( check_strict_weak_ordering(vec3) ); + } + + SECTION( "small collection" ) + { + std::vector vec = { 1, 4, 32, 5, 89, 43, 56, 8, 7, 2, 44, 37, 73 }; + CHECK( check_strict_weak_ordering(vec) ); + } + + SECTION( "small collection with duplicates" ) + { + std::vector vec = { 1, 4, 32, 5, 1, 89, 43, 56, 8, 7, 2, 2, 4, 44, 37, 73 }; + CHECK( check_strict_weak_ordering(vec) ); + } + + SECTION( "small collection with NaN" ) + { + std::vector vec = { + 1.0, 4.0, 32.0, 5.0, 89.0, 43.0, 56.0, 345.0, + 8.0, 7.0, 2.0, std::nan("2"), 44.0, 37.0, 73.0, + }; + CHECK_FALSE( check_strict_weak_ordering(vec) ); + } + + SECTION( "small collection with std::less_equal" ) + { + std::vector vec = { 1, 4, 32, 5, 89, 43, 56, 8, 7, 2, 44, 37, 73 }; + CHECK_FALSE( check_strict_weak_ordering(vec, std::less_equal{}) ); + } + + SECTION( "small collection with duplicates with std::less_equal" ) + { + std::vector vec = { 1, 4, 32, 5, 1, 89, 43, 56, 8, 7, 2, 2, 4, 44, 37, 73 }; + CHECK_FALSE( check_strict_weak_ordering(vec, std::less_equal{}) ); + } +} From 2b69ba2765a9cdc18f6356e237181e5cb8053c29 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 12 Jan 2026 01:34:48 +0100 Subject: [PATCH 29/40] Add utility::is_sorted[_until] --- docs/Miscellaneous-utilities.md | 13 ++++ include/cpp-sort/utility/is_sorted.h | 58 +++++++++++++++ include/cpp-sort/utility/is_sorted_until.h | 58 +++++++++++++++ tests/CMakeLists.txt | 1 + tests/utility/is_sorted.cpp | 84 ++++++++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 include/cpp-sort/utility/is_sorted.h create mode 100644 include/cpp-sort/utility/is_sorted_until.h create mode 100644 tests/utility/is_sorted.cpp diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index dc81fc0f..0b82ba7a 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -137,6 +137,17 @@ struct dynamic_buffer; This buffer provider allocates on the heap a number of elements depending on a given *size policy* (a class whose `operator()` takes the size of the collection and returns another size). You can use the function objects from `utility/functional.h` as basic size policies. The buffer construction may throw an instance of [`std::bad_alloc`][std-bad-alloc] if it fails to allocate the required memory. +### `is_sorted` and `is_sorted_until` + +```cpp +#include +#include +``` + +Simple reimplementations of the standard library algorithms [`std::is_sorted`][std-is-sorted] and [`std::is_sorted_until`][std-is-sorted-until], reimplemented as function objects that follow the library's *unified sorting interface*. + +*New in version 2.1.0* + ### Miscellaneous function objects ```cpp @@ -543,6 +554,8 @@ assert(not cppsort::utility::check_strict_weak_ordering(vec, std::less{});) [std-invoke]: https://en.cppreference.com/w/cpp/utility/functional/invoke [std-is-arithmetic]: https://en.cppreference.com/w/cpp/types/is_arithmetic [std-is-member-function-pointer]: https://en.cppreference.com/w/cpp/types/is_member_function_pointer + [std-is-sorted]: https://en.cppreference.com/w/cpp/algorithm/is_sorted.html + [std-is-sorted-until]: https://en.cppreference.com/w/cpp/algorithm/is_sorted_until.html [std-less]: https://en.cppreference.com/w/cpp/utility/functional/less [std-less-void]: https://en.cppreference.com/w/cpp/utility/functional/less_void [std-mem-fn]: https://en.cppreference.com/w/cpp/utility/functional/mem_fn diff --git a/include/cpp-sort/utility/is_sorted.h b/include/cpp-sort/utility/is_sorted.h new file mode 100644 index 00000000..7885fec9 --- /dev/null +++ b/include/cpp-sort/utility/is_sorted.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_UTILITY_CHECK_IS_SORTED_H_ +#define CPPSORT_UTILITY_CHECK_IS_SORTED_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include "../detail/is_sorted_until.h" +#include "../detail/type_traits.h" + +namespace cppsort::utility +{ + namespace detail + { + struct is_sorted_impl + { + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = cppsort::detail::enable_if_t< + is_projection_iterator_v + > + > + constexpr auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> bool + { + return cppsort::detail::is_sorted( + first, last, + std::move(compare), std::move(projection) + ); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::forward_iterator_tag; + }; + } + + struct is_sorted_t: + sorter_facade + {}; + + inline constexpr is_sorted_t is_sorted{}; +} + +#endif // CPPSORT_UTILITY_CHECK_IS_SORTED_H_ diff --git a/include/cpp-sort/utility/is_sorted_until.h b/include/cpp-sort/utility/is_sorted_until.h new file mode 100644 index 00000000..99170469 --- /dev/null +++ b/include/cpp-sort/utility/is_sorted_until.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_UTILITY_CHECK_IS_SORTED_UNTIL_H_ +#define CPPSORT_UTILITY_CHECK_IS_SORTED_UNTIL_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include "../detail/is_sorted_until.h" +#include "../detail/type_traits.h" + +namespace cppsort::utility +{ + namespace detail + { + struct is_sorted_until_impl + { + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = cppsort::detail::enable_if_t< + is_projection_iterator_v + > + > + constexpr auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> ForwardIterator + { + return cppsort::detail::is_sorted_until( + first, last, + std::move(compare), std::move(projection) + ); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::forward_iterator_tag; + }; + } + + struct is_sorted_until_t: + sorter_facade + {}; + + inline constexpr is_sorted_until_t is_sorted_until{}; +} + +#endif // CPPSORT_UTILITY_CHECK_IS_SORTED_UNTIL_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1c61e4de..f2f5fc25 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -264,6 +264,7 @@ add_executable(main-tests utility/buffer.cpp utility/chainable_projections.cpp utility/check_strict_weak_ordering.cpp + utility/is_sorted.cpp utility/iter_swap.cpp utility/metric_tools.cpp utility/quicksort_adversary.cpp diff --git a/tests/utility/is_sorted.cpp b/tests/utility/is_sorted.cpp new file mode 100644 index 00000000..2acaa138 --- /dev/null +++ b/tests/utility/is_sorted.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include + +namespace utility = cppsort::utility; + +TEST_CASE( "is_sorted and is_sorted_until test", "[utility][is_sorted]" ) +{ + SECTION( "empty collection" ) + { + std::vector vec = {}; + CHECK( utility::is_sorted(vec) ); + CHECK( utility::is_sorted_until(vec) == vec.end() ); + } + + SECTION( "collection with one element" ) + { + std::vector vec = {5}; + CHECK( utility::is_sorted(vec) ); + CHECK( utility::is_sorted_until(vec) == vec.end() ); + } + + SECTION( "collection with two elements" ) + { + std::vector vec1 = {0, 0}; + CHECK( utility::is_sorted(vec1) ); + CHECK( utility::is_sorted_until(vec1) == vec1.end() ); + + std::vector vec2 = {0, 5}; + CHECK( utility::is_sorted(vec2) ); + CHECK( utility::is_sorted_until(vec2) == vec2.end() ); + + std::vector vec3 = {5, 0}; + CHECK_FALSE( utility::is_sorted(vec3) ); + CHECK( utility::is_sorted_until(vec3) == std::next(vec3.begin()) ); + } + + SECTION( "distribution: ascending" ) + { + std::vector vec; + vec.reserve(250); + auto distribution = dist::ascending{}; + distribution(std::back_inserter(vec), 250); + CHECK( utility::is_sorted(vec) ); + CHECK( utility::is_sorted_until(vec) == vec.end() ); + } + + SECTION( "distribution: ascending_duplicates" ) + { + std::vector vec; + vec.reserve(250); + auto distribution = dist::ascending_duplicates{}; + distribution(std::back_inserter(vec), 250); + CHECK( utility::is_sorted(vec) ); + CHECK( utility::is_sorted_until(vec) == vec.end() ); + } + + SECTION( "distribution: all_equal" ) + { + std::vector vec; + vec.reserve(250); + auto distribution = dist::all_equal{}; + distribution(std::back_inserter(vec), 250); + CHECK( utility::is_sorted(vec) ); + CHECK( utility::is_sorted_until(vec) == vec.end() ); + } + + SECTION( "distribution: descending" ) + { + std::vector vec; + vec.reserve(250); + auto distribution = dist::descending{}; + distribution(std::back_inserter(vec), 250); + CHECK_FALSE( utility::is_sorted(vec) ); + CHECK( utility::is_sorted_until(vec) == std::next(vec.begin()) ); + } +} From 844bf24f893c44ea9dcf5e32c70cb07e4deaf2ad Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 13 Jan 2026 12:06:28 +0100 Subject: [PATCH 30/40] Add a visual explanation of the measure of disorder Osc --- README.md | 2 +- docs/Measures-of-disorder.md | 6 +++ .../images/measures-of-disorder-osc-cross.png | Bin 0 -> 7334 bytes tools/measures-of-disorder-osc-cross.tex | 50 ++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 docs/images/measures-of-disorder-osc-cross.png create mode 100755 tools/measures-of-disorder-osc-cross.tex diff --git a/README.md b/README.md index 972a2fd5..9ebe4c9e 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ Libraries*](https://arxiv.org/abs/1505.01962). * The algorithm behind `utility::quicksort_adversary` is a fairly straightforward adaptation of the one provided by M. D. McIlroy in [*A Killer Adversary for Quicksort*](https://www.cs.dartmouth.edu/~doug/mdmspe.pdf). -* The algorithm used by [`utility::check_strict_weak_ordering`][utility-check-strict-weak-ordering] is a reimplementation of the one desribed in the README file of danlark1's [quadratic_strict_weak_ordering project](https://github.com/danlark1/quadratic_strict_weak_ordering). +* The algorithm used by [`utility::check_strict_weak_ordering`][utility-check-strict-weak-ordering] is a reimplementation of the one desribed in the README file of Danila Kutenin's [quadratic_strict_weak_ordering project](https://github.com/danlark1/quadratic_strict_weak_ordering). * The test suite reimplements random number algorithms originally found in the following places: - [xoshiro256\*\*](https://prng.di.unimi.it/) diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 6e899eab..0d8758f9 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -332,6 +332,12 @@ The measure of disorder is slightly different from its original description in [ Computes the *Oscillation* measure described by C. Levcopoulos and O. Petersson in *Adaptive Heapsort*, using an algorithm devised by J. Nehring. +Let $\lvert \lvert \mathit{Cross}(x_i) \rvert \rvert$ be the number of links between adjacent pairs that "cross" the value $x_i$. We define the oscillation measure as $\mathit{Osc}(X) = \sum_{i=0}^{\lvert X \rvert - 1} \lvert \lvert \mathit{Cross}(x_i) \rvert \rvert$. + +![Plot of Cross(x_5) over the sequence 6, 3, 9, 8, 4, 7, 1, 10](images/measures-of-disorder-osc-cross.png) + +In the illustration above, we can see that a horizontal line drawn from $x_5$ crosses three pairs of adjacent elements in the geometrical representation of the sequence $X = \langle 6, 3, 9, 8, 4, 7, 1, 10 \rangle$. Thus $\lvert \lvert \mathit{Cross}(x_5) \rvert \rvert = 3$. In this example, we have $\mathit{Osc(X) = 17}$. + | Complexity | Memory | Iterators | Monotonic | | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | No | diff --git a/docs/images/measures-of-disorder-osc-cross.png b/docs/images/measures-of-disorder-osc-cross.png new file mode 100644 index 0000000000000000000000000000000000000000..77a9719a9d81ca0e20e1a8577c239583a44b01d1 GIT binary patch literal 7334 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 B#gG61 literal 0 HcmV?d00001 diff --git a/tools/measures-of-disorder-osc-cross.tex b/tools/measures-of-disorder-osc-cross.tex new file mode 100755 index 00000000..baa544b7 --- /dev/null +++ b/tools/measures-of-disorder-osc-cross.tex @@ -0,0 +1,50 @@ +\documentclass[margin=0.5cm]{standalone} +\usepackage{tikz} +\pagestyle{empty} + +\begin{document} +\begin{tikzpicture} + +\draw[thin,->] (-0.25,0) -- (8,0) node[right] {$pos$}; +\draw[thin,->] (0,-0.25) -- (0,6) node[above] {$val$}; + +% ticks +\foreach \x [count=\xi starting from 0] in {1,2,3,4,5,6,7}{ + \draw (\x,2pt) -- (\x,-2pt); + \ifodd\xi + \node[anchor=north] at (\x,0) {$\x$}; + \fi +} +\foreach \x [count=\xi starting from 0] in {1,2,3,4,5,6,7,8,9,10,11}{ + \draw (2pt,\x/2) -- (-2pt,\x/2); + \ifodd\xi + \node[anchor=east] at (0,\x/2) {$\x$}; + \fi +} + +% points +\foreach \y [count=\x] in { + 6, 3, 9, 8, 4, 7, 1, 10 +}{ + \fill (\x-1,\y/2) circle (2pt); +} + +\tikzset{every edge/.append style = {gray}}; + +\path[every node/.style={font=\sffamily\small}] + (0,6/2) edge (1,3/2) + (1,3/2) edge (2,9/2) + (2,9/2) edge (3,8/2) + (3,8/2) edge (4,4/2) + (4,4/2) edge (5,7/2) + (5,7/2) edge (6,1/2) + (6,1/2) edge (7,10/2); + + +\tikzset{every edge/.append style = {dashed,gray}}; + +\path[every node/.style={font=\sffamily\small}] + (0,7/2) edge (8,7/2); + +\end{tikzpicture} +\end{document} From bffea675eb059ffcab554787c594e561b878565c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 13 Jan 2026 12:25:08 +0100 Subject: [PATCH 31/40] Add a visual explanation of the measure of disorder Exc --- docs/Measures-of-disorder.md | 4 ++- docs/images/sorting-exchange-cycles.png | Bin 0 -> 11096 bytes tools/sorting-exchange-cycles.tex | 35 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 docs/images/sorting-exchange-cycles.png create mode 100755 tools/sorting-exchange-cycles.tex diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 0d8758f9..0df6fda9 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -246,7 +246,9 @@ $$ Computes the minimum number of exchanges required to sort $X$, which corresponds to $\lvert X \rvert$ minus the number of cycles in the sequence. A cycle corresponds to a number of elements in a sequence that need to be rotated to be in their sorted position; for example, let $\langle 2, 4, 0, 6, 3, 1, 5 \rangle$ be a sequence, the cycles are $\langle 0, 2 \rangle$ and $\langle 1, 3, 4, 5, 6 \rangle$ so $\mathit{Exc}(X) = \lvert X \rvert - 2 = 5$. -**Warning:** `probe::exc` generally returns a result higher than the minimum number of exchanges required to sort $X$ when it contains *equivalent elements*. This is because extending $\mathit{Exc}$ to *equivalent elements* is a NP-hard problem (see *On the Cost of Interchange Rearrangement in Strings* by Amir et al). The function does handle such elements in some simple cases, but not in the general case. +![Visual representation of the two cycles of exchanges required to sort the aforementioned sequence](images/sorting-exchange-cycles.png) + +**Warning:** `probe::exc` generally returns a result greater than the minimum number of exchanges required to sort $X$ when it contains *equivalent elements*. This is because extending $\mathit{Exc}$ to *equivalent elements* is a NP-hard problem (see *On the Cost of Interchange Rearrangement in Strings* by Amir et al). The function does handle such elements in some simple cases, but not in the general case. | Complexity | Memory | Iterators | Monotonic | | ----------- | ----------- | ------------- | --------- | diff --git a/docs/images/sorting-exchange-cycles.png b/docs/images/sorting-exchange-cycles.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b75ac444841443d410abdb8e706d91ed9bedac GIT binary patch literal 11096 zcmcIqgFy& z_uilI-sf}fJ@?+3IiLB=nR90DoNuCzwki=m9X;I2!X<%OC?w(XIJueRdo zD}B7ye;Y97o1SCkJT(Fo_wH9f(%hfY172Ztzf`fOKc+f9l&SCW!_%`Q=ELJ#+?cjy zJlKCq2#~{&OOrdY6YF|+$-t@KYL2WXM2299v&2jhToVEluA&Nr3P^R@r-aOzXo>lV z_$VGaWR9sJF^WlB5_JPk0@`9if1cE0yc*okgrr^L&?csgs6xjCDub$RE=7<1GFO_E zL`rkU(F`W|*MdTf@DjbONpFq8M?qR7HMSV07|twq4%RY`_P~DG`*KG*cxv=Y>5)6t zs^O|U!UV}k_-GW-{?ugs2xNQdMXXaa=j6@<6%vAz{dr304{InigJ+FhYnSI1pfH5rq*~rP)bd za^@3WEWy%&YxS~NZ!^Tc?<&q}obwl*IRv?+VakLB{g6uvL?R|i^)u_n0^VB?jnlI8 z%776f8i5DxqA+3Q^gzx&QGxA04aCgtST3^jUjHqc9?;K(f6sVLNsDzqrk~l!g1p7N zgGCQ0uRcdI;_+na3%7cE)4}paoo$>}#a3OBRwTSx6qN8ZL_I$v3apn!+5cv+~z1W?kf2za5WtoPG`beIge67HVK7FG;*b_e~d*p zLy1WErWA_GN12x$20O$dk8R1ak_iRKlqLnK>Pe1lr zF4q-ND}T&9S<>DfDHKqfOAzqw$FHx}I>v!)LZH^f_I7%I|8-hvv04l5@>cWODPd_^ z36KN>{Ig>6x&+v^S#;F)imibN(p}x6;kB+;UG;04%f`~?DRPc8D7|X0+H~0PHTcZnMBj@#_2i-}{3;A*4$wc{M9eHCZw( zQw!OSCHQDiS^FVv!bj`Tds^^dz#~!^?mV1@=__kf7^E}X+n+}9R(<}XWw}JbSZ^VD zov%4748jgd_&P17OS$1?BfCnBl)}y#PD}*l*4pLt?c>{!Ht0Kh({s{7tg#9(eWQoE zo&-8hep32yrV?tf;cRNNwj;vkl;W}$gw^~sB1ul9JTejq3}xqlni99t(E{9~(IknM z3*J0r@DkX$%FtTO@1)W(y4+50tOkN(?qh*tA|Wd19HieZDiTnXALhD^8ZSf0A)cbP zEp05ugDTM!NY^M~#3{mv(RE*fOvC49ZqiQTq-vDmHW-FOi_e!U;_0>qT}FsYu2*I` zn$nKFye$LjMQ+F+l8(%^hQl`}2^uSnll|lG*2A~&#m)rO?SFLhd zrY)tyED&D(IAMF$)az4N*DOv3S~WMF!i2vFegT6hT7hHz-6TBFbq69iR+Lhdme8F* zqeNO2?K(L);OAD#ZkZ!DXD|$_;YEYE5VsROmC3WA5k^QZKq+c0yzV*3VYzi9HFmOU zg9KqJxjv6zWsf%vT?N>r%zgDLG_nhjog{)6DJB1Qkg<|^v!7<6h!u5EGun_q5Ook` z^pk9&pz_3>REY@! zR2pmsZc2Cyo0A7IQZa>!G)j2n)s9=>ln#ISZiqI`_s6KmCU&W_O_X421+{X%Su7DZ z(q7F^(+`G*;}H7wkr%%jx2R!+Cr5*YH3b|A--}CO#$!fej*kIzIfBE7ybD_${I~SF zpmlM*$rS0gqd@uX6wRon=%v%a5cQU|N2hk6f>%s=vT8&>E>MbhHlAOr&N`l4b2+6D zvd2Gu%9K%1X+gyeOGg=&?e!)z42_UO{@7ezP1-fSP;Aq#$7#nj50=V2%@B_$5zoQn zk6+LFaDqsJ9*~>8Z&pSx*`bP4Rxm+Zgs-P{Ag6L?v#=?+fj@J`DYi(wzKMTUP;?9%#4Hq zI+HH_oBHLWDx$Dq6H|yBL^^_)Tfrm0)$-0PuNBGV!n6NUc%m-3K$L{0t0F|4LdS6VwWB+|MZIYAzlTwq z6^q57xmI~sxos3(vsK27FqZq>J$dH7C?nf3zF^(iT{-|4y6~Y-?wO7iT@zo78DU$c z^90}wiR*q!ML!@ip^lzP_RA8~5~Q?SZq|lz=ye1>LJP484QDhdcel9&*>EB+@%S~$ zt7L9PxEyDR5ZTahpx#G=6wPH5HmAurQn1oroW*H&L%<6}+*(by}0hj_Id2=l4N;A!*rhSG>fP5dSb$)EJX>Z_R{zoJPSTk@)dWXcta>4h z5Vf2-@t>-+{e4*>vEOH05VwdI7@ICB=t}6p)7oToeAz%X<~)Xz%`ETj%>;@rYf_yKj)4Fn9)>Uc7=&m|sKGv#YYZXTEP+=92oEmMO$CD4hRUF1mmE1C zMhP)18^-QA^gzsXex`&^U06l@twlu@wyxmKJ|6 zQ_;Y#!L`>9s~+DjpIrHYIigPwNiKa5`W#2iPI{+YV#l@9S$c&=#6m4|KiX8=Ha~ex z2YElCU(F9+8HwSF>NAT+lSFTFXa9-* zc{+%gd}{!RebHR~m|7X`<^NW2_7J*I3;=FOCH)W=d%6=hb?qL9o~TyK=AZ*G;1*J} zjgVIwtbe0zAj9uiApS+kzf7}(6NX8@!n6BLHPlAS#*Nao>jl0-ZR%euXob7k2pOBC zJqP}0+1Br0asgtWthNhEvG-jov4q|aN-@zsFz)$V(X@ehDBxE*HPAlLeKXCJufjG6 zwp}{kO7x2y9t$gRLxnEsY`f_(SuW^oTj%OeDhr1af!i78S6jk$BCRHGRH}Le^IQKm z)7hBYyr%R-69r+%(r?2`jKRry>k|v_S~~nz_hF3HNnwVXFx{n3JB;#*ac55FqymWn zz1`{_2h;oQe?W_2z5^4p91K77?B_b$wpfZ>VIIVQI0F6+16ZDw6Qdci0Dv`>;YV}O1)77I=qU}R(n&zB72y2 zK2QFmeEavmN#9e|J*T7WL8+baqPNQ{YdhhdY4uf1t+W>eS7j099Hbuv$vV5Fr)#WKqNjQ^dA&yGgD0eu5&1lLcm= z&T#NjRIiI8_)TgzN_{poINiWzkIjqz9C< z`wBUo=V)^kOqM;`tzyiME9@Gh)QR^?6Rr=aGa6sku}Hp@hCNYHS-L#!vh_)QwpdqG z?uVncXDdEf)XDW{&$GWW`qNKMr{P(+1)ks2=OCJ~Fu=B-BzEd?sRg zh4c95sBxUHK1~pt!BO=6`r}T!shAKa%fqug;XvRyy`$vEV3%j>x_H)Cd^3>_XhdP6 zgN4j@H-Y&5fiR`~t7pM|dZm-WPTp``@n;1Ueh+NAk_%w{_6RqL_vb0rf-}+V53HQu z6^8*IFJ!2tj$-xxLn0r^i96x-TdqDRamG&0s6P>zN%zy-y{%h&HYE~)jRiN>e0mP9 zZ!pwz?tCdI>d#nc2deO*nqT<9K~qjxV`>L(43hoAoI3kWG^9T1a_kdQ3$c^6K+DjS z$!?@{s|H`5I^Aj0c+sVXx0t%zC)&o?`g~I?$x&!8aJo?aON!re%Px}-A~Va$dbGYO z&EJSxR$O&$fZ*ezwyNJsN5!JH#eCUty3h6Sl~Qt~spU7ZN0;P9#}h64`tvpEyXv$F8F~yDsCBVkaotFXqY;J+!LC)*W0S(5cJ! z=hEIz|3GA{PUMs7Zn6<9ixP>H^pZM(xr@CNK=ExHNRb9LCaj0Tw6bB7zTLo+AATE? zXHBZYn4grNHYQlQR|N&07g0(7SRlXPHC^S(Dw8D!!J<`dVeUkyGW(jHJ19CEnQxpu z$Ilvkk(q(YEQ8XeG)~V(Spq;Ofk*W*Dt^`MTFE1yoJE|uXPB{W7(#Y`pFT_CCK>bX zPp1U}Qote$0?s9UUBi?83=b_^9oiWl(N4({-3-rb1To@EcglaFq)Elww&Bkxil)u1 zSR#n)4p6J!STiG@N%wo{Wb(MBzmBB%0~GE8e@}uAUddypUiYZ3D14Jpplt$u02soL9Z zd$DB-c0cL-LFtJea85lamBM5^c^eP(qM7cr>oxCbCtViukUtBj%w(%n*LFBG{qtGC zFv$WVxIolF>{wp3A)(_hso6Fbs{9a1J2cB|e3Icji#jg6n-tiNw{N}^@%h?sS)dv7 z-I#Q0}6) zZfcCzbaidDr0^xYsu-fCR)(u?1-1z-9nKSKvzaA~5vD-h4i*-Fs~8^}t%TrvKT5?1 zaR*w3m7zgrG2a&_grYm$N_4q;D`(GegO8fHWUdFu$LkbK!5T!CjISLmLdvQm>8-%r zY{jmpcM8sfr~4pAU~LoMX><-IMr%+TTL`kW?Vf5ryeQPYre>nMQ>SusEN0VAsf)?9c0}6$z#t z!gc{H0tDq(5$u#&qV);B@~S8^?){TJBfPLDqu(lmdgE&X03k#i<|m%jqN&_ z^Uwc%Wb$=2E4;VZmZBV;1;W5!{qz_BrDTgR312eotNIFrtX&ex(t)*TGu?*_j+eA`PIx+6LBP{t&^OIIY$ zqEt?GZp5c;+s<0#{nOz@Aup9uY!sx@L1-}s|Iz8|riJTJzloTHqu=IPUQ++4 z!;k7r|Kz_ducS`?o7yo~ffcf>n0+s>CHp%6{ZX*q)dSYWZo%(QiF^1PZj+D_y*(j9 zCG~%Qqp1HTz2FPi9l8MHd2oJDuJTRj%NY)o1_1Mxbes?Ob~e$Zbp!nuUx*dS_*kHc zu9E$Uc6rf^Q*;qsrqAOU%O#z87~-?vlgJswPUR+iIdgG738C&`45zkuOF$k;;i6Xb zFDgZ8v8p?bOVDH*V_3QhZV`J8&;5xyqZS7+yCelqWI{+Q_^UX-(>OOTiEo|!DXXK> za(grOsTQC(=vtW$tDyYyeV zIjt?PD}Vl39xnR^%Xsp7*{ST#$NlJnv>=KNFP zix=nL5%1_$B0n5;N5v# zvb_UUY{g7JN_SlstJ*~qKA%Z)nK@AR2oxJt@&2Vgfq5(bxepheP7!%qLM-!k)*zoc z)Ev+jVx@T3u{ESIOgoS|`YNVX>dosPY%_<$Rj*(^et&W3?l1U#JKW0tI{M@-c&J$v zO@NOEo2snew2HMdhg_|&{AE6+DGF57uaXcbV&pR0;?HucISyt_JscvNc-lqkBiTO& z&Y|Xn2{ux?MuT)Y8@0%`A7UFiW)wc?SW^fRc7n{@+H(ipd}C0M_AdM8HrlD1-jjh> zv2fk-S_!_`8Vxb}?{lRNb^3|&Tp;j$3a-mn+}pp~+w)ZliTGb&-j_*qS^Yj@^V{T{ z@5@L+#uewp+i<%A2PPR?ae!0&8qQ~C8G?$&e45NfL#7GVjNz}f^h1l@cE_}uX6Gth zZscyShi$E_H7ONvo}FFGsCC94(yq=zdq0iXQML>dgzVr`Ek!6^J+MFj1{&L59N$SM zX<8{`$t9ELSCtn{VoH=+^w;r`VvA^;;T~Qh&u7<-pObxbF=nhY3rde;;@eJFG6i)f#wh&*wHd`Yj5q|d z=8lrnL;w4Gr!t9EkE)*3KI$s#Wtut>>of>o2dRAcxSGT3h%%Br=5>MVrnmu=f%n++ zMymK9DBKGA!uYpL-w-}(+Yfe(D`dauVtE+j1O5$Gkc6})OLA(}tCGF;HpM*{m&ZHs z;D!kK;CU&L4ONZ+9GHL8wHG4}xjwA_Rg3gy8^0~v<|V-5j*>>Lw$fX~+-CDNP~(>; zf_}D2XoUKahioMW&l)RAz1dUQ(#c|!KKLzn#nNMCw3B8L;^O4Y7?w zVR^esHlyGe8yo#vU0%u8az@+P*hWbke5;m8KYteBK6*f@WUC|1w;9l^{Gh&==PfBg zbJ5{4MZEbzy0HR_k!|UDsVXat((tRYv9Dwr6byls)_k%kyI*PCmOTcpItrB{`tIw` z8aZ;alip!BD^g)iYsk-Vw*Q^|4}G2`yfDiS{*1Qvit`B*=Shn#ed@5@v;;%9K{_I90o}L`MuRQkcT|#F<2xTyWUA$d@*SE-dbC zdi_#vfv;9vbV#@ge)ChbJDV@|GNAv$KevGWgO#@0+PqWgHn*n8d0gS!<)M#6ujFSU z=`)V~Gs^|@bt`md?`0gsw>lV`)cjVdo@LTh%7(0db2q`Kf6Ms3iK2aZ&)HVUZ)W)3 zgubG@2kPLO68KX8Bru^TQ<_p0CWQPbHcymPPt%g7!WF{yP^kLcu|>LkU~z7^-j>_z zd5GY8)?sSjJeSYu>bv?}>K~;M*Mu4BiPJ!IVj`t(i7N}GlQpNM<(fdx}tUm}8h>?XgUR z&2K{AN)_3O&3Qgqj4AK^eHcDME~}eboQ`@W;z_$(jyhpd-LBl6c%6=crLC+hccrr! z@*T1xc~f|O$Na57V@LJ@(ADCtx}sScv8F%s+JR-GRpRzap6FsCa(#~5+Ke@RA0hw2 z@|SJI(}{F#uH0GM9nE{eZy|zZg*FwqL#0B@AO>xFfZ}ZEA|yUAl9TyWMf`rQ+hr>pYQO-7EY`StAljJD>Vke)-|7 zWXQN1=FeILJ6iWyrB3>Cn8!Y=D7Avo7Ed5m^6u~}+g{ekvowaMQ}Atyu=^9qcR$z+ zr68dg+je4r)GZRFD+%JID_*q{$&Y6*CcD1GEFYk_sQ#T>TXy)+zV++ouTeLgym8{f zQdMnC6MHf?HLDD7MD_m|{lJeT#!MS>7iu86_#-q?eiuQt+ld42s#EV8?@>IuVVH5k zf0JXUq+^U3r$SVh#Mn$9(y;tYZ?te)^2?{V1)rS6)C~1Wh8x3p=8}*%=LjnDH{F1g z?e|KhP7}#OKSm?cer=?#>-G=T7QRT@GRNKx^E&@mueL2;K0`;LbFRDDveE?n3etwc^pKhpasl<~u?e=nvf%M@A!^vQ`KXi_% zu^+~ts+J5?&)qoqtx)K2S_H>H;PgIA#e`ugrYk#wl7ecMSO5V2-~V6%_MRpwO-57+ zsBKSAN|`Baf1Ng}_}0VnW*?V64?c56=0E~(iCVEYdezizv*D=biP@|~GV?<6$9=Cu zz$C#POM@&#FrJ!|8d8LEw-<4c5E$gs4Am?ADEuc0Vh$>ez4jH7W*g&}PEzV&+Q}53 z)!Mx^-3nPPomA{+d2_DJmT~aGHcx@QJwVYeM^*ZmS8*G9p@5P#XYPAW)FxfzI^Yic zxSm=ycjM+FUXbKH0`%2MSa)%;kYBeTa8EX!3gskce?zE3@(Zx1H14DuUZ3XUMb*Fy zL8qmOfYKIod3C;=wY83arhyZ^x7}3@XGGAAmY#_QPA)$o95%0|BmWtL7UzQPGbtrc zkq55}HG0iq-53)1^-Aae7A|7mHT)&{HyxmFbO)E)?kb4_c(Po|s z$`w;t-bqj#_?Imr;CWp6bXZ$hkdp{0r_eDn0Fv@3NZOSz(mLTL2~Mcb}NFD?OJb z0ED*LssrJ+P(zIXhavUCZ*;kZCwTbiFq!9~97hDfcg{g<2=C!j{|edXpT-g4PPl#&-%!j{ZV^iTA0Z_u53y=T2l9YKgd9H1onL% zA5w`Gnn70d6a?N*Awc~;%`S7`;)VF?SQb7AEj&NiI?XY`Gp;mfBnWX@RYN9m_x_v@ z_{rfOgC=owo5PDoFC)6SzSwfO{(0h$+DL)x)`l-2_6ZEW9^E%C+6S}cifdysj)JvurFEi}#Ui9BYR3D9FG+*mJ*Eb^v{5z) zWV0=5c`0sVk6ac|dKB2fbe~~v(u52*j6x zOxfG6vLdzlZePJSg%@OkKNKSE#7b+|_VM1EuIn*7A;*9%`qr{vZ*^QYq$uS=w94K( zF4Ja+dx9(CLjm@^c)QP%dZ)ynTq5?r{VLmqm;)vNnxD9eGTD*MSc8g=9{j_#=C!n} zG;~x#$!buWh;+<=aN{WgCYQ8}xRI~|^SDuD~kRN(3iVLit5GE5#E z@WzL3hP_7(R$&$yS9m8XElc1?;9T?F-hcW2@5p7G8Qip;n;KDzL9JAuWQ7=$T%U%F zo8oM~4#j}(4EB2$Hot7DZOiI)wh3RASk+z)H!u&;%%ba&#KD?VDfz6kg}%_ikAw95 zC_M2Vm*96=EJ?5x=GA;~;{-qmT8=~Kf}5r?#y>}iN&8l-QI_A6n=Q&>z`tN=Zaj-d zyK;#y@*xA9Pg~3$VNdF-&yd~xJyF`foxlGLTXjPQknn5MHE!l^+mslo)caW zG|5(~ynyn@qdgGzh@L3Rd`Sq05G9nTGa6m&n2l1WhW~{wVawnylI$~>l+PTeRqF|x zQ?zYCFyrkmLu?WYTcP~;g+%wnKdsuHz+HsLL4+2mI5d&R`$JUk(B#e(*8vDHqNm6E(i`;{GHfOP zf1o{?R7fdXBN{Wp$`q*opnKn>odb4YvZ2W19-o2@6AFDqVt8P}i-$e@nUAfJ#XNr? z5{?1CsCVyUzcnr7JVGEO=zlQ<(FgI=zCoBgGNPTTW$s>w~* zS)ZNVR&foOwl$FB_`C*GW#G&8sL-f*J8(or>ZP(rvR^m37Z4JO_eisCmh-|Lb$;+_ zQjL%tzZW(at|3{Y)LhKxHC2lJ)J)8;0@kaRngGc8dehm9kp@Z5H|#>|qAi%%-iMyt zBgom){r-|ee3g-3BW@XXOwD`s_>nm;&{#AzH^p{ zCzlXGD@mLDpkc_TdFwJe$JemYoOG;o)}lyigL)sB&$Js`(ZtK!?x(cY{!)Szo)(4H zu%-S27=BPenW9m={iTjh+h{EMgiHD|=n>e&uWc&y2W(#y`SdYlt}ATv&_Fmbm{^PM z%EK@p5oKGcKDl*(R|ES$Ab*#!e^%$w8crb@leWz8O$bt7#RHD)npJh$z9soBGhKf} zZ&X2+bZv%{^DKvrICd*^7%EuQjs)$SBX!#5Ngp+twQZL1;@xxq1Z$VbghhTqxQ8Wq zw5T@u`9PZtP-npo^zv9%7{#il6`WIgT_H}(&^p3J+}b2a6On%eH$<@VSHRPcXKhd| z*EOi|hk`fcss4XiA@UU*1=F}I+rJx$+frxKR63INtYzxNH5z^`N+x64>&Bl`&~3~p z|66{CF5)k|Vs2ob#PesAjS`G1h|)KI? Date: Tue, 13 Jan 2026 13:54:27 +0100 Subject: [PATCH 32/40] Document the measure disorder SMS --- docs/Measures-of-disorder.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 0df6fda9..0f15d262 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -402,9 +402,9 @@ Spearman's footrule distance: sum of distances between the position of individua #include ``` -Computes the minimum number of non-decreasing subsequences (of possibly not adjacent elements) into which $X$ can be partitioned, minus 1. It happens to correspond to the size of the [longest decreasing subsequence][longest-increasing-subsequence] of $X$ minus 1. +Computes the minimum number of non-decreasing subsequences (of possibly non-adjacent elements) into which $X$ can be partitioned, minus 1. It happens to correspond to the size of the [longest decreasing subsequence][longest-increasing-subsequence] of $X$ minus 1. -*SUS* stands for *Shuffled Up-Sequences* and was introduced in *Sorting Shuffled Monotone Sequences* by C. Levcopoulos and O. Petersson. +*SUS* stands for *Shuffled UpSequences* and was introduced in *Sorting Shuffled Monotone Sequences* by C. Levcopoulos and O. Petersson. | Complexity | Memory | Iterators | Monotonic | | ----------- | ----------- | ------------- | --------- | @@ -448,8 +448,20 @@ In their subsequent papers, those authors consistently use *Dis* instead of *Par T. Altman and Y. Igarashi mention the concept of *k*-sortedness and the measure *Radius*($X$) in *Roughly Sorting: Sequential and Parallel Approach*. However *k*-sortedness is the same as *p*-sortedness, and *Radius* is just another name for *Par* (and thus for *Dis*). +### *SMS* + +*SMS* stands for *Shuffled Monotone Sequences* and was introduced in *Sorting Shuffled Monotone Sequences* by C. Levcopoulos and O. Petersson. It computes the minimum number of increasing or decreasing subsequences (of possibly non-adjacent elements) into which a sequence $X$ can be partitioned, minus 1. + +The concept in itself is fiarly straightforward, yet the problem of computing $\mathit{SMS}(X) \le k$ in NP-complete, as shown by K. Wagner in *Monotonic coverings of finite sets*. For this reason, the library does not provide an implementation. It is technically possible to compute an approximation of $\mathit{SMS}(X)$ by repeatedly removing the longest monotonic subsequence from $X$. + +Nevertheless we do know a few of the measure's properties: +* $1 \le \mathit{SMS}(X) \le \min {\mathit{SUS}(X), \mathit{SDS}(X)}$, where $\mathit{SUS}$ and $\mathit{SDS}$ respectivey standard for [*Shuffled UpSequences*][probe-sus] and *Shuffled DownSequences*, the first being a measure of presortedness that we provide. +* $\mathit{SMS}(X) \le \lfloor \sqrt{2n + \frac{1}{4}} - \frac{1}{2} \rfloor$, as proven by A. Brandstädt and D. Kratsch in *On partitions of permutations into increasing and decreasing subsequences*. + + [hamming-distance]: https://en.wikipedia.org/wiki/Hamming_distance [longest-increasing-subsequence]: https://en.wikipedia.org/wiki/Longest_increasing_subsequence [original-research]: Original-research.md#partial-ordering-of-mono + [probe-sus]: Measures-of-disorder.md#sus [sort-race]: https://arxiv.org/ftp/arxiv/papers/1609/1609.04471.pdf From 6342d0efe83d219a76117bbb9a2e98bc12dce755 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 13 Jan 2026 14:38:00 +0100 Subject: [PATCH 33/40] Document measures of disorder Las, Lds and Lads --- docs/Measures-of-disorder.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 0f15d262..0398a0da 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -414,7 +414,7 @@ Computes the minimum number of non-decreasing subsequences (of possibly non-adja ## Other measures of disorder -Some additional measures of disorder have been described in the literature but do not appear in the partial ordering graph. This section describes some of them but is not an exhaustive list. +Some additional measures of disorder have been described in the literature but do not appear in the partial ordering graph, or are not provided in the library. This section describes some of them but is not an exhaustive list. ### *DS* @@ -424,6 +424,13 @@ A measure called *DS* appears in *Computing and ranking measures of presortednes In other domains, that value is called *F* (for *Footrule*). It is no more helpful a name than *D* or *DS*, so I decided to use *Spear* for this library's name (for *Spearman*) - following the same naming pattern that led to *Ham* -, despite there being no precedent in the literature. +### *Las*, *Lds* and *Lads* + +Those names tend to appear in papers authored by Jinseng Chen, such as *On Partitions and Presortedness of Sequences* and *Computing and Ranking Measures of Presortedness*: +* *Las(X)* is the length of the *longest ascending subsequence* of *X*. We do not provide it because it grows with _order_ in the sequence intead of growing with _disorder_. $\mathit{Rem}(X) = \lvert X \rvert - \mathit{Las}(X)$ is the closest measure provided by the library. +* *Lds(X)* is the length of the *longest descending subsequence* of *X*. It corresponds to to [$\mathit{SUS}$][probe-sus] in the library, the minimum number of increasing subsequences into which *X* can be decomposed. +* *Lads(X)* is an extension of both of the measures above, computing the minimum number of monotonic subsequences (ascending or descending) into which *X* can be decomposed. It is a different name for [*SMS*][probe-sms]. + ### *Par* *Par* is described by V. Estivill-Castro and D. Wood in *A New Measure of Presortedness* as follows: @@ -463,5 +470,6 @@ Nevertheless we do know a few of the measure's properties: [hamming-distance]: https://en.wikipedia.org/wiki/Hamming_distance [longest-increasing-subsequence]: https://en.wikipedia.org/wiki/Longest_increasing_subsequence [original-research]: Original-research.md#partial-ordering-of-mono + [probe-sms]: Measures-of-disorder.md#sms [probe-sus]: Measures-of-disorder.md#sus [sort-race]: https://arxiv.org/ftp/arxiv/papers/1609/1609.04471.pdf From f2fc7ffde506ec0a7bc4813ed1ef8017e8b985c0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 14 Jan 2026 23:51:18 +0100 Subject: [PATCH 34/40] Complete flip documentation with a link to my blog --- docs/Comparator-adapters.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Comparator-adapters.md b/docs/Comparator-adapters.md index b3faba1a..3ef2b661 100644 --- a/docs/Comparator-adapters.md +++ b/docs/Comparator-adapters.md @@ -20,7 +20,7 @@ Those unwrappings are meant to be simple and only intended to work with "well-fo #include ``` -The class template `flip_t` is a function object which, when called, passes the arguments in reversed order to the *Callable* it holds with and returns the result. It is named after the [`flip`][prelude-flip] function from Haskell's Prelude module. +The class template `flip_t` is a function object which, when called, passes the arguments in reversed order to the *Callable* it holds and returns the result. It is named after the [`flip`][prelude-flip] function from Haskell's Prelude module. You can find more trivia about this function object, as well as examples of use [in a article][blog-std-flip] on my blog. `flip_t` has the following member functions: @@ -137,6 +137,7 @@ constexpr auto projection() const [binary-predicate]: https://en.cppreference.com/w/cpp/concept/BinaryPredicate + [blog-std-flip]: https://morwenn.github.io/c++/2025/09/25/TSB004-std-flip.html [branchless-traits]: Miscellaneous-utilities.md#branchless-traits [callable]: https://en.cppreference.com/w/cpp/named_req/Callable [prelude-flip]: https://hackage.haskell.org/package/base-4.16.0.0/docs/Prelude.html#v:flip From 072d76e0c7dd406869d9afb10cfcaa514b7c4b94 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 15 Jan 2026 10:24:21 +0100 Subject: [PATCH 35/40] Add a section about metrics in the Quickstart --- docs/Quickstart.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/Quickstart.md b/docs/Quickstart.md index ba35c45d..d4420377 100644 --- a/docs/Quickstart.md +++ b/docs/Quickstart.md @@ -164,6 +164,40 @@ Almost any sorter can be passed to any adapter, with a few exceptions: The specific restrictions are all documented in the adapters descriptions. +## Metrics + +[Metrics][metrics] are a special kind of sorter adapters that can be used to retrieve information about about, such as the number of comparisons performed by a sorter, or the time it took to sort a collection. They are used together with the [metrics tools][metrics-tools] from the library utilities. + +* Count the number of comparisons performed by a comparison sort: + ```cpp + #include + #include + #include + + int main() + { + auto sorter = cppsort::metrics::comparisons{}; + std::vector collection = { /* ... */ }; + auto comps = sorter(collection); + std::print("slabsort perform {} comparisons", comps.value()); + } + ``` + +* Compute the time it takes to sort a collection: + ```cpp + #include + #include + #include + + int main() + { + auto sorter = cppsort::metrics::running_time{}; + std::vector collection = { /* ... */ }; + auto comps = sorter(collection); + std::print("melsort took {}", comps.value()); + } + ``` + ## 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: @@ -212,8 +246,10 @@ The previous sections describe some of the main tools provided by **cpp-sort** b [cmake]: https://cmake.org/ [conan]: https://conan.io/ - [merge-sorter]: Sorters.md#merge_sorter [measures-of-disorder]: Measures-of-disorder.md + [merge-sorter]: Sorters.md#merge_sorter + [metrics]: Metrics.md + [metrics-tools]: Miscellaneous-utilities.md#metrics-tools [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 From 6a946367b15d2d7eb5bb78b2ec1e25a1d37299ea Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 15 Jan 2026 19:58:15 +0100 Subject: [PATCH 36/40] Metnion metrics in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9ebe4c9e..7319c607 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ of the library: * Sorters can be wrapped in [sorter adapters][sorter-adapters] to augment their behaviour * The library provides a [sorter facade][sorter-facade] to easily build sorters * [Fixed-size sorters][fixed-size-sorters] can be used to efficiently sort tiny fixed-size collections +* [Metrics][metrics] can be used to gather information about the sorting operation * [Measures of disorder][measures-of-disorder] can be used to evaluate the disorder in a collection Here is a more complete example of what can be done with the library: @@ -256,6 +257,7 @@ developed by Thøger Rivera-Thorsen. [fixed-size-sorters]: https://codeberg.org/Morwenn/cpp-sort/wiki/Fixed-size-sorters [heap-sorter]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorters#heap_sorter [measures-of-disorder]: https://codeberg.org/Morwenn/cpp-sort/wiki/Measures-of-disorder + [metrics]: https://codeberg.org/Morwenn/cpp-sort/wiki/Metrics [original-research]: https://codeberg.org/Morwenn/cpp-sort/wiki/Original-research [sorter-adapters]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorter-adapters [sorter-facade]: https://codeberg.org/Morwenn/cpp-sort/wiki/Sorter-facade From 13512aeb8474826218116c64b5c56b9de198deaf Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 16 Jan 2026 23:42:13 +0100 Subject: [PATCH 37/40] errorbar-plot: order the legend alphabetically --- benchmarks/errorbar-plot/plot.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/benchmarks/errorbar-plot/plot.py b/benchmarks/errorbar-plot/plot.py index 46b55152..1c02ff44 100644 --- a/benchmarks/errorbar-plot/plot.py +++ b/benchmarks/errorbar-plot/plot.py @@ -15,13 +15,14 @@ def 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") - parser.add_argument('--alternative-palette', dest='use_alt_palette', - action='store_true', default=False, + parser.add_argument('--alternative-palette', + dest='use_alt_palette', + action='store_true', help="Use another color palette") args = parser.parse_args() root = pathlib.Path(args.root) - result_files = list(root.glob('*.csv')) + result_files = sorted(root.glob('*.csv')) if len(result_files) == 0: print(f"There are no files to plot in {root}") sys.exit(1) From 40a6e2787f08a78fa7bef4b3eceacee29f034801 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 17 Jan 2026 00:01:24 +0100 Subject: [PATCH 38/40] Update benchmark: measures of disorder --- docs/Benchmarks.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index f4b0dbdb..1ca87fa7 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -1,7 +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:* -* *2.0.0 for measures of disorder* +* *2.1.0 for measures of disorder* * *1.16.0 for slow O(n log n) sorts* * *1.14.0 for small array sorts* * *1.13.1 for unstable random-access sorts, forward sorts, and the expensive move/cheap comparison benchmark* @@ -13,7 +13,9 @@ It is worth noting that most benchmarks on this page use collections of `double` All of the graphs on this page have been generated with slightly modified versions of the scripts found in the project's benchmarks folder. There are just too many things to check; if you ever want a specific benchmark, don't hesitate to ask for it. -*The latest benchmarks were run on Windows 10 with 64-bit MinGW-w64 g++12.0, with the flags -O3 -march=native -std=c++20.* +*The benchmarks were run on:* +* *For version 2.0.0 and newer: EndeavourOS with g++ 15.2, with the flags -O3 -march=native -std=c++26.* +* *For older versions: Windows 10 with 64-bit MinGW-w64 g++ 12.0, with the flags -O3 -march=native -std=c++20.* # Random-access collections @@ -174,10 +176,10 @@ We can see several trends in these benchmarks, rather consistant across `int` an This benchmark for [measures of disorder][Measures-of-disorder] is small and only intends to show the cost that these tools might incur. It is not meant to be exhaustive in any way. -![Benchmark speed of measures of disorder for increasing size for std::vector<int>](https://i.imgur.com/7QZqe0m.png) +![Benchmark speed of measures of disorder for increasing size for std::vector<int>](https://i.imgur.com/uGoVIcE.png) It makes rather easy to see the different groups of complexities: -* *Runs(X)* and *Mono(X)* are obvious O(n) algorithms. +* *Amp(X)*, *Runs(X)* and *Mono(X)* are obvious O(n) algorithms. * *Dis(X)* is a more involved O(n) algorithm. * All of the other measures of disorder run in O(n log n) time. From 8a67fc4e0586336099071e06992565d780402bc9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 17 Jan 2026 00:11:44 +0100 Subject: [PATCH 39/40] Preparing release 2.1.0 --- CMakeLists.txt | 4 ++-- README.md | 4 ++-- conanfile.py | 4 ++-- docs/Home.md | 2 +- docs/Tooling.md | 4 ++-- include/cpp-sort/version.h | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d9f3b98..a760d814 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ -# Copyright (c) 2015-2025 Morwenn +# Copyright (c) 2015-2026 Morwenn # SPDX-License-Identifier: MIT cmake_minimum_required(VERSION 3.11.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 2.0.0 LANGUAGES CXX) +project(cpp-sort VERSION 2.1.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(cpp-sort-utils) diff --git a/README.md b/README.md index 7319c607..55617ae9 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-2.0.0-blue.svg)](https://codeberg.org/Morwenn/cpp-sort/releases/tag/v2.0.0) -[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F2.0.0-blue.svg)](https://conan.io/center/recipes/cpp-sort?version=2.0.0) +[![Latest Release](https://img.shields.io/badge/release-2.1.0-blue.svg)](https://codeberg.org/Morwenn/cpp-sort/releases/tag/v2.1.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F2.1.0-blue.svg)](https://conan.io/center/recipes/cpp-sort?version=2.1.0) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/2.x.y-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 88e17b00..7a7b52c4 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018-2025 Morwenn +# Copyright (c) 2018-2026 Morwenn # SPDX-License-Identifier: MIT import os.path @@ -16,7 +16,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "2.0.0" + version = "2.1.0" description = "Sorting algorithms & related tools" license = "MIT" url = "https://codeberg.org/Morwenn/cpp-sort" diff --git a/docs/Home.md b/docs/Home.md index 79b16d03..68437350 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 2.0.0** documentation! +Welcome to the **cpp-sort 2.1.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 ab8e60d1..a56e4ed6 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -45,10 +45,10 @@ Note: when `CPPSORT_ENABLE_AUDITS` is `ON`, assertions in the library are enable conan search cpp-sort --remote=conancenter ``` -And then install any version to your local cache as follows (here with version 2.0.0): +And then install any version to your local cache as follows (here with version 2.1.0): ```sh -conan install --requires=cpp-sort/2.0.0 +conan install --requires=cpp-sort/2.1.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 create 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 d2c34fea..0ea4eb92 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 Morwenn + * Copyright (c) 2018-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_VERSION_H_ @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 2 -#define CPPSORT_VERSION_MINOR 0 +#define CPPSORT_VERSION_MINOR 1 #define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_ From 71019153d6c4d48a160e39705891e0c9ababffe3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 17 Jan 2026 13:21:49 +0100 Subject: [PATCH 40/40] conanfile.py: add missing cpp-sort-utils.cmake to export_sources --- conanfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 7a7b52c4..37f81c03 100644 --- a/conanfile.py +++ b/conanfile.py @@ -29,7 +29,8 @@ class CppSortConan(ConanFile): exports_sources = [ "include/*", "CMakeLists.txt", - "cmake/cpp-sort-config.cmake.in" + "cmake/cpp-sort-config.cmake.in", + "cmake/cpp-sort-utils.cmake", ] settings = "os", "compiler", "build_type", "arch" package_type = "header-library"