From 3736c3f7c4a2f5741a5b5aed6413161ee0db8ab3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 18 Jan 2026 01:28:48 +0100 Subject: [PATCH 01/27] CI: upgrade actions/checkout to v6 --- .forgejo/workflows/deploy-to-wiki.yml | 2 +- .github/workflows/code-coverage.yml | 4 ++-- .github/workflows/deploy-to-wiki.yml | 6 +++--- .github/workflows/mirror-to-codeberg.yml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.forgejo/workflows/deploy-to-wiki.yml b/.forgejo/workflows/deploy-to-wiki.yml index eda21ce6..b5662dc5 100644 --- a/.forgejo/workflows/deploy-to-wiki.yml +++ b/.forgejo/workflows/deploy-to-wiki.yml @@ -25,7 +25,7 @@ jobs: path: main - name: Checkout wiki - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{forgejo.repository}}.wiki path: wiki diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 673b6941..e8c87930 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2025 Morwenn +# Copyright (c) 2020-2026 Morwenn # SPDX-License-Identifier: MIT name: Coverage Upload to Codecov @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout project - uses: actions/checkout@v4 + uses: actions/checkout@v6 - uses: rui314/setup-mold@v1 - name: Install LCOV diff --git a/.github/workflows/deploy-to-wiki.yml b/.github/workflows/deploy-to-wiki.yml index f2b1b964..a9612004 100644 --- a/.github/workflows/deploy-to-wiki.yml +++ b/.github/workflows/deploy-to-wiki.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2025 Morwenn +# Copyright (c) 2020-2026 Morwenn # SPDX-License-Identifier: MIT name: Wiki Deployment @@ -19,13 +19,13 @@ jobs: steps: - name: Checkout /docs - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{github.repository}} path: main - name: Checkout wiki - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{github.repository}}.wiki path: wiki diff --git a/.github/workflows/mirror-to-codeberg.yml b/.github/workflows/mirror-to-codeberg.yml index d13a05b6..7ebe5497 100644 --- a/.github/workflows/mirror-to-codeberg.yml +++ b/.github/workflows/mirror-to-codeberg.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Morwenn +# Copyright (c) 2025-2026 Morwenn # SPDX-License-Identifier: MIT name: Mirror Commits to Codeberg @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 From f0d68ea550887beadf2f4189d6a3e9427b131367 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 18 Jan 2026 11:07:57 +0100 Subject: [PATCH 02/27] Mention mirrors in the release checklist --- tools/release-checklist.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/release-checklist.md b/tools/release-checklist.md index 4cca2e63..7072cd7b 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -25,6 +25,7 @@ List of actions to perform when releasing a new cpp-sort version. ### After the release - [ ] Add the Zenodo badge to the release notes. +- [ ] Copy the release to mirrors as needed. - [ ] Close the new version's milestone. - [ ] Check that the documentation was correctly uploaded. - [ ] Add the new version to Conan Center Index. From b219e3012431391c964b1a2724cfcb75dc8e7d6c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 18 Jan 2026 17:57:40 +0100 Subject: [PATCH 03/27] Documentation: fix missing and useless link references --- docs/Changelog.md | 1 - docs/Comparators.md | 1 - docs/Miscellaneous-utilities.md | 1 - docs/Original-research.md | 1 - docs/Quickstart.md | 3 +-- docs/Sorter-adapters.md | 2 -- docs/Sorter-facade.md | 2 -- docs/Sorters.md | 5 ++--- docs/Writing-a-bubble_sorter.md | 1 - docs/Writing-a-randomizing_adapter.md | 3 --- docs/Writing-a-sorter.md | 1 - 11 files changed, 3 insertions(+), 18 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index f52a0847..611381bd 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -44,7 +44,6 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe [feature-test-macros]: https://wg21.link/SD6 [pdq-sorter]: Sorters.md#pdq_sorter [ska-sorter]: Sorters.md#ska_sorter - [sorter-facade]: Sorter-facade.md [std-greater-void]: https://en.cppreference.com/w/cpp/utility/functional/greater_void [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity [std-less-void]: https://en.cppreference.com/w/cpp/utility/functional/less_void diff --git a/docs/Comparators.md b/docs/Comparators.md index 0fb46650..88857160 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -99,7 +99,6 @@ The two-parameter version of the customization point calls the three-parameter o [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 [custom-point]: https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 0b82ba7a..dd697bba 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -549,7 +549,6 @@ assert(not cppsort::utility::check_strict_weak_ordering(vec, std::less{});) [std-greater]: https://en.cppreference.com/w/cpp/utility/functional/greater [std-greater-void]: https://en.cppreference.com/w/cpp/utility/functional/greater_void [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity - [std-integer-sequence]: https://en.cppreference.com/w/cpp/utility/integer_sequence [std-integral-constant]: https://en.cppreference.com/w/cpp/types/integral_constant [std-invoke]: https://en.cppreference.com/w/cpp/utility/functional/invoke [std-is-arithmetic]: https://en.cppreference.com/w/cpp/types/is_arithmetic diff --git a/docs/Original-research.md b/docs/Original-research.md index 8416583d..6bc747a3 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -239,7 +239,6 @@ The following relations have yet to be analyzed: 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 [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort [divide-sort-merge-strategy]: https://apps.dtic.mil/sti/tr/pdf/AD0737270.pdf [exact-sort]: https://www.geocities.ws/p356spt/ diff --git a/docs/Quickstart.md b/docs/Quickstart.md index d4420377..8cce1ef2 100644 --- a/docs/Quickstart.md +++ b/docs/Quickstart.md @@ -201,7 +201,7 @@ The specific restrictions are all documented in the adapters descriptions. ## Two-step sorting Sometimes the information is not represented as simple collection of class instances, but as [parallel arrays][parallel-arrays] (also known as structure of arrays). To sort those, **cpp-sort** provides components for two-step sorting of random-access collections: -1. Extract the sorted indices of a collection with [`utility::sorted_indices`][utility-sorter-indices] (similar to [`numpy.argsort`][numpy-argsort]). +1. Extract the sorted indices of a collection with [`utility::sorted_indices`][utility-sorted-indices] (similar to [`numpy.argsort`][numpy-argsort]). 2. Use the sorted indices to permute the arrays with [`utility::apply_permutation`][utility-apply-permutation]. ```cpp @@ -259,7 +259,6 @@ The previous sections describe some of the main tools provided by **cpp-sort** b [ska-sorter]: Sorters.md#ska_sorter [sorter-adapters]: Sorter-adapters.md [sorter-facade]: Sorter-facade.md - [sorter-traits]: Sorter-traits.md [sorters]: Sorters.md [std-searchers]: https://en.cppreference.com/w/cpp/utility/functional#Searchers [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index 2ab17ab7..5d163d8e 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -322,7 +322,6 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [This page][vergesort-fallbacks] contains benchmarks of vergesort on top of several sorting algorithms, showing that it can be valuable tool to add on top of most sorting algorithms. - [branchless-traits]: Miscellaneous-utilities.md#branchless-traits [ctad]: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort [drop-merge-sort]: https://github.com/emilk/drop-merge-sort @@ -335,7 +334,6 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [iterator-category]: Sorter-traits.md#iterator_category [iterator-tags]: https://en.cppreference.com/w/cpp/iterator/iterator_tags [low-moves-sorter]: Fixed-size-sorters.md#low_moves_sorter - [metrics-comparisons]: Metrics.md#comparisons [mountain-sort]: https://github.com/Morwenn/mountain-sort [probe-mono]: Measures-of-disorder.md#mono [probe-rem]: Measures-of-disorder.md#rem diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index 77b1e7f4..535f1d3f 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -182,8 +182,6 @@ While it does not appear in this documentation, `sorter_facade` actually relies [issue-185]: https://github.com/Morwenn/cpp-sort/issues/185 [selection-sort]: https://en.wikipedia.org/wiki/Selection_sort - [std-begin]: https://en.cppreference.com/w/cpp/iterator/begin - [std-end]: https://en.cppreference.com/w/cpp/iterator/end [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity [std-less-void]: https://en.cppreference.com/w/cpp/utility/functional/less_void [std-ranges-less]: https://en.cppreference.com/w/cpp/utility/functional/ranges/less diff --git a/docs/Sorters.md b/docs/Sorters.md index ca82711c..207954f4 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -285,7 +285,7 @@ Implements a [smoothsort][smoothsort]. | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | | n | n log n | n log n | 1 | No | Random-access | -While the complexity guarantees of this algorithm are optimal, this smoothsort isn't actually *fast* in practice. Except for some specific patterns (where `tim_sorter` or `pdq_sorter` are still faster anyway), it is always almost twice as slow as `heap_sorter`. Huge collections and/or huge objects may make a difference, but I have yet to see a case where this is a useful sorting algorithm. +While the complexity guarantees of this algorithm are optimal, this smoothsort isn't actually *fast* in practice. Except for some specific patterns (where [`tim_sorter`][tim-sorter] or [`pdq_sorter`][pdq-sorter] are still faster anyway), it is always almost twice as slow as `heap_sorter`. Huge collections and/or huge objects may make a difference, but I have yet to see a case where this is a useful sorting algorithm. ### `spin_sorter` @@ -452,7 +452,6 @@ struct spread_sorter: [adaptive-quickselect]: https://arxiv.org/abs/1606.00484 [adaptive-shivers-sort]: https://arxiv.org/abs/1809.08411 - [bitmap-allocator]: https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html [block-sort]: https://en.wikipedia.org/wiki/Block_sort [bottom-up-heapsort]: https://en.wikipedia.org/wiki/Heapsort#Bottom-up_heapsort [branchless-traits]: Miscellaneous-utilities.md#branchless-traits @@ -487,8 +486,8 @@ struct spread_sorter: [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/stable_sort [std-vector-bool]: https://en.cppreference.com/w/cpp/container/vector_bool + [tim-sorter]: Sorters.md#tim_sorter [timsort]: https://en.wikipedia.org/wiki/Timsort [wiki-sort]: https://github.com/BonzaiThePenguin/WikiSort - [wiki-sorter]: Sorters.md#wiki_sorter [writing-a-sorter]: Writing-a-sorter.md [writing-a-bubble-sorter]: Writing-a-bubble_sorter.md diff --git a/docs/Writing-a-bubble_sorter.md b/docs/Writing-a-bubble_sorter.md index 2ba74f75..d9b96aa2 100644 --- a/docs/Writing-a-bubble_sorter.md +++ b/docs/Writing-a-bubble_sorter.md @@ -421,7 +421,6 @@ That's it: we have covered pretty much every interesting aspect of writing a sim [as-function]: Miscellaneous-utilities.md#as_function [bubble-sort]: https://en.wikipedia.org/wiki/Bubble_sort - [bubble-sorter]: https://en.wikipedia.org/wiki/Bubble_sort [heap-sorter]: Sorters.md#heap_sorter [hybrid-adapter]: Sorter-adapters.md#hybrid_adapter [is-projection]: Sorter-traits.md#is_projection-and-is_projection_iterator diff --git a/docs/Writing-a-randomizing_adapter.md b/docs/Writing-a-randomizing_adapter.md index a29c274a..6d730492 100644 --- a/docs/Writing-a-randomizing_adapter.md +++ b/docs/Writing-a-randomizing_adapter.md @@ -143,12 +143,9 @@ The full implementation can be found in the `examples` folder. [golden-tests]: https://en.wikipedia.org/wiki/Characterization_test [hyrums-law]: https://www.hyrumslaw.com/ [iterator-category]: https://en.cppreference.com/w/cpp/iterator - [proxy-iterators]: https://wg21.link/P0022 [quick-sorter]: Sorters.md#quick_sorter [sfinae]: https://en.cppreference.com/w/cpp/language/sfinae [sorter-adapters]: Sorter-adapters.md [sorter-facade]: Sorter-facade.md - [sorter-traits]: Sorter-traits.md#sorter_traits [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort.html - [writing-a-sorter]: Writing-a-sorter.md [writing-a-bubble-sorter]: Writing-a-bubble_sorter.md diff --git a/docs/Writing-a-sorter.md b/docs/Writing-a-sorter.md index 64c3a154..523f8fca 100644 --- a/docs/Writing-a-sorter.md +++ b/docs/Writing-a-sorter.md @@ -593,7 +593,6 @@ In the example above, the resulting sorter will use our `low_projections_sorter` [std-ranges-less]: https://en.cppreference.com/w/cpp/utility/functional/ranges/less [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort [stlab]: https://stlab.adobe.com/ - [utility-as-function]: Miscellaneous-utilities.md#as_function [utility-function-objects]: Miscellaneous-utilities.md#miscellaneous-function-objects [utility-identity]: Miscellaneous-utilities.md#miscellaneous-function-objects [wiki-sorter]: Sorters.md#wiki_sorter From ccf3b51ffb2df13e65bb246da8c945ce9ddffbb6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 23 Jan 2026 20:19:09 +0100 Subject: [PATCH 04/27] Smaller sorting network for 26 inputs --- docs/Fixed-size-sorters.md | 2 +- .../cpp-sort/detail/sorting_network/sort26.h | 228 +++++++++--------- tools/generate_sorting_network.py | 9 +- 3 files changed, 120 insertions(+), 119 deletions(-) diff --git a/docs/Fixed-size-sorters.md b/docs/Fixed-size-sorters.md index e4570f8b..a2f3e424 100644 --- a/docs/Fixed-size-sorters.md +++ b/docs/Fixed-size-sorters.md @@ -149,7 +149,7 @@ Size | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: **CEs** | 0 | 1 | 3 | 5 | 9 | 12 | 16 | 19 | 25 | 29 | 35 | 39 | 45 | 51 | 56 | 60 **Size** | **17** | **18** | **19** | **20** | **21** | **22** | **23** | **24** | **25** | **26** | **27** | **28** | **29** | **30** | **31** | **32** -**CEs** | 71 | 77 | 85 | 91 | 99 | 106 | 114 | 120 | 130 | 139 | 147 | 155 | 164 | 172 | 180 | 185 +**CEs** | 71 | 77 | 85 | 91 | 99 | 106 | 114 | 120 | 130 | 138 | 147 | 155 | 164 | 172 | 180 | 185 **Size** | **33** | **34** | **35** | **36** | **37** | **38** | **39** | **40** | **41** | **42** | **43** | **44** | **45** | **46** | **47** | **48** **CEs** | 199 | 209 | 220 | 227 | 240 | 250 | 259 | 265 | 282 | 291 | 303 | 309 | 324 | 332 | 340 | 346 **Size** | **49** | **50** | **51** | **52** | **53** | **54** | **55** | **56** | **57** | **58** | **59** | **60** | **61** | **62** | **63** | **64** diff --git a/include/cpp-sort/detail/sorting_network/sort26.h b/include/cpp-sort/detail/sorting_network/sort26.h index d19f8e80..2dd5845d 100644 --- a/include/cpp-sort/detail/sorting_network/sort26.h +++ b/include/cpp-sort/detail/sorting_network/sort26.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2025 Morwenn + * Copyright (c) 2015-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SORTING_NETWORK_SORT26_H_ @@ -24,132 +24,131 @@ namespace cppsort::detail { iter_swap_if(first, first + 1, compare, projection); iter_swap_if(first + 2, first + 3, compare, projection); - iter_swap_if(first + 4, first + 21, compare, projection); - iter_swap_if(first + 5, first + 6, compare, projection); - iter_swap_if(first + 7, first + 8, compare, projection); - iter_swap_if(first + 9, first + 10, compare, projection); - iter_swap_if(first + 11, first + 12, compare, projection); - iter_swap_if(first + 13, first + 14, compare, projection); - iter_swap_if(first + 15, first + 16, compare, projection); - iter_swap_if(first + 17, first + 18, compare, projection); - iter_swap_if(first + 19, first + 20, compare, projection); + iter_swap_if(first + 4, first + 5, compare, projection); + iter_swap_if(first + 6, first + 7, compare, projection); + iter_swap_if(first + 8, first + 9, compare, projection); + iter_swap_if(first + 10, first + 11, compare, projection); + iter_swap_if(first + 12, first + 13, compare, projection); + iter_swap_if(first + 14, first + 15, compare, projection); + iter_swap_if(first + 16, first + 17, compare, projection); + iter_swap_if(first + 18, first + 19, compare, projection); + iter_swap_if(first + 20, first + 21, compare, projection); iter_swap_if(first + 22, first + 23, compare, projection); iter_swap_if(first + 24, first + 25, compare, projection); - iter_swap_if(first, first + 24, compare, projection); - iter_swap_if(first + 1, first + 25, compare, projection); - iter_swap_if(first + 3, first + 21, compare, projection); - iter_swap_if(first + 4, first + 22, compare, projection); + iter_swap_if(first, first + 2, compare, projection); + iter_swap_if(first + 1, first + 3, compare, projection); + iter_swap_if(first + 4, first + 6, compare, projection); iter_swap_if(first + 5, first + 7, compare, projection); - iter_swap_if(first + 6, first + 8, compare, projection); + iter_swap_if(first + 8, first + 10, compare, projection); iter_swap_if(first + 9, first + 11, compare, projection); - iter_swap_if(first + 10, first + 12, compare, projection); - iter_swap_if(first + 13, first + 15, compare, projection); iter_swap_if(first + 14, first + 16, compare, projection); - iter_swap_if(first + 17, first + 19, compare, projection); + iter_swap_if(first + 15, first + 17, compare, projection); iter_swap_if(first + 18, first + 20, compare, projection); - iter_swap_if(first, first + 4, compare, projection); - iter_swap_if(first + 2, first + 22, compare, projection); - iter_swap_if(first + 3, first + 23, compare, projection); - iter_swap_if(first + 5, first + 9, compare, projection); - iter_swap_if(first + 6, first + 10, compare, projection); - iter_swap_if(first + 7, first + 11, compare, projection); - iter_swap_if(first + 8, first + 12, compare, projection); - iter_swap_if(first + 13, first + 17, compare, projection); - iter_swap_if(first + 14, first + 18, compare, projection); - iter_swap_if(first + 15, first + 19, compare, projection); - iter_swap_if(first + 16, first + 20, compare, projection); - iter_swap_if(first + 21, first + 25, compare, projection); - iter_swap_if(first, first + 2, compare, projection); - iter_swap_if(first + 1, first + 3, compare, projection); - iter_swap_if(first + 4, first + 21, compare, projection); - iter_swap_if(first + 5, first + 13, compare, projection); - iter_swap_if(first + 6, first + 14, compare, projection); - iter_swap_if(first + 7, first + 15, compare, projection); - iter_swap_if(first + 8, first + 16, compare, projection); - iter_swap_if(first + 9, first + 17, compare, projection); - iter_swap_if(first + 10, first + 18, compare, projection); - iter_swap_if(first + 11, first + 19, compare, projection); - iter_swap_if(first + 12, first + 20, compare, projection); + iter_swap_if(first + 19, first + 21, compare, projection); iter_swap_if(first + 22, first + 24, compare, projection); iter_swap_if(first + 23, first + 25, compare, projection); - iter_swap_if(first, first + 5, compare, projection); - iter_swap_if(first + 1, first + 7, compare, projection); - iter_swap_if(first + 2, first + 4, compare, projection); - iter_swap_if(first + 3, first + 10, compare, projection); - iter_swap_if(first + 15, first + 22, compare, projection); - iter_swap_if(first + 18, first + 24, compare, projection); - iter_swap_if(first + 20, first + 25, compare, projection); - iter_swap_if(first + 21, first + 23, compare, projection); - iter_swap_if(first + 2, first + 9, compare, projection); + iter_swap_if(first, first + 4, compare, projection); + iter_swap_if(first + 1, first + 6, compare, projection); + iter_swap_if(first + 2, first + 5, compare, projection); iter_swap_if(first + 3, first + 7, compare, projection); - iter_swap_if(first + 4, first + 14, compare, projection); - iter_swap_if(first + 5, first + 13, compare, projection); - iter_swap_if(first + 6, first + 15, compare, projection); - iter_swap_if(first + 10, first + 19, compare, projection); - iter_swap_if(first + 11, first + 21, compare, projection); - iter_swap_if(first + 12, first + 20, compare, projection); - iter_swap_if(first + 16, first + 23, compare, projection); + iter_swap_if(first + 8, first + 14, compare, projection); + iter_swap_if(first + 9, first + 16, compare, projection); + iter_swap_if(first + 10, first + 15, compare, projection); + iter_swap_if(first + 11, first + 17, compare, projection); iter_swap_if(first + 18, first + 22, compare, projection); - iter_swap_if(first + 1, first + 5, compare, projection); - iter_swap_if(first + 2, first + 6, compare, projection); - iter_swap_if(first + 3, first + 15, compare, projection); - iter_swap_if(first + 7, first + 17, compare, projection); - iter_swap_if(first + 8, first + 18, compare, projection); - iter_swap_if(first + 10, first + 22, compare, projection); - iter_swap_if(first + 11, first + 13, compare, projection); - iter_swap_if(first + 12, first + 14, compare, projection); - iter_swap_if(first + 19, first + 23, compare, projection); - iter_swap_if(first + 20, first + 24, compare, projection); - iter_swap_if(first + 1, first + 2, compare, projection); - iter_swap_if(first + 4, first + 7, compare, projection); - iter_swap_if(first + 5, first + 11, compare, projection); + iter_swap_if(first + 19, first + 24, compare, projection); + iter_swap_if(first + 20, first + 23, compare, projection); + iter_swap_if(first + 21, first + 25, compare, projection); + iter_swap_if(first, first + 18, compare, projection); + iter_swap_if(first + 1, first + 19, compare, projection); + iter_swap_if(first + 2, first + 20, compare, projection); + iter_swap_if(first + 3, first + 21, compare, projection); + iter_swap_if(first + 4, first + 22, compare, projection); + iter_swap_if(first + 5, first + 23, compare, projection); + iter_swap_if(first + 6, first + 24, compare, projection); + iter_swap_if(first + 7, first + 25, compare, projection); + iter_swap_if(first + 9, first + 12, compare, projection); + iter_swap_if(first + 13, first + 16, compare, projection); + iter_swap_if(first + 3, first + 11, compare, projection); iter_swap_if(first + 8, first + 9, compare, projection); - iter_swap_if(first + 10, first + 12, compare, projection); - iter_swap_if(first + 13, first + 15, compare, projection); - iter_swap_if(first + 14, first + 20, compare, projection); - iter_swap_if(first + 16, first + 17, compare, projection); - iter_swap_if(first + 18, first + 21, compare, projection); - iter_swap_if(first + 23, first + 24, compare, projection); - iter_swap_if(first + 3, first + 8, compare, projection); - iter_swap_if(first + 4, first + 6, compare, projection); - iter_swap_if(first + 7, first + 16, compare, projection); - iter_swap_if(first + 9, first + 18, compare, projection); iter_swap_if(first + 10, first + 13, compare, projection); iter_swap_if(first + 12, first + 15, compare, projection); - iter_swap_if(first + 17, first + 22, compare, projection); - iter_swap_if(first + 19, first + 21, compare, projection); - iter_swap_if(first + 4, first + 5, compare, projection); - iter_swap_if(first + 6, first + 11, compare, projection); + iter_swap_if(first + 14, first + 22, compare, projection); + iter_swap_if(first + 16, first + 17, compare, projection); + iter_swap_if(first, first + 8, compare, projection); + iter_swap_if(first + 1, first + 9, compare, projection); + iter_swap_if(first + 2, first + 14, compare, projection); + iter_swap_if(first + 6, first + 12, compare, projection); + iter_swap_if(first + 7, first + 15, compare, projection); + iter_swap_if(first + 10, first + 18, compare, projection); + iter_swap_if(first + 11, first + 23, compare, projection); + iter_swap_if(first + 13, first + 19, compare, projection); + iter_swap_if(first + 16, first + 24, compare, projection); + iter_swap_if(first + 17, first + 25, compare, projection); + iter_swap_if(first + 1, first + 2, compare, projection); + iter_swap_if(first + 3, first + 18, compare, projection); + iter_swap_if(first + 4, first + 8, compare, projection); + iter_swap_if(first + 7, first + 22, compare, projection); + iter_swap_if(first + 17, first + 21, compare, projection); + iter_swap_if(first + 23, first + 24, compare, projection); + iter_swap_if(first + 3, first + 14, compare, projection); + iter_swap_if(first + 4, first + 10, compare, projection); + iter_swap_if(first + 5, first + 18, compare, projection); + iter_swap_if(first + 7, first + 20, compare, projection); + iter_swap_if(first + 8, first + 13, compare, projection); + iter_swap_if(first + 11, first + 22, compare, projection); + iter_swap_if(first + 12, first + 17, compare, projection); + iter_swap_if(first + 15, first + 21, compare, projection); + iter_swap_if(first + 1, first + 4, compare, projection); + iter_swap_if(first + 5, first + 6, compare, projection); iter_swap_if(first + 7, first + 9, compare, projection); - iter_swap_if(first + 14, first + 19, compare, projection); + iter_swap_if(first + 8, first + 10, compare, projection); + iter_swap_if(first + 15, first + 17, compare, projection); iter_swap_if(first + 16, first + 18, compare, projection); - iter_swap_if(first + 20, first + 21, compare, projection); + iter_swap_if(first + 19, first + 20, compare, projection); + iter_swap_if(first + 21, first + 24, compare, projection); + iter_swap_if(first + 2, first + 5, compare, projection); + iter_swap_if(first + 3, first + 10, compare, projection); + iter_swap_if(first + 6, first + 14, compare, projection); + iter_swap_if(first + 9, first + 13, compare, projection); + iter_swap_if(first + 11, first + 19, compare, projection); + iter_swap_if(first + 12, first + 16, compare, projection); + iter_swap_if(first + 15, first + 22, compare, projection); + iter_swap_if(first + 20, first + 23, compare, projection); + iter_swap_if(first + 2, first + 8, compare, projection); + iter_swap_if(first + 5, first + 7, compare, projection); + iter_swap_if(first + 6, first + 9, compare, projection); + iter_swap_if(first + 11, first + 12, compare, projection); + iter_swap_if(first + 13, first + 14, compare, projection); + iter_swap_if(first + 16, first + 19, compare, projection); + iter_swap_if(first + 17, first + 23, compare, projection); + iter_swap_if(first + 18, first + 20, compare, projection); iter_swap_if(first + 2, first + 4, compare, projection); - iter_swap_if(first + 3, first + 6, compare, projection); + iter_swap_if(first + 3, first + 5, compare, projection); + iter_swap_if(first + 6, first + 11, compare, projection); iter_swap_if(first + 7, first + 10, compare, projection); - iter_swap_if(first + 8, first + 11, compare, projection); - iter_swap_if(first + 9, first + 12, compare, projection); - iter_swap_if(first + 13, first + 16, compare, projection); - iter_swap_if(first + 14, first + 17, compare, projection); + iter_swap_if(first + 9, first + 16, compare, projection); + iter_swap_if(first + 12, first + 13, compare, projection); + iter_swap_if(first + 14, first + 19, compare, projection); iter_swap_if(first + 15, first + 18, compare, projection); - iter_swap_if(first + 19, first + 22, compare, projection); + iter_swap_if(first + 20, first + 22, compare, projection); iter_swap_if(first + 21, first + 23, compare, projection); iter_swap_if(first + 3, first + 4, compare, projection); iter_swap_if(first + 5, first + 8, compare, projection); iter_swap_if(first + 6, first + 7, compare, projection); - iter_swap_if(first + 9, first + 10, compare, projection); - iter_swap_if(first + 11, first + 13, compare, projection); - iter_swap_if(first + 12, first + 14, compare, projection); - iter_swap_if(first + 15, first + 16, compare, projection); + iter_swap_if(first + 9, first + 11, compare, projection); + iter_swap_if(first + 10, first + 12, compare, projection); + iter_swap_if(first + 13, first + 15, compare, projection); + iter_swap_if(first + 14, first + 16, compare, projection); iter_swap_if(first + 17, first + 20, compare, projection); iter_swap_if(first + 18, first + 19, compare, projection); iter_swap_if(first + 21, first + 22, compare, projection); iter_swap_if(first + 5, first + 6, compare, projection); iter_swap_if(first + 7, first + 8, compare, projection); - iter_swap_if(first + 9, first + 11, compare, projection); - iter_swap_if(first + 10, first + 13, compare, projection); - iter_swap_if(first + 12, first + 15, compare, projection); - iter_swap_if(first + 14, first + 16, compare, projection); + iter_swap_if(first + 9, first + 10, compare, projection); + iter_swap_if(first + 11, first + 12, compare, projection); + iter_swap_if(first + 13, first + 14, compare, projection); + iter_swap_if(first + 15, first + 16, compare, projection); iter_swap_if(first + 17, first + 18, compare, projection); iter_swap_if(first + 19, first + 20, compare, projection); iter_swap_if(first + 4, first + 5, compare, projection); @@ -166,22 +165,23 @@ namespace cppsort::detail template [[nodiscard]] static constexpr auto index_pairs() noexcept - -> std::array, 139> + -> std::array, 138> { return {{ - {0, 1}, {2, 3}, {4, 21}, {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}, {17, 18}, {19, 20}, {22, 23}, {24, 25}, - {0, 24}, {1, 25}, {3, 21}, {4, 22}, {5, 7}, {6, 8}, {9, 11}, {10, 12}, {13, 15}, {14, 16}, {17, 19}, {18, 20}, - {0, 4}, {2, 22}, {3, 23}, {5, 9}, {6, 10}, {7, 11}, {8, 12}, {13, 17}, {14, 18}, {15, 19}, {16, 20}, {21, 25}, - {0, 2}, {1, 3}, {4, 21}, {5, 13}, {6, 14}, {7, 15}, {8, 16}, {9, 17}, {10, 18}, {11, 19}, {12, 20}, {22, 24}, {23, 25}, - {0, 5}, {1, 7}, {2, 4}, {3, 10}, {15, 22}, {18, 24}, {20, 25}, {21, 23}, - {2, 9}, {3, 7}, {4, 14}, {5, 13}, {6, 15}, {10, 19}, {11, 21}, {12, 20}, {16, 23}, {18, 22}, - {1, 5}, {2, 6}, {3, 15}, {7, 17}, {8, 18}, {10, 22}, {11, 13}, {12, 14}, {19, 23}, {20, 24}, - {1, 2}, {4, 7}, {5, 11}, {8, 9}, {10, 12}, {13, 15}, {14, 20}, {16, 17}, {18, 21}, {23, 24}, - {3, 8}, {4, 6}, {7, 16}, {9, 18}, {10, 13}, {12, 15}, {17, 22}, {19, 21}, - {4, 5}, {6, 11}, {7, 9}, {14, 19}, {16, 18}, {20, 21}, - {2, 4}, {3, 6}, {7, 10}, {8, 11}, {9, 12}, {13, 16}, {14, 17}, {15, 18}, {19, 22}, {21, 23}, - {3, 4}, {5, 8}, {6, 7}, {9, 10}, {11, 13}, {12, 14}, {15, 16}, {17, 20}, {18, 19}, {21, 22}, - {5, 6}, {7, 8}, {9, 11}, {10, 13}, {12, 15}, {14, 16}, {17, 18}, {19, 20}, + {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, {22, 23}, {24, 25}, + {0, 2}, {1, 3}, {4, 6}, {5, 7}, {8, 10}, {9, 11}, {14, 16}, {15, 17}, {18, 20}, {19, 21}, {22, 24}, {23, 25}, + {0, 4}, {1, 6}, {2, 5}, {3, 7}, {8, 14}, {9, 16}, {10, 15}, {11, 17}, {18, 22}, {19, 24}, {20, 23}, {21, 25}, + {0, 18}, {1, 19}, {2, 20}, {3, 21}, {4, 22}, {5, 23}, {6, 24}, {7, 25}, {9, 12}, {13, 16}, + {3, 11}, {8, 9}, {10, 13}, {12, 15}, {14, 22}, {16, 17}, + {0, 8}, {1, 9}, {2, 14}, {6, 12}, {7, 15}, {10, 18}, {11, 23}, {13, 19}, {16, 24}, {17, 25}, + {1, 2}, {3, 18}, {4, 8}, {7, 22}, {17, 21}, {23, 24}, + {3, 14}, {4, 10}, {5, 18}, {7, 20}, {8, 13}, {11, 22}, {12, 17}, {15, 21}, + {1, 4}, {5, 6}, {7, 9}, {8, 10}, {15, 17}, {16, 18}, {19, 20}, {21, 24}, + {2, 5}, {3, 10}, {6, 14}, {9, 13}, {11, 19}, {12, 16}, {15, 22}, {20, 23}, + {2, 8}, {5, 7}, {6, 9}, {11, 12}, {13, 14}, {16, 19}, {17, 23}, {18, 20}, + {2, 4}, {3, 5}, {6, 11}, {7, 10}, {9, 16}, {12, 13}, {14, 19}, {15, 18}, {20, 22}, {21, 23}, + {3, 4}, {5, 8}, {6, 7}, {9, 11}, {10, 12}, {13, 15}, {14, 16}, {17, 20}, {18, 19}, {21, 22}, + {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}, {17, 18}, {19, 20}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, }}; } diff --git a/tools/generate_sorting_network.py b/tools/generate_sorting_network.py index 7e909c2b..d5f5ef61 100644 --- a/tools/generate_sorting_network.py +++ b/tools/generate_sorting_network.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2022-2026 Morwenn +# SPDX-License-Identifier: MIT + import argparse import ast import textwrap @@ -69,9 +72,7 @@ def verify_network(pairs: list[tuple]): def generate_cxx(network: list[list[tuple]]): template = textwrap.dedent(""" - namespace cppsort - {{ - namespace detail + namespace cppsort::detail {{ template<> struct sorting_network_sorter_impl<{nb_inputs}> @@ -101,7 +102,7 @@ def generate_cxx(network: list[list[tuple]]): }}}}; }} }}; - }}}} + }} """) pairs = sum(network, []) From a03719490fa64fd3d840415f24eab7c71b481214 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 23 Jan 2026 21:08:50 +0100 Subject: [PATCH 05/27] Update sorting_network_sorter with latest results from SorterHunter - Improve depth for 33 and 41 inputs - Make the generator script more usable --- .../cpp-sort/detail/sorting_network/sort33.h | 191 +++++----- .../cpp-sort/detail/sorting_network/sort41.h | 357 +++++++++--------- tools/generate_sorting_network.py | 20 +- 3 files changed, 285 insertions(+), 283 deletions(-) diff --git a/include/cpp-sort/detail/sorting_network/sort33.h b/include/cpp-sort/detail/sorting_network/sort33.h index 63e71c33..2576c801 100644 --- a/include/cpp-sort/detail/sorting_network/sort33.h +++ b/include/cpp-sort/detail/sorting_network/sort33.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023-2025 Morwenn +* Copyright (c) 2023-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SORTING_NETWORK_SORT33_H_ @@ -102,113 +102,114 @@ namespace cppsort::detail iter_swap_if(first + 13, first + 29, compare, projection); iter_swap_if(first + 14, first + 30, compare, projection); iter_swap_if(first + 15, first + 31, compare, projection); - iter_swap_if(first + 1, first + 4, compare, projection); - iter_swap_if(first + 2, first + 16, compare, projection); - iter_swap_if(first + 3, first + 20, compare, projection); - iter_swap_if(first + 5, first + 18, compare, projection); - iter_swap_if(first + 6, first + 17, compare, projection); - iter_swap_if(first + 7, first + 22, compare, projection); - iter_swap_if(first + 9, first + 24, compare, projection); - iter_swap_if(first + 10, first + 12, compare, projection); - iter_swap_if(first + 11, first + 28, compare, projection); - iter_swap_if(first + 13, first + 26, compare, projection); - iter_swap_if(first + 14, first + 25, compare, projection); - iter_swap_if(first + 15, first + 29, compare, projection); - iter_swap_if(first + 19, first + 21, compare, projection); - iter_swap_if(first + 23, first + 32, compare, projection); - iter_swap_if(first + 27, first + 30, compare, projection); iter_swap_if(first + 1, first + 2, compare, projection); - iter_swap_if(first + 3, first + 9, compare, projection); + iter_swap_if(first + 3, first + 12, compare, projection); + iter_swap_if(first + 4, first + 8, compare, projection); + iter_swap_if(first + 5, first + 10, compare, projection); + iter_swap_if(first + 6, first + 9, compare, projection); + iter_swap_if(first + 7, first + 11, compare, projection); + iter_swap_if(first + 13, first + 14, compare, projection); + iter_swap_if(first + 15, first + 32, compare, projection); + iter_swap_if(first + 17, first + 18, compare, projection); + iter_swap_if(first + 19, first + 28, compare, projection); + iter_swap_if(first + 20, first + 24, compare, projection); + iter_swap_if(first + 21, first + 26, compare, projection); + iter_swap_if(first + 22, first + 25, compare, projection); + iter_swap_if(first + 23, first + 27, compare, projection); + iter_swap_if(first + 29, first + 30, compare, projection); + iter_swap_if(first + 3, first + 17, compare, projection); iter_swap_if(first + 4, first + 16, compare, projection); - iter_swap_if(first + 6, first + 10, compare, projection); - iter_swap_if(first + 7, first + 19, compare, projection); - iter_swap_if(first + 11, first + 17, compare, projection); - iter_swap_if(first + 12, first + 24, compare, projection); - iter_swap_if(first + 13, first + 23, compare, projection); - iter_swap_if(first + 15, first + 27, compare, projection); - iter_swap_if(first + 22, first + 26, compare, projection); + iter_swap_if(first + 5, first + 20, compare, projection); + iter_swap_if(first + 7, first + 13, compare, projection); + iter_swap_if(first + 10, first + 22, compare, projection); + iter_swap_if(first + 11, first + 29, compare, projection); + iter_swap_if(first + 12, first + 21, compare, projection); + iter_swap_if(first + 14, first + 23, compare, projection); + iter_swap_if(first + 15, first + 19, compare, projection); + iter_swap_if(first + 18, first + 24, compare, projection); iter_swap_if(first + 25, first + 28, compare, projection); - iter_swap_if(first + 29, first + 30, compare, projection); - iter_swap_if(first + 3, first + 6, compare, projection); - iter_swap_if(first + 7, first + 12, compare, projection); - iter_swap_if(first + 9, first + 18, compare, projection); - iter_swap_if(first + 10, first + 20, compare, projection); + iter_swap_if(first + 27, first + 30, compare, projection); + iter_swap_if(first + 3, first + 5, compare, projection); + iter_swap_if(first + 7, first + 18, compare, projection); + iter_swap_if(first + 8, first + 16, compare, projection); + iter_swap_if(first + 9, first + 20, compare, projection); + iter_swap_if(first + 10, first + 17, compare, projection); + iter_swap_if(first + 12, first + 15, compare, projection); iter_swap_if(first + 13, first + 14, compare, projection); - iter_swap_if(first + 15, first + 28, compare, projection); - iter_swap_if(first + 17, first + 22, compare, projection); - iter_swap_if(first + 21, first + 25, compare, projection); + iter_swap_if(first + 19, first + 26, compare, projection); + iter_swap_if(first + 21, first + 22, compare, projection); + iter_swap_if(first + 23, first + 29, compare, projection); + iter_swap_if(first + 28, first + 32, compare, projection); + iter_swap_if(first + 1, first + 8, compare, projection); + iter_swap_if(first + 2, first + 16, compare, projection); + iter_swap_if(first + 5, first + 10, compare, projection); + iter_swap_if(first + 6, first + 12, compare, projection); + iter_swap_if(first + 11, first + 19, compare, projection); + iter_swap_if(first + 13, first + 24, compare, projection); + iter_swap_if(first + 14, first + 25, compare, projection); + iter_swap_if(first + 15, first + 20, compare, projection); + iter_swap_if(first + 17, first + 21, compare, projection); + iter_swap_if(first + 26, first + 28, compare, projection); iter_swap_if(first + 27, first + 32, compare, projection); - iter_swap_if(first + 5, first + 13, compare, projection); - iter_swap_if(first + 6, first + 16, compare, projection); - iter_swap_if(first + 9, first + 10, compare, projection); - iter_swap_if(first + 12, first + 20, compare, projection); - iter_swap_if(first + 14, first + 19, compare, projection); - iter_swap_if(first + 15, first + 23, compare, projection); - iter_swap_if(first + 25, first + 26, compare, projection); + iter_swap_if(first, first + 6, compare, projection); + iter_swap_if(first + 2, first + 8, compare, projection); + iter_swap_if(first + 7, first + 10, compare, projection); + iter_swap_if(first + 9, first + 16, compare, projection); + iter_swap_if(first + 11, first + 18, compare, projection); + iter_swap_if(first + 12, first + 15, compare, projection); + iter_swap_if(first + 20, first + 22, compare, projection); + iter_swap_if(first + 24, first + 26, compare, projection); + iter_swap_if(first + 27, first + 28, compare, projection); iter_swap_if(first + 30, first + 32, compare, projection); iter_swap_if(first + 5, first + 8, compare, projection); - iter_swap_if(first + 11, first + 13, compare, projection); - iter_swap_if(first + 14, first + 18, compare, projection); - iter_swap_if(first + 15, first + 21, compare, projection); + iter_swap_if(first + 6, first + 9, compare, projection); + iter_swap_if(first + 11, first + 17, compare, projection); + iter_swap_if(first + 12, first + 16, compare, projection); + iter_swap_if(first + 13, first + 15, compare, projection); + iter_swap_if(first + 14, first + 20, compare, projection); + iter_swap_if(first + 18, first + 21, compare, projection); iter_swap_if(first + 19, first + 22, compare, projection); iter_swap_if(first + 23, first + 27, compare, projection); + iter_swap_if(first + 28, first + 29, compare, projection); iter_swap_if(first + 31, first + 32, compare, projection); - iter_swap_if(first + 4, first + 5, compare, projection); - iter_swap_if(first + 7, first + 11, compare, projection); - iter_swap_if(first + 8, first + 16, compare, projection); - iter_swap_if(first + 12, first + 14, compare, projection); - iter_swap_if(first + 13, first + 24, compare, projection); - iter_swap_if(first + 15, first + 17, compare, projection); - iter_swap_if(first + 18, first + 20, compare, projection); - iter_swap_if(first + 19, first + 21, compare, projection); - iter_swap_if(first + 22, first + 28, compare, projection); - iter_swap_if(first + 23, first + 25, compare, projection); - iter_swap_if(first + 27, first + 29, compare, projection); - iter_swap_if(first + 1, first + 4, compare, projection); - iter_swap_if(first + 3, first + 5, compare, projection); - iter_swap_if(first + 6, first + 8, compare, projection); - iter_swap_if(first + 7, first + 9, compare, projection); - iter_swap_if(first + 10, first + 11, compare, projection); - iter_swap_if(first + 13, first + 15, compare, projection); - iter_swap_if(first + 17, first + 24, compare, projection); - iter_swap_if(first + 22, first + 23, compare, projection); - iter_swap_if(first + 26, first + 27, compare, projection); - iter_swap_if(first + 29, first + 30, compare, projection); - iter_swap_if(first, first + 1, compare, projection); - iter_swap_if(first + 2, first + 6, compare, projection); + iter_swap_if(first + 3, first + 6, compare, projection); + iter_swap_if(first + 7, first + 12, compare, projection); iter_swap_if(first + 8, first + 9, compare, projection); - iter_swap_if(first + 11, first + 16, compare, projection); - iter_swap_if(first + 12, first + 13, compare, projection); + iter_swap_if(first + 10, first + 16, compare, projection); + iter_swap_if(first + 11, first + 13, compare, projection); iter_swap_if(first + 14, first + 17, compare, projection); iter_swap_if(first + 15, first + 18, compare, projection); - iter_swap_if(first + 19, first + 22, compare, projection); - iter_swap_if(first + 20, first + 24, compare, projection); - iter_swap_if(first + 25, first + 26, compare, projection); - iter_swap_if(first + 2, first + 4, compare, projection); - iter_swap_if(first + 5, first + 8, compare, projection); + iter_swap_if(first + 19, first + 24, compare, projection); + iter_swap_if(first + 20, first + 21, compare, projection); + iter_swap_if(first + 22, first + 25, compare, projection); + iter_swap_if(first + 23, first + 26, compare, projection); + iter_swap_if(first + 27, first + 28, compare, projection); + iter_swap_if(first + 29, first + 30, compare, projection); + iter_swap_if(first + 3, first + 4, compare, projection); iter_swap_if(first + 6, first + 7, compare, projection); iter_swap_if(first + 9, first + 11, compare, projection); iter_swap_if(first + 10, first + 12, compare, projection); - iter_swap_if(first + 13, first + 16, compare, projection); - iter_swap_if(first + 14, first + 15, compare, projection); + iter_swap_if(first + 13, first + 14, compare, projection); + iter_swap_if(first + 15, first + 16, compare, projection); iter_swap_if(first + 17, first + 20, compare, projection); iter_swap_if(first + 18, first + 19, compare, projection); - iter_swap_if(first + 21, first + 24, compare, projection); - iter_swap_if(first + 23, first + 25, compare, projection); - iter_swap_if(first + 26, first + 28, compare, projection); - iter_swap_if(first + 3, first + 4, compare, projection); + iter_swap_if(first + 21, first + 22, compare, projection); + iter_swap_if(first + 23, first + 24, compare, projection); + iter_swap_if(first + 25, first + 26, compare, projection); + iter_swap_if(first + 1, first + 3, compare, projection); + iter_swap_if(first + 2, first + 4, compare, projection); iter_swap_if(first + 5, first + 6, compare, projection); iter_swap_if(first + 7, first + 8, compare, projection); iter_swap_if(first + 9, first + 10, compare, projection); iter_swap_if(first + 11, first + 12, compare, projection); - iter_swap_if(first + 13, first + 14, compare, projection); - iter_swap_if(first + 15, first + 16, compare, projection); + iter_swap_if(first + 13, first + 15, compare, projection); + iter_swap_if(first + 14, first + 16, compare, projection); iter_swap_if(first + 17, first + 18, compare, projection); iter_swap_if(first + 19, first + 20, compare, projection); - iter_swap_if(first + 21, first + 22, compare, projection); - iter_swap_if(first + 23, first + 24, compare, projection); - iter_swap_if(first + 25, first + 26, compare, projection); - iter_swap_if(first + 27, first + 28, compare, projection); + iter_swap_if(first + 21, first + 23, compare, projection); + iter_swap_if(first + 22, first + 24, compare, projection); + iter_swap_if(first + 26, first + 27, compare, projection); + iter_swap_if(first + 2, first + 3, compare, projection); iter_swap_if(first + 4, first + 5, compare, projection); iter_swap_if(first + 6, first + 7, compare, projection); iter_swap_if(first + 8, first + 9, compare, projection); @@ -220,7 +221,6 @@ namespace cppsort::detail iter_swap_if(first + 20, first + 21, compare, projection); iter_swap_if(first + 22, first + 23, compare, projection); iter_swap_if(first + 24, first + 25, compare, projection); - iter_swap_if(first + 28, first + 29, compare, projection); } template @@ -234,17 +234,16 @@ namespace cppsort::detail {0, 4}, {1, 5}, {2, 6}, {3, 7}, {8, 12}, {9, 13}, {10, 14}, {11, 15}, {16, 20}, {17, 21}, {18, 22}, {19, 23}, {24, 28}, {25, 29}, {26, 30}, {27, 31}, {0, 8}, {1, 9}, {2, 10}, {3, 11}, {4, 12}, {5, 13}, {6, 14}, {7, 15}, {16, 24}, {17, 25}, {18, 26}, {19, 27}, {20, 28}, {21, 29}, {22, 30}, {23, 31}, {0, 16}, {1, 17}, {2, 18}, {3, 19}, {4, 20}, {5, 21}, {6, 22}, {7, 23}, {8, 24}, {9, 25}, {10, 26}, {11, 27}, {12, 28}, {13, 29}, {14, 30}, {15, 31}, - {1, 4}, {2, 16}, {3, 20}, {5, 18}, {6, 17}, {7, 22}, {9, 24}, {10, 12}, {11, 28}, {13, 26}, {14, 25}, {15, 29}, {19, 21}, {23, 32}, {27, 30}, - {1, 2}, {3, 9}, {4, 16}, {6, 10}, {7, 19}, {11, 17}, {12, 24}, {13, 23}, {15, 27}, {22, 26}, {25, 28}, {29, 30}, - {3, 6}, {7, 12}, {9, 18}, {10, 20}, {13, 14}, {15, 28}, {17, 22}, {21, 25}, {27, 32}, - {5, 13}, {6, 16}, {9, 10}, {12, 20}, {14, 19}, {15, 23}, {25, 26}, {30, 32}, - {5, 8}, {11, 13}, {14, 18}, {15, 21}, {19, 22}, {23, 27}, {31, 32}, - {4, 5}, {7, 11}, {8, 16}, {12, 14}, {13, 24}, {15, 17}, {18, 20}, {19, 21}, {22, 28}, {23, 25}, {27, 29}, - {1, 4}, {3, 5}, {6, 8}, {7, 9}, {10, 11}, {13, 15}, {17, 24}, {22, 23}, {26, 27}, {29, 30}, - {0, 1}, {2, 6}, {8, 9}, {11, 16}, {12, 13}, {14, 17}, {15, 18}, {19, 22}, {20, 24}, {25, 26}, - {2, 4}, {5, 8}, {6, 7}, {9, 11}, {10, 12}, {13, 16}, {14, 15}, {17, 20}, {18, 19}, {21, 24}, {23, 25}, {26, 28}, - {3, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}, {17, 18}, {19, 20}, {21, 22}, {23, 24}, {25, 26}, {27, 28}, - {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, {22, 23}, {24, 25}, {28, 29}, + {1, 2}, {3, 12}, {4, 8}, {5, 10}, {6, 9}, {7, 11}, {13, 14}, {15, 32}, {17, 18}, {19, 28}, {20, 24}, {21, 26}, {22, 25}, {23, 27}, {29, 30}, + {3, 17}, {4, 16}, {5, 20}, {7, 13}, {10, 22}, {11, 29}, {12, 21}, {14, 23}, {15, 19}, {18, 24}, {25, 28}, {27, 30}, + {3, 5}, {7, 18}, {8, 16}, {9, 20}, {10, 17}, {12, 15}, {13, 14}, {19, 26}, {21, 22}, {23, 29}, {28, 32}, + {1, 8}, {2, 16}, {5, 10}, {6, 12}, {11, 19}, {13, 24}, {14, 25}, {15, 20}, {17, 21}, {26, 28}, {27, 32}, + {0, 6}, {2, 8}, {7, 10}, {9, 16}, {11, 18}, {12, 15}, {20, 22}, {24, 26}, {27, 28}, {30, 32}, + {5, 8}, {6, 9}, {11, 17}, {12, 16}, {13, 15}, {14, 20}, {18, 21}, {19, 22}, {23, 27}, {28, 29}, {31, 32}, + {3, 6}, {7, 12}, {8, 9}, {10, 16}, {11, 13}, {14, 17}, {15, 18}, {19, 24}, {20, 21}, {22, 25}, {23, 26}, {27, 28}, {29, 30}, + {3, 4}, {6, 7}, {9, 11}, {10, 12}, {13, 14}, {15, 16}, {17, 20}, {18, 19}, {21, 22}, {23, 24}, {25, 26}, + {1, 3}, {2, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 15}, {14, 16}, {17, 18}, {19, 20}, {21, 23}, {22, 24}, {26, 27}, + {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, {22, 23}, {24, 25}, }}; } }; diff --git a/include/cpp-sort/detail/sorting_network/sort41.h b/include/cpp-sort/detail/sorting_network/sort41.h index 6205fc37..be427fd2 100644 --- a/include/cpp-sort/detail/sorting_network/sort41.h +++ b/include/cpp-sort/detail/sorting_network/sort41.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023-2025 Morwenn +* Copyright (c) 2023-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SORTING_NETWORK_SORT41_H_ @@ -38,10 +38,10 @@ namespace cppsort::detail iter_swap_if(first + 26, first + 27, compare, projection); iter_swap_if(first + 28, first + 29, compare, projection); iter_swap_if(first + 30, first + 31, compare, projection); - iter_swap_if(first + 32, first + 35, compare, projection); - iter_swap_if(first + 33, first + 39, compare, projection); - iter_swap_if(first + 34, first + 37, compare, projection); - iter_swap_if(first + 36, first + 40, compare, projection); + iter_swap_if(first + 32, first + 33, compare, projection); + iter_swap_if(first + 34, first + 35, compare, projection); + iter_swap_if(first + 36, first + 37, compare, projection); + iter_swap_if(first + 38, first + 39, compare, projection); iter_swap_if(first, first + 2, compare, projection); iter_swap_if(first + 1, first + 3, compare, projection); iter_swap_if(first + 4, first + 6, compare, projection); @@ -58,10 +58,10 @@ namespace cppsort::detail iter_swap_if(first + 25, first + 27, compare, projection); iter_swap_if(first + 28, first + 30, compare, projection); iter_swap_if(first + 29, first + 31, compare, projection); - iter_swap_if(first + 32, first + 39, compare, projection); - iter_swap_if(first + 34, first + 36, compare, projection); - iter_swap_if(first + 35, first + 40, compare, projection); - iter_swap_if(first + 37, first + 38, compare, projection); + iter_swap_if(first + 32, first + 34, compare, projection); + iter_swap_if(first + 33, first + 35, compare, projection); + iter_swap_if(first + 36, first + 38, compare, projection); + iter_swap_if(first + 37, first + 39, compare, projection); iter_swap_if(first, first + 4, compare, projection); iter_swap_if(first + 1, first + 5, compare, projection); iter_swap_if(first + 2, first + 6, compare, projection); @@ -78,10 +78,10 @@ namespace cppsort::detail iter_swap_if(first + 25, first + 29, compare, projection); iter_swap_if(first + 26, first + 30, compare, projection); iter_swap_if(first + 27, first + 31, compare, projection); - iter_swap_if(first + 32, first + 34, compare, projection); - iter_swap_if(first + 33, first + 35, compare, projection); - iter_swap_if(first + 36, first + 37, compare, projection); - iter_swap_if(first + 39, first + 40, compare, projection); + iter_swap_if(first + 32, first + 36, compare, projection); + iter_swap_if(first + 33, first + 37, compare, projection); + iter_swap_if(first + 34, first + 38, compare, projection); + iter_swap_if(first + 35, first + 39, compare, projection); iter_swap_if(first, first + 8, compare, projection); iter_swap_if(first + 1, first + 9, compare, projection); iter_swap_if(first + 2, first + 10, compare, projection); @@ -98,9 +98,9 @@ namespace cppsort::detail iter_swap_if(first + 21, first + 29, compare, projection); iter_swap_if(first + 22, first + 30, compare, projection); iter_swap_if(first + 23, first + 31, compare, projection); - iter_swap_if(first + 33, first + 36, compare, projection); - iter_swap_if(first + 35, first + 38, compare, projection); - iter_swap_if(first + 37, first + 39, compare, projection); + iter_swap_if(first + 33, first + 34, compare, projection); + iter_swap_if(first + 36, first + 40, compare, projection); + iter_swap_if(first + 37, first + 38, compare, projection); iter_swap_if(first, first + 16, compare, projection); iter_swap_if(first + 1, first + 17, compare, projection); iter_swap_if(first + 2, first + 18, compare, projection); @@ -117,11 +117,8 @@ namespace cppsort::detail iter_swap_if(first + 13, first + 29, compare, projection); iter_swap_if(first + 14, first + 30, compare, projection); iter_swap_if(first + 15, first + 31, compare, projection); - iter_swap_if(first + 32, first + 33, compare, projection); - iter_swap_if(first + 34, first + 36, compare, projection); - iter_swap_if(first + 35, first + 37, compare, projection); - iter_swap_if(first + 38, first + 40, compare, projection); - iter_swap_if(first, first + 32, compare, projection); + iter_swap_if(first + 33, first + 36, compare, projection); + iter_swap_if(first + 34, first + 40, compare, projection); iter_swap_if(first + 1, first + 2, compare, projection); iter_swap_if(first + 3, first + 12, compare, projection); iter_swap_if(first + 4, first + 8, compare, projection); @@ -136,148 +133,163 @@ namespace cppsort::detail iter_swap_if(first + 22, first + 25, compare, projection); iter_swap_if(first + 23, first + 27, compare, projection); iter_swap_if(first + 29, first + 30, compare, projection); - iter_swap_if(first + 31, first + 40, compare, projection); - iter_swap_if(first + 34, first + 35, compare, projection); - iter_swap_if(first + 36, first + 37, compare, projection); - iter_swap_if(first + 38, first + 39, compare, projection); - iter_swap_if(first + 5, first + 6, compare, projection); + iter_swap_if(first + 32, first + 33, compare, projection); + iter_swap_if(first + 34, first + 36, compare, projection); + iter_swap_if(first + 35, first + 40, compare, projection); + iter_swap_if(first + 1, first + 4, compare, projection); + iter_swap_if(first + 2, first + 8, compare, projection); + iter_swap_if(first + 3, first + 20, compare, projection); + iter_swap_if(first + 5, first + 17, compare, projection); iter_swap_if(first + 7, first + 13, compare, projection); iter_swap_if(first + 9, first + 10, compare, projection); - iter_swap_if(first + 11, first + 39, compare, projection); - iter_swap_if(first + 15, first + 31, compare, projection); + iter_swap_if(first + 11, first + 26, compare, projection); + iter_swap_if(first + 14, first + 28, compare, projection); iter_swap_if(first + 16, first + 32, compare, projection); iter_swap_if(first + 18, first + 24, compare, projection); + iter_swap_if(first + 19, first + 39, compare, projection); iter_swap_if(first + 21, first + 22, compare, projection); - iter_swap_if(first + 23, first + 29, compare, projection); - iter_swap_if(first + 25, first + 26, compare, projection); - iter_swap_if(first + 33, first + 34, compare, projection); - iter_swap_if(first + 35, first + 36, compare, projection); - iter_swap_if(first + 37, first + 38, compare, projection); - iter_swap_if(first + 1, first + 36, compare, projection); - iter_swap_if(first + 2, first + 33, compare, projection); + iter_swap_if(first + 35, first + 37, compare, projection); + iter_swap_if(first + 38, first + 40, compare, projection); + iter_swap_if(first, first + 16, compare, projection); + iter_swap_if(first + 2, first + 34, compare, projection); + iter_swap_if(first + 3, first + 36, compare, projection); + iter_swap_if(first + 4, first + 35, compare, projection); iter_swap_if(first + 6, first + 17, compare, projection); - iter_swap_if(first + 10, first + 21, compare, projection); - iter_swap_if(first + 11, first + 22, compare, projection); - iter_swap_if(first + 12, first + 37, compare, projection); - iter_swap_if(first + 14, first + 38, compare, projection); - iter_swap_if(first + 15, first + 19, compare, projection); - iter_swap_if(first + 18, first + 34, compare, projection); - iter_swap_if(first + 23, first + 25, compare, projection); - iter_swap_if(first + 28, first + 35, compare, projection); - iter_swap_if(first + 30, first + 39, compare, projection); - iter_swap_if(first + 1, first + 16, compare, projection); - iter_swap_if(first + 2, first + 4, compare, projection); - iter_swap_if(first + 3, first + 28, compare, projection); - iter_swap_if(first + 6, first + 18, compare, projection); - iter_swap_if(first + 8, first + 33, compare, projection); - iter_swap_if(first + 14, first + 34, compare, projection); - iter_swap_if(first + 15, first + 36, compare, projection); - iter_swap_if(first + 19, first + 29, compare, projection); - iter_swap_if(first + 26, first + 37, compare, projection); - iter_swap_if(first + 27, first + 38, compare, projection); - iter_swap_if(first + 30, first + 35, compare, projection); - iter_swap_if(first + 1, first + 2, compare, projection); - iter_swap_if(first + 3, first + 20, compare, projection); - iter_swap_if(first + 7, first + 14, compare, projection); - iter_swap_if(first + 8, first + 16, compare, projection); - iter_swap_if(first + 9, first + 28, compare, projection); - iter_swap_if(first + 11, first + 34, compare, projection); - iter_swap_if(first + 12, first + 33, compare, projection); - iter_swap_if(first + 15, first + 32, compare, projection); - iter_swap_if(first + 17, first + 26, compare, projection); - iter_swap_if(first + 19, first + 27, compare, projection); - iter_swap_if(first + 22, first + 30, compare, projection); - iter_swap_if(first + 25, first + 36, compare, projection); - iter_swap_if(first + 31, first + 38, compare, projection); - iter_swap_if(first + 3, first + 4, compare, projection); - iter_swap_if(first + 5, first + 15, compare, projection); - iter_swap_if(first + 7, first + 18, compare, projection); + iter_swap_if(first + 11, first + 25, compare, projection); + iter_swap_if(first + 12, first + 32, compare, projection); + iter_swap_if(first + 15, first + 39, compare, projection); + iter_swap_if(first + 19, first + 21, compare, projection); + iter_swap_if(first + 24, first + 40, compare, projection); + iter_swap_if(first + 26, first + 38, compare, projection); + iter_swap_if(first + 2, first + 12, compare, projection); + iter_swap_if(first + 4, first + 16, compare, projection); + iter_swap_if(first + 5, first + 34, compare, projection); + iter_swap_if(first + 6, first + 33, compare, projection); + iter_swap_if(first + 8, first + 24, compare, projection); iter_swap_if(first + 10, first + 32, compare, projection); - iter_swap_if(first + 11, first + 20, compare, projection); + iter_swap_if(first + 15, first + 23, compare, projection); + iter_swap_if(first + 17, first + 35, compare, projection); + iter_swap_if(first + 19, first + 36, compare, projection); + iter_swap_if(first + 22, first + 26, compare, projection); + iter_swap_if(first + 25, first + 37, compare, projection); + iter_swap_if(first + 27, first + 39, compare, projection); + iter_swap_if(first + 28, first + 40, compare, projection); + iter_swap_if(first + 1, first + 6, compare, projection); + iter_swap_if(first + 3, first + 5, compare, projection); + iter_swap_if(first + 7, first + 19, compare, projection); + iter_swap_if(first + 8, first + 12, compare, projection); + iter_swap_if(first + 10, first + 22, compare, projection); + iter_swap_if(first + 11, first + 36, compare, projection); + iter_swap_if(first + 18, first + 33, compare, projection); + iter_swap_if(first + 20, first + 34, compare, projection); + iter_swap_if(first + 21, first + 25, compare, projection); + iter_swap_if(first + 28, first + 37, compare, projection); + iter_swap_if(first + 30, first + 40, compare, projection); + iter_swap_if(first + 32, first + 35, compare, projection); + iter_swap_if(first + 1, first + 4, compare, projection); + iter_swap_if(first + 3, first + 6, compare, projection); + iter_swap_if(first + 8, first + 16, compare, projection); + iter_swap_if(first + 9, first + 21, compare, projection); + iter_swap_if(first + 10, first + 18, compare, projection); + iter_swap_if(first + 11, first + 19, compare, projection); iter_swap_if(first + 12, first + 17, compare, projection); - iter_swap_if(first + 13, first + 25, compare, projection); - iter_swap_if(first + 21, first + 33, compare, projection); - iter_swap_if(first + 24, first + 34, compare, projection); - iter_swap_if(first + 27, first + 36, compare, projection); - iter_swap_if(first + 31, first + 35, compare, projection); - iter_swap_if(first + 38, first + 39, compare, projection); - iter_swap_if(first + 4, first + 16, compare, projection); - iter_swap_if(first + 5, first + 6, compare, projection); - iter_swap_if(first + 9, first + 11, compare, projection); - iter_swap_if(first + 10, first + 15, compare, projection); - iter_swap_if(first + 14, first + 24, compare, projection); - iter_swap_if(first + 20, first + 28, compare, projection); - iter_swap_if(first + 23, first + 32, compare, projection); + iter_swap_if(first + 13, first + 34, compare, projection); + iter_swap_if(first + 14, first + 33, compare, projection); + iter_swap_if(first + 24, first + 32, compare, projection); + iter_swap_if(first + 27, first + 37, compare, projection); + iter_swap_if(first + 28, first + 35, compare, projection); + iter_swap_if(first + 29, first + 36, compare, projection); + iter_swap_if(first + 30, first + 38, compare, projection); + iter_swap_if(first + 31, first + 40, compare, projection); + iter_swap_if(first + 3, first + 8, compare, projection); + iter_swap_if(first + 6, first + 10, compare, projection); + iter_swap_if(first + 9, first + 20, compare, projection); + iter_swap_if(first + 13, first + 21, compare, projection); + iter_swap_if(first + 14, first + 22, compare, projection); + iter_swap_if(first + 15, first + 29, compare, projection); + iter_swap_if(first + 17, first + 24, compare, projection); + iter_swap_if(first + 23, first + 36, compare, projection); iter_swap_if(first + 25, first + 34, compare, projection); + iter_swap_if(first + 26, first + 33, compare, projection); + iter_swap_if(first + 27, first + 30, compare, projection); + iter_swap_if(first + 28, first + 32, compare, projection); + iter_swap_if(first + 31, first + 37, compare, projection); + iter_swap_if(first + 39, first + 40, compare, projection); + iter_swap_if(first + 2, first + 8, compare, projection); + iter_swap_if(first + 5, first + 9, compare, projection); + iter_swap_if(first + 10, first + 16, compare, projection); + iter_swap_if(first + 13, first + 20, compare, projection); + iter_swap_if(first + 14, first + 18, compare, projection); + iter_swap_if(first + 15, first + 19, compare, projection); + iter_swap_if(first + 21, first + 25, compare, projection); + iter_swap_if(first + 22, first + 26, compare, projection); + iter_swap_if(first + 23, first + 30, compare, projection); iter_swap_if(first + 29, first + 35, compare, projection); - iter_swap_if(first + 5, first + 8, compare, projection); - iter_swap_if(first + 7, first + 10, compare, projection); + iter_swap_if(first + 33, first + 34, compare, projection); + iter_swap_if(first + 36, first + 38, compare, projection); + iter_swap_if(first + 37, first + 39, compare, projection); + iter_swap_if(first + 2, first + 4, compare, projection); + iter_swap_if(first + 5, first + 10, compare, projection); + iter_swap_if(first + 7, first + 13, compare, projection); iter_swap_if(first + 9, first + 12, compare, projection); - iter_swap_if(first + 11, first + 17, compare, projection); - iter_swap_if(first + 13, first + 23, compare, projection); - iter_swap_if(first + 15, first + 21, compare, projection); - iter_swap_if(first + 18, first + 20, compare, projection); - iter_swap_if(first + 19, first + 32, compare, projection); - iter_swap_if(first + 22, first + 28, compare, projection); - iter_swap_if(first + 24, first + 25, compare, projection); - iter_swap_if(first + 27, first + 34, compare, projection); - iter_swap_if(first + 29, first + 37, compare, projection); - iter_swap_if(first + 35, first + 38, compare, projection); - iter_swap_if(first + 2, first + 5, compare, projection); - iter_swap_if(first + 3, first + 8, compare, projection); - iter_swap_if(first + 7, first + 9, compare, projection); - iter_swap_if(first + 12, first + 16, compare, projection); - iter_swap_if(first + 13, first + 14, compare, projection); - iter_swap_if(first + 15, first + 18, compare, projection); - iter_swap_if(first + 19, first + 26, compare, projection); - iter_swap_if(first + 20, first + 21, compare, projection); - iter_swap_if(first + 22, first + 24, compare, projection); - iter_swap_if(first + 23, first + 33, compare, projection); - iter_swap_if(first + 25, first + 28, compare, projection); - iter_swap_if(first + 29, first + 30, compare, projection); - iter_swap_if(first + 31, first + 32, compare, projection); - iter_swap_if(first + 35, first + 37, compare, projection); - iter_swap_if(first + 3, first + 5, compare, projection); - iter_swap_if(first + 4, first + 9, compare, projection); - iter_swap_if(first + 6, first + 12, compare, projection); - iter_swap_if(first + 10, first + 16, compare, projection); - iter_swap_if(first + 11, first + 13, compare, projection); + iter_swap_if(first + 11, first + 18, compare, projection); iter_swap_if(first + 14, first + 17, compare, projection); - iter_swap_if(first + 19, first + 23, compare, projection); - iter_swap_if(first + 26, first + 33, compare, projection); + iter_swap_if(first + 15, first + 28, compare, projection); + iter_swap_if(first + 19, first + 25, compare, projection); + iter_swap_if(first + 20, first + 24, compare, projection); + iter_swap_if(first + 21, first + 22, compare, projection); + iter_swap_if(first + 23, first + 33, compare, projection); + iter_swap_if(first + 26, first + 32, compare, projection); iter_swap_if(first + 27, first + 29, compare, projection); iter_swap_if(first + 30, first + 34, compare, projection); - iter_swap_if(first + 32, first + 36, compare, projection); - iter_swap_if(first + 4, first + 7, compare, projection); + iter_swap_if(first + 31, first + 36, compare, projection); + iter_swap_if(first + 37, first + 38, compare, projection); + iter_swap_if(first + 3, first + 4, compare, projection); + iter_swap_if(first + 6, first + 9, compare, projection); + iter_swap_if(first + 7, first + 14, compare, projection); + iter_swap_if(first + 11, first + 20, compare, projection); + iter_swap_if(first + 12, first + 16, compare, projection); + iter_swap_if(first + 13, first + 17, compare, projection); + iter_swap_if(first + 15, first + 21, compare, projection); + iter_swap_if(first + 18, first + 24, compare, projection); + iter_swap_if(first + 19, first + 26, compare, projection); + iter_swap_if(first + 22, first + 28, compare, projection); + iter_swap_if(first + 23, first + 27, compare, projection); + iter_swap_if(first + 25, first + 32, compare, projection); + iter_swap_if(first + 29, first + 33, compare, projection); + iter_swap_if(first + 30, first + 35, compare, projection); + iter_swap_if(first + 36, first + 37, compare, projection); iter_swap_if(first + 6, first + 8, compare, projection); + iter_swap_if(first + 7, first + 9, compare, projection); iter_swap_if(first + 10, first + 12, compare, projection); - iter_swap_if(first + 11, first + 15, compare, projection); - iter_swap_if(first + 13, first + 20, compare, projection); - iter_swap_if(first + 14, first + 18, compare, projection); - iter_swap_if(first + 17, first + 21, compare, projection); - iter_swap_if(first + 19, first + 22, compare, projection); - iter_swap_if(first + 23, first + 25, compare, projection); - iter_swap_if(first + 24, first + 26, compare, projection); - iter_swap_if(first + 27, first + 31, compare, projection); - iter_swap_if(first + 28, first + 33, compare, projection); + iter_swap_if(first + 11, first + 16, compare, projection); + iter_swap_if(first + 13, first + 14, compare, projection); + iter_swap_if(first + 15, first + 20, compare, projection); + iter_swap_if(first + 17, first + 18, compare, projection); + iter_swap_if(first + 19, first + 24, compare, projection); + iter_swap_if(first + 21, first + 22, compare, projection); + iter_swap_if(first + 23, first + 26, compare, projection); + iter_swap_if(first + 25, first + 28, compare, projection); + iter_swap_if(first + 27, first + 30, compare, projection); iter_swap_if(first + 29, first + 32, compare, projection); - iter_swap_if(first + 35, first + 36, compare, projection); - iter_swap_if(first + 4, first + 6, compare, projection); - iter_swap_if(first + 7, first + 8, compare, projection); - iter_swap_if(first + 9, first + 11, compare, projection); - iter_swap_if(first + 13, first + 15, compare, projection); + iter_swap_if(first + 31, first + 35, compare, projection); + iter_swap_if(first + 5, first + 8, compare, projection); + iter_swap_if(first + 7, first + 10, compare, projection); + iter_swap_if(first + 9, first + 12, compare, projection); + iter_swap_if(first + 11, first + 13, compare, projection); iter_swap_if(first + 14, first + 16, compare, projection); - iter_swap_if(first + 17, first + 19, compare, projection); + iter_swap_if(first + 15, first + 17, compare, projection); iter_swap_if(first + 18, first + 20, compare, projection); - iter_swap_if(first + 21, first + 23, compare, projection); + iter_swap_if(first + 19, first + 21, compare, projection); iter_swap_if(first + 22, first + 24, compare, projection); - iter_swap_if(first + 25, first + 26, compare, projection); - iter_swap_if(first + 27, first + 28, compare, projection); - iter_swap_if(first + 29, first + 33, compare, projection); - iter_swap_if(first + 30, first + 31, compare, projection); - iter_swap_if(first + 32, first + 34, compare, projection); - iter_swap_if(first + 4, first + 5, compare, projection); - iter_swap_if(first + 6, first + 7, compare, projection); + iter_swap_if(first + 23, first + 25, compare, projection); + iter_swap_if(first + 26, first + 28, compare, projection); + iter_swap_if(first + 27, first + 29, compare, projection); + iter_swap_if(first + 30, first + 32, compare, projection); + iter_swap_if(first + 31, first + 33, compare, projection); + iter_swap_if(first + 34, first + 35, compare, projection); + iter_swap_if(first + 5, first + 6, compare, projection); + iter_swap_if(first + 7, first + 8, compare, projection); iter_swap_if(first + 9, first + 10, compare, projection); iter_swap_if(first + 11, first + 12, compare, projection); iter_swap_if(first + 13, first + 14, compare, projection); @@ -286,24 +298,12 @@ namespace cppsort::detail iter_swap_if(first + 19, first + 20, compare, projection); iter_swap_if(first + 21, first + 22, compare, projection); iter_swap_if(first + 23, first + 24, compare, projection); - iter_swap_if(first + 25, first + 27, compare, projection); - iter_swap_if(first + 26, first + 28, compare, projection); + iter_swap_if(first + 25, first + 26, compare, projection); + iter_swap_if(first + 27, first + 28, compare, projection); iter_swap_if(first + 29, first + 30, compare, projection); - iter_swap_if(first + 31, first + 33, compare, projection); - iter_swap_if(first + 34, first + 35, compare, projection); - iter_swap_if(first + 8, first + 9, compare, projection); - iter_swap_if(first + 10, first + 11, compare, projection); - iter_swap_if(first + 12, first + 13, compare, projection); - iter_swap_if(first + 14, first + 15, compare, projection); - iter_swap_if(first + 16, first + 17, compare, projection); - iter_swap_if(first + 18, first + 19, compare, projection); - iter_swap_if(first + 20, first + 21, compare, projection); - iter_swap_if(first + 22, first + 23, compare, projection); - iter_swap_if(first + 24, first + 25, compare, projection); - iter_swap_if(first + 26, first + 27, compare, projection); - iter_swap_if(first + 28, first + 29, compare, projection); - iter_swap_if(first + 30, first + 31, compare, projection); - iter_swap_if(first + 32, first + 33, compare, projection); + iter_swap_if(first + 31, first + 32, compare, projection); + iter_swap_if(first + 33, first + 34, compare, projection); + iter_swap_if(first + 35, first + 36, compare, projection); } template @@ -312,25 +312,24 @@ namespace cppsort::detail -> std::array, 282> { return {{ - {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, {22, 23}, {24, 25}, {26, 27}, {28, 29}, {30, 31}, {32, 35}, {33, 39}, {34, 37}, {36, 40}, - {0, 2}, {1, 3}, {4, 6}, {5, 7}, {8, 10}, {9, 11}, {12, 14}, {13, 15}, {16, 18}, {17, 19}, {20, 22}, {21, 23}, {24, 26}, {25, 27}, {28, 30}, {29, 31}, {32, 39}, {34, 36}, {35, 40}, {37, 38}, - {0, 4}, {1, 5}, {2, 6}, {3, 7}, {8, 12}, {9, 13}, {10, 14}, {11, 15}, {16, 20}, {17, 21}, {18, 22}, {19, 23}, {24, 28}, {25, 29}, {26, 30}, {27, 31}, {32, 34}, {33, 35}, {36, 37}, {39, 40}, - {0, 8}, {1, 9}, {2, 10}, {3, 11}, {4, 12}, {5, 13}, {6, 14}, {7, 15}, {16, 24}, {17, 25}, {18, 26}, {19, 27}, {20, 28}, {21, 29}, {22, 30}, {23, 31}, {33, 36}, {35, 38}, {37, 39}, - {0, 16}, {1, 17}, {2, 18}, {3, 19}, {4, 20}, {5, 21}, {6, 22}, {7, 23}, {8, 24}, {9, 25}, {10, 26}, {11, 27}, {12, 28}, {13, 29}, {14, 30}, {15, 31}, {32, 33}, {34, 36}, {35, 37}, {38, 40}, - {0, 32}, {1, 2}, {3, 12}, {4, 8}, {5, 10}, {6, 9}, {7, 11}, {13, 14}, {17, 18}, {19, 28}, {20, 24}, {21, 26}, {22, 25}, {23, 27}, {29, 30}, {31, 40}, {34, 35}, {36, 37}, {38, 39}, - {5, 6}, {7, 13}, {9, 10}, {11, 39}, {15, 31}, {16, 32}, {18, 24}, {21, 22}, {23, 29}, {25, 26}, {33, 34}, {35, 36}, {37, 38}, - {1, 36}, {2, 33}, {6, 17}, {10, 21}, {11, 22}, {12, 37}, {14, 38}, {15, 19}, {18, 34}, {23, 25}, {28, 35}, {30, 39}, - {1, 16}, {2, 4}, {3, 28}, {6, 18}, {8, 33}, {14, 34}, {15, 36}, {19, 29}, {26, 37}, {27, 38}, {30, 35}, - {1, 2}, {3, 20}, {7, 14}, {8, 16}, {9, 28}, {11, 34}, {12, 33}, {15, 32}, {17, 26}, {19, 27}, {22, 30}, {25, 36}, {31, 38}, - {3, 4}, {5, 15}, {7, 18}, {10, 32}, {11, 20}, {12, 17}, {13, 25}, {21, 33}, {24, 34}, {27, 36}, {31, 35}, {38, 39}, - {4, 16}, {5, 6}, {9, 11}, {10, 15}, {14, 24}, {20, 28}, {23, 32}, {25, 34}, {29, 35}, - {5, 8}, {7, 10}, {9, 12}, {11, 17}, {13, 23}, {15, 21}, {18, 20}, {19, 32}, {22, 28}, {24, 25}, {27, 34}, {29, 37}, {35, 38}, - {2, 5}, {3, 8}, {7, 9}, {12, 16}, {13, 14}, {15, 18}, {19, 26}, {20, 21}, {22, 24}, {23, 33}, {25, 28}, {29, 30}, {31, 32}, {35, 37}, - {3, 5}, {4, 9}, {6, 12}, {10, 16}, {11, 13}, {14, 17}, {19, 23}, {26, 33}, {27, 29}, {30, 34}, {32, 36}, - {4, 7}, {6, 8}, {10, 12}, {11, 15}, {13, 20}, {14, 18}, {17, 21}, {19, 22}, {23, 25}, {24, 26}, {27, 31}, {28, 33}, {29, 32}, {35, 36}, - {4, 6}, {7, 8}, {9, 11}, {13, 15}, {14, 16}, {17, 19}, {18, 20}, {21, 23}, {22, 24}, {25, 26}, {27, 28}, {29, 33}, {30, 31}, {32, 34}, - {4, 5}, {6, 7}, {9, 10}, {11, 12}, {13, 14}, {15, 16}, {17, 18}, {19, 20}, {21, 22}, {23, 24}, {25, 27}, {26, 28}, {29, 30}, {31, 33}, {34, 35}, - {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, {22, 23}, {24, 25}, {26, 27}, {28, 29}, {30, 31}, {32, 33}, + {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, {22, 23}, {24, 25}, {26, 27}, {28, 29}, {30, 31}, {32, 33}, {34, 35}, {36, 37}, {38, 39}, + {0, 2}, {1, 3}, {4, 6}, {5, 7}, {8, 10}, {9, 11}, {12, 14}, {13, 15}, {16, 18}, {17, 19}, {20, 22}, {21, 23}, {24, 26}, {25, 27}, {28, 30}, {29, 31}, {32, 34}, {33, 35}, {36, 38}, {37, 39}, + {0, 4}, {1, 5}, {2, 6}, {3, 7}, {8, 12}, {9, 13}, {10, 14}, {11, 15}, {16, 20}, {17, 21}, {18, 22}, {19, 23}, {24, 28}, {25, 29}, {26, 30}, {27, 31}, {32, 36}, {33, 37}, {34, 38}, {35, 39}, + {0, 8}, {1, 9}, {2, 10}, {3, 11}, {4, 12}, {5, 13}, {6, 14}, {7, 15}, {16, 24}, {17, 25}, {18, 26}, {19, 27}, {20, 28}, {21, 29}, {22, 30}, {23, 31}, {33, 34}, {36, 40}, {37, 38}, + {0, 16}, {1, 17}, {2, 18}, {3, 19}, {4, 20}, {5, 21}, {6, 22}, {7, 23}, {8, 24}, {9, 25}, {10, 26}, {11, 27}, {12, 28}, {13, 29}, {14, 30}, {15, 31}, {33, 36}, {34, 40}, + {1, 2}, {3, 12}, {4, 8}, {5, 10}, {6, 9}, {7, 11}, {13, 14}, {17, 18}, {19, 28}, {20, 24}, {21, 26}, {22, 25}, {23, 27}, {29, 30}, {32, 33}, {34, 36}, {35, 40}, + {1, 4}, {2, 8}, {3, 20}, {5, 17}, {7, 13}, {9, 10}, {11, 26}, {14, 28}, {16, 32}, {18, 24}, {19, 39}, {21, 22}, {35, 37}, {38, 40}, + {0, 16}, {2, 34}, {3, 36}, {4, 35}, {6, 17}, {11, 25}, {12, 32}, {15, 39}, {19, 21}, {24, 40}, {26, 38}, + {2, 12}, {4, 16}, {5, 34}, {6, 33}, {8, 24}, {10, 32}, {15, 23}, {17, 35}, {19, 36}, {22, 26}, {25, 37}, {27, 39}, {28, 40}, + {1, 6}, {3, 5}, {7, 19}, {8, 12}, {10, 22}, {11, 36}, {18, 33}, {20, 34}, {21, 25}, {28, 37}, {30, 40}, {32, 35}, + {1, 4}, {3, 6}, {8, 16}, {9, 21}, {10, 18}, {11, 19}, {12, 17}, {13, 34}, {14, 33}, {24, 32}, {27, 37}, {28, 35}, {29, 36}, {30, 38}, {31, 40}, + {3, 8}, {6, 10}, {9, 20}, {13, 21}, {14, 22}, {15, 29}, {17, 24}, {23, 36}, {25, 34}, {26, 33}, {27, 30}, {28, 32}, {31, 37}, {39, 40}, + {2, 8}, {5, 9}, {10, 16}, {13, 20}, {14, 18}, {15, 19}, {21, 25}, {22, 26}, {23, 30}, {29, 35}, {33, 34}, {36, 38}, {37, 39}, + {2, 4}, {5, 10}, {7, 13}, {9, 12}, {11, 18}, {14, 17}, {15, 28}, {19, 25}, {20, 24}, {21, 22}, {23, 33}, {26, 32}, {27, 29}, {30, 34}, {31, 36}, {37, 38}, + {3, 4}, {6, 9}, {7, 14}, {11, 20}, {12, 16}, {13, 17}, {15, 21}, {18, 24}, {19, 26}, {22, 28}, {23, 27}, {25, 32}, {29, 33}, {30, 35}, {36, 37}, + {6, 8}, {7, 9}, {10, 12}, {11, 16}, {13, 14}, {15, 20}, {17, 18}, {19, 24}, {21, 22}, {23, 26}, {25, 28}, {27, 30}, {29, 32}, {31, 35}, + {5, 8}, {7, 10}, {9, 12}, {11, 13}, {14, 16}, {15, 17}, {18, 20}, {19, 21}, {22, 24}, {23, 25}, {26, 28}, {27, 29}, {30, 32}, {31, 33}, {34, 35}, + {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}, {17, 18}, {19, 20}, {21, 22}, {23, 24}, {25, 26}, {27, 28}, {29, 30}, {31, 32}, {33, 34}, {35, 36}, }}; } }; diff --git a/tools/generate_sorting_network.py b/tools/generate_sorting_network.py index d5f5ef61..64c6cceb 100644 --- a/tools/generate_sorting_network.py +++ b/tools/generate_sorting_network.py @@ -9,6 +9,7 @@ from pathlib import Path import z3 +from natsort import natsorted def find_sorter_hunter_file(path: Path, size: int): @@ -17,10 +18,7 @@ def find_sorter_hunter_file(path: Path, size: int): one corresponding for sorting a network of the given size, pereferring the ones that minimize the number of compare-exchanges. """ - return min( - path.glob(f"Sort_{size}_*.json"), - key=lambda x: int(x.name.split('_')[2]) - ) + return natsorted(path.glob(f"Sort_{size}_*.json"))[0] def parse_sorter_hunter_network(path: Path) -> list[list[tuple]]: @@ -71,7 +69,7 @@ def verify_network(pairs: list[tuple]): def generate_cxx(network: list[list[tuple]]): - template = textwrap.dedent(""" + template = textwrap.dedent("""\ namespace cppsort::detail {{ template<> @@ -137,6 +135,8 @@ def main(): parser = argparse.ArgumentParser(description="Turn a SorterHunter network into a cpp-sort one") parser.add_argument('-s', '--size', type=int, help="Number of inputs the network should sort") + parser.add_argument('-o', '--output-file', type=str, + help="File where to store the generate C++ code") parser.add_argument('directory', help="Directory containing the SorterHunter networks") args = parser.parse_args() @@ -145,15 +145,19 @@ def main(): network = parse_sorter_hunter_network(path) pairs = sum(network, []) - print(f"Number of pairs: {len(pairs)}") + print(f"Number of pairs: {len(pairs)}\n") valid, failing_input = verify_network(pairs) if not valid: print("Network failed to sort: {failing_input}") exit(1) - print(generate_cxx(network)) - + generated_code = generate_cxx(network) + if args.output_file: + with open(args.output_file, "w", encoding="utf-8") as fd: + fd.write(generated_code) + else: + print(generated_code) if __name__ == '__main__': main() From d3e229ab46d81b05cfe217fc8cd44d6aeda88869 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 25 Jan 2026 23:14:38 +0100 Subject: [PATCH 06/27] Add test section for total_greater --- tests/CMakeLists.txt | 2 +- tests/comparators/total_less.cpp | 35 ----------------- tests/comparators/total_order.cpp | 62 +++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 36 deletions(-) delete mode 100644 tests/comparators/total_less.cpp create mode 100644 tests/comparators/total_order.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f2f5fc25..0cce8ebb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -187,7 +187,7 @@ add_executable(main-tests comparators/flip_not.cpp comparators/natural_less.cpp comparators/projection_compare.cpp - comparators/total_less.cpp + comparators/total_order.cpp comparators/transparent_comparators.cpp # Distributions tests diff --git a/tests/comparators/total_less.cpp b/tests/comparators/total_less.cpp deleted file mode 100644 index a661dd0a..00000000 --- a/tests/comparators/total_less.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2016-2022 Morwenn - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include -#include - -TEST_CASE( "IEEE 754 totalOrder implementation", "[comparison]" ) -{ - static constexpr double nan = std::numeric_limits::quiet_NaN(); - static constexpr double inf = std::numeric_limits::infinity(); - - double array[] = { +1.0, +inf, -1.0, -nan, +0.0, -inf, +nan, -0.0 }; - cppsort::heap_sort(array, cppsort::total_less); - - // Check for IEEE 754 totalOrder, - // ignore quiet vs. signaling NaNs - CHECK( std::isnan(array[0]) ); - CHECK( std::signbit(array[0]) ); - CHECK( std::isinf(array[1]) ); - CHECK( std::signbit(array[1]) ); - CHECK( array[2] == -1.0 ); - CHECK( array[3] == 0.0 ); - CHECK( std::signbit(array[3]) ); - CHECK( array[4] == 0.0 ); - CHECK( not std::signbit(array[4]) ); - CHECK( array[5] == +1.0 ); - CHECK( std::isinf(array[6]) ); - CHECK( not std::signbit(array[6]) ); - CHECK( std::isnan(array[7]) ); - CHECK( not std::signbit(array[7]) ); -} diff --git a/tests/comparators/total_order.cpp b/tests/comparators/total_order.cpp new file mode 100644 index 00000000..21f3d31c --- /dev/null +++ b/tests/comparators/total_order.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include + +TEST_CASE( "IEEE 754 totalOrder implementation", "[comparison]" ) +{ + constexpr double nan = std::numeric_limits::quiet_NaN(); + constexpr double inf = std::numeric_limits::infinity(); + + double array[] = { +1.0, +inf, -1.0, -nan, +0.0, -inf, +nan, -0.0 }; + + SECTION( "total_less" ) + { + cppsort::heap_sort(array, cppsort::total_less); + + // Check for IEEE 754 totalOrder, + // ignore quiet vs. signaling NaNs + CHECK( std::isnan(array[0]) ); + CHECK( std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( std::signbit(array[1]) ); + CHECK( array[2] == -1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( std::signbit(array[3]) ); + CHECK( array[4] == 0.0 ); + CHECK( not std::signbit(array[4]) ); + CHECK( array[5] == +1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( not std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( not std::signbit(array[7]) ); + } + + SECTION( "total_greater" ) + { + cppsort::heap_sort(array, cppsort::total_greater); + + // Check for IEEE 754 totalOrder, + // ignore quiet vs. signaling NaNs + CHECK( not std::signbit(array[0]) ); + CHECK( std::isnan(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( not std::signbit(array[1]) ); + CHECK( array[2] == +1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( not std::signbit(array[3]) ); + CHECK( array[4] == 0.0 ); + CHECK( std::signbit(array[4]) ); + CHECK( array[5] == -1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( std::signbit(array[7]) ); + } +} From 0cfb2b56d12d14600ddc25c43d689159d9207c7f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 25 Jan 2026 23:27:45 +0100 Subject: [PATCH 07/27] Use std::strong_order for total_less and total_greater when possible --- docs/Changelog.md | 7 +++++++ docs/Comparators.md | 8 ++++++-- include/cpp-sort/comparators/total_greater.h | 17 ++++++++++++++--- include/cpp-sort/comparators/total_less.h | 17 +++++++++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 611381bd..53b342bd 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -11,6 +11,11 @@ While **cpp-sort** theoretically requires a fully C++17-compliant compiler, a fe When compiled with C++20, **cpp-sort** might gain a few additional features depending on the level of C++20 support provided by the compiler. The availability of those features depends on the presence of corresponding [feature-testing macros][feature-test-macros] when possible, even though some checks are more granular. Don't hesitate to open an issue if your compiler and standard library supports one of those features but it doesn't seem to work in **cpp-sort**. +**Correctness improvements:** +* When possible, the total order predicates [`total_less` and `total_greater`][total-greater-less] use [`std::strong_order`][std-strong-order] to compare floating-point numbers, leading to a supposedly correct implementation of the IEEE *totalOrder* operator. In C++17 and earlier, those comparators don't actually implement a proper total order: they are unable to differentiate between quiet and signaling NaNs, and connot read NaN payloads. + + The feature-test macro `__cpp_lib_three_way_comparison` can be used to check whether `std::strong_order` is available. + **New features:** * When available, [`std::identity`][std-identity] benefits from dedicated support wherever [`utility::identity`][cpp-sort-function-objects] is supported, with equivalent semantics. @@ -50,5 +55,7 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe [std-mem-fn]: https://en.cppreference.com/w/cpp/utility/functional/mem_fn [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-strong-order]: https://en.cppreference.com/w/cpp/utility/compare/strong_order [std-swap]: https://en.cppreference.com/w/cpp/algorithm/swap + [total-greater-less]: Comparators.md#total-order-comparators [utility-iter-move]: Miscellaneous-utilities.md#iter_move-and-iter_swap diff --git a/docs/Comparators.md b/docs/Comparators.md index 88857160..9fb590bf 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -9,7 +9,7 @@ Every non-refined comparator described below is also a [transparent comparator][ #include ``` -The comparators `total_less` and `total_order` are [customization points][custom-point] implementing a [total order][total-order], inspired by the similar functions described in [P0100][P0100]. The provided functions handle built-in integer out of the box (using the built-in relational operators) and implement IEEE 754's `totalOrder` for floating point numbers (from lesser to greater): +The comparators `total_less` and `total_order` are [customization points][custom-point] implementing a [total order][total-order], inspired by the similar functions described in [P0100][P0100]. The provided functions handle built-in integer types out of the box (using the built-in relational operators) and attempt to implement IEEE 754's `totalOrder` for floating point numbers (from lesser to greater): * positive quiet NaNs * positive signaling NaNs * positive infinity @@ -21,10 +21,14 @@ The comparators `total_less` and `total_order` are [customization points][custom * negative signaling NaNs * negative quiet NaNs -That said, the comparators are currently unable to discriminate between quiet and signaling NaNs, so they are considered to be *equivalent*. When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). +That said, the comparators are unable to discriminate between quiet and signaling NaNs, and have no way to inspect the bit patterns of NaNs to compare them. All NaNs that share a same sign are therefore considered to be *equivalent* in C++17. In C++20 however, the comparators rely on [`std::strong_order`][std-strong-order] when available, which correctly implements `totalOrder` for IEEE 754 floating point numbers. + +When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). Total order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_integral`][std-is-integral]. +*Changed in version 2.1.0:* `total_less` and `total_greater` now correctly implement IEEE 754 `totalOrder` in C++20 mode, when the featur-test macro `__cpp_lib_three_way_comparison` is defined. + ### Weak order comparators ```cpp diff --git a/include/cpp-sort/comparators/total_greater.h b/include/cpp-sort/comparators/total_greater.h index 83a0e86c..bf0a91f9 100644 --- a/include/cpp-sort/comparators/total_greater.h +++ b/include/cpp-sort/comparators/total_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_TOTAL_GREATER_H_ @@ -8,13 +8,18 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include #include #include #include -#include "../detail/floating_point_weight.h" #include "../detail/type_traits.h" +#if defined(__cpp_lib_three_way_comparison) +# include +#else +# include +# include "../detail/floating_point_weight.h" +#endif + namespace cppsort { namespace detail @@ -36,6 +41,11 @@ namespace cppsort auto total_greater(T lhs, T rhs) -> detail::enable_if_t, bool> { +#if defined(__cpp_lib_three_way_comparison) + // Full implementation of totalOrder + return std::is_gt(std::strong_order(lhs, rhs)); +#else + // Approximation, unable to differentiate NaNs if (std::isfinite(lhs) && std::isfinite(rhs)) { if (lhs == 0 && rhs == 0) { return std::signbit(rhs) && not std::signbit(lhs); @@ -46,6 +56,7 @@ namespace cppsort int lhs_weight = total_weight(lhs); int rhs_weight = total_weight(rhs); return lhs_weight > rhs_weight; +#endif } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/comparators/total_less.h b/include/cpp-sort/comparators/total_less.h index 7bbfdd0c..9d7d52b1 100644 --- a/include/cpp-sort/comparators/total_less.h +++ b/include/cpp-sort/comparators/total_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_TOTAL_LESS_H_ @@ -11,10 +11,17 @@ #include #include #include +#include #include -#include "../detail/floating_point_weight.h" #include "../detail/type_traits.h" +#if defined(__cpp_lib_three_way_comparison) +# include +#else +# include +# include "../detail/floating_point_weight.h" +#endif + namespace cppsort { namespace detail @@ -36,6 +43,11 @@ namespace cppsort auto total_less(T lhs, T rhs) -> detail::enable_if_t, bool> { +#if defined(__cpp_lib_three_way_comparison) + // Full implementation of totalOrder + return std::is_lt(std::strong_order(lhs, rhs)); +#else + // Approximation, unable to differentiate NaNs if (std::isfinite(lhs) && std::isfinite(rhs)) { if (lhs == 0 && rhs == 0) { return std::signbit(lhs) && not std::signbit(rhs); @@ -46,6 +58,7 @@ namespace cppsort int lhs_weight = total_weight(lhs); int rhs_weight = total_weight(rhs); return lhs_weight < rhs_weight; +#endif } //////////////////////////////////////////////////////////// From 9ea5a28ac2949c8428bfa7bcaebd5f2b69765914 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 11 Feb 2026 23:01:07 +0100 Subject: [PATCH 08/27] Fix weak_less and weak_greater with floating-point numbers They simply never worked for floating-point numbers because the overload was ambiguous. Adding simple tests revealed the bug. --- include/cpp-sort/comparators/weak_greater.h | 7 ++- include/cpp-sort/comparators/weak_less.h | 7 ++- tests/CMakeLists.txt | 1 + tests/comparators/weak_order.cpp | 57 +++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 tests/comparators/weak_order.cpp diff --git a/include/cpp-sort/comparators/weak_greater.h b/include/cpp-sort/comparators/weak_greater.h index 7c464ff4..5ae5fe5e 100644 --- a/include/cpp-sort/comparators/weak_greater.h +++ b/include/cpp-sort/comparators/weak_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_WEAK_GREATER_H_ @@ -42,7 +42,10 @@ namespace cppsort template auto weak_greater(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::total_greater(lhs, rhs))) - -> decltype(cppsort::total_greater(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point_v, + decltype(cppsort::total_greater(lhs, rhs)) + > { return cppsort::total_greater(lhs, rhs); } diff --git a/include/cpp-sort/comparators/weak_less.h b/include/cpp-sort/comparators/weak_less.h index 4a372bb8..3c824201 100644 --- a/include/cpp-sort/comparators/weak_less.h +++ b/include/cpp-sort/comparators/weak_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_WEAK_LESS_H_ @@ -42,7 +42,10 @@ namespace cppsort template auto weak_less(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::total_less(lhs, rhs))) - -> decltype(cppsort::total_less(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point_v, + decltype(cppsort::total_less(lhs, rhs)) + > { return cppsort::total_less(lhs, rhs); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0cce8ebb..b55b4dab 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -189,6 +189,7 @@ add_executable(main-tests comparators/projection_compare.cpp comparators/total_order.cpp comparators/transparent_comparators.cpp + comparators/weak_order.cpp # Distributions tests distributions/all_equal.cpp diff --git a/tests/comparators/weak_order.cpp b/tests/comparators/weak_order.cpp new file mode 100644 index 00000000..34dc5043 --- /dev/null +++ b/tests/comparators/weak_order.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include + +TEST_CASE( "Weak ordering of floating-point numbers", "[comparison]" ) +{ + constexpr double inf = std::numeric_limits::infinity(); + + double array[] = { +1.0, +inf, -1.0, -std::nan("2"), +0.0, -inf, +std::nan("1"), -0.0 }; + + SECTION( "weak_less" ) + { + cppsort::heap_sort(array, cppsort::weak_less); + + // Check that equivalent values compare equivalent, + // regardless of their representation + CHECK( std::isnan(array[0]) ); + CHECK( std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( std::signbit(array[1]) ); + CHECK( array[2] == -1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( array[4] == 0.0 ); + CHECK( array[5] == +1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( not std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( not std::signbit(array[7]) ); + } + + SECTION( "weak_greater" ) + { + cppsort::heap_sort(array, cppsort::weak_greater); + + // Check that equivalent values compare equivalent, + // regardless of their representation + CHECK( not std::signbit(array[0]) ); + CHECK( std::isnan(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( not std::signbit(array[1]) ); + CHECK( array[2] == +1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( array[4] == 0.0 ); + CHECK( array[5] == -1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( std::signbit(array[7]) ); + } +} From 46243d13b84aada5b4b8efa6ef7e516458b20139 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 1 Mar 2026 17:19:49 +0100 Subject: [PATCH 09/27] Test new conjecture about Exc and Inv --- tests/probes/relations.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/probes/relations.cpp b/tests/probes/relations.cpp index f4a14288..bdd773fd 100644 --- a/tests/probes/relations.cpp +++ b/tests/probes/relations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -191,8 +191,13 @@ TEST_CASE( "relations between measures of disorder", "[probe]" ) return amp(sequence) <= 2 * runs(sequence); }); - // Conjecture + // Conjectures rc::prop("Mono(X) ≤ Amp(X)", [](const std::vector& sequence) { return mono(sequence) <= amp(sequence); }); + + rc::prop("Inv(X) % 2 = Exc(X) % 2", []() { + auto sequence = *rc::gen::unique>(rc::gen::arbitrary()); + return inv(sequence) % 2 == exc(sequence) % 2; + }); } From f365ae55eeb0ef34378d619bcc01aa225acaa680 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 1 Mar 2026 19:05:58 +0100 Subject: [PATCH 10/27] Mark [weak/partial]_[less/greater] fallbacks constexpr --- .../cpp-sort/comparators/partial_greater.h | 4 +- include/cpp-sort/comparators/partial_less.h | 4 +- include/cpp-sort/comparators/weak_greater.h | 2 +- include/cpp-sort/comparators/weak_less.h | 2 +- tests/CMakeLists.txt | 1 + tests/comparators/partial_order.cpp | 23 +++++ tests/comparators/total_order.cpp | 10 +++ tests/comparators/weak_order.cpp | 13 +++ tests/testing-tools/comparators.h | 83 +++++++++++++++++++ 9 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 tests/comparators/partial_order.cpp create mode 100644 tests/testing-tools/comparators.h diff --git a/include/cpp-sort/comparators/partial_greater.h b/include/cpp-sort/comparators/partial_greater.h index 8fea4f5f..9e9c5b75 100644 --- a/include/cpp-sort/comparators/partial_greater.h +++ b/include/cpp-sort/comparators/partial_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_PARTIAL_GREATER_H_ @@ -32,7 +32,7 @@ namespace cppsort // Generic overload: a weak order is also a partial order template - auto partial_greater(const T& lhs, const T& rhs) + constexpr auto partial_greater(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::weak_greater(lhs, rhs))) -> decltype(cppsort::weak_greater(lhs, rhs)) { diff --git a/include/cpp-sort/comparators/partial_less.h b/include/cpp-sort/comparators/partial_less.h index a076e15c..2ba02066 100644 --- a/include/cpp-sort/comparators/partial_less.h +++ b/include/cpp-sort/comparators/partial_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_PARTIAL_LESS_H_ @@ -32,7 +32,7 @@ namespace cppsort // Generic overload: a weak order is also a partial order template - auto partial_less(const T& lhs, const T& rhs) + constexpr auto partial_less(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::weak_less(lhs, rhs))) -> decltype(cppsort::weak_less(lhs, rhs)) { diff --git a/include/cpp-sort/comparators/weak_greater.h b/include/cpp-sort/comparators/weak_greater.h index 5ae5fe5e..528680e9 100644 --- a/include/cpp-sort/comparators/weak_greater.h +++ b/include/cpp-sort/comparators/weak_greater.h @@ -40,7 +40,7 @@ namespace cppsort // Generic overload: a total order is also a weak order template - auto weak_greater(const T& lhs, const T& rhs) + constexpr auto weak_greater(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::total_greater(lhs, rhs))) -> detail::enable_if_t< not std::is_floating_point_v, diff --git a/include/cpp-sort/comparators/weak_less.h b/include/cpp-sort/comparators/weak_less.h index 3c824201..66fa7474 100644 --- a/include/cpp-sort/comparators/weak_less.h +++ b/include/cpp-sort/comparators/weak_less.h @@ -40,7 +40,7 @@ namespace cppsort // Generic overload: a total order is also a weak order template - auto weak_less(const T& lhs, const T& rhs) + constexpr auto weak_less(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::total_less(lhs, rhs))) -> detail::enable_if_t< not std::is_floating_point_v, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b55b4dab..b6d32618 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -186,6 +186,7 @@ add_executable(main-tests comparators/case_insensitive_less.cpp comparators/flip_not.cpp comparators/natural_less.cpp + comparators/partial_order.cpp comparators/projection_compare.cpp comparators/total_order.cpp comparators/transparent_comparators.cpp diff --git a/tests/comparators/partial_order.cpp b/tests/comparators/partial_order.cpp new file mode 100644 index 00000000..544786b4 --- /dev/null +++ b/tests/comparators/partial_order.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include + +TEST_CASE( "Partial order customization point", "[comparison]" ) +{ + helpers::totally_comparable ta, tb; + helpers::weakly_comparable wa, wb; + helpers::partially_comparable pa, pb; + + // Ensure that overload resolution is correct + STATIC_CHECK( cppsort::partial_less(ta, tb) == helpers::compare_result::total_less ); + STATIC_CHECK( cppsort::partial_greater(ta, tb) == helpers::compare_result::total_greater ); + STATIC_CHECK( cppsort::partial_less(wa, wb) == helpers::compare_result::weak_less ); + STATIC_CHECK( cppsort::partial_greater(wa, wb) == helpers::compare_result::weak_greater ); + STATIC_CHECK( cppsort::partial_less(pa, pb) == helpers::compare_result::partial_less ); + STATIC_CHECK( cppsort::partial_greater(pa, pb) == helpers::compare_result::partial_greater ); +} diff --git a/tests/comparators/total_order.cpp b/tests/comparators/total_order.cpp index 21f3d31c..f1aa923b 100644 --- a/tests/comparators/total_order.cpp +++ b/tests/comparators/total_order.cpp @@ -8,6 +8,7 @@ #include #include #include +#include TEST_CASE( "IEEE 754 totalOrder implementation", "[comparison]" ) { @@ -60,3 +61,12 @@ TEST_CASE( "IEEE 754 totalOrder implementation", "[comparison]" ) CHECK( std::signbit(array[7]) ); } } + +TEST_CASE( "Total order customization point", "[comparison]" ) +{ + helpers::totally_comparable ta, tb; + + // Ensure that overload resolution is correct + STATIC_CHECK( cppsort::total_less(ta, tb) == helpers::compare_result::total_less ); + STATIC_CHECK( cppsort::total_greater(ta, tb) == helpers::compare_result::total_greater ); +} diff --git a/tests/comparators/weak_order.cpp b/tests/comparators/weak_order.cpp index 34dc5043..7e529c54 100644 --- a/tests/comparators/weak_order.cpp +++ b/tests/comparators/weak_order.cpp @@ -8,6 +8,7 @@ #include #include #include +#include TEST_CASE( "Weak ordering of floating-point numbers", "[comparison]" ) { @@ -55,3 +56,15 @@ TEST_CASE( "Weak ordering of floating-point numbers", "[comparison]" ) CHECK( std::signbit(array[7]) ); } } + +TEST_CASE( "Weak order customization point", "[comparison]" ) +{ + helpers::totally_comparable ta, tb; + helpers::weakly_comparable wa, wb; + + // Ensure that overload resolution is correct + STATIC_CHECK( cppsort::weak_less(ta, tb) == helpers::compare_result::total_less ); + STATIC_CHECK( cppsort::weak_greater(ta, tb) == helpers::compare_result::total_greater ); + STATIC_CHECK( cppsort::weak_less(wa, wb) == helpers::compare_result::weak_less ); + STATIC_CHECK( cppsort::weak_greater(wa, wb) == helpers::compare_result::weak_greater ); +} diff --git a/tests/testing-tools/comparators.h b/tests/testing-tools/comparators.h new file mode 100644 index 00000000..84e89f04 --- /dev/null +++ b/tests/testing-tools/comparators.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_TESTSUITE_COMPARATORS_H_ +#define CPPSORT_TESTSUITE_COMPARATORS_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +namespace helpers +{ + // Identify the function that was called + enum struct compare_result + { + total_less, + total_greater, + weak_less, + weak_greater, + partial_less, + partial_greater, + }; + + // Collection of empty classes and empty comparison overloads used + // to identify what overload was selected during resolution + + struct totally_comparable + { + friend constexpr auto total_less(const totally_comparable&, + const totally_comparable&) + -> compare_result + { + return compare_result::total_less; + } + }; + + constexpr auto total_greater(const totally_comparable&, + const totally_comparable&) + -> compare_result + { + return compare_result::total_greater; + } + + struct weakly_comparable + { + friend constexpr auto weak_less(const weakly_comparable&, + const weakly_comparable&) + -> compare_result + { + return compare_result::weak_less; + } + }; + + constexpr auto weak_greater(const weakly_comparable&, + const weakly_comparable&) + -> compare_result + { + return compare_result::weak_greater; + } + + struct partially_comparable + { + friend constexpr auto partial_less(const partially_comparable&, + const partially_comparable&) + -> compare_result + { + return compare_result::partial_less; + } + }; + + constexpr auto partial_greater(const partially_comparable&, + const partially_comparable&) + -> compare_result + { + return compare_result::partial_greater; + } +} + +#endif // CPPSORT_TESTSUITE_COMPARATORS_H_ From be3ed7fe1729865b31ee1bd885d6e5f78be65146 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 1 Mar 2026 20:47:16 +0100 Subject: [PATCH 11/27] Fix partial_less and partial_greater with floating-point numbers They simply never worked for floating-point numbers because the overload was ambiguous. Adding simple tests revealed the bug. --- .../cpp-sort/comparators/partial_greater.h | 5 ++- include/cpp-sort/comparators/partial_less.h | 5 ++- tests/comparators/partial_order.cpp | 45 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/include/cpp-sort/comparators/partial_greater.h b/include/cpp-sort/comparators/partial_greater.h index 9e9c5b75..a491400d 100644 --- a/include/cpp-sort/comparators/partial_greater.h +++ b/include/cpp-sort/comparators/partial_greater.h @@ -34,7 +34,10 @@ namespace cppsort template constexpr auto partial_greater(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::weak_greater(lhs, rhs))) - -> decltype(cppsort::weak_greater(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point_v, + decltype(cppsort::weak_greater(lhs, rhs)) + > { return cppsort::weak_greater(lhs, rhs); } diff --git a/include/cpp-sort/comparators/partial_less.h b/include/cpp-sort/comparators/partial_less.h index 2ba02066..35cd7d8c 100644 --- a/include/cpp-sort/comparators/partial_less.h +++ b/include/cpp-sort/comparators/partial_less.h @@ -34,7 +34,10 @@ namespace cppsort template constexpr auto partial_less(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::weak_less(lhs, rhs))) - -> decltype(cppsort::weak_less(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point_v, + decltype(cppsort::weak_less(lhs, rhs)) + > { return cppsort::weak_less(lhs, rhs); } diff --git a/tests/comparators/partial_order.cpp b/tests/comparators/partial_order.cpp index 544786b4..53f52286 100644 --- a/tests/comparators/partial_order.cpp +++ b/tests/comparators/partial_order.cpp @@ -2,11 +2,56 @@ * Copyright (c) 2026 Morwenn * SPDX-License-Identifier: MIT */ +#include +#include +#include #include #include #include +#include +#include #include +TEST_CASE( "Partial ordering of floating-point numbers", "[comparison]" ) +{ + constexpr double inf = std::numeric_limits::infinity(); + + // We can't do anything with NaNs in the partial order + double array[] = { +1.0, +inf, -1.0, -2.3, +0.0, -inf, +5.6, -0.0, +inf, -inf }; + + SECTION( "partial_less" ) + { + cppsort::heap_sort(array, cppsort::partial_less); + + CHECK( std::isinf(array[0]) ); + CHECK( std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( std::signbit(array[1]) ); + CHECK( std::isinf(array[8]) ); + CHECK( not std::signbit(array[8]) ); + CHECK( std::isinf(array[9]) ); + CHECK( not std::signbit(array[9]) ); + + CHECK( cppsort::utility::is_sorted(array) ); + } + + SECTION( "weak_greater" ) + { + cppsort::heap_sort(array, cppsort::partial_greater); + + CHECK( std::isinf(array[0]) ); + CHECK( not std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( not std::signbit(array[1]) ); + CHECK( std::isinf(array[8]) ); + CHECK( std::signbit(array[8]) ); + CHECK( std::isinf(array[9]) ); + CHECK( std::signbit(array[9]) ); + + CHECK( cppsort::utility::is_sorted(array, std::greater{}) ); + } +} + TEST_CASE( "Partial order customization point", "[comparison]" ) { helpers::totally_comparable ta, tb; From 158b7bda560fd56d1a49ab3edf9d67dbed96b145 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 13 Mar 2026 20:30:08 +0100 Subject: [PATCH 12/27] Test that M(Unique(X))=M(X) for Amp, Mono, Runs --- tests/probes/amp.cpp | 11 ++++++++++- tests/probes/mono.cpp | 11 ++++++++++- tests/probes/runs.cpp | 14 +++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/tests/probes/amp.cpp b/tests/probes/amp.cpp index 4cebd6ca..221ad41f 100644 --- a/tests/probes/amp.cpp +++ b/tests/probes/amp.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025 Morwenn + * Copyright (c) 2025-2026 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -43,4 +43,13 @@ TEST_CASE( "measure of disorder: amp", "[probe][amp]" ) std::reverse(sequence.begin(), sequence.end()); return amp(sequence) == amp_x; }); + + rc::prop("Amp(Unique(X)) = Amp(X)", [](std::vector sequence) { + auto amp_x = amp(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return amp(sequence) == amp_x; + }); } diff --git a/tests/probes/mono.cpp b/tests/probes/mono.cpp index e0825a4f..26eb8bd1 100644 --- a/tests/probes/mono.cpp +++ b/tests/probes/mono.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 Morwenn + * Copyright (c) 2018-2026 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -61,4 +61,13 @@ TEST_CASE( "measure of disorder: mono", "[probe][mono]" ) std::reverse(sequence.begin(), sequence.end()); return mono(sequence) == mono_x; }); + + rc::prop("Mono(Unique(X)) = Mono(X)", [](std::vector sequence) { + auto mono_x = mono(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return mono(sequence) == mono_x; + }); } diff --git a/tests/probes/runs.cpp b/tests/probes/runs.cpp index 129e8311..79780691 100644 --- a/tests/probes/runs.cpp +++ b/tests/probes/runs.cpp @@ -1,10 +1,13 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include +#include +#include #include #include #include @@ -40,4 +43,13 @@ TEST_CASE( "measure of disorder: runs", "[probe][runs]" ) CHECK( runs(li) == max_n ); CHECK( runs(li.begin(), li.end()) == max_n ); } + + rc::prop("Runs(Unique(X)) = Runs(X)", [](std::vector sequence) { + auto runs_x = runs(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return runs(sequence) == runs_x; + }); } From bb6526ba4fe313eed0b9c4d8bf8207886176dd4b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 16 Mar 2026 22:21:52 +0100 Subject: [PATCH 13/27] Test that M(Unique(X))=M(X) for Block, Enc, SUS --- tests/probes/block.cpp | 14 +++++++++++++- tests/probes/enc.cpp | 14 +++++++++++++- tests/probes/sus.cpp | 14 +++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/tests/probes/block.cpp b/tests/probes/block.cpp index 05748f04..fe64f6bd 100644 --- a/tests/probes/block.cpp +++ b/tests/probes/block.cpp @@ -1,11 +1,14 @@ /* - * Copyright (c) 2021-2025 Morwenn + * Copyright (c) 2021-2026 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include #include +#include +#include #include #include #include @@ -36,4 +39,13 @@ TEST_CASE( "measure of disorder: block", "[probe][block]" ) CHECK( block(li) == max_n ); CHECK( block(li.begin(), li.end()) == max_n ); } + + rc::prop("Block(Unique(X)) = Block(X)", [](std::vector sequence) { + auto block_x = block(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return block(sequence) == block_x; + }); } diff --git a/tests/probes/enc.cpp b/tests/probes/enc.cpp index 35625792..d4fb92fb 100644 --- a/tests/probes/enc.cpp +++ b/tests/probes/enc.cpp @@ -1,10 +1,13 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include +#include +#include #include #include #include @@ -46,4 +49,13 @@ TEST_CASE( "measure of disorder: enc", "[probe][enc]" ) CHECK( enc(li.begin(), li.end()) == max_n ); } } + + rc::prop("Enc(Unique(X)) = Enc(X)", [](std::vector sequence) { + auto enc_x = enc(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return enc(sequence) == enc_x; + }); } diff --git a/tests/probes/sus.cpp b/tests/probes/sus.cpp index d98e60fd..842b0396 100644 --- a/tests/probes/sus.cpp +++ b/tests/probes/sus.cpp @@ -1,10 +1,13 @@ /* - * Copyright (c) 2021-2025 Morwenn + * Copyright (c) 2021-2026 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include +#include +#include #include #include #include @@ -34,4 +37,13 @@ TEST_CASE( "measure of disorder: sus", "[probe][sus]" ) CHECK( sus(li) == max_n ); CHECK( sus(li.begin(), li.end()) == max_n ); } + + rc::prop("SUS(Unique(X)) = SUS(X)", [](std::vector sequence) { + auto sus_x = sus(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return sus(sequence) == sus_x; + }); } From 9aacbb9f1ed0d192929b33b70309410f61f91234 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 3 Jun 2026 21:07:24 +0200 Subject: [PATCH 14/27] Rollback Block(Unique(X)) = Block(X) test --- tests/probes/block.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/probes/block.cpp b/tests/probes/block.cpp index fe64f6bd..305d351f 100644 --- a/tests/probes/block.cpp +++ b/tests/probes/block.cpp @@ -2,13 +2,10 @@ * Copyright (c) 2021-2026 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include #include -#include -#include #include #include #include @@ -39,13 +36,4 @@ TEST_CASE( "measure of disorder: block", "[probe][block]" ) CHECK( block(li) == max_n ); CHECK( block(li.begin(), li.end()) == max_n ); } - - rc::prop("Block(Unique(X)) = Block(X)", [](std::vector sequence) { - auto block_x = block(sequence); - sequence.erase( - std::unique(sequence.begin(), sequence.end()), - sequence.end() - ); - return block(sequence) == block_x; - }); } From 4e2396811ac61bc9ce6a3373c5f6a98a72bb34ff Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 3 Jun 2026 21:26:56 +0200 Subject: [PATCH 15/27] Upgrade downloaded Catch2 version to v3.15.0 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b6d32618..bd5a1ce5 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 88abf9bf325c798c33f54f6b9220ef885b267f4f # v3.12.0 + GIT_TAG 6ee0826dcae55ed1e06b2c5701981221e979e1e6 # v3.15.0 ) FetchContent_GetProperties(Catch2) if (NOT Catch2_POPULATED) From 19ee7d43e5cc233a8046114a36c61200e4dca5ec Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 28 Jun 2026 20:21:07 +0200 Subject: [PATCH 16/27] Floor integer divisions in doc of measures of disorder --- docs/Measures-of-disorder.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 0398a0da..d505bfad 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -236,7 +236,7 @@ $$ | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | No | -`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). +`max_for_size`: $\lfloor \frac{\lvert X \rvert}{2} \rfloor$ 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* @@ -290,7 +290,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{\lvert X \rvert(\lvert X \rvert - 1)}{2}$ when $X$ is sorted in reverse order. +`max_for_size`: $\lfloor \frac{\lvert X \rvert(\lvert X \rvert - 1)}{2} \rfloor$ when $X$ is sorted in reverse order. ### *Max* @@ -322,7 +322,7 @@ The measure of disorder is slightly different from its original description in [ | ----------- | ----------- | ------------- | --------- | | n | 1 | Forward | No | -`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. +`max_for_size`: $\lfloor \frac{\lvert X \rvert + 1}{2} \rfloor - 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: $\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$. @@ -392,7 +392,7 @@ Spearman's footrule distance: sum of distances between the position of individua | ----------- | ----------- | ------------- | --------- | | n log n | n | Forward | Yes | -`max_for_size`: $\frac{\lvert X \rvert²}{2}$ when $X$ is sorted in reverse order. +`max_for_size`: $\lfloor \frac{\lvert X \rvert^2}{2} \rfloor$ when $X$ is sorted in reverse order. **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)$. From 438745c06e8f7f1764308186c5cc65ed8676710a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 1 Aug 2026 13:52:24 +0200 Subject: [PATCH 17/27] CI: upgrade actions/checkout to v7 --- .forgejo/workflows/deploy-to-wiki.yml | 4 ++-- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/build-mingw.yml | 4 ++-- .github/workflows/build-msvc.yml | 4 ++-- .github/workflows/build-ubuntu.yml | 4 ++-- .github/workflows/code-coverage.yml | 2 +- .github/workflows/deploy-to-wiki.yml | 4 ++-- .github/workflows/mirror-to-codeberg.yml | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.forgejo/workflows/deploy-to-wiki.yml b/.forgejo/workflows/deploy-to-wiki.yml index b5662dc5..c8f195dd 100644 --- a/.forgejo/workflows/deploy-to-wiki.yml +++ b/.forgejo/workflows/deploy-to-wiki.yml @@ -19,13 +19,13 @@ jobs: steps: - name: Checkout /docs - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: ${{forgejo.repository}} path: main - name: Checkout wiki - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: ${{forgejo.repository}}.wiki path: wiki diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index d53467d8..c748ed5f 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2025 Morwenn +# Copyright (c) 2021-2026 Morwenn # SPDX-License-Identifier: MIT name: MacOS Builds @@ -40,7 +40,7 @@ jobs: sanitize: address,undefined steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: rui314/setup-mold@v1 - name: Configure CMake diff --git a/.github/workflows/build-mingw.yml b/.github/workflows/build-mingw.yml index 45b13574..b31aeed5 100644 --- a/.github/workflows/build-mingw.yml +++ b/.github/workflows/build-mingw.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2025 Morwenn +# Copyright (c) 2021-2026 Morwenn # SPDX-License-Identifier: MIT name: MinGW-w64 Builds @@ -31,7 +31,7 @@ jobs: build_type: [Debug, Release] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Configure CMake shell: pwsh diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 66186857..4352a8c3 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2025 Morwenn +# Copyright (c) 2021-2026 Morwenn # SPDX-License-Identifier: MIT name: MSVC Builds @@ -36,7 +36,7 @@ jobs: build_tools: '-T ClangCL' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Configure CMake shell: pwsh diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 1c96c444..78fa7b29 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2025 Morwenn +# Copyright (c) 2021-2026 Morwenn # SPDX-License-Identifier: MIT name: Ubuntu Builds @@ -41,7 +41,7 @@ jobs: sanitize: address,undefined steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: rui314/setup-mold@v1 - name: Install GCC diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index e8c87930..a2fcc7de 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout project - uses: actions/checkout@v6 + uses: actions/checkout@v7 - uses: rui314/setup-mold@v1 - name: Install LCOV diff --git a/.github/workflows/deploy-to-wiki.yml b/.github/workflows/deploy-to-wiki.yml index a9612004..faae526f 100644 --- a/.github/workflows/deploy-to-wiki.yml +++ b/.github/workflows/deploy-to-wiki.yml @@ -19,13 +19,13 @@ jobs: steps: - name: Checkout /docs - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: ${{github.repository}} path: main - name: Checkout wiki - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: ${{github.repository}}.wiki path: wiki diff --git a/.github/workflows/mirror-to-codeberg.yml b/.github/workflows/mirror-to-codeberg.yml index 7ebe5497..416b1c49 100644 --- a/.github/workflows/mirror-to-codeberg.yml +++ b/.github/workflows/mirror-to-codeberg.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 From 4621e67b5e3d79be57f98cee9229ae4ad530449f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 25 Jul 2026 17:07:22 +0200 Subject: [PATCH 18/27] New measure of disorder: Reve --- docs/Measures-of-disorder.md | 18 ++++ docs/images/measure-of-disorder-reve.png | Bin 0 -> 28803 bytes .../partial-ordering-measures-of-disorder.png | Bin 41936 -> 42483 bytes include/cpp-sort/probes.h | 3 +- include/cpp-sort/probes/reve.h | 85 ++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/probes/every_probe_common.cpp | 18 ++-- .../every_probe_heap_memory_exhaustion.cpp | 5 +- tests/probes/relations.cpp | 38 +++++--- tests/probes/reve.cpp | 72 +++++++++++++++ .../partial-ordering-measures-of-disorder.tex | 12 +-- 11 files changed, 227 insertions(+), 25 deletions(-) create mode 100644 docs/images/measure-of-disorder-reve.png create mode 100644 include/cpp-sort/probes/reve.h create mode 100644 tests/probes/reve.cpp diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index d505bfad..d33f5434 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -366,6 +366,24 @@ Computes the minimum number of elements that must be removed from $X$ to obtain `max_for_size`: $\lvert X \rvert - 1$ when $X$ is sorted in reverse order. +### *Reve* + +```cpp +#include +``` + +The number of reversals in the growth direction of a sequence. + +![Illustration showing a line diagram of a sequence of integer numbers, highlighting the inflection points in the growth direction](images/measure-of-disorder-reve.png) + +| Complexity | Memory | Iterators | Monotonic | +| ----------- | ----------- | ------------- | --------- | +| n | 1 | Forward | No | + +`max_for_size`: $\lvert X \rvert - 2$ when $X$ is sorted in reverse order. + +**Note:** `probe::reve` does not respect Mannila's criterion 4: $\mathit{Reve}(\langle 1, 2, 3, 4, 5 \rangle) = 0$ and $\mathit{Reve}(\langle 10, 9, 8, 7, 6 \rangle) = 0$, but $\mathit{Reve}(\langle 1, 2, 3, 4, 5, 10, 9, 8, 7, 6 \rangle) = 1$. + ### *Runs* ```cpp diff --git a/docs/images/measure-of-disorder-reve.png b/docs/images/measure-of-disorder-reve.png new file mode 100644 index 0000000000000000000000000000000000000000..f096029ebd7fa8c5b64bdb2ece4b77b0939ee10c GIT binary patch literal 28803 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 literal 41936 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 LVi{xMyzPGgl #include #include +#include #include #include #include diff --git a/include/cpp-sort/probes/reve.h b/include/cpp-sort/probes/reve.h new file mode 100644 index 00000000..b1ce900b --- /dev/null +++ b/include/cpp-sort/probes/reve.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_PROBES_REVE_H_ +#define CPPSORT_PROBES_REVE_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" + +namespace cppsort::probe +{ + namespace detail + { + struct reve_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; + } + + // Current "direction" of the run: + // -1: we are in a descending run + // 0: initial value, we don't know the direction of the first run yet + // 1: we are in an ascending run + int direction = 0; + + auto current = first; + auto next = std::next(first); + + difference_type count = 0; // Number of "reversals" in the sequence + for (;next != last; ++current, ++next) { + if (comp(proj(*current), proj(*next))) { + if (direction == -1) { + ++count; + } + direction = 1; + } else if (comp(proj(*next), proj(*current))) { + if (direction == 1) { + ++count; + } + direction = -1; + } + // When neighbors compare equivalent, the direction does not change + } + return count; + } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n < 2 ? 0 : n - 2; + } + }; + } + + inline constexpr sorter_facade reve{}; +} + +#endif // CPPSORT_PROBES_REVE_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bd5a1ce5..9358e70e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -220,6 +220,7 @@ add_executable(main-tests probes/mono.cpp probes/osc.cpp probes/rem.cpp + probes/reve.cpp probes/runs.cpp probes/spear.cpp probes/sus.cpp diff --git a/tests/probes/every_probe_common.cpp b/tests/probes/every_probe_common.cpp index 6c90ff5b..2d787cf3 100644 --- a/tests/probes/every_probe_common.cpp +++ b/tests/probes/every_probe_common.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2025 Morwenn + * Copyright (c) 2021-2026 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -29,6 +29,7 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) @@ -55,6 +56,7 @@ TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]", decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) @@ -82,6 +84,7 @@ TEMPLATE_TEST_CASE( "test every probe with a 0 or 1 element", "[probe]", decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) @@ -121,6 +124,7 @@ TEMPLATE_TEST_CASE( "test order isomorphism for every probe", "[probe]", decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) @@ -177,6 +181,7 @@ TEMPLATE_TEST_CASE( "test M(subsequence(X)) <= M(X) for most probes M", "[probe] decltype(cppsort::probe::max), decltype(cppsort::probe::mono), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) @@ -193,8 +198,9 @@ TEMPLATE_TEST_CASE( "test M(subsequence(X)) <= M(X) for most probes M", "[probe] } TEMPLATE_TEST_CASE( "test M(subsequence(X)) <= M(X) for most probes M (unique elements)", "[probe]", - decltype(cppsort::probe::osc), - decltype(cppsort::probe::block) ) + decltype(cppsort::probe::block), + decltype(cppsort::probe::reve), + decltype(cppsort::probe::osc) ) { // Same as above, but for probes that aren't well-behaved // with regard to equivalent elements @@ -284,7 +290,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: Amp, Block, Mono, Osc + // The following probes don't satisfy it: Amp, Block, Mono, Osc, Reve int size = 1000; std::vector sequence(size, 0); @@ -309,6 +315,7 @@ TEMPLATE_TEST_CASE( "test M(aX) <= |X| + M(X) for most probes M", "[probe]", decltype(cppsort::probe::max), decltype(cppsort::probe::mono), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::sus) ) { @@ -341,7 +348,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: Amp, Block, Mono, Osc + // The following probes don't satisfy it: Amp, Block, Mono, Osc, Reve // Note: the original paper claims that Osc also satisfies this property, // but it fails for X=⟨3, 0⟩ Y=⟨⟩ Z=⟨4, 2⟩ @@ -441,6 +448,7 @@ TEMPLATE_TEST_CASE( "test that probes never produce more disorder than their the decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) diff --git a/tests/probes/every_probe_heap_memory_exhaustion.cpp b/tests/probes/every_probe_heap_memory_exhaustion.cpp index 436cff71..eb801bca 100644 --- a/tests/probes/every_probe_heap_memory_exhaustion.cpp +++ b/tests/probes/every_probe_heap_memory_exhaustion.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2025 Morwenn + * Copyright (c) 2020-2026 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -23,6 +23,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for random-access probes", "[probe][heap_ex decltype(cppsort::probe::amp), decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs) ) { std::vector collection; collection.reserve(491); @@ -42,6 +43,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional probes", "[probe][heap_ex decltype(cppsort::probe::amp), decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs) ) { std::list collection; @@ -61,6 +63,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for forward probes", "[probe][heap_exhausti decltype(cppsort::probe::amp), decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs) ) { std::forward_list collection; diff --git a/tests/probes/relations.cpp b/tests/probes/relations.cpp index bdd773fd..0fea763b 100644 --- a/tests/probes/relations.cpp +++ b/tests/probes/relations.cpp @@ -174,30 +174,44 @@ TEST_CASE( "relations between measures of disorder", "[probe]" ) return spear(sequence) <= 2 * inv(sequence); }); - // Original research about Mono + // Parity of permutations + // https://en.wikipedia.org/wiki/Parity_of_a_permutation#Equivalence_of_the_two_definitions - rc::prop("Mono(X) ≤ Runs(X)", [](const std::vector& sequence) { - return mono(sequence) <= runs(sequence); + rc::prop("Inv(X) % 2 = Exc(X) % 2", []() { + auto sequence = *rc::gen::unique>(rc::gen::arbitrary()); + return inv(sequence) % 2 == exc(sequence) % 2; }); - rc::prop("Enc(X) ≤ Mono(X) + 1", [](const std::vector& sequence) { - return enc(sequence) <= mono(sequence) + 1; - }); + // Original research about Amp, Mono and Reve: + // https://morwenn.github.io/presortedness/2025/11/09/TSB007-relationship-between-amp-and-runs.html + // https://morwenn.github.io/presortedness/2026/07/26/TSB012-relationship-between-amp-and-mono.html - // Original research about Amp + rc::prop("Mono(X) ≤ Runs(X)", [](const std::vector& sequence) { + return mono(sequence) <= runs(sequence); + }); - // 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); }); - // Conjectures rc::prop("Mono(X) ≤ Amp(X)", [](const std::vector& sequence) { return mono(sequence) <= amp(sequence); }); - rc::prop("Inv(X) % 2 = Exc(X) % 2", []() { - auto sequence = *rc::gen::unique>(rc::gen::arbitrary()); - return inv(sequence) % 2 == exc(sequence) % 2; + rc::prop("Mono(X) ≤ Reve(X)", [](const std::vector& sequence) { + return mono(sequence) <= reve(sequence); + }); + + rc::prop("Reve(X) ≤ 2 Mono(X)", [](const std::vector& sequence) { + return reve(sequence) <= 2 * mono(sequence); + }); + + rc::prop("Reve(X) ≤ Amp(X)", [](const std::vector& sequence) { + return reve(sequence) <= amp(sequence); + }); + + // See "Original research" page in the documentation + rc::prop("Enc(X) ≤ Mono(X) + 1", [](const std::vector& sequence) { + return enc(sequence) <= mono(sequence) + 1; }); } diff --git a/tests/probes/reve.cpp b/tests/probes/reve.cpp new file mode 100644 index 00000000..904966fc --- /dev/null +++ b/tests/probes/reve.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "measure of disorder: reve", "[probe][reve]" ) +{ + using cppsort::probe::reve; + + SECTION( "simple test" ) + { + const std::forward_list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; + CHECK( reve(li) == 3 ); + CHECK( reve(li.begin(), li.end()) == 3 ); + + std::vector> tricky(li.begin(), li.end()); + CHECK( reve(tricky, &internal_compare::compare_to) == 3 ); + } + + SECTION( "lower bound" ) + { + const std::forward_list li1 = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + CHECK( reve(li1) == 0 ); + } + + SECTION( "upper bound" ) + { + // The upper bound should correspond to: size - 2 + + const std::forward_list li = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; + auto max_n = reve.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 9 ); + CHECK( reve(li) == max_n ); + CHECK( reve(li.begin(), li.end()) == max_n ); + } + + SECTION( "equal neighbours in the sequence" ) + { + const std::forward_list li = { 0, 0, 0, 1, 2, 3, 4, 6, 5, 3 }; + CHECK( reve(li) == 1 ); + + const std::forward_list li1 = { 6, 5, 4, 3, 2, 2, 2, 2 }; + CHECK( reve(li1) == 0 ); + + const std::forward_list li2 = { 1, 1, 2, 8, 3, 3, 2, 1, 1, 5, 6 }; + CHECK( reve(li2) == 2 ); + } + + rc::prop("Reve(Reversed(X)) = Reve(X)", [](std::vector sequence) { + auto reve_x = reve(sequence); + std::reverse(sequence.begin(), sequence.end()); + return reve(sequence) == reve_x; + }); + + rc::prop("Reve(Unique(X)) = Reve(X)", [](std::vector sequence) { + auto reve_x = reve(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return reve(sequence) == reve_x; + }); +} diff --git a/tools/partial-ordering-measures-of-disorder.tex b/tools/partial-ordering-measures-of-disorder.tex index 7d36686c..4d4942da 100644 --- a/tools/partial-ordering-measures-of-disorder.tex +++ b/tools/partial-ordering-measures-of-disorder.tex @@ -1,4 +1,4 @@ -% Copyright (c) 2021-2025 Morwenn +% Copyright (c) 2021-2026 Morwenn % SPDX-License-Identifier: MIT \documentclass{standalone} @@ -65,13 +65,13 @@ \path[-] (runs) edge node {} (m01); \path[-] (m01) edge node {} (m0); - % Sort Race - % by H. Zhang, B. Meng and Y. Liang - \node[state] (mono) [right=of sus] {$\bm{\mathit{Mono}}$}; + % Mono: Sort Race, by H. Zhang, B. Meng and Y. Liang + % Reve: original research + \node[state] (mono) [right=of sus] {$\bm{\mathit{Mono}}\equiv \bm{\mathit{Reve}}$}; % See the Original Research page of the docs \path[-] (enc) edge node {} (mono); - - % Original research: Amp + + % Amp: original research \node[state] (amp) [below=of mono] {$\bm{\mathit{Amp}}$}; \path[-] (amp) edge node {} (runs); % Conjecture From d8dab90b0e859635117408be82ab19d52afcd3ad Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 1 Aug 2026 15:42:59 +0200 Subject: [PATCH 19/27] Tests: add missing amp/reve to move tests --- tests/probes/every_probe_move_compare_projection.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/probes/every_probe_move_compare_projection.cpp b/tests/probes/every_probe_move_compare_projection.cpp index bea72d37..91f89b6d 100644 --- a/tests/probes/every_probe_move_compare_projection.cpp +++ b/tests/probes/every_probe_move_compare_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2025 Morwenn + * Copyright (c) 2020-2026 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -10,6 +10,7 @@ #include TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), @@ -20,6 +21,7 @@ TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[pr decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) @@ -34,6 +36,7 @@ TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[pr } TEMPLATE_TEST_CASE( "every probe with projection function altered by move", "[probe]", + decltype(cppsort::probe::amp), decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), @@ -44,6 +47,7 @@ TEMPLATE_TEST_CASE( "every probe with projection function altered by move", "[pr decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), decltype(cppsort::probe::rem), + decltype(cppsort::probe::reve), decltype(cppsort::probe::runs), decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) From c3bcd9b91af3537b20d7d5d370864a046faf07eb Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 1 Aug 2026 21:14:01 +0200 Subject: [PATCH 20/27] Move Unique & Reversed probe tests to a common file --- tests/CMakeLists.txt | 1 + tests/probes/amp.cpp | 18 ------- tests/probes/enc.cpp | 12 ----- tests/probes/every_probe_common.cpp | 7 ++- tests/probes/every_probe_common_extra.cpp | 63 +++++++++++++++++++++++ tests/probes/mono.cpp | 18 ------- tests/probes/osc.cpp | 11 +--- tests/probes/reve.cpp | 18 ------- tests/probes/runs.cpp | 12 ----- tests/probes/sus.cpp | 12 ----- 10 files changed, 70 insertions(+), 102 deletions(-) create mode 100644 tests/probes/every_probe_common_extra.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9358e70e..e3b83f31 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -226,6 +226,7 @@ add_executable(main-tests probes/sus.cpp probes/relations.cpp probes/every_probe_common.cpp + probes/every_probe_common_extra.cpp probes/every_probe_move_compare_projection.cpp # Sorters tests diff --git a/tests/probes/amp.cpp b/tests/probes/amp.cpp index 221ad41f..1898776e 100644 --- a/tests/probes/amp.cpp +++ b/tests/probes/amp.cpp @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include #include #include @@ -36,20 +34,4 @@ TEST_CASE( "measure of disorder: amp", "[probe][amp]" ) 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; - }); - - rc::prop("Amp(Unique(X)) = Amp(X)", [](std::vector sequence) { - auto amp_x = amp(sequence); - sequence.erase( - std::unique(sequence.begin(), sequence.end()), - sequence.end() - ); - return amp(sequence) == amp_x; - }); } diff --git a/tests/probes/enc.cpp b/tests/probes/enc.cpp index d4fb92fb..f0e5e9d6 100644 --- a/tests/probes/enc.cpp +++ b/tests/probes/enc.cpp @@ -2,12 +2,9 @@ * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include -#include -#include #include #include #include @@ -49,13 +46,4 @@ TEST_CASE( "measure of disorder: enc", "[probe][enc]" ) CHECK( enc(li.begin(), li.end()) == max_n ); } } - - rc::prop("Enc(Unique(X)) = Enc(X)", [](std::vector sequence) { - auto enc_x = enc(sequence); - sequence.erase( - std::unique(sequence.begin(), sequence.end()), - sequence.end() - ); - return enc(sequence) == enc_x; - }); } diff --git a/tests/probes/every_probe_common.cpp b/tests/probes/every_probe_common.cpp index 2d787cf3..5e7cfc09 100644 --- a/tests/probes/every_probe_common.cpp +++ b/tests/probes/every_probe_common.cpp @@ -14,7 +14,10 @@ #include "testing-tools/random.h" // -// Test common properties of measures of presortedness +// Test common properties of measures of presortedness: +// - Basic properties such a checking max_for_size consistency +// - Mannilla 5 axioms for measures of presortedness +// - Estivill-Castro additional properties such as monotonicity // TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", @@ -34,7 +37,7 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", decltype(cppsort::probe::spear), decltype(cppsort::probe::sus) ) { - // First property formalized by Mannila + // First axiom formalized by Mannila // Ensure that all measures of disorder return 0 when // given a collection where all elements are equal diff --git a/tests/probes/every_probe_common_extra.cpp b/tests/probes/every_probe_common_extra.cpp new file mode 100644 index 00000000..29fb2455 --- /dev/null +++ b/tests/probes/every_probe_common_extra.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include "testing-tools/random.h" + +// +// Test extra commonenough properties of measures of disorder: +// - Whether neighbors that compare equivalent affect the sequence +// - Horizontal symmetry +// + +TEMPLATE_TEST_CASE( "test measures that ignore equal neighbors", "[probe]", + decltype(cppsort::probe::amp), + decltype(cppsort::probe::enc), + decltype(cppsort::probe::mono), + decltype(cppsort::probe::reve), + decltype(cppsort::probe::runs), + decltype(cppsort::probe::sus) ) +{ + // The literature about measures of disorder generally only considers + // sequences of distinct elements. This test checks that some of them + // return the same result with neighboring elements that compare + // equivalent, and when reducing these groups to one element. + + rc::prop("M(Unique(X)) = M(X)", [](std::vector sequence) { + std::decay_t measure; + + auto measure_x = measure(sequence); + sequence.erase( + std::unique(sequence.begin(), sequence.end()), + sequence.end() + ); + return measure(sequence) == measure_x; + }); +} + +TEMPLATE_TEST_CASE( "test measures that find the same disorder in a reversed sequence", "[probe]", + decltype(cppsort::probe::amp), + decltype(cppsort::probe::mono), + decltype(cppsort::probe::osc), + decltype(cppsort::probe::reve) ) +{ + // Several measures of presortedness find as much disorder in a sequence, + // than in the same sequence flipped horizontally (reversed) + // Those tend to violate Mannila's 4th axiom: + // https://morwenn.github.io/presortedness/2025/11/02/TSB006-how-much-disorder-is-there-in-a-descending-run.html + + rc::prop("M(Reversed(X)) = M(X)", [](std::vector sequence) { + std::decay_t measure; + + auto measure_x = measure(sequence); + std::reverse(sequence.begin(), sequence.end()); + return measure(sequence) == measure_x; + }); +} diff --git a/tests/probes/mono.cpp b/tests/probes/mono.cpp index 26eb8bd1..7f3c1bd4 100644 --- a/tests/probes/mono.cpp +++ b/tests/probes/mono.cpp @@ -2,12 +2,9 @@ * Copyright (c) 2018-2026 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include -#include -#include #include #include #include @@ -55,19 +52,4 @@ TEST_CASE( "measure of disorder: mono", "[probe][mono]" ) const std::forward_list li2 = { 1, 1, 2, 8, 3, 3, 2, 1, 1, 5, 6 }; CHECK( mono(li2) == 2 ); } - - rc::prop("Mono(Reversed(X)) = Mono(X)", [](std::vector sequence) { - auto mono_x = mono(sequence); - std::reverse(sequence.begin(), sequence.end()); - return mono(sequence) == mono_x; - }); - - rc::prop("Mono(Unique(X)) = Mono(X)", [](std::vector sequence) { - auto mono_x = mono(sequence); - sequence.erase( - std::unique(sequence.begin(), sequence.end()), - sequence.end() - ); - return mono(sequence) == mono_x; - }); } diff --git a/tests/probes/osc.cpp b/tests/probes/osc.cpp index f757650a..4166008b 100644 --- a/tests/probes/osc.cpp +++ b/tests/probes/osc.cpp @@ -1,14 +1,11 @@ /* - * Copyright (c) 2016-2025 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include #include -#include -#include #include #include #include @@ -68,12 +65,6 @@ TEST_CASE( "measure of disorder: osc", "[probe][osc]" ) CHECK( osc(vec, comp, &wrapper::value) == 17 ); } - rc::prop("Osc(Reversed(X)) = Osc(X)", [](std::vector sequence) { - auto osc_x = cppsort::probe::osc(sequence); - std::reverse(sequence.begin(), sequence.end()); - return cppsort::probe::osc(sequence) == osc_x; - }); - SECTION( "Sorting and Measures of Disorder, Theorem 3.18" ) { int size = 1000; diff --git a/tests/probes/reve.cpp b/tests/probes/reve.cpp index 904966fc..339b7be8 100644 --- a/tests/probes/reve.cpp +++ b/tests/probes/reve.cpp @@ -2,12 +2,9 @@ * Copyright (c) 2026 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include -#include -#include #include #include #include @@ -54,19 +51,4 @@ TEST_CASE( "measure of disorder: reve", "[probe][reve]" ) const std::forward_list li2 = { 1, 1, 2, 8, 3, 3, 2, 1, 1, 5, 6 }; CHECK( reve(li2) == 2 ); } - - rc::prop("Reve(Reversed(X)) = Reve(X)", [](std::vector sequence) { - auto reve_x = reve(sequence); - std::reverse(sequence.begin(), sequence.end()); - return reve(sequence) == reve_x; - }); - - rc::prop("Reve(Unique(X)) = Reve(X)", [](std::vector sequence) { - auto reve_x = reve(sequence); - sequence.erase( - std::unique(sequence.begin(), sequence.end()), - sequence.end() - ); - return reve(sequence) == reve_x; - }); } diff --git a/tests/probes/runs.cpp b/tests/probes/runs.cpp index 79780691..5a87b852 100644 --- a/tests/probes/runs.cpp +++ b/tests/probes/runs.cpp @@ -2,12 +2,9 @@ * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include -#include -#include #include #include #include @@ -43,13 +40,4 @@ TEST_CASE( "measure of disorder: runs", "[probe][runs]" ) CHECK( runs(li) == max_n ); CHECK( runs(li.begin(), li.end()) == max_n ); } - - rc::prop("Runs(Unique(X)) = Runs(X)", [](std::vector sequence) { - auto runs_x = runs(sequence); - sequence.erase( - std::unique(sequence.begin(), sequence.end()), - sequence.end() - ); - return runs(sequence) == runs_x; - }); } diff --git a/tests/probes/sus.cpp b/tests/probes/sus.cpp index 842b0396..6c61d074 100644 --- a/tests/probes/sus.cpp +++ b/tests/probes/sus.cpp @@ -2,12 +2,9 @@ * Copyright (c) 2021-2026 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include -#include -#include #include #include #include @@ -37,13 +34,4 @@ TEST_CASE( "measure of disorder: sus", "[probe][sus]" ) CHECK( sus(li) == max_n ); CHECK( sus(li.begin(), li.end()) == max_n ); } - - rc::prop("SUS(Unique(X)) = SUS(X)", [](std::vector sequence) { - auto sus_x = sus(sequence); - sequence.erase( - std::unique(sequence.begin(), sequence.end()), - sequence.end() - ); - return sus(sequence) == sus_x; - }); } From 342eb8e19606862111c4c4962199257afb139699 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 1 Aug 2026 22:47:20 +0200 Subject: [PATCH 21/27] Doc: add an illustration for Mono --- docs/Measures-of-disorder.md | 2 ++ docs/images/measure-of-disorder-mono.png | Bin 0 -> 18107 bytes 2 files changed, 2 insertions(+) create mode 100644 docs/images/measure-of-disorder-mono.png diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index d33f5434..3f157168 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -318,6 +318,8 @@ The measure of disorder is slightly different from its original description in [ * It subtracts 1 from the number of runs, thus returning 0 when $X$ is sorted. * It explicitly handles non-increasing and non-decreasing runs, not only the strictly increasing or decreasing ones. +![Illustration of Mono showing how a sequence of integers can be split into monotonic runs](images/measure-of-disorder-mono.png) + | Complexity | Memory | Iterators | Monotonic | | ----------- | ----------- | ------------- | --------- | | n | 1 | Forward | No | diff --git a/docs/images/measure-of-disorder-mono.png b/docs/images/measure-of-disorder-mono.png new file mode 100644 index 0000000000000000000000000000000000000000..bda61f16733c8400b65589bec7a74b7092b1a6f5 GIT binary patch literal 18107 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 Date: Sat, 1 Aug 2026 22:54:58 +0200 Subject: [PATCH 22/27] Test partial/weak/greater comparators over integers --- tests/comparators/partial_order.cpp | 19 ++++++++++++++++++- tests/comparators/total_order.cpp | 18 ++++++++++++++++++ tests/comparators/weak_order.cpp | 18 ++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/tests/comparators/partial_order.cpp b/tests/comparators/partial_order.cpp index 53f52286..e3aad3f2 100644 --- a/tests/comparators/partial_order.cpp +++ b/tests/comparators/partial_order.cpp @@ -12,6 +12,23 @@ #include #include +TEST_CASE( "Partial ordering of integers", "[comparison]" ) +{ + long array[] = { -52, 1, 4, 32, 9, -9, -8, -7, 123456, -7, 12, -987 }; + + SECTION( "partial_less" ) + { + cppsort::heap_sort(array, cppsort::partial_less); + CHECK( cppsort::utility::is_sorted(array) ); + } + + SECTION( "partial_greater" ) + { + cppsort::heap_sort(array, cppsort::partial_greater); + CHECK( cppsort::utility::is_sorted(array, std::greater{}) ); + } +} + TEST_CASE( "Partial ordering of floating-point numbers", "[comparison]" ) { constexpr double inf = std::numeric_limits::infinity(); @@ -35,7 +52,7 @@ TEST_CASE( "Partial ordering of floating-point numbers", "[comparison]" ) CHECK( cppsort::utility::is_sorted(array) ); } - SECTION( "weak_greater" ) + SECTION( "partial_greater" ) { cppsort::heap_sort(array, cppsort::partial_greater); diff --git a/tests/comparators/total_order.cpp b/tests/comparators/total_order.cpp index f1aa923b..2be5c8c3 100644 --- a/tests/comparators/total_order.cpp +++ b/tests/comparators/total_order.cpp @@ -8,8 +8,26 @@ #include #include #include +#include #include +TEST_CASE( "Total ordering of integers", "[comparison]" ) +{ + long array[] = { -52, 1, 4, 32, 9, -9, -8, -7, 123456, -7, 12, -987 }; + + SECTION( "total_less" ) + { + cppsort::heap_sort(array, cppsort::total_less); + CHECK( cppsort::utility::is_sorted(array) ); + } + + SECTION( "total_greater" ) + { + cppsort::heap_sort(array, cppsort::total_greater); + CHECK( cppsort::utility::is_sorted(array, std::greater{}) ); + } +} + TEST_CASE( "IEEE 754 totalOrder implementation", "[comparison]" ) { constexpr double nan = std::numeric_limits::quiet_NaN(); diff --git a/tests/comparators/weak_order.cpp b/tests/comparators/weak_order.cpp index 7e529c54..83278b5e 100644 --- a/tests/comparators/weak_order.cpp +++ b/tests/comparators/weak_order.cpp @@ -8,8 +8,26 @@ #include #include #include +#include #include +TEST_CASE( "Weak ordering of integers", "[comparison]" ) +{ + long array[] = { -52, 1, 4, 32, 9, -9, -8, -7, 123456, -7, 12, -987 }; + + SECTION( "weak_less" ) + { + cppsort::heap_sort(array, cppsort::weak_less); + CHECK( cppsort::utility::is_sorted(array) ); + } + + SECTION( "weak_greater" ) + { + cppsort::heap_sort(array, cppsort::weak_greater); + CHECK( cppsort::utility::is_sorted(array, std::greater{}) ); + } +} + TEST_CASE( "Weak ordering of floating-point numbers", "[comparison]" ) { constexpr double inf = std::numeric_limits::infinity(); From bfa74583e75c802c9c086272f55f0bc167a9b181 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 1 Aug 2026 23:49:49 +0200 Subject: [PATCH 23/27] Upgrade downloaded Catch2 version to v3.15.3 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e3b83f31..5b4cfc2d 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 6ee0826dcae55ed1e06b2c5701981221e979e1e6 # v3.15.0 + GIT_TAG 8b08d4d79514f45f7e4ce2a607ac9c94e920d1bb # v3.15.3 ) FetchContent_GetProperties(Catch2) if (NOT Catch2_POPULATED) From d97471aecd38fcb2833d0a0499966e9933eb769b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 2 Aug 2026 00:06:37 +0200 Subject: [PATCH 24/27] Doc: small improvements around measures of disorder --- docs/Measures-of-disorder.md | 15 +++++++++++---- docs/Original-research.md | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/Measures-of-disorder.md b/docs/Measures-of-disorder.md index 3f157168..58bbb8ad 100644 --- a/docs/Measures-of-disorder.md +++ b/docs/Measures-of-disorder.md @@ -164,10 +164,12 @@ Where $N_{\mathit{eq}}(X)$ is the number of pairs of neighbors that compare equi | ----------- | ----------- | ------------- | --------- | | n | 1 | Forward | No | -`max_for_size`: $\lvert X \rvert - 2$ when the sign of $comp$ changes for every pair of neighbors. +`max_for_size`: $\lvert X \rvert - 2$ for [zigzag permutations][zigzag-permutation]. **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$. +*New in version 2.1.0* + ### *Block* ```cpp @@ -324,7 +326,7 @@ The measure of disorder is slightly different from its original description in [ | ----------- | ----------- | ------------- | --------- | | n | 1 | Forward | No | -`max_for_size`: $\lfloor \frac{\lvert X \rvert + 1}{2} \rfloor - 1$ when $X$ is a sequence of elements that are alternatively greater then lesser than their previous neighbour. +`max_for_size`: $\lfloor \frac{\lvert X \rvert + 1}{2} \rfloor - 1$ for [zigzag permutations][zigzag-permutation] (but not only those). **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$. @@ -382,10 +384,12 @@ The number of reversals in the growth direction of a sequence. | ----------- | ----------- | ------------- | --------- | | n | 1 | Forward | No | -`max_for_size`: $\lvert X \rvert - 2$ when $X$ is sorted in reverse order. +`max_for_size`: $\lvert X \rvert - 2$ for [zigzag permutations][zigzag-permutation]. **Note:** `probe::reve` does not respect Mannila's criterion 4: $\mathit{Reve}(\langle 1, 2, 3, 4, 5 \rangle) = 0$ and $\mathit{Reve}(\langle 10, 9, 8, 7, 6 \rangle) = 0$, but $\mathit{Reve}(\langle 1, 2, 3, 4, 5, 10, 9, 8, 7, 6 \rangle) = 1$. +*New in version 2.2.0* + ### *Runs* ```cpp @@ -414,7 +418,9 @@ Spearman's footrule distance: sum of distances between the position of individua `max_for_size`: $\lfloor \frac{\lvert X \rvert^2}{2} \rfloor$ when $X$ is sorted in reverse order. -**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)$. +**Note:** *Spear* does not respect Mannila's criterion 5: $\mathit{Spear}(\langle 4, 1, 2, 3 \rangle) \not \le \lvert \langle 4 \rangle \rvert + \mathit{Spear}(\langle 1, 2, 3 \rangle)$. + +**Note²:** $\lfloor \frac{\mathit{Spear}(X)}{2} \rfloor$ respects Mannila's criterion 5, and is a proper measure of presortedness. A future version of **cpp-sort** might replace the current implementation with one that halves its result. ### *SUS* @@ -493,3 +499,4 @@ Nevertheless we do know a few of the measure's properties: [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 + [zigzag-permutation]: https://en.wikipedia.org/wiki/Alternating_permutation diff --git a/docs/Original-research.md b/docs/Original-research.md index 6bc747a3..285e7461 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -236,7 +236,7 @@ The following relations have yet to be analyzed: - $\mathit{Osc} \preceq \mathit{Mono}$ - $\mathit{Loc} \preceq \mathit{Mono}$ -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. +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 shared with $\mathit{Amp}$, $\mathit{Osc}$ and $\mathit{Reve}$ in the library. [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort @@ -247,7 +247,7 @@ Another interesting property of $\mathit{Mono}$ is that it returns the same amou [mountain_sort]: https://github.com/Morwenn/mountain-sort [poplar-heap]: https://github.com/Morwenn/poplar-heap [post-order-heap]: https://people.csail.mit.edu/nickh/Publications/PostOrderHeap/FUN04-PostOrderHeap.pdf - [probe-mono]: Measures-of-disorder.md + [probe-mono]: Measures-of-disorder.md#mono [quick-merge-sort]: https://github.com/Morwenn/quick_merge_sort [quick-merge-sort-arxiv]: https://arxiv.org/pdf/1804.10062.pdf [sort-race]: https://arxiv.org/ftp/arxiv/papers/1609/1609.04471.pdf From 89a7ea15ef29bdc8473c6158c1e6f9f5e4bbd76e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 23 Aug 2026 14:10:21 +0200 Subject: [PATCH 25/27] CI: get rid of CMake warning in MSVC builds --- .github/workflows/build-msvc.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 4352a8c3..a429281e 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -44,7 +44,6 @@ jobs: run: | cmake -H${{github.event.repository.name}} -Bbuild ` -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} ` - -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ` -G"Visual Studio 17 2022" -A x64 ${{matrix.config.build_tools}} ` -DCPPSORT_BUILD_TESTING=ON ` -DCPPSORT_BUILD_EXAMPLES=ON From 0c478dcfc97b95162b81697476c4dc6d6f62e032 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 23 Aug 2026 18:48:18 +0200 Subject: [PATCH 26/27] Doc: update benchmark for measures of disorder --- docs/Benchmarks.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index 1ca87fa7..a8ea19e1 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.1.0 for measures of disorder* +* *2.2.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* @@ -14,7 +14,7 @@ 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 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 version 2.0.0 and newer: EndeavourOS with g++ 16.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 @@ -176,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/uGoVIcE.png) +![Benchmark speed of measures of disorder for increasing size for std::vector<int>](https://i.imgur.com/xOJIhm1.png) It makes rather easy to see the different groups of complexities: -* *Amp(X)*, *Runs(X)* and *Mono(X)* are obvious O(n) algorithms. +* *Amp(X)*, *Reve(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 28dc96dce1dbe9195d5303bf2be77b5941593754 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 23 Aug 2026 18:50:47 +0200 Subject: [PATCH 27/27] Preparing release 2.2.0 --- CMakeLists.txt | 2 +- README.md | 4 ++-- conanfile.py | 2 +- docs/Home.md | 2 +- docs/Tooling.md | 4 ++-- include/cpp-sort/version.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a760d814..4b51af75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.11.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 2.1.0 LANGUAGES CXX) +project(cpp-sort VERSION 2.2.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(cpp-sort-utils) diff --git a/README.md b/README.md index 55617ae9..8ed95ace 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.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) +[![Latest Release](https://img.shields.io/badge/release-2.2.0-blue.svg)](https://codeberg.org/Morwenn/cpp-sort/releases/tag/v2.2.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F2.2.0-blue.svg)](https://conan.io/center/recipes/cpp-sort?version=2.2.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 37f81c03..e9a2bbe3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -16,7 +16,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "2.1.0" + version = "2.2.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 68437350..b8694446 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.1.0** documentation! +Welcome to the **cpp-sort 2.2.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 a56e4ed6..1a2bd8af 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.1.0): +And then install any version to your local cache as follows (here with version 2.2.0): ```sh -conan install --requires=cpp-sort/2.1.0 +conan install --requires=cpp-sort/2.2.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 0ea4eb92..6536414e 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 2 -#define CPPSORT_VERSION_MINOR 1 +#define CPPSORT_VERSION_MINOR 2 #define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_