diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-mingw.yml similarity index 91% rename from .github/workflows/build-windows.yml rename to .github/workflows/build-mingw.yml index f5097cfa..d78f9505 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-mingw.yml @@ -1,12 +1,12 @@ # Copyright (c) 2021 Morwenn # SPDX-License-Identifier: MIT -name: Windows Builds +name: MinGW-w64 Builds on: push: paths: - - '.github/workflows/build-windows.yml' + - '.github/workflows/build-mingw.yml' - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' @@ -14,7 +14,7 @@ on: - 'testsuite/**' pull_request: paths: - - '.github/workflows/build-windows.yml' + - '.github/workflows/build-mingw.yml' - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml new file mode 100644 index 00000000..bb3b05b5 --- /dev/null +++ b/.github/workflows/build-msvc.yml @@ -0,0 +1,54 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: MSVC Builds + +on: + push: + paths: + - '.github/workflows/build-msvc.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + pull_request: + paths: + - '.github/workflows/build-msvc.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + +jobs: + build: + runs-on: windows-2019 + + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + shell: pwsh + working-directory: ${{runner.workspace}} + run: | + cmake -H${{github.event.repository.name}} -Bbuild ` + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` + -G"Visual Studio 16 2019" -A x64 ` + -DCPPSORT_BUILD_EXAMPLES=ON + + - name: Build the test suite + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} diff --git a/CMakeLists.txt b/CMakeLists.txt index b000a91b..b28f653b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 1.9.0 LANGUAGES CXX) +project(cpp-sort VERSION 1.10.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) @@ -25,6 +25,11 @@ target_include_directories(cpp-sort INTERFACE target_compile_features(cpp-sort INTERFACE cxx_std_14) +# MSVC won't work without a stricter standard compliance +if (MSVC) + target_compile_options(cpp-sort INTERFACE /permissive-) +endif() + add_library(cpp-sort::cpp-sort ALIAS cpp-sort) # Install targets and files diff --git a/README.md b/README.md index 3c941297..45565d0a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-1.9.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.9.0) -[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.9.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.9.0) +[![Latest Release](https://img.shields.io/badge/release-1.10.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.10.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.10.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.10.0) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/develop/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) > *It would be nice if only one or two of the sorting methods would dominate all of the others, @@ -116,8 +116,9 @@ wiki page](https://github.com/Morwenn/cpp-sort/wiki/Benchmarks). **cpp-sort** currently requires C++14 support, and only works with g++5 and clang++3.8 or more recent versions of these compilers. So far, the library should work with the following compilers: -* g++5.5 or more recent. It it know not to work with some older g++5 versions. +* g++5.5 or more recent. It is known not to work with some older g++5 versions. * clang++6 or more recent. It should work with clang++ versions all the way back to 3.8, but the CI pipeline doesn't have test for those anymore. +* Visual Studio 2019 version 16.8.3 or more recent, only with `/permissive-`. A few features are unavailable. * The versions of MinGW-w64 and AppleClang equivalent to the compilers mentioned above. * Clang is notably tested with both libstdc++ and libc++. @@ -126,10 +127,6 @@ with the most recent versions of those compilers on a regular basis. All the oth versions in-between are untested, but should also work. Feel free to open an issue if it isn't the case. -Last time I tried it did not work with the latest MSVC. Future development on the C++14 branch -will try to remain compatible with the compiler versions listed above. I might try to make it -work with MSVC in the future. - The features in the library might differ depending on the C++ version used and on the compiler extensions enabled. Those changes [are documented](https://github.com/Morwenn/cpp-sort/wiki/Changelog) in the wiki. diff --git a/benchmarks/benchmarking-tools/distributions.h b/benchmarks/benchmarking-tools/distributions.h index bf528a77..ffdaf2ad 100644 --- a/benchmarks/benchmarking-tools/distributions.h +++ b/benchmarks/benchmarking-tools/distributions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -32,15 +32,15 @@ namespace dist struct base_distribution { template - using fptr_t = void(*)(OutputIterator, std::size_t); + using fptr_t = void(*)(OutputIterator, long long int); template - using fptr_proj_t = void(*)(OutputIterator, std::size_t, Projection); + using fptr_proj_t = void(*)(OutputIterator, long long int, Projection); template operator fptr_t() const { - return [](OutputIterator out, std::size_t size) + return [](OutputIterator out, long long int size) { return Derived{}(out, size); }; @@ -49,7 +49,7 @@ namespace dist template operator fptr_proj_t() const { - return [](OutputIterator out, std::size_t size, Projection projection) + return [](OutputIterator out, long long int size, Projection projection) { return Derived{}(out, size, std::move(projection)); }; @@ -60,11 +60,11 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { std::vector vec; - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { vec.emplace_back(i); } std::shuffle(vec.begin(), vec.end(), distributions_prng); @@ -80,11 +80,11 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { std::vector vec; - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { vec.emplace_back(i % 16); } std::shuffle(vec.begin(), vec.end(), distributions_prng); @@ -100,11 +100,11 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { *out++ = proj(0); } } @@ -116,11 +116,11 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { *out++ = proj(i); } } @@ -132,7 +132,7 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); @@ -148,14 +148,14 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size / 2 ; ++i) { + for (long long int i = 0 ; i < size / 2 ; ++i) { *out++ = proj(i); } - for (std::size_t i = size / 2 ; i < size ; ++i) { + for (long long int i = size / 2 ; i < size ; ++i) { *out++ = proj(size - i); } } @@ -167,12 +167,12 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); if (size > 0) { - for (std::size_t i = 0 ; i < size - 1 ; ++i) { + for (long long int i = 0 ; i < size - 1 ; ++i) { *out++ = proj(i); } *out = proj(0); @@ -186,12 +186,12 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); if (size > 0) { - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { if (i != size / 2) { *out++ = proj(i); } @@ -207,12 +207,12 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) + 50; - for (std::size_t i = 0 ; i < size ; ++i) { + long long int limit = size / cppsort::detail::log2(size) + 50; + for (long long int i = 0 ; i < size ; ++i) { *out++ = proj(i % limit); } } @@ -224,12 +224,12 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) - 50; - for (std::size_t i = 0 ; i < size ; ++i) { + long long int limit = size / cppsort::detail::log2(size) - 50; + for (long long int i = 0 ; i < size ; ++i) { *out++ = proj(i % limit); } } @@ -241,11 +241,11 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) + 50; + long long int limit = size / cppsort::detail::log2(size) + 50; while (size--) { *out++ = proj(size % limit); } @@ -258,11 +258,11 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) - 50; + long long int limit = size / cppsort::detail::log2(size) - 50; while (size--) { *out++ = proj(size % limit); } @@ -275,11 +275,11 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { *out++ = proj((i % 2) ? i : -i); } } @@ -287,32 +287,34 @@ namespace dist static constexpr const char* output = "alternating.txt"; }; - struct alternating_16_values: - base_distribution + struct reversed_alternating: + base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { + // Especially interesting for a special case of melsort + auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj((i % 2) ? i % 16 : -(i % 16)); + for (long long int i = size ; i > 0 ; --i) { + *out++ = proj((i % 2) ? i : -i); } } - static constexpr const char* output = "alternating_16_values.txt"; + static constexpr const char* output = "reversed_alternating.txt"; }; struct descending_plateau: base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); - std::size_t i = size; + long long int i = size; while (i > 2 * size / 3) { *out++ = proj(i); --i; @@ -343,7 +345,7 @@ namespace dist {} template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { auto&& proj = cppsort::utility::as_function(projection); @@ -351,9 +353,9 @@ namespace dist // Generate a percentage of error std::uniform_real_distribution percent_dis(0.0, 1.0); // Generate a random value - std::uniform_int_distribution value_dis(0, size - 1); + std::uniform_int_distribution value_dis(0, size - 1); - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { if (percent_dis(distributions_prng) < factor) { *out++ = value_dis(distributions_prng); } else { @@ -369,7 +371,7 @@ namespace dist base_distribution { template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + auto operator()(OutputIterator out, long long int size, Projection projection={}) const -> void { // WARNING: not for small collections, mostly because I'm lazy... diff --git a/benchmarks/patterns/bars.py b/benchmarks/patterns/bars.py index 7a76a215..0ac3945f 100644 --- a/benchmarks/patterns/bars.py +++ b/benchmarks/patterns/bars.py @@ -49,8 +49,8 @@ def main(): "push_middle": "Push middle", "ascending_sawtooth": "Ascending sawtooth", "descending_sawtooth": "Descending sawtooth", + "descending_sawtooth": "Descending sawtooth", "alternating": "Alternating", - "alternating_16_values": "Alternating (16 values)", } # Algorithm results will be displayed in this order @@ -58,7 +58,7 @@ def main(): "heap_sort", "pdq_sort", "quick_sort", - "spread_sort", + "ska_sort", "std_sort", "verge_sort", ] @@ -96,11 +96,9 @@ def main(): "Descending", "Pipe organ", "Push front", - "Push middle", "Ascending sawtooth", "Descending sawtooth", "Alternating", - "Alternating (16 values)", ) groupnames = distributions diff --git a/benchmarks/patterns/bench.cpp b/benchmarks/patterns/bench.cpp index 4bdb6605..9d3f4e21 100644 --- a/benchmarks/patterns/bench.cpp +++ b/benchmarks/patterns/bench.cpp @@ -60,30 +60,28 @@ int main() >; std::pair distributions[] = { - { "shuffled", dist::shuffled() }, - { "shuffled_16_values", dist::shuffled_16_values() }, - { "all_equal", dist::all_equal() }, - { "ascending", dist::ascending() }, - { "descending", dist::descending() }, - { "pipe_organ", dist::pipe_organ() }, - { "push_front", dist::push_front() }, - { "push_middle", dist::push_middle() }, - { "ascending_sawtooth", dist::ascending_sawtooth() }, - { "descending_sawtooth", dist::descending_sawtooth() }, - { "alternating", dist::alternating() }, - { "alternating_16_values", dist::alternating_16_values() }, + { "shuffled", dist::shuffled() }, + { "shuffled_16_values", dist::shuffled_16_values() }, + { "all_equal", dist::all_equal() }, + { "ascending", dist::ascending() }, + { "descending", dist::descending() }, + { "pipe_organ", dist::pipe_organ() }, + { "push_front", dist::push_front() }, + { "ascending_sawtooth", dist::ascending_sawtooth() }, + { "descending_sawtooth", dist::descending_sawtooth() }, + { "alternating", dist::alternating() }, }; std::pair sorts[] = { - { "heap_sort", cppsort::heap_sort }, - { "pdq_sort", cppsort::pdq_sort }, - { "quick_sort", cppsort::quick_sort }, - { "spread_sort", cppsort::spread_sort }, - { "std_sort", cppsort::std_sort }, - { "verge_sort", cppsort::verge_sort }, + { "heap_sort", cppsort::heap_sort }, + { "pdq_sort", cppsort::pdq_sort }, + { "quick_sort", cppsort::quick_sort }, + { "ska_sort", cppsort::ska_sort }, + { "std_sort", cppsort::std_sort }, + { "verge_sort", cppsort::verge_sort }, }; - std::size_t sizes[] = { 1'000'000 }; + std::size_t sizes[] = { 10'000'000 }; // Poor seed, yet enough for our benchmarks std::uint_fast32_t seed = std::time(nullptr); diff --git a/cmake/cpp-sort-utils.cmake b/cmake/cpp-sort-utils.cmake index cbd79511..db53818e 100644 --- a/cmake/cpp-sort-utils.cmake +++ b/cmake/cpp-sort-utils.cmake @@ -3,11 +3,15 @@ # Add a selection of warnings to a target macro(cppsort_add_warnings target) - target_compile_options(${target} PRIVATE - -Wall -Wextra -Wcast-align -Winline -Wmissing-declarations -Wmissing-include-dirs - -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code - $<$:-Wlogical-op -Wuseless-cast -Wzero-as-null-pointer-constant> - # The warning when initializing an std::array is just too much of a bother - $<$:-Wno-missing-braces> - ) + if (MSVC) + target_compile_options(${target} PRIVATE /W2) + else() + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wcast-align -Winline -Wmissing-declarations -Wmissing-include-dirs + -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code + $<$:-Wlogical-op -Wuseless-cast -Wzero-as-null-pointer-constant> + # The warning when initializing an std::array is just too much of a bother + $<$:-Wno-missing-braces> + ) + endif() endmacro() diff --git a/conanfile.py b/conanfile.py index 09aec752..481a8a47 100644 --- a/conanfile.py +++ b/conanfile.py @@ -8,7 +8,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.9.0" + version = "1.10.0" description = "Additional sorting algorithms & related tools" topics = "conan", "cpp-sort", "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" @@ -26,7 +26,7 @@ class CppSortConan(ConanFile): no_copy_source = True settings = "os", "compiler", "build_type", "arch" - def configure(self): + def validate(self): if self.settings.get_safe("compiler.cppstd"): tools.check_min_cppstd(self, 14) @@ -42,5 +42,9 @@ def package(self): for file in ["LICENSE.txt", "NOTICE.txt"]: self.copy(file, dst="licenses") + def package_info(self): + if self.settings.compiler == "Visual Studio": + self.cpp_info.cxxflags = ["/permissive-"] + def package_id(self): self.info.header_only() diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index 2e98c0f9..a0c1316d 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -16,28 +16,28 @@ Most sorting algorithms are designed to work with random-access iterators, so th ## Unstable sorts -Unstable sorts are the most common sorting algorithms, and unstable sorts on random-access iterators are generally the fastest comparison sorts. If you don't know what algorithm you know, it's probably one of these ones. +Sorting a random-access collection with an unstable sort is probably one of the most common things to want, and not only are those sorts among the fastest comparison sorts, but type-specific sorters can also be used to sort a variety of types. If you don't know what algorithm you want and don't have specific needs, then you probably want one of these. -![Benchmark speed of unstable sorts with increasing size for std::vector](https://i.imgur.com/6Jfj768.png) -![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/C9GypoJ.png) +![Benchmark speed of unstable sorts with increasing size for std::vector](https://i.imgur.com/Q3IEeci.png) +![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/XjRGUmc.png) The plots above show a few general tendencies: * `selection_sort` is O(n²) and doesn't scale. -* The three heap sorts are consistently the slowest. +* `heap_sort`, despite its good complexity guarantees, is consistently the slowest O(n log n) sort there. * `ska_sort` is almost always the fastest. -The quicksort derivatives and the hybrid radix sorts are generally the fastest of the lot, yet `drop_merge_sort` seems to offer interesting speedups for `std::deque` despite not being designed to be the fastest on truly shuffled data. Part of the explanation is that it uses `pdq_sort` in a buffer underneath, which might be faster for `std::deque` than truly sorting in-place. +The quicksort derivatives and the hybrid radix sorts are generally the fastest of the lot, yet `drop_merge_sort` seems to offer interesting speedups for `std::deque` despite not being designed to be the fastest on truly shuffled data. Part of the explanation is that it uses `pdq_sort` in a contiguous memory buffer underneath, which might be faster for `std::deque` than sorting completely in-place. -![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/te098uq.png) -![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/aRbP7wY.png) +![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/MlEcGuL.png) +![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/o7sOfMB.png) A few random takeways: * All the algorithms are more or less adaptive, not always for the same patterns. * `ska_sort` beats all the other algorithms on average, and almost systematically beats the other hybrid radix sort, `spread_sort`. * `quick_merge_sort` is slow, but it wasn't specifically designed for random-access iterators. -* libstdc++ `std::sort` doesn't adapt well to some patterns meant to defeat median-of-3 quicksort. +* libstdc++ `std::sort` doesn't adapt well to patterns meant to defeat median-of-3 quicksort. * `verge_sort` adapts well to most patterns (but it was kind of designed to beat this specific benchmark). -* `pdq_sort` is the best unstable sorting algorithm there which doesn't allocate any heap memory. +* `pdq_sort` is the best unstable sorting algorithm here that doesn't allocate any heap memory. * `drop_merge_sort` and `split_sort` were not designed to beat those patterns yet they are surprisingly good on average. ## Stable sorts @@ -57,45 +57,48 @@ These plots highlight a few important things: * `block_sort` and `grail_sort` in this benchmark run with O(1) extra memory, which makes them decent stable sorting algorithms for their category. * Interestingly `stable_adapter(pdq_sort)` is among the best algorithms, despite the overhead of making `pdq_sort` artificially stable. -## Heap sorts +## Slow O(n log n) sorts -I decided to include a dedicated category for heap sorts, because I find this class of algorithms interesting. Frankly, it's difficult to beat a plain `heap_sort` in this category, despite it being otherwise noticeably slower than quicksort derivatives or hybrid radix sorts. Heap sorts are not used a lot on their own, but they are often used to ensure the O(n log n) complexity of introsort-like algorithms, so having good heap sorts is generally desirable. +I decided to include a dedicated category for slow O(n log n) sorts, because I find this class of algorithms interesting. This category contains experimental algorithms, often taken from rather old research papers. `heap_sort` is used as the "fast" algorithm in this category, despite it being consistently the slowest in the previous category. -![Benchmark heap sorts over different patterns for std::vector](https://i.imgur.com/1rY17x4.png) -![Benchmark heap sorts over different patterns for std::deque](https://i.imgur.com/gWe7j6m.png) +![Benchmark speed of slow O(n log n) sorts with increasing size for std::vector](https://i.imgur.com/nSX9n1q.png) +![Benchmark slow O(n log n) sorts over different patterns for std::vector](https://i.imgur.com/z9dR16G.png) +![Benchmark slow O(n log n) sorts over different patterns for std::deque](https://i.imgur.com/Viu13nj.png) The analysis is pretty simple here: -* `heap_sort` is generally the best solution, but it is less adaptive than the other sorts. +* Most of the algorithms in this category are slow, but exhibit a good adaptiveness with most kinds of patterns. It isn't all that surprising since I specifically found them in literature about adaptive sorting. * `poplar_sort` is slower for `std::vector` than for `std::deque`, which makes me suspect a codegen issue somewhere. * As a result `smooth_sort` and `poplar_sort` beat each other depending on the type of the collection to sort. +* Slabsort has an unusual graph: it seems that even for shuffled data it might end up beating `heap_sort` when the collection grows big enough. # Bidirectional iterables Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`][std-list-sort] which are aware of the data structure they are sorting. -![Benchmark speed of sorts with increasing size for std::list](https://i.imgur.com/Z2BDhpz.png) +![Benchmark speed of sorts with increasing size for std::list](https://i.imgur.com/yNQG8kk.png) For elements as small as `double`, there are two clear winners here: `drop_merge_sort` and `out_of_place_adapter(pdq_sort)`. Both have in common the fact that they move a part of the collection (or the whole collection) to a contiguous memory buffer and sort it there using `pdq_sort`. The only difference is that `drop_merge_sort` does that "accidentally" while `out_of_place_adapter` was specifically introduced to sort into a contiguous memory buffer and move back for speed. -![Benchmark sorts over different patterns for std::list](https://i.imgur.com/RcmJ8gL.png) +![Benchmark sorts over different patterns for std::list](https://i.imgur.com/zlHzRLd.png) `out_of_place_adapter(pdq_sort)` was not included in this benchmark, because it adapts to patterns the same way `pdq_sort` does. Comments can be added for these results: -* `std::list::sort` would require elements more expensive to move for node relinking to be faster than move-based algorithms. +* `std::list::sort` would require more expensive to move elements for node relinking to be faster than move-based algorithms. * `drop_merge_sort` adapts well to every pattern and shows that out-of-place sorting really is the best thing here. -* `quick_sort` doesn't scale well with patterns. -* `quick_merge_sort` does an honorable job for an algorithm that doesn't allocate any extra heap memory. +* `quick_sort` and `quick_merge_sort` are good enough contenders when trying to avoid heap memory allocations. +* `mel_sort` is bad. # Forward iterables Even fewer sorters can handle forward iterators. `out_of_place_adapter(pdq_sort)` was not included in the patterns benchmark, because it adapts to patterns the same way `pdq_sort` does. -![Benchmark speed of sorts with increasing size for std::forward_list](https://i.imgur.com/Ly4kbaN.png) -![Benchmark sorts over different patterns for std::forward_list](https://i.imgur.com/bWZRega.png) +![Benchmark speed of sorts with increasing size for std::forward_list](https://i.imgur.com/SMTKhqG.png) +![Benchmark sorts over different patterns for std::forward_list](https://i.imgur.com/XLndRbU.png) The results are roughly the same than with bidirectional iterables: -* [`std::forward_list::sort`][std-forward-list-sort] doesn't scale well unless moves are expensive. * Sorting out-of-place is faster than anything else. -* If no extra heap memory is available, `quick_merge_sort` is the only O(n log n) algorithm that can be used, and does a fine job despite never being excellent. +* [`std::forward_list::sort`][std-forward-list-sort] doesn't scale well unless moves are expensive. +* `quick_sort` and `quick_merge_sort` are good enough contenders when trying to avoid heap memory allocations. +* `mel_sort` is still bad, but becomes a dcent alternative when the input exhibits recognizable patterns. # Sorting under specific constraints @@ -166,7 +169,7 @@ The spikes in the otherwise smooth sorting networks curve when sorting arrays of This benchmark for [measures of presortedness][measures-of-presortedness] 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 presortedness for increasing size for std::vector](https://i.imgur.com/5XniqE1.png) +![Benchmark speed of measures of presortedness for increasing size for std::vector](https://i.imgur.com/5Hxpb37.png) While the graph reasonably shows the relative cost of the different measures of presortedness, there are a few hidden traps: * *Par(X)* seems to beat every other measure, but it is a highly adaptative O(n² log n) algorithm, whose theoretical worst case might be the worst of all measures of presortedness. diff --git a/docs/Changelog.md b/docs/Changelog.md index 672604bc..5e936fda 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -50,9 +50,9 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe This feature is available when the feature-testing macro `__cpp_nontype_template_parameter_auto` is defined. -* The function pointer conversion operators of `sorter_facade` are now `constexpr` when possible. +* [[`sorter_facade`|Sorter facade]] range overloads can now be used in `constexpr` functions. - This feature is made available through the check `__cpp_constexpr >= 201603`. + There is no specific feature macro available to test this, it starts working when `std::begin` and `std::end` are `constexpr`. **Correctness improvements:** * Some handy C++17 type traits such as `std::is_invocable` are manually reimplemented in C++14 mode while they are used as is in C++17 mode if available. It's likely that the C++17 implementation covers more corner cases and is thus more often correct than the manual C++14 implementation. @@ -68,6 +68,10 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe * When available, [`std::ranges::less`][std-ranges-less] and [`std::ranges::greater`][std-ranges-greater] benefit from dedicated support wherever [`std::less<>`][std-less-void] and [`std::greater<>`][std-greater-void] are supported, with equivalent semantics. +* [`utility::iter_swap`][utility-iter-move] can now be used in more `constexpr` functions thanks to [`std::swap`][std-swap] begin `constexpr`. + + The feature-test macro `__cpp_lib_constexpr_algorithms` can be used to check whether `std::swap` is `constexpr`. + ## Other features **cpp-sort** tries to take advantage of more than just standard features when possible by using implementation-specific tweaks to improve the user experience. The following improvements might be available depending on the your standard implementation: @@ -78,9 +82,9 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe **Performance improvements:** * Bit manipulation intrinsics: there are a few places where bit tricks are used to perform a few operations faster. Some of those operations are made faster with bitwise manipulation intrinsics when those are available. -* Assumptions: some algorithms use assumptions in select places to make the compiler generate more efficient code. Whether such assumptions are available depend on the compiler. +* Assumptions: some algorithms use assumptions in select places to make the compiler generate more efficient code. Whether such assumptions are available depends on the compiler. -* When using libstdc++ or libc++, the return type of [`std::mem_fn`][std-mem-fn] is considered ["probably branchless"][branchless-traits] when it wraps a pointer to data member, which can improve the speed of [`pdq_sorter`][pdq-sorter] and everything that relies on it in some scenarios. +* When using libstdc++, libc++ or the Microsoft STL, the return type of [`std::mem_fn`][std-mem-fn] is considered ["probably branchless"][branchless-traits] when it wraps a pointer to data member, which can improve the speed of [`pdq_sorter`][pdq-sorter] and everything that relies on it in some scenarios. [branchless-traits]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits @@ -97,3 +101,4 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe [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-string-view]: https://en.cppreference.com/w/cpp/string/basic_string_view) + [utility-iter-move]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#iter_move-and-iter_swap diff --git a/docs/Home.md b/docs/Home.md index 035189c4..21a60f46 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,4 +1,4 @@ -Welcome to the **cpp-sort 1.9.0** documentation! +Welcome to the **cpp-sort 1.10.0** documentation! You probably read the introduction in the README, so I won't repeat it here. This wiki contains documentation about the library: basic documentation about the many sorting tools and how to use them, documentation about the additional utilities provided by the library and even some detailed tutorials if you ever want to write your own sorters or sorter adapters. This main page explains a few general things that didn't quite fit in other parts of the documentation. diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index aa8280ff..76ff8154 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -1,6 +1,10 @@ -Also known as *measures of disorder*, the *measures of presortedness* are algorithms used to tell how much a sequence is already sorted, or how much disorder there is in it. Some adaptive sorting algorithms are known to take advantage of the order already present in a sequence, and happen to be "optimal" with regard to some measures of presortedness. +Also known as *measures of disorder*, the *measures of presortedness* are algorithms used to tell how much a sequence is already sorted, or how much disorder there is in it. -Measures of presortedness were formally defined by Manilla in *Measures of presortedness and optimal sorting algorithms*. Here is the formal definition as reformulated by La rocca & Cantone in [*NeatSort - A practical adaptive algorithm*](https://arxiv.org/pdf/1407.6183.pdf): +Given a measure of presortedness *M*, a comparison sort is said to be *M*-optimal if it takes a number of comparisons that is within a constant factor of the lower bound. + +## Formal definition + +Measures of presortedness were formally defined by Manilla in *Measures of presortedness and optimal sorting algorithms*. Here is the formal definition as reformulated by La rocca & Cantone in [*NeatSort - A practical adaptive algorithm*][neatsort]: > Given two sequences *X* and *Y* of distinct elements, a measure of disorder *M* is a function that satisfies the following properties: > @@ -12,9 +16,28 @@ Measures of presortedness were formally defined by Manilla in *Measures of preso A few measures of presortedness described in the research papers actually return 1 when *X* is already sorted, thus violating the first property above. We implement these measures in a such way that they return 0 instead, generally by subtracting 1 from the result of the described operation. ---- +### Partial ordering of measures of presortedness + +La rocca & Cantone also define a partial order on measure of presortedness as follows: + +Let *M1* and *M2* be two measures of presortedness. +- *M1* is algorithmically finer than *M2* if and only if any *M1*-optimal algorithm is also *M2*-optimal. +- *M1* and *M2* are algorithmically equivalent (denoted *M1*≡*M2* in the graph below) if and only if *M1* is algorithmically finer than *M2* and *M2* is algorithmically finer than *M1*. + +The graph below shows the partial ordering of several measures of presortedness: +- *Reg* is algorithmically finest measure of presortedness. +- *m₀* is a measure of presortedness that always returns 0. +- *m₀₁* is a measure of presortedness that returns 0 when *X* is sorted and 1 otherwise. + +![Partial ordering of measures of presortedness](https://github.com/Morwenn/cpp-sort/wiki/images/mep-partial-ordering.png) -In **cpp-sort**, measures of presortedness are implemented as instances of some specific function objects; they take an iterable or a pair of iterators and return how much disorder there is in the sequence according to the measure. Just like sorters, measures of presortedness can handle custom comparison and projection functions, and with the same degree of freedom when it comes to how they can be called: +This graph is more complete version of the one in *A framework for adaptive sorting* by O. Petersson and A. Moffat. The *Max* ≡ *Dis* equivalence comes from [*NeatSort - A practical adaptive algorithm*][neatsort] by La rocca & Cantone. The relations of *Mono* are empirically derived [original research][original-research]. + +The measures of presortedness in bold in the graph are available in **cpp-sort**, the others are not. + +## Measures of presortedness in cpp-sort + +In **cpp-sort**, measures of presortedness are implemented as instances of some specific function objects. They take an iterable or a pair of iterators and return how much disorder there is in the sequence according to the measure. Just like sorters, measures of presortedness can handle custom comparison and projection functions, and with the same degree of freedom when it comes to how they can be called: ```cpp using namespace cppsort; @@ -36,13 +59,31 @@ auto inv = cppsort::indirect_adapter{}; auto inv = cppsort::indirect_adapter(cppsort::probe::inv); ``` -Every measure of presortedness lives in the subnamespace `cppsort::probe`. Even though all of them are available in their own header, it is possible to include all of them at once with the following include: +All measures of presortedness live in the subnamespace `cppsort::probe`. Even though all of them are available in their own header, it is possible to include all of them at once with the following include: ```cpp #include ``` -Measures of presortedness are pretty formalized, so the names of the functions in the library are short and correspond to the ones used in the litterature. As per this litterature, we will use the symbols *X* to represent the analyzed sequence, and *n* to represent the size of that sequence. +### `max_for_size` + +All measures of presortedness in the library have the following `static` member function: + +```cpp +template +static constexpr auto max_for_size(Integer n) + -> Integer; +``` + +It takes an integer `n` and returns the maximum value that the measure of presortedness might return for a collection of size `n`. + +*New in version 1.10.0* + +## Available measures of presortedness + +Measures of presortedness are pretty formalized, so the names of the functions in the library are short and correspond to the ones used in the literature. + +In the following descriptions we use *X* to represent the input sequence, and |*X*| to represent the size of that sequence. ### *Dis* @@ -50,12 +91,14 @@ Measures of presortedness are pretty formalized, so the names of the functions i #include ``` -Computes the maximum distance determined by an inversion. Its worst case returns *n* - 1 when *X* is sorted in reverse order. +Computes the maximum distance determined by an inversion. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n² | 1 | Forward | +`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. + *Warning: this algorithm might be noticeably slower when the passed iterable is not random-access.* *Changed in version 1.8.0:* `probe::dis` is now O(n²) instead of accidentally being O(n³) when passed forward or bidirectional iterators. @@ -66,24 +109,28 @@ Computes the maximum distance determined by an inversion. Its worst case returns #include ``` -Computes the number of encroaching lists that can be extracted from *X* minus one (see Skiena's *Encroaching lists as a measure of presortedness*). Its worst case returns *n* / 2 - 1 when the values already extracted from *X* constitute stronger bounds than the values yet to be extracted (for example the sequence {0 9 1 8 2 7 3 6 4 5} will trigger the worst case). +Computes the number of encroaching lists that can be extracted from *X* minus one (see Skiena's *Encroaching lists as a measure of presortedness*). | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: (|*X*| + 1) / 2 - 1 when the values already extracted from *X* constitute stronger bounds than the values yet to be extracted (for example the sequence {0 9 1 8 2 7 3 6 4 5} will trigger the worst case). + ### *Exc* ```cpp #include ``` -Computes the minimum number of exchanges required to sort *X*, which corresponds to *n* minus the number of cycles in the sequence. A cycle corresponds to a number of elements in a sequence that need to be rotated to be in their sorted position; for example, let {2, 4, 0, 6, 3, 1, 5} be a sequence, the cycles are {0, 2} and {1, 3, 4, 5, 6} so *Exc*(*X*) = *n* - 2 = 5. There is always at least one cycle in any sequence, so *Exc* has a worst case of *n* - 1 when every element in *X* is one element away from its sorted position. +Computes the minimum number of exchanges required to sort *X*, which corresponds to |*X*| minus the number of cycles in the sequence. A cycle corresponds to a number of elements in a sequence that need to be rotated to be in their sorted position; for example, let {2, 4, 0, 6, 3, 1, 5} be a sequence, the cycles are {0, 2} and {1, 3, 4, 5, 6} so *Exc*(*X*) = |*X*| - 2 = 5. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: |*X*| - 1 when every element in *X* is one element away from its sorted position. + *Warning: this algorithm might be noticeably slower when the passed iterable is not random-access.* ### *Ham* @@ -92,36 +139,42 @@ Computes the minimum number of exchanges required to sort *X*, which corresponds #include ``` -Computes the number of elements in *X* that are not in their sorted position, which corresponds to the [Hamming distance](https://en.wikipedia.org/wiki/Hamming_distance) between *X* and its sorted permutation. Its worst case returns *n* when every element in *X* is one element away from its sorted position. +Computes the number of elements in *X* that are not in their sorted position, which corresponds to the [Hamming distance][hamming-distance] between *X* and its sorted permutation. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: |*X*| when every element in *X* is one element away from its sorted position. + ### *Inv* ```cpp #include ``` -Computes the number of inversions in *X*, where an inversion corresponds to a pair (a, b) of elements not in order. For example, the sequence {2, 1, 3, 0} has 4 inversions: (2, 1), (2, 0), (1, 0) and (3, 0). Its worst case returns *n* * (*n* - 1) / 2 when *X* is sorted in reverse order. +Computes the number of inversions in *X*, where an inversion corresponds to a pair (a, b) of elements not in order. For example, the sequence {2, 1, 3, 0} has 4 inversions: (2, 1), (2, 0), (1, 0) and (3, 0). | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: |*X*| * (|*X*| - 1) / 2 when *X* is sorted in reverse order. + ### *Max* ```cpp #include ``` -Computes the maximum distance an element in *X* must travel to find its sorted position. Its worst case returns *n* - 1 when *X* is sorted in reverse order. +Computes the maximum distance an element in *X* must travel to find its sorted position. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. + *Warning: this algorithm might be noticeably slower when the passed iterable is not random-access.* ### *Mono* @@ -130,16 +183,18 @@ Computes the maximum distance an element in *X* must travel to find its sorted p #include ``` -Computes the number of non-increasing and non-decreasing runs in *X* minus one. Its worst case returns *n* / 2 - 1 when *X* is a sequence of elements that are alternatively greater then lesser than their previous neighbour. +Computes the number of non-increasing and non-decreasing runs in *X* minus one. -The measure of presortedness is slightly different from its original description in *Sort Rase* by Zhang, Meng and Liang: +The measure of presortedness is slightly different from its original description in [*Sort Race*][sort-race] by H. Zhang, B. Meng and Y. Liang: * It subtracts 1 from the number of runs, thus returning 0 when *X* is sorted -* It explicitly handles non-increasing and non-decreasing runs, not only the strictly ascending or descending ones +* It explicitly handles non-increasing and non-decreasing runs, not only the strictly increasing or decreasing ones | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n | 1 | Forward | +`max_for_size`: (|*X*| + 1) / 2 - 1 when *X* is a sequence of elements that are alternatively greater then lesser than their previous neighbour. + *New in version 1.1.0* ### *Osc* @@ -148,12 +203,14 @@ The measure of presortedness is slightly different from its original description #include ``` -Computes the *Oscillation* measure described by Levcopoulos and Petersson in *Adaptive Heapsort*. Its worst case returns (*n* * (*n* - 2) - 1) / 2 when the values in *X* are strongly oscillating. +Computes the *Oscillation* measure described by Levcopoulos and Petersson in *Adaptive Heapsort*. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n² | 1 | Forward | +`max_for_size`: (|*X*| * (|*X*| - 2) - 1) / 2 when the values in *X* are strongly oscillating. + ### *Par* ```cpp @@ -166,34 +223,63 @@ Computes the *Par* measure described by Estivill-Castro and Wood in *A New Measu The following definition is also given to determine whether a sequence is *p*-sorted: -> *X* is *p*-sorted iff for all *i*, *j* ∈ {1, 2, ..., *n*}, *i* - *j* > *p* implies *Xj* ≤ *Xi*. - -Its worst case returns (*n* - 1) when the last element of *X* is smaller than the first one. +> *X* is *p*-sorted iff for all *i*, *j* ∈ {1, 2, ..., |*X*|}, *i* - *j* > *p* implies *Xj* ≤ *Xi*. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n² log n | 1 | Random-access | +`max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. + ### *Rem* ```cpp #include ``` -Computes the minimum number of elements that must be removed from *X* to obtain a sorted subsequence, which corresponds to *n* minus the size of the [longest increasing subsequence](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) of *X* (strictly speaking, the longest *non-decreasing* subsequence is used). Its worst case returns *n* - 1 when *X* is sorted in reverse order. +Computes the minimum number of elements that must be removed from *X* to obtain a sorted subsequence, which corresponds to |*X*| minus the size of the [longest non-decreasing subsequence][longest-increasing-subsequence] of *X*. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. + ### *Runs* ```cpp #include ``` -Computes the number of non-decreasing runs in *X* minus one. Its worst case returns *n* - 1 when *X* is sorted in reverse order. +Computes the number of non-decreasing runs in *X* minus one. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n | 1 | Forward | + +`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. + +### *SUS* + +```cpp +#include +``` + +Computes the minimum number of non-decreasing subsequences (of possibly not adjacent elements) into which *X* can be partitioned. It happens to correspond to the size of the [longest decreasing subsequence][longest-increasing-subsequence] of *X*. + +*SUS* stands for *Shuffled Up-Sequences* and was introduced in *Sorting Shuffled Monotone Sequences* by Levcopoulos and Petersson. + +| Complexity | Memory | Iterators | +| ----------- | ----------- | ------------- | +| n log n | n | Forward | + +`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. + +*New in version 1.10.0* + + + [hamming-distance]: https://en.wikipedia.org/wiki/Hamming_distance + [longest-increasing-subsequence]: https://en.wikipedia.org/wiki/Longest_increasing_subsequence + [neatsort]: https://arxiv.org/pdf/1407.6183.pdf + [original-research]: https://github.com/Morwenn/cpp-sort/wiki/Original-research#partial-ordering-of-mono + [sort-race]: https://arxiv.org/ftp/arxiv/papers/1609/1609.04471.pdf diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 3f72ce17..c73bb0c6 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -6,7 +6,7 @@ The following utilities are available in the directory `cpp-sort/utility` and li #include ``` -`adapter_storage` is a wrapper type meant to be used to store a *sorter* in the internals of *sorter adapter* which adapts a single *sorter*. One of its goal is to remain an empty type when possible in order to play nice with the parts of the libraries that allow to cast empty sorters to function pointers. It provides three different operations: +`adapter_storage` is a wrapper type meant to be used to store a *sorter* in the internals of [[*sorter adapter*|Sorter adapters]] which adapts a single [[*sorter*|Sorters]]. One of its goal is to remain an empty type when possible in order to play nice with the parts of the libraries that allow to cast empty sorters to function pointers. It provides three different operations: * *Construction:* it is constructed with a copy of the wrapped sorter which it stores in its internals. If `Sorter` is empty and default-constructible, then `adapter_storage` is also empty and default-constructible. * *Sorter access:* a `get()` member function returns a reference to the wrapped sorter with `const` and reference qualifications matching those of the `adapter_storage` instance. If `Sorter` is empty and default-constructible, then `get()` returns a default-constructed instance of `Sorter` instead. * *Invocation:* it has a `const`-qualified `operator()` which forwards its parameter to the corresponding operator in the wrapped `Sorter` (or in a default-constructed instance of `Sorter`) and returns its result. @@ -26,17 +26,17 @@ The usual way to use it when implementing a *sorter adapter* is to make said ada #include ``` -When given a *Callable* that satisfies both the comparison and projection concepts, every sorter in **cpp-sort** considers that it is a comparison function (unless it is a projection-only sorter, in which case the function is considered to be a projection). To make such ambiguous callables usable as projections, the utility function `as_projection` is provided, which wraps a function and exposes only its unary overload. A similar `as_comparison` function is also provided in case one needs to explicitly expose the binary overload of a function. +When given a [*Callable*][callable] that satisfies both the comparison and projection concepts, every sorter in **cpp-sort** considers that it is a comparison function (unless it is a projection-only sorter, in which case the function is considered to be a projection). To make such ambiguous callables usable as projections, the utility function `as_projection` is provided, which wraps a function and exposes only its unary overload. A similar `as_comparison` function is also provided in case one needs to explicitly expose the binary overload of a function. The result of `as_projection` also inherits from `projection_base`, which makes it usable to [[compose projections|Chainable projections]] with `operator|`. ```cpp template -auto as_projection(Function&& func) +constexpr auto as_projection(Function&& func) -> /* implementation-defined */; template -auto as_comparison(Function&& func) +constexpr auto as_comparison(Function&& func) -> /* implementation-defined */; ``` @@ -50,9 +50,9 @@ auto as_comparison(Function&& func) #include ``` -`as_function` is an utility function borrowed from Eric Niebler's [Ranges v3](https://github.com/ericniebler/range-v3) library. This function takes a parameter and does what it can to return an object callable with the usual function call syntax. It is notably useful to transform a pointer to member data into a function taking an instance of the class and returning the member. +`as_function` is an utility function borrowed from Eric Niebler's [Range-v3][range-v3] library. This function takes a parameter and does what it can to return an object callable with the usual function call syntax. It is notably useful to transform a pointer to member data into a function taking an instance of the class and returning the member. -To be more specific, `as_function` returns the passed object as is if it is already callable with the usual function call syntax, and uses [`std::mem_fn`](https://en.cppreference.com/w/cpp/utility/functional/mem_fn) to wrap the passed object otherwise. It is worth noting that when the original object is returned, its potential to be called in a `constexpr` context remains the same, which makes `as_function` superior to `std::invoke` in this regard (prior to C++20). +To be more specific, `as_function` returns the passed object as is if it is already callable with the usual function call syntax, and uses [`std::mem_fn`][std-mem-fn] to wrap the passed object otherwise. It is worth noting that when the original object is returned, its potential to be called in a `constexpr` context remains the same, which makes `as_function` superior to [`std::invoke`][std-invoke] in this regard (prior to C++20). ```cpp struct wrapper { int foo; }; @@ -67,7 +67,7 @@ auto&& func = utility::as_function(&wrapper::foo); #include ``` -Some of the library's algorithms ([`pdq_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#pdq_sorter) and everything that relies on it, which means many things since it's the most common fallback for other algorithms) may use a different logic depending on whether the use comparison and projection functions are branchful and branchless. Unfortunately, it isn't something that can be deterministically determined; in order to provide the information, the following traits are available (they always inherit from either `std::true_type` or `std::false_type`): +Some of the library's algorithms ([`pdq_sorter`][pdq-sorter] and everything that relies on it, which means many things since it's the most common fallback for other algorithms) may use a different logic depending on whether the use comparison and projection functions are branchful and branchless. Unfortunately, it isn't something that can be deterministically determined; in order to provide the information, the following traits are available (they always inherit from either `std::true_type` or `std::false_type`): ```cpp template @@ -79,8 +79,8 @@ constexpr bool is_probably_branchless_comparison_v ``` This trait tells whether the comparison function `Compare` is likely to generate branchless code when comparing two instances of `T`. By default it considers that the following comparison functions are likely to be branchless: -* `std::less<>`, `std::ranges::less` and `std::less` for any `T` which satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic) -* `std::greater<>`, `std::ranges::greater` and `std::greater` for any `T` which satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic) +* [`std::less<>`][std-less-void], [`std::ranges::less`][std-ranges-less] and [`std::less`][std-less] for any `T` satisfying [`std::is_arithmetic`][std-is-arithmetic] +* [`std::greater<>`][std-greater-void], [`std::ranges::greater`][std-ranges-greater] and [`std::greater`][std-greater] for any `T` satisfying [`std::is_arithmetic`][std-is-arithmetic] ```cpp template @@ -93,14 +93,14 @@ constexpr bool is_probably_branchless_projection_v This trait tells whether the projection function `Projection` is likely to generate branchless code when called with an instance of `T`. By default it considers that the following projection functions are likely to be branchless: * `cppsort::utility::identity` for any type -* `std::identity` for any type (when available) -* Any type that satisfies [`std::is_member_function_pointer`](https://en.cppreference.com/w/cpp/types/is_member_function_pointer) provided it is called with an instance of the appropriate class +* [`std::identity`][std-identity] for any type (when available) +* Any type that satisfies [`std::is_member_function_pointer`][std-is-member-function-pointer] provided it is called with an instance of the appropriate class These traits can be specialized for user-defined types. If one of the traits is specialized to consider that a user-defined type is likely to be branchless with a comparison/projection function, cv-qualified and reference-qualified versions of the same user-defined type will also be considered to produce branchless code when compared/projected with the same function. -*Changed in version 1.9.0:* conditional support for [`std::ranges::less`](https://en.cppreference.com/w/cpp/utility/functional/ranges/less) and [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). +*Changed in version 1.9.0:* conditional support for [`std::ranges::less`][std-ranges-less] and [`std::ranges::greater`][std-ranges-greater]. -*Changed in version 1.9.0:* conditional support for [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity). +*Changed in version 1.9.0:* conditional support for [`std::identity`][std-identity]. ### Buffer providers @@ -115,14 +115,14 @@ template struct fixed_buffer; ``` -This buffer provider allocates `N` elements on the stack. It uses [`std::array`](https://en.cppreference.com/w/cpp/container/array) behind the scenes, so it also allows buffers of size 0. The runtime size passed to the buffer at construction is discarded. +This buffer provider allocates `N` elements on the stack. It uses [`std::array`][std-array] behind the scenes, so it also allows buffers of size 0. The runtime size passed to the buffer at construction is discarded. ```cpp template struct dynamic_buffer; ``` -This buffer provider allocates on the heap a number of elements depending on a given *size policy* (a class whose `operator()` takes the size of the collection and returns another size). You can use the function objects from `utility/functional.h` as basic size policies. The buffer construction may throw an instance of `std::bad_alloc` if it fails to allocate the required memory. +This buffer provider allocates on the heap a number of elements depending on a given *size policy* (a class whose `operator()` takes the size of the collection and returns another size). You can use the function objects from `utility/functional.h` as basic size policies. The buffer construction may throw an instance of [`std::bad_alloc`][std-bad-alloc] if it fails to allocate the required memory. ### Miscellaneous function objects @@ -148,7 +148,7 @@ struct identity: }; ``` -It is equivalent to the C++20 [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity). Wherever the documentation mentions special handling of `utility::identity`, the same support is provided for `std::identity` when it is available. +It is equivalent to the C++20 [`std::identity`][std-identity]. Wherever the documentation mentions special handling of `utility::identity`, the same support is provided for `std::identity` when it is available. This header also provides additional function objects implementing basic unary operations. These functions objects are designed to be used as *size policies* with `dynamic_buffer` and similar classes. The following function objects are available: * `half`: returns the passed value divided by 2. @@ -174,7 +174,7 @@ struct function_constant }; ``` -This utility is modeled after [`std::integral_constant`](https://en.cppreference.com/w/cpp/types/integral_constant), but is different in that it takes its parameter as `template`, and `operator()` calls the wrapped value instead of returning it. The goal is to store function pointers and pointer to members "for free": they are only "stored" as a template parameter, which allows `function_constant` to be an empty class. This has two main advantages: `function_constant` can benefit from *Empty Base Class Optimization* since it weights virtually nothing, and it won't need to be pushed on the stack when passed to a function, while the wrapped pointer would have been if passed unwrapped. Unless you are micro-optimizing some specific piece of code, you shouldn't need this class. +This utility is modeled after [`std::integral_constant`][std-integral-constant], but is different in that it takes its parameter as `template`, and `operator()` calls the wrapped value instead of returning it. The goal is to store function pointers and pointer to members "for free": they are only "stored" as a template parameter, which allows `function_constant` to be an empty class. This has two main advantages: `function_constant` can benefit from [*Empty Base Optimization*][ebo] since it weights virtually nothing, and it won't need to be pushed on the stack when passed to a function, while the wrapped pointer would have been if passed unwrapped. Unless you are micro-optimizing some specific piece of code, you shouldn't need this class. `is_probably_branchless_comparison` and `is_probably_branchless_projection` will correspond to `std::true_type` if the wrapped `Function` also gives `std::true_type`. Moreover, you can even specialize these traits for specific `function_constant` instanciations if you need even more performance. @@ -190,20 +190,24 @@ This utility is modeled after [`std::integral_constant`](https://en.cppreference #include ``` -The functions `iter_move` and `iter_swap` are equivalent to the same functions as proposed by [P0022](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0022r1.html): utility functions intended to be used with ADL to handle proxy iterators among other things. An algorithm can use them instead of `std::move` and possibly ADL-found `swap` to handle tricky classes such as `std::vector`. +The functions `iter_move` and `iter_swap` are equivalent to the same functions as proposed by [P0022][p0022]: utility functions intended to be used with ADL to handle proxy iterators among other things. An algorithm can use them instead of `std::move` and possibly ADL-found `swap` to handle tricky classes such as `std::vector`. The default implementation of `iter_move` simply move-returns the dereferenced iterator. `iter_swap` is a bit more tricky: if the iterators to be swapped have a custom `iter_move`, then `iter_swap` will use it, otherwise it will call an ADL-found `swap` or `std::swap` on the dereferenced iterators. ```cpp template -auto iter_move(Iterator it) +constexpr auto iter_move(Iterator it) -> decltype(std::move(*it)); template -auto iter_swap(Iterator lhs, Iterator rhs) +constexpr auto iter_swap(Iterator lhs, Iterator rhs) -> void; ``` +*NOTE:* while both overloads are marked as `constexpr`, the generic version of `iter_swap` might use `std::swap`, which is not `constexpr` before C++20. + +*Changed in version 1.10.0:* generic `iter_move` and `iter_swap` overloads are now marked as `constexpr`. + ### `make_integer_range` ```cpp @@ -212,7 +216,7 @@ auto iter_swap(Iterator lhs, Iterator rhs) ***WARNING:** `make_integer_range` and `make_index_range` are deprecated in version 1.8.0 and removed in version 2.0.0.* -The class template `make_integer_range` can be used wherever an [`std::integer_sequence`](https://en.cppreference.com/w/cpp/utility/integer_sequence) can be used. An `integer_range` takes a type template parameter that shall be an integer type, then three integer template parameters which correspond to the beginning of the range, the end of the range and the « step ». +The class template `make_integer_range` can be used wherever an [`std::integer_sequence`][std-integer-sequence] can be used. An `integer_range` takes a type template parameter that shall be an integer type, then three integer template parameters which correspond to the beginning of the range, the end of the range and the « step ». ```cpp template< @@ -241,7 +245,7 @@ using make_index_range = make_integer_range; #include ``` -`size` is a function that can be used to get the size of an iterable. It is equivalent to the C++17 function [`std::size`](https://en.cppreference.com/w/cpp/iterator/size) but has an additional tweak so that, if the iterable is not a fixed-size C array and doesn't have a `size` method, it calls `std::distance(std::begin(iter), std::end(iter))` on the iterable. Therefore, this function can also be used for `std::forward_list` as well as some implementations of ranges. +`size` is a function that can be used to get the size of an iterable. It is equivalent to the C++17 function [`std::size`][std-size] but has an additional tweak so that, if the iterable is not a fixed-size C array and doesn't have a `size` method, it calls `std::distance(std::begin(iter), std::end(iter))` on the iterable. Therefore, this function can also be used for `std::forward_list` as well as some implementations of ranges. ### `static_const` @@ -249,7 +253,9 @@ using make_index_range = make_integer_range; #include ``` -`static_const` is a tiny utility used to instantiate function objects (for example sorters) and expose a single global instance to users of the library while avoiding ODR problems. It is taken straight from [Ranges v3](https://github.com/ericniebler/range-v3). The general pattern to instantiate function objects is as follows: +***WARNING:** `utility::static_const` is removed in version 2.0.0, use [`inline` variables][inline-variables] instead.* + +`static_const` is a tiny utility used to instantiate function objects (for example sorters) and expose a single global instance to users of the library while avoiding ODR problems. It is taken straight from [Range-v3][range-v3]. The general pattern to instantiate function objects is as follows: ```cpp namespace @@ -259,6 +265,29 @@ namespace } ``` -You can read more about this instantiation pattern in [an article](https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) by Eric Niebler. - -*Warning: this header does not exist anymore in the C++17 branch; use [`inline` variables](https://en.cppreference.com/w/cpp/language/inline) instead.* +You can read more about this instantiation pattern in [this article][eric-niebler-static-const] by Eric Niebler. + + + [callable]: https://en.cppreference.com/w/cpp/named_req/Callable + [ebo]: https://en.cppreference.com/w/cpp/language/ebo + [eric-niebler-static-const]: https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ + [inline-variables]: https://en.cppreference.com/w/cpp/language/inline + [p0022]: https://wg21.link/P0022 + [pdq-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#pdq_sorter + [range-v3]: https://github.com/ericniebler/range-v3 + [std-array]: https://en.cppreference.com/w/cpp/container/array + [std-bad-alloc]: https://en.cppreference.com/w/cpp/memory/new/bad_alloc + [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 + [std-is-member-function-pointer]: https://en.cppreference.com/w/cpp/types/is_member_function_pointer + [std-less]: https://en.cppreference.com/w/cpp/utility/functional/less + [std-less-void]: https://en.cppreference.com/w/cpp/utility/functional/less_void + [std-mem-fn]: https://en.cppreference.com/w/cpp/utility/functional/mem_fn + [std-ranges-greater]: https://en.cppreference.com/w/cpp/utility/functional/ranges/greater + [std-ranges-less]: https://en.cppreference.com/w/cpp/utility/functional/ranges/less + [std-size]: https://en.cppreference.com/w/cpp/iterator/size diff --git a/docs/Original-research.md b/docs/Original-research.md index 4274439d..ac5e58a0 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -8,7 +8,7 @@ One of the main observations which naturally occured as long as I was putting to * Algorithms that work on random-access iterators can run in O(n log n) time with O(1) extra memory, and can even be stable with such guarantees (block sort being the best example). * Unstable algorithms that work on bidirectional iterators can run in O(n log n) time with O(1) extra memory: QuickMergesort [can be implemented][https://github.com/Morwenn/quick_merge_sort] with a bottom-up mergesort and a raw median-of-medians algorithms (instead of the introselect mutual recursion). * Stable algorithms that work on bidirectional iterators can run in O(n log n) time with O(n) extra memory (mergesort), or in O(n log² n) time with O(1) extra memory (mergesort with in-place merge). -* Stable algorithms that work on forward iterators can get down to the same time and memory complexities than the the ones working on bidirectional iterators: mergesort works just as well. +* Stable algorithms that work on forward iterators can get down to the same time and memory complexities than the ones working on bidirectional iterators: mergesort works just as well. * Unstable algorithms that work on forward iterators can run in O(n log² n) time and O(1) space, QuickMergesort being once again the prime example of such an algorithm. * Taking advantage of the list data structure allows for sorting algorithms running in O(n log n) time with O(1) extra memory, be it for stable sorting (mergesort) or unstable sorting (melsort), but those techniques can't be generically retrofitted to generically work with bidirectional iterators @@ -95,9 +95,9 @@ While trying to reimplement size-optimal sorting networks as described by [*Find ### Sorting network for 29 inputs -The following sorting network for 29 inputs has 165 compare-exchange-units, which is one less that the most size-optimal 29-input sorting networks that I could find in the litterature. Here is how I generated it: first it sorts the first 16 inputs and the last 13 inputs independently. Then it merges the two sorted subarrays using a size 32 Batcher odd-even merge network (the version that does not need the inputs to be interleaved), where all compare-exchange units working on indexes greater than 28 have been dropped. Dropping comparators in such a way is ok: consider that the values at the indexes [29, 32) are greater than every other value in the array to sort, and it will become intuitive that dropping them generates a correct merging network of a smaller size. +The following sorting network for 29 inputs has 165 compare-exchange-units, which is one less that the most size-optimal 29-input sorting networks that I could find in the literature. Here is how I generated it: first it sorts the first 16 inputs and the last 13 inputs independently. Then it merges the two sorted subarrays using a size 32 Batcher odd-even merge network (the version that does not need the inputs to be interleaved), where all compare-exchange units working on indexes greater than 28 have been dropped. Dropping comparators in such a way is ok: consider that the values at the indexes [29, 32) are greater than every other value in the array to sort, and it will become intuitive that dropping them generates a correct merging network of a smaller size. -That said, even though I have been unable to find a 29-input sorting network with as few compare-exchange units as 165 in the litterature, I can't claim that I found the technique used to generate it: the unclassified 1971 paper [*A Generalization of the Divide-Sort-Merge Strategy for Sorting Networks*][divide-sort-merge-strategy] by David C. Van Voorhis already describes the as follows: +That said, even though I have been unable to find a 29-input sorting network with as few compare-exchange units as 165 in the literature, I can't claim that I found the technique used to generate it: the unclassified 1971 paper [*A Generalization of the Divide-Sort-Merge Strategy for Sorting Networks*][divide-sort-merge-strategy] by David C. Van Voorhis already describes the as follows: > The improved 26-,27-,28-, and 34-sorters all use two initial sort units, one of them the particularly efficient 16-sorter designed by M. W. Green, followed by Batcher's [2,2] merge network. @@ -172,7 +172,7 @@ The mountain sort is a new indirect sorting algorithm designed to perform a mini Best Average Worst Memory Stable n log n n log n n log n n No -However, **cpp-sort** implements it [as a sorter adapter][indirect_adapter] so you can actually choose the sorting algorithm that will be used to sort the iterators, making it possible to use a stable sorting algorithm instead. Note that the memory footprint of the algorithm is negligible when the size of the elements to sort is big since only iterators and booleans are stored. It makes mountain sort the ideal sorting algorithm when the objects to sort are huge and the comparisons are cheap (remember: you may want to sort fridges by price, or mountains by height). You can find a standalone implementation of mountain sort in [the dedicated repository][mountain_sort]. +However, **cpp-sort** implements it [as a sorter adapter][indirect-adapter] so you can actually choose the sorting algorithm that will be used to sort the iterators, making it possible to use a stable sorting algorithm instead. Note that the memory footprint of the algorithm is negligible when the size of the elements to sort is big since only iterators and booleans are stored. It makes mountain sort the ideal sorting algorithm when the objects to sort are huge and the comparisons are cheap (remember: you may want to sort fridges by price, or mountains by height). You can find a standalone implementation of mountain sort in [the dedicated repository][mountain_sort]. ### Improvements to poplar sort & poplar heap @@ -186,16 +186,42 @@ I borrowed some ideas from Edelkamp and Weiß QuickXsort and QuickMergesort algo Somehow Edelkamp and Weiß eventually [published a paper][quick-merge-sort-arxiv] afew years later decribing the same flavour of QuickMergesort with properly computed algorithmic complexities. I have a [standalone implementation][quick-merge-sort] of `quick_merge_sort` in another repository, albeit currently lacking a proper explanation of how it works. It has the time and space complexity mentioned earlier, as opposed to the **cpp-sort** version of the algorithm where I chose to have theoretically worse algorithms from a complexity point of view, but that are nonetheless generally faster in practice. +### Partial ordering of *Mono* + +The measure of presortedness *Mono* is described in [*Sort Race*][sort-race] by H. Zhang, B. Meng and Y. Liang. They describe it as follows: + +> Intuitively, if *Mono*(*X*) = *k*, then *X* is the concatenation of *k* monotonic lists (either sorted or reversely sorted). + +It counts the number of ascending or descending runs in *X*. Technically this definition in the paper makes it return 1 when the *X* is sorted, which goes against the original definition of a measure of presortedness by Manilla, which starts with the following condition: + +> If *X* is sorted, then *M*(*X*) = 0 + +Therefore we redefine *Mono*(*X*) as the number of non-increasing and non-decreasing consecutive runs of adjacent elements that need to be removed from *X* to make it sorted. +- ***Mono* ⊇ *Runs***: this relation is already mentioned in *Sort Race* and rather intuitive: since *Mono* detects both non-increasing and non-decreasing runs, it is as least as good as *Runs* that only detects non-decreasing runs. +- ***SMS* ⊇ *Mono***: this one seems intuitive too: *SMS* which removes runs of non-adjacent elements should be at least as good as *Mono* which only removes runs of adjacent elements. +- ***Enc* ⊇ *Mono***: when making encroaching lists, *Enc* is guaranteed to not create no more than one such new list per non-increasing or non-decreasing runs, so the result will be at most as big as that of *Mono*. However *Enc* can also find presortedness in patterns such as {5, 6, 4, 7, 3, 8, 2, 9, 1, 10} where *Mono* will find maximum disorder. Therefore *Enc* is strictly better than *Mono*. + +The following relations can be transitively deduced from the results presented in *A framework for adaptive sorting*: +- ***Mono* ⊋ *Exc***: we know that *SMS* ⊇ *Mono* and *SMS* ⊋ *Exc* +- ***Mono* ⊋ *Inv***: we know that *SMS* ⊇ *Mono* and *SMS* ⊋ *Inv* +- ***Hist* ⊋ *Mono***: we know that *Mono* ⊇ *Runs* and *Hist* ⊋ *Runs* + +The following relations have yet to be analyzed: +- ***Mono* ⊇ *Max*** +- ***Mono* ≡ *SUS*** +- ***Osc* ⊇ *Mono*** + [better-sorting-networks]: https://etd.ohiolink.edu/!etd.send_file?accession=kent1239814529 [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort [divide-sort-merge-strategy]: http://www.dtic.mil/dtic/tr/fulltext/u2/737270.pdf [exact-sort]: https://www.geocities.ws/p356spt/ - [indirect_adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#indirect_adapter + [indirect-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#indirect_adapter [morwenn-gist]: https://gist.github.com/Morwenn [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 [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 [vergesort]: https://github.com/Morwenn/vergesort diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index 70e0ca2b..c6f9334a 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -1,4 +1,4 @@ -To make a decent full-fledged sorter, implementers have to implement a variety of `operator()` overloads with a rather high redundancy factor. To make the task simpler, **cpp-sort** provides a wrapper class which generates most of the boilerplate for the required operations in the simplest cases. To benefit from it, one needs to create a *sorter implementation* and to wrap it into `sorter_facade`: +To write a full-fledged sorter, implementers have to implement a variety of `operator()` overloads with a rather high redundancy factor. To make the task simpler, **cpp-sort** provides a wrapper class which generates most of the boilerplate for the required operations in the simplest cases. To benefit from it, one needs to create a *sorter implementation* and to wrap it into `sorter_facade`: ```cpp struct frob_sorter_impl @@ -43,38 +43,40 @@ template constexpr operator Ret(*)(Iterator, Iterator, Args...)() const; ``` -Note that the function pointer conversion syntax above is made up, but it allows to clearly highlight what it does while hiding the ugly `typedef`s needed for the syntax to be valid. In these signatures, `Ret` is an [`std::result_of_t`](https://en.cppreference.com/w/cpp/types/result_of) of the parameters (well, it is what you would expect it to be). The actual implementation is more verbose and redundant, but it allows to transform a sorter into a function pointer corresponding to any valid overload of `operator()`. +Note that the function pointer conversion syntax above is made up, but it allows to clearly highlight what it does while hiding the `typedef`s needed for the syntax to be valid. In these signatures, `Ret` is the [`std::result_of_t`][std-result-of] of the sorter called with the parameters. The actual implementation is more verbose and redundant, but it allows to transform a sorter into a function pointer corresponding to any valid overload of `operator()`. -Since C++17, these function pointer conversion operators are also `constexpr`. +***WARNING:** conversion to function pointers does not work with MSVC (issue #185).* *Changed in version 1.5.0:* these conversion operators exists if and only if the wrapped *sorter implementation* is empty and default-constructible. +*Changed in version 1.10.0:* the conversion operators are always `constexpr` (it used to be a C++17 feature). + ### `operator()` for pairs of iterators `sorter_facade` provides the following overloads of `operator()` to handle pairs of iterators: ```cpp template -auto operator()(Iterator first, Iterator last) const +constexpr auto operator()(Iterator first, Iterator last) const -> /* implementation-defined */; template -auto operator()(Iterator first, Iterator last, Compare compare) const +constexpr auto operator()(Iterator first, Iterator last, Compare compare) const -> /* implementation-defined */; template -auto operator()(Iterator first, Iterator last, Projection projection) const +constexpr auto operator()(Iterator first, Iterator last, Projection projection) const -> /* implementation-defined */; template -auto operator()(Iterator first, Iterator last, - Compare compare, Projection projection) const +constexpr auto operator()(Iterator first, Iterator last, + Compare compare, Projection projection) const -> /* implementation-defined */; ``` -These overloads will generally forward the parameters to the corresponding `operator()` in the wrapped *sorter implementation*. It does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in the *sorter implementation* and to complete the call with instances of [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and/or [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. +These overloads will generally forward the parameters to the corresponding `operator()` in the wrapped *sorter implementation*. It does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in the *sorter implementation* and to complete the call with instances of [`std::less<>`][std-less-void] and/or [`utility::identity`][utility-identity] when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. -Provided you have a sorting function with a standard iterator interface, creating the corresponding sorter becomes trivial thanks to `sorter_facade`. For instance, here is a simple sorter wrapping a [`selection_sort`](https://en.wikipedia.org/wiki/Selection_sort): +Provided you have a sorting function with a standard iterator interface, creating the corresponding sorter becomes trivial thanks to `sorter_facade`. For instance, here is a simple sorter wrapping a [`selection_sort`][selection-sort]: ```cpp struct selection_sorter_impl @@ -97,32 +99,38 @@ struct selection_sorter: {}; ``` +*Changed in version 1.10.0:* those overloads are now `constexpr`. + ### `operator()` for ranges `sorter_facade` provides the following overloads of `operator()` to handle ranges: ```cpp template -auto operator()(Iterable&& iterable) const +constexpr auto operator()(Iterable&& iterable) const -> /* implementation-defined */; template -auto operator()(Iterable&& iterable, Compare compare) const +constexpr auto operator()(Iterable&& iterable, Compare compare) const -> /* implementation-defined */; template -auto operator()(Iterable&& iterable, Projection projection) const +constexpr auto operator()(Iterable&& iterable, Projection projection) const -> /* implementation-defined */; template -auto operator()(Iterable&& iterable, Compare compare, Projection projection) const +constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const -> /* implementation-defined */; ``` -These overloads will generally forward the parameters to the corresponding `operator()` overloads in the wrapped *sorter implementation* if they exist, or try to call an equivalent `operator()` taking a pair of iterators in the wrapped sorter by using `utility::begin` and `utility::end` on the iterable to sort. It also does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in `sorter` and to complete the call with instances of [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and/or [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. +These overloads will generally forward the parameters to the corresponding `operator()` overloads in the wrapped *sorter implementation* if they exist, or try to call an equivalent `operator()` taking a pair of iterators in the wrapped sorter by using `utility::begin` and `utility::end` on the iterable to sort. It also does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in `sorter` and to complete the call with instances of [`std::less<>`][std-less-void] and/or [`utility::identity`][utility-identity] when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. It will always call the most suitable iterable `operator()` overload in the wrapped *sorter implementation* if there is one, and dispatch the call to an overload taking a pair of iterators when it cannot do otherwise. +*NOTE:* range overloads are marked as `constexpr` but rely on [`std::begin`][std-begin] and [`std::end`][std-end], which means that they can't actually be used in a `constexpr` context before C++17 (except for arrays). + +*Changed in version 1.10.0:* those overloads are now `constexpr`. + ### Projection support for comparison-only sorters Some *sorter implementations* are able to handle custom comparison functions but don't have any dedicated support for projections. If such an implementation is wrapped by `sorter_facade` and is given a projection function, `sorter_facade` will bake the projection into the comparison function and give the result to the *sorter implementation* as a comparison function. Basically it means that a *sorter implementation* with a single `operator()` taking a pair of iterators and a comparison function can take any iterable, pair of iterators, comparison and/or projection function once it wrapped into `sorter_facade`. @@ -169,12 +177,24 @@ auto operator()(Iterator first, Iterator last, -> /* implementation-defined */; ``` -When [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity) is available, special overloads are provided with the same behaviour as the `utility::identity` ones. +When [`std::identity`][std-identity] is available, special overloads are provided with the same behaviour as the `utility::identity` ones. -When [`std::ranges::less`](https://en.cppreference.com/w/cpp/utility/functional/ranges/less) is available, special overloads are provided with a behaviour similar to that of the `std::less<>` ones. +When [`std::ranges::less`][std-ranges-less] is available, special overloads are provided with a behaviour similar to that of the `std::less<>` ones. While it does not appear in this documentation, `sorter_facade` actually relies on an extensive amount of SFINAE tricks to ensure that only the `operator()` overloads that are needed and viable are generated. For example, the magic `std::less<>` overloads won't be generated if the wrapped *sorter implementation* already accepts a comparison function. *Changed in version 1.9.0:* when `std::identity` is available, special overloads are provided. *Changed in version 1.9.0:* when `std::ranges::less` is available, special overloads are provided. + +*Changed in version 1.10.0:* those overloads are now `constexpr`. + + + [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 + [std-result-of]: https://en.cppreference.com/w/cpp/types/result_of + [utility-identity]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects diff --git a/docs/Sorters.md b/docs/Sorters.md index 7bb3e92f..a9c3bfdf 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -37,6 +37,20 @@ struct block_sorter; Whether this sorter works with types that are not default-constructible depends on the memory allocation strategy of the buffer provider. The default specialization does not work with such types. +### `cartesian_tree_sorter` + +```cpp +#include +``` + +Implements a [Cartesian tree sort][cartesian-tree-sort], a rather slow but highly adaptive algorithm described by C. Levcopoulos and O. Petersson in *Heapsort - Adapted for Presorted Files*. + +| Best | Average | Worst | Memory | Stable | Iterators | +| ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | +| n | n log n | n log n | n | No | Random-access | + +*New in version 1.10.0* + ### `default_sorter` ```cpp @@ -147,6 +161,31 @@ This sorter also has the following dedicated algorithms when used together with None of the container-aware algorithms invalidates iterators. +### `mel_sorter` + +```cpp +#include +``` + +Implements melsort, a rather slow but *Enc*-adaptive algorithm described by S. Skiena in *Encroaching lists as a measure of presortedness*. + +*MEL* stands for *Merge Encroaching Lists*. + +| Best | Average | Worst | Memory | Stable | Iterators | +| ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | +| n | n log n | n log n | n | No | Forward | + +This sorter also has the following dedicated algorithms when used together with [`container_aware_adapter`][container-aware-adapter]: + +| Container | Best | Average | Worst | Memory | Stable | +| ------------------- | ----------- | ----------- | ----------- | ----------- | ----------- | +| `std::list` | n | n log n | n log n | sqrt n | No | +| `std::forward_list` | n | n log n | n log n | sqrt n | No | + +None of the container-aware algorithms invalidates iterators. + +*New in version 1.10.0* + ### `merge_insertion_sorter` ```cpp @@ -283,6 +322,22 @@ This sorter also has the following dedicated algorithms when used together with None of the container-aware algorithms invalidates iterators. +### `slab_sort` + +```cpp +#include +``` + +Implements a variant of slabsort, a rather slow but highly adaptive algorithm described by C. Levcopoulos and O. Petersson in *Sorting Shuffled Monotone Sequences*. + +| Best | Average | Worst | Memory | Stable | Iterators | +| ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | +| n | n log n | n log n | n | No | Random-access | + +This algorithm actually uses a rather big amount of memory but scales better than other O(n log n) algorithms of the library described as "slow" when the collections get bigger. + +*New in version 1.10.0* + ### `smooth_sorter` ```cpp @@ -317,7 +372,7 @@ Implements a [spinsort](https://www.boost.org/doc/libs/1_72_0/libs/sort/doc/html #include ``` -Implements an in-place *SplitSort* as descirbed in *Splitsort — an adaptive sorting algorithm* by Levcopoulos and Petersson. This library implements the simpler "in-place" version of the algorithm described in the paper. +Implements an in-place *SplitSort* as descirbed in *Splitsort — an adaptive sorting algorithm* by C. Levcopoulos and O. Petersson. This library implements the simpler "in-place" version of the algorithm described in the paper. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -353,7 +408,7 @@ The adapter [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-a | n log n | n log n | n log n | n | Yes | Random-access | | n log² n | n log² n | n log² n | 1 | Yes | Random-access | -`std::sort` and `std::stable_sort` are likely not able to handle proxy iterators, therefore trying to use `std_sorter` with code that relies on proxy iterators (*e.g.* [`schwartz_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#schwartz_adapter)) is deemed to cause errors. However, some standard libraries provide overloads of standard algorithms for some containers; for example, libc++ has an overload of `std::sort` for bit iterators, which means that `std_sorter` could the the best choice to sort an [`std::vector`](https://en.cppreference.com/w/cpp/container/vector_bool). +`std::sort` and `std::stable_sort` are likely not able to handle proxy iterators, therefore trying to use `std_sorter` with code that relies on proxy iterators (*e.g.* [`schwartz_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#schwartz_adapter)) is deemed to cause errors. However, some standard libraries provide overloads of standard algorithms for some containers; for example, libc++ has an overload of `std::sort` for bit iterators, which means that `std_sorter` could be the best choice to sort an [`std::vector`](https://en.cppreference.com/w/cpp/container/vector_bool). This sorter can't throw `std::bad_alloc`. @@ -481,6 +536,8 @@ struct spread_sorter: [adaptive-quickselect]: https://arxiv.org/abs/1606.00484 + [cartesian-tree-sort]: https://en.wikipedia.org/wiki/Cartesian_tree#Application_in_sorting + [container-aware-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#container_aware_adapter [introselect]: https://en.wikipedia.org/wiki/Introselect [quick-mergesort]: https://arxiv.org/abs/1307.3033 [selection-algorithm]: https://en.wikipedia.org/wiki/Selection_algorithm diff --git a/docs/Tooling.md b/docs/Tooling.md index 12d93cce..7381f7ba 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -54,18 +54,13 @@ conan search cpp-sort --remote=conan-center And then install any version to your local cache as follows (here with version 1.9.0): ```sh -conan install cpp-sort/1.9.0 +conan install cpp-sort/1.10.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 use the source available in this GitHub repository. +The packages downloaded from conan-center are minimal and only contain the files required to use **cpp-sort** as a library: the headers, CMake files and licensing information. If you need anything else you have to build your own package with the `conanfile.py` available in this repository. -Alternatively you can find the packages on [Bintray][bintray], generated with the recipe in this repository. They are more or less equivalent to the conan-center ones, except that they contain the CMake config files for the library instead of relying on Conan to generate them. Unlike the ones on conan-center those packages have `@morwenn/stable` in their name. -*Note: the `@morwenn/stable` packages on Bintray up to version 1.5.1 can also be found on conan-center, but the later conan-center versions use conan-center-index and follow its conventions.* - - - [bintray]: https://bintray.com/morwenn/cpp-sort/cpp-sort%3Amorwenn [catch2]: https://github.com/catchorg/Catch2 [cmake]: https://cmake.org/ [conan]: https://conan.io/ - [conan-center]: https://bintray.com/conan/conan-center + [conan-center]: https://conan.io/center/cpp-sort diff --git a/docs/images/mops-partial-ordering.png b/docs/images/mops-partial-ordering.png new file mode 100644 index 00000000..258cfd96 Binary files /dev/null and b/docs/images/mops-partial-ordering.png differ diff --git a/include/cpp-sort/adapters/container_aware_adapter.h b/include/cpp-sort/adapters/container_aware_adapter.h index a3bf4939..adac55a5 100644 --- a/include/cpp-sort/adapters/container_aware_adapter.h +++ b/include/cpp-sort/adapters/container_aware_adapter.h @@ -361,6 +361,10 @@ namespace cppsort #include "../detail/container_aware/insertion_sort.h" #endif +#ifdef CPPSORT_SORTERS_MEL_SORTER_DONE_ +#include "../detail/container_aware/mel_sort.h" +#endif + #ifdef CPPSORT_SORTERS_MERGE_SORTER_DONE_ #include "../detail/container_aware/merge_sort.h" #endif diff --git a/include/cpp-sort/adapters/indirect_adapter.h b/include/cpp-sort/adapters/indirect_adapter.h index cf55866d..e6ac5b80 100644 --- a/include/cpp-sort/adapters/indirect_adapter.h +++ b/include/cpp-sort/adapters/indirect_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_INDIRECT_ADAPTER_H_ @@ -17,11 +17,11 @@ #include #include #include -#include #include #include #include #include "../detail/checkers.h" +#include "../detail/functional.h" #include "../detail/indiesort.h" #include "../detail/iterator_traits.h" #include "../detail/memory.h" @@ -55,7 +55,6 @@ namespace cppsort -> decltype(auto) { using utility::iter_move; - auto&& proj = utility::as_function(projection); //////////////////////////////////////////////////////////// // Indirectly sort the iterators @@ -75,10 +74,8 @@ namespace cppsort #ifndef __cpp_lib_uncaught_exceptions // Sort the iterators on pointed values std::forward(sorter)( - iterators.get(), iterators.get() + size, std::move(compare), - [&proj](RandomAccessIterator it) -> decltype(auto) { - return proj(*it); - } + iterators.get(), iterators.get() + size, + std::move(compare), indirect(projection) ); #else // Work around the sorters that return void @@ -122,14 +119,12 @@ namespace cppsort }); if (size < 2) { - exit_function.release(); + exit_function.deactivate(); } return std::forward(sorter)( - iterators.get(), iterators.get() + size, std::move(compare), - [&proj](RandomAccessIterator it) -> decltype(auto) { - return proj(*it); - } + iterators.get(), iterators.get() + size, + std::move(compare), indirect(projection) ); #endif } diff --git a/include/cpp-sort/adapters/out_of_place_adapter.h b/include/cpp-sort/adapters/out_of_place_adapter.h index 76dcce95..77c7dd5e 100644 --- a/include/cpp-sort/adapters/out_of_place_adapter.h +++ b/include/cpp-sort/adapters/out_of_place_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_OUT_OF_PLACE_ADAPTER_H_ @@ -36,20 +36,20 @@ namespace cppsort -> decltype(auto) { using utility::iter_move; - using rvalue_reference = remove_cvref_t>; + using rvalue_type = rvalue_type_t; // Copy the collection into contiguous memory buffer - std::unique_ptr buffer( - static_cast(::operator new(size * sizeof(rvalue_reference))), - operator_deleter(size * sizeof(rvalue_reference)) + std::unique_ptr buffer( + static_cast(::operator new(size * sizeof(rvalue_type))), + operator_deleter(size * sizeof(rvalue_type)) ); - destruct_n d(0); - std::unique_ptr&> h2(buffer.get(), d); + destruct_n d(0); + std::unique_ptr&> h2(buffer.get(), d); auto it = first; auto ptr = buffer.get(); for (Size i = 0 ; i < size ; ++i) { - ::new(ptr) rvalue_reference(iter_move(it)); + ::new(ptr) rvalue_type(iter_move(it)); ++it; ++ptr; ++d; diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index a140b002..d689ad55 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_STABLE_ADAPTER_H_ @@ -25,6 +25,7 @@ #include "../detail/iterator_traits.h" #include "../detail/memory.h" #include "../detail/sized_iterator.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -41,8 +42,12 @@ namespace cppsort { private: - using projection_t = decltype(utility::as_function(std::declval())); - using compare_t = decltype(utility::as_function(std::declval())); + using projection_t = detail::remove_cvref_t< + decltype(utility::as_function(std::declval())) + >; + using compare_t = detail::remove_cvref_t< + decltype(utility::as_function(std::declval())) + >; std::tuple data; public: diff --git a/include/cpp-sort/comparators/projection_compare.h b/include/cpp-sort/comparators/projection_compare.h index e2e6fdde..63113cb0 100644 --- a/include/cpp-sort/comparators/projection_compare.h +++ b/include/cpp-sort/comparators/projection_compare.h @@ -22,10 +22,10 @@ namespace cppsort private: using compare_t = detail::remove_cvref_t< - decltype(utility::as_function(std::declval())) + decltype(utility::as_function(std::declval())) >; using projection_t = detail::remove_cvref_t< - decltype(utility::as_function(std::declval())) + decltype(utility::as_function(std::declval())) >; std::tuple data; diff --git a/include/cpp-sort/detail/block_sort.h b/include/cpp-sort/detail/block_sort.h index 74f15646..fe4a81b6 100644 --- a/include/cpp-sort/detail/block_sort.h +++ b/include/cpp-sort/detail/block_sort.h @@ -335,7 +335,7 @@ namespace detail -> void { using utility::iter_swap; - using rvalue_reference = remove_cvref_t>; + using rvalue_type = rvalue_type_t; using difference_type = difference_type_t; difference_type size = last - first; @@ -409,7 +409,7 @@ namespace detail // use a small cache to speed up some of the operations // just keep in mind that making it too small ruins the point (nothing will fit into it), // and making it too large also ruins the point (so much for "low memory"!) - typename BufferProvider::template buffer cache(size); + typename BufferProvider::template buffer cache(size); difference_type cache_size = cache.size(); // may be pointer of something else @@ -518,7 +518,7 @@ namespace detail // 7. sort the second internal buffer if it exists // 8. redistribute the two internal buffers back into the array - difference_type block_size = std::sqrt(iterator.length()); + difference_type block_size = static_cast(std::sqrt(iterator.length())); difference_type buffer_size = iterator.length() / block_size + 1; // as an optimization, we really only need to pull out the internal buffers once for each level of merges @@ -756,7 +756,7 @@ namespace detail // when we leave an A block behind we'll need to merge the previous A block with any B blocks that follow it, so track that information as well Range lastA = firstA; Range lastB = { first, first }; - Range blockB = { B.start, B.start + std::min(block_size, B.length()) }; + Range blockB = { B.start, B.start + (std::min)(block_size, B.length()) }; blockA.start += firstA.length(); RandomAccessIterator indexA = buffer1.start; @@ -799,7 +799,7 @@ namespace detail if (cache_size > 0 && lastA.length() <= cache_size) { half_inplace_merge(cache.begin(), cache.begin() + lastA.length(), lastA.end, B_split, lastA.start, - std::min(lastA.length(), B_split - lastA.end), + (std::min)(lastA.length(), B_split - lastA.end), compare, projection); } else if (buffer2.length() > 0) { MergeInternal(lastA.start, lastA.end, lastA.end, B_split, @@ -861,7 +861,7 @@ namespace detail if (cache_size > 0 && lastA.length() <= cache_size) { half_inplace_merge(cache.begin(), cache.begin() + lastA.length(), lastA.end, B.end, lastA.start, - std::min(lastA.length(), B.end - lastA.end), + (std::min)(lastA.length(), B.end - lastA.end), compare, projection); } else if (buffer2.length() > 0) { MergeInternal(lastA.start, lastA.end, lastA.end, B.end, diff --git a/include/cpp-sort/detail/buffered_inplace_merge.h b/include/cpp-sort/detail/buffered_inplace_merge.h index e1e9ad66..4037a305 100644 --- a/include/cpp-sort/detail/buffered_inplace_merge.h +++ b/include/cpp-sort/detail/buffered_inplace_merge.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -23,6 +23,7 @@ #include #include #include "config.h" +#include "functional.h" #include "iterator_traits.h" #include "memory.h" #include "move.h" @@ -98,52 +99,19 @@ namespace detail // Prepare the buffer prior to the blind merge (only for // bidirectional iterator) - template - class invert - { - private: - - Predicate predicate; - - public: - - invert() {} - - explicit invert(Predicate predicate): - predicate(std::move(predicate)) - {} - - template - auto operator()(T1&& x, T2&& y) - -> bool - { - auto&& pred = utility::as_function(predicate); - return pred(std::forward(y), std::forward(x)); - } - - template - auto operator()(T1&& x, T2&& y) const - -> bool - { - auto&& pred = utility::as_function(predicate); - return pred(std::forward(y), std::forward(x)); - } - }; - - template + template auto buffered_inplace_merge(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last, Compare compare, Projection projection, difference_type_t len1, difference_type_t len2, - RandomAccessIterator buff) + rvalue_type_t* buff) -> void { using utility::iter_move; - using rvalue_reference = remove_cvref_t>; - destruct_n d(0); - std::unique_ptr&> h2(buff, d); + using rvalue_type = rvalue_type_t; + destruct_n d(0); + std::unique_ptr&> h2(buff, d); if (len1 <= len2) { auto ptr = uninitialized_move(first, middle, buff, d); half_inplace_merge(buff, ptr, middle, last, first, len1, @@ -151,11 +119,11 @@ namespace detail } else { auto ptr = uninitialized_move(middle, last, buff, d); using rbi = std::reverse_iterator; - using rv = std::reverse_iterator; + using rv = std::reverse_iterator; half_inplace_merge(rv(ptr), rv(buff), rbi(middle), rbi(first), rbi(last), len2, - invert(compare), std::move(projection)); + invert(compare), std::move(projection)); } } }} diff --git a/include/cpp-sort/detail/cartesian_tree_sort.h b/include/cpp-sort/detail/cartesian_tree_sort.h new file mode 100644 index 00000000..44515052 --- /dev/null +++ b/include/cpp-sort/detail/cartesian_tree_sort.h @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_CARTESIAN_TREE_SORT_H_ +#define CPPSORT_DETAIL_CARTESIAN_TREE_SORT_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include "../detail/heapsort.h" +#include "../detail/functional.h" +#include "../detail/iterator_traits.h" +#include "../detail/memory.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ +namespace detail +{ + template + struct cartesian_tree_node + { + constexpr cartesian_tree_node(T&& value, cartesian_tree_node* parent, cartesian_tree_node* left_child) + noexcept(std::is_nothrow_move_constructible::value): + value(std::move(value)), + parent(parent), + left_child(left_child) + {} + + // Make tree nodes immovable + cartesian_tree_node(const cartesian_tree_node&) = delete; + cartesian_tree_node(cartesian_tree_node&&) = delete; + cartesian_tree_node& operator=(const cartesian_tree_node&) = delete; + cartesian_tree_node& operator=(cartesian_tree_node&&) = delete; + + // Stored value + T value; + + // Parent node + cartesian_tree_node* parent = nullptr; + // Children nodes + cartesian_tree_node* left_child = nullptr; + cartesian_tree_node* right_child = nullptr; + }; + + template + struct cartesian_tree + { + public: + + //////////////////////////////////////////////////////////// + // Public types + + using difference_type = std::ptrdiff_t; + using node_type = cartesian_tree_node; + + //////////////////////////////////////////////////////////// + // Constructor + + template + explicit cartesian_tree(Iterator first, Iterator last, Compare compare, Projection projection): + // Allocate enough space to store N nodes + size_(last - first), + buffer_(static_cast(::operator new(size_ * sizeof(node_type))), + operator_deleter(size_ * sizeof(node_type))), + root_(buffer_.get()) // Original root is first element + { + using utility::iter_move; + auto&& proj = utility::as_function(projection); + + // Pointer to the next memory where to allocate a node + node_type* current_node = buffer_.get(); + // Pointer to the last node that was inserted + node_type* prev_node = current_node; + // Create the first node + ::new(current_node) node_type(iter_move(first), nullptr, nullptr); + + // Advance to the next element + ++first; + ++current_node; + + for (; first != last ; ++first, ++current_node) { + auto&& proj_value = proj(*first); + node_type* new_parent = _find_insertion_parent(prev_node, proj_value, compare, projection); + if (new_parent == nullptr) { + ::new(current_node) node_type(iter_move(first), nullptr, root_); + root_ = current_node; + } else { + ::new(current_node) node_type(iter_move(first), new_parent, new_parent->right_child); + new_parent->right_child = current_node; + } + prev_node = current_node; + } + } + + //////////////////////////////////////////////////////////// + // Destructor + + ~cartesian_tree() + { + // Destroy the nodes + auto ptr = buffer_.get(); + for (difference_type idx = 0 ; idx < size_ ; ++idx) { + detail::destroy_at(ptr); + ++ptr; + } + } + + //////////////////////////////////////////////////////////// + // Accessors + + auto root() const + -> node_type* + { + return root_; + } + + private: + + //////////////////////////////////////////////////////////// + // Helper function + + template + auto _find_insertion_parent(node_type* prev_node, U&& value, + Compare compare, Projection projection) const + -> node_type* + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + while (comp(value, proj(prev_node->value))) { + if (prev_node == root_) { + return nullptr; + } + prev_node = prev_node->parent; + } + return prev_node; + } + + //////////////////////////////////////////////////////////// + // Data members + + // Number of nodes in the tree + difference_type size_; + // Backing storage + std::unique_ptr buffer_; + // Root of the Cartesian tree + node_type* root_; + }; + + template + auto cartesian_tree_sort(Iterator first, Iterator last, Compare compare, Projection projection) + -> void + { + using tree_type = cartesian_tree>; + using node_type = typename tree_type::node_type; + + if ((last - first) < 2) { + return; + } + + tree_type tree(first, last, compare, projection); + std::vector pq; // Priority queue + pq.push_back(tree.root()); + + auto&& comp = invert(compare); + auto proj_value = [proj=utility::as_function(projection)](auto* node) -> decltype(auto) { + return proj(node->value); + }; + + while (not pq.empty()) { + // Retrieve biggest element + node_type* node = pq.front(); + + // Add the element back to the original collection + *first = std::move(node->value); + ++first; + + // Add the node's children to the priority queue + if (node->left_child != nullptr) { + pq.front() = node->left_child; + detail::sift_down(pq.begin(), pq.end(), comp, proj_value, pq.size(), pq.begin()); + if (node->right_child != nullptr) { + pq.push_back(node->right_child); + detail::push_heap(pq.begin(), pq.end(), comp, proj_value, pq.size()); + } + } else if (node->right_child != nullptr) { + pq.front() = node->right_child; + detail::sift_down(pq.begin(), pq.end(), comp, proj_value, pq.size(), pq.begin()); + } else { + detail::pop_heap(pq.begin(), pq.end(), comp, proj_value, pq.size()); + pq.pop_back(); + } + } + } +}} + +#endif // CPPSORT_DETAIL_CARTESIAN_TREE_SORT_H_ diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 72d01d48..4fd795bc 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -16,19 +16,6 @@ # define __has_cpp_attribute(x) 0 #endif -//////////////////////////////////////////////////////////// -// Check for C++17 features - -#ifdef __cpp_constexpr -# if __cpp_constexpr >= 201603 -# define CPPSORT_CONSTEXPR_AFTER_CXX14 constexpr -# else -# define CPPSORT_CONSTEXPR_AFTER_CXX14 -# endif -#else -# define CPPSORT_CONSTEXPR_AFTER_CXX14 -#endif - //////////////////////////////////////////////////////////// // Check for C++20 features @@ -37,7 +24,7 @@ // compiler and standard versions #if defined(__GNUC__) -# if __GNUC__ > 3 && __cplusplus > 201703L +# if __GNUC__ > 9 && __cplusplus > 201703L # define CPPSORT_STD_IDENTITY_AVAILABLE 1 # else # define CPPSORT_STD_IDENTITY_AVAILABLE 0 @@ -46,9 +33,9 @@ # define CPPSORT_STD_IDENTITY_AVAILABLE 0 #else # if defined(__cpp_lib_ranges) -# CPPSORT_STD_IDENTITY_AVAILABLE 1 +# define CPPSORT_STD_IDENTITY_AVAILABLE 1 # else -# CPPSORT_STD_IDENTITY_AVAILABLE 0 +# define CPPSORT_STD_IDENTITY_AVAILABLE 0 # endif #endif diff --git a/include/cpp-sort/detail/container_aware/mel_sort.h b/include/cpp-sort/detail/container_aware/mel_sort.h new file mode 100644 index 00000000..724b4850 --- /dev/null +++ b/include/cpp-sort/detail/container_aware/mel_sort.h @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_CONTAINER_AWARE_MEL_SORT_H_ +#define CPPSORT_DETAIL_CONTAINER_AWARE_MEL_SORT_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../functional.h" +#include "../lower_bound.h" + +namespace cppsort +{ + namespace detail + { + template + auto list_mel_sort(std::list& collection, Compare compare, Projection projection) + -> void + { + // NOTE: see detail/melsort.h for detailed explanations + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (collection.size() < 2) return; + + // Encroaching lists + std::vector> lists; + lists.emplace_back(); + lists.back().splice(lists.back().begin(), collection, collection.begin()); + + //////////////////////////////////////////////////////////// + // Create encroaching lists + + while (not collection.empty()) { + auto&& value = proj(collection.front()); + + auto& last_list = lists.back(); + if (not comp(value, proj(last_list.back()))) { + // Element belongs to the tails (bigger elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, invert(compare), + [&proj](auto& list) -> decltype(auto) { return proj(list.back()); } + ); + insertion_point->splice(insertion_point->end(), collection, collection.begin()); + } else if (not comp(proj(last_list.front()), value)) { + // Element belongs to the heads (smaller elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, compare, + [&proj](auto& list) -> decltype(auto) { return proj(list.front()); } + ); + insertion_point->splice(insertion_point->begin(), collection, collection.begin()); + } else { + // Element does not belong to the existing encroaching lists, + // create a new list for it + lists.emplace_back(); + lists.back().splice(lists.back().begin(), collection, collection.begin()); + } + } + + //////////////////////////////////////////////////////////// + // Merge encroaching lists + + while (lists.size() > 1) { + if (lists.size() % 2 != 0) { + auto last_it = std::prev(lists.end()); + auto last_1_it = std::prev(last_it); + last_1_it->merge(*last_it, make_projection_compare(comp, proj)); + lists.pop_back(); + } + + auto first_it = lists.begin(); + auto half_it = first_it + lists.size() / 2; + while (half_it != lists.end()) { + first_it->merge(*half_it, make_projection_compare(comp, proj)); + ++first_it; + ++half_it; + } + + lists.erase(lists.begin() + lists.size() / 2, lists.end()); + } + + // Merge lists back into the original collection + collection.splice(collection.begin(), std::move(lists.front())); + } + + template + auto flist_mel_sort(std::forward_list& collection, Compare compare, Projection projection) + -> void + { + // NOTE: see detail/melsort.h for detailed explanations + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (collection.begin() == collection.end() || std::next(collection.begin()) == collection.end()) { + return; + } + + // A list and its last element, the latter being required + // to implement melsort for forward iterators + struct flist + { + using iterator = typename std::forward_list::iterator; + + flist(): + list(), + last(list.begin()) + {} + + std::forward_list list; + iterator last; + }; + + // Encroaching lists + std::vector lists; + lists.emplace_back(); + lists.back().list.splice_after(lists.back().list.before_begin(), + collection, collection.before_begin()); + lists.back().last = lists.back().list.begin(); + + //////////////////////////////////////////////////////////// + // Create encroaching lists + + while (not collection.empty()) { + auto&& value = proj(collection.front()); + + auto& last_list = lists.back(); + if (not comp(value, proj(*last_list.last))) { + // Element belongs to the tails (bigger elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, invert(compare), + [&proj](auto& list) -> decltype(auto) { return proj(*list.last); } + ); + insertion_point->list.splice_after(insertion_point->last, collection, + collection.before_begin()); + insertion_point->last = std::next(insertion_point->last); + } else if (not comp(proj(last_list.list.front()), value)) { + // Element belongs to the heads (smaller elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, compare, + [&proj](auto& list) -> decltype(auto) { return proj(list.list.front()); } + ); + insertion_point->list.splice_after(insertion_point->list.before_begin(), + collection, collection.before_begin()); + } else { + // Element does not belong to the existing encroaching lists, + // create a new list for it + lists.emplace_back(); + lists.back().list.splice_after(lists.back().list.before_begin(), + collection, collection.before_begin()); + lists.back().last = lists.back().list.begin(); + } + } + + //////////////////////////////////////////////////////////// + // Merge encroaching lists + + while (lists.size() > 1) { + if (lists.size() % 2 != 0) { + auto last_it = std::prev(lists.end()); + auto last_1_it = std::prev(last_it); + last_1_it->list.merge(last_it->list, make_projection_compare(comp, proj)); + lists.pop_back(); + } + + auto first_it = lists.begin(); + auto half_it = first_it + lists.size() / 2; + while (half_it != lists.end()) { + first_it->list.merge(half_it->list, make_projection_compare(comp, proj)); + ++first_it; + ++half_it; + } + + lists.erase(lists.begin() + lists.size() / 2, lists.end()); + } + + // Merge lists back into the original collection + collection.splice_after(collection.before_begin(), std::move(lists.front().list)); + } + } + + template<> + struct container_aware_adapter: + detail::container_aware_adapter_base, + detail::sorter_facade_fptr< + container_aware_adapter, + std::is_empty::value + > + { + using detail::container_aware_adapter_base::operator(); + + container_aware_adapter() = default; + constexpr explicit container_aware_adapter(mel_sorter) noexcept {} + + //////////////////////////////////////////////////////////// + // std::list + + template + auto operator()(std::list& iterable) const + -> void + { + detail::list_mel_sort(iterable, std::less<>{}, utility::identity{}); + } + + template + auto operator()(std::list& iterable, Compare compare) const + -> std::enable_if_t< + is_projection_v, Compare> + > + { + detail::list_mel_sort(iterable, std::move(compare), utility::identity{}); + } + + template + auto operator()(std::list& iterable, Projection projection) const + -> std::enable_if_t< + is_projection_v> + > + { + detail::list_mel_sort(iterable, std::less<>{}, std::move(projection)); + } + + template< + typename Compare, + typename Projection, + typename... Args, + typename = std::enable_if_t< + is_projection_v, Compare> + > + > + auto operator()(std::list& iterable, + Compare compare, Projection projection) const + -> void + { + detail::list_mel_sort(iterable, std::move(compare), std::move(projection)); + } + + //////////////////////////////////////////////////////////// + // std::forward_list + + template + auto operator()(std::forward_list& iterable) const + -> void + { + detail::flist_mel_sort(iterable, std::less<>{}, utility::identity{}); + } + + template + auto operator()(std::forward_list& iterable, Compare compare) const + -> std::enable_if_t< + is_projection_v, Compare> + > + { + detail::flist_mel_sort(iterable, std::move(compare), utility::identity{}); + } + + template + auto operator()(std::forward_list& iterable, Projection projection) const + -> std::enable_if_t< + is_projection_v> + > + { + detail::flist_mel_sort(iterable, std::less<>{}, std::move(projection)); + } + + template< + typename Compare, + typename Projection, + typename... Args, + typename = std::enable_if_t< + is_projection_v, Compare> + > + > + auto operator()(std::forward_list& iterable, + Compare compare, Projection projection) const + -> void + { + detail::flist_mel_sort(iterable, std::move(compare), std::move(projection)); + } + }; +} + +#endif // CPPSORT_DETAIL_CONTAINER_AWARE_MEL_SORT_H_ diff --git a/include/cpp-sort/detail/count_inversions.h b/include/cpp-sort/detail/count_inversions.h index f5e06fc7..0e476335 100644 --- a/include/cpp-sort/detail/count_inversions.h +++ b/include/cpp-sort/detail/count_inversions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_COUNT_INVERSIONS_H_ @@ -22,32 +22,33 @@ namespace detail typename ResultType, typename RandomAccessIterator1, typename RandomAccessIterator2, - typename Compare + typename Compare, + typename Projection > auto count_inversions_merge(RandomAccessIterator1 first, RandomAccessIterator1 middle, RandomAccessIterator1 last, RandomAccessIterator2 cache, - Compare compare) + Compare compare, Projection projection) -> ResultType { using utility::iter_move; auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); ResultType inversions = 0; // Shrink the problem size on the left side - while (comp(*first, *middle)) { + while (comp(proj(*first), proj(*middle))) { ++first; } auto first2 = middle; auto result = cache; - for (auto first1 = first ; first1 != middle ; ++result) - { + for (auto first1 = first ; first1 != middle ; ++result) { if (first2 == last) { detail::move(first1, middle, result); break; } - if (comp(*first2, *first1)) { + if (comp(proj(*first2), proj(*first1))) { *result = iter_move(first2); ++first2; inversions += middle - first1; @@ -69,10 +70,11 @@ namespace detail typename ResultType, typename RandomAccessIterator1, typename RandomAccessIterator2, - typename Compare + typename Compare, + typename Projection > auto count_inversions(RandomAccessIterator1 first, RandomAccessIterator1 last, - RandomAccessIterator2 cache, Compare compare) + RandomAccessIterator2 cache, Compare compare, Projection projection) -> ResultType { auto size = last - first; @@ -83,11 +85,11 @@ namespace detail ResultType inversions = 0; auto middle = first + size / 2; - inversions += count_inversions(first, middle, cache, compare); - inversions += count_inversions(middle, last, cache, compare); + inversions += count_inversions(first, middle, cache, compare, projection); + inversions += count_inversions(middle, last, cache, compare, projection); inversions += count_inversions_merge(std::move(first), std::move(middle), std::move(last), std::move(cache), - std::move(compare)); + std::move(compare), std::move(projection)); return inversions; } }} diff --git a/include/cpp-sort/detail/drop_merge_sort.h b/include/cpp-sort/detail/drop_merge_sort.h index be765288..0ebe7a0a 100644 --- a/include/cpp-sort/detail/drop_merge_sort.h +++ b/include/cpp-sort/detail/drop_merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -58,8 +58,8 @@ namespace detail auto&& proj = utility::as_function(projection); using difference_type = difference_type_t; - using rvalue_reference = remove_cvref_t>; - std::vector dropped; + using rvalue_type = rvalue_type_t; + std::vector dropped; difference_type num_dropped_in_row = 0; auto write = begin; @@ -85,7 +85,7 @@ namespace detail } else { for (difference_type i = 0 ; i < num_dropped_in_row ; ++i) { --read; - if (not std::is_trivially_copyable::value) { + if (not std::is_trivially_copyable::value) { // If the value is trivially copyable, then it shouldn't have // been modified by the call to iter_move, and the original // value is still fully where it should be @@ -100,7 +100,7 @@ namespace detail num_dropped_in_row = 0; } } else { - if (std::is_trivially_copyable::value) { + if (std::is_trivially_copyable::value) { // If the type is trivially copyable, the potential self-move // should not trigger any issue *write = iter_move(read); diff --git a/include/cpp-sort/detail/fixed_size_list.h b/include/cpp-sort/detail/fixed_size_list.h index 52fb9424..c0735bc0 100644 --- a/include/cpp-sort/detail/fixed_size_list.h +++ b/include/cpp-sort/detail/fixed_size_list.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_FIXED_SIZE_LIST_H_ @@ -12,6 +12,7 @@ #include #include #include +#include #include "config.h" #include "memory.h" @@ -30,38 +31,203 @@ namespace detail // constructed or destructed. template - struct fixed_size_list_node + struct list_node { - constexpr explicit fixed_size_list_node(fixed_size_list_node* next) noexcept: + using value_type = T; + + constexpr explicit list_node(list_node* next) noexcept: next(next) {} - constexpr fixed_size_list_node(fixed_size_list_node* prev, fixed_size_list_node* next) noexcept: + constexpr list_node(list_node* prev, list_node* next) noexcept: prev(prev), next(next) {} // Make list nodes immovable - fixed_size_list_node(const fixed_size_list_node&) = delete; - fixed_size_list_node(fixed_size_list_node&&) = delete; - fixed_size_list_node& operator=(const fixed_size_list_node&) = delete; - fixed_size_list_node& operator=(fixed_size_list_node&&) = delete; + list_node(const list_node&) = delete; + list_node(list_node&&) = delete; + list_node& operator=(const list_node&) = delete; + list_node& operator=(list_node&&) = delete; + + // The list takes care of managing the lifetime of value + ~list_node() {} // Inhibit construction of the node with a value union { T value; }; // Pointers to the previous and next nodes - fixed_size_list_node* prev; - fixed_size_list_node* next; + list_node* prev; + list_node* next; + }; + + //////////////////////////////////////////////////////////// + // Function to destroy a node + + template + auto destroy_node_contents(NodeType* node) + -> void + { + detail::destroy_at(&(node->*Ptr)); + } + + //////////////////////////////////////////////////////////// + // Fixed size node pool + // + // This is an immovable doubly linked list node pool that can + // be constructed with a max capacity (N thereafter): it + // allocates a contiguous block of memory for N nodes and never + // performs any other allocation through the course of its + // lifetime. It is meant to be used from algorithms which know + // how many elements will be needed in a list or collection of + // lists, and allows to reuse the list nodes: the contiguous + // storage and the node reuse are expected to make the code + // using it faster than equivalent code using std::list. + // + // The free nodes are tracked as a singly linked list which + // reuses a node's internal pointers: that singly linked list + // of free nodes starts at first_free_ and ends when the "next" + // field of a node is nullptr. + // + // This node pool does not check for overflow: when asking for + // a new node, an assertion will fire if the pool is out of + // free nodes. + // + // It is worth nothing that the node pool only manages the + // lifetime of the nodes themselves, it is the responsibility + // of the list to manage the lifetime of the stored values. + + template + struct fixed_size_list_node_pool + { + public: + + //////////////////////////////////////////////////////////// + // Public types + + using node_type = NodeType; + + //////////////////////////////////////////////////////////// + // Constructors + + // Make the node pool immovable + fixed_size_list_node_pool(const fixed_size_list_node_pool&) = delete; + fixed_size_list_node_pool(fixed_size_list_node_pool&&) = delete; + fixed_size_list_node_pool& operator=(const fixed_size_list_node_pool&) = delete; + fixed_size_list_node_pool& operator=(fixed_size_list_node_pool&&) = delete; + + explicit fixed_size_list_node_pool(std::ptrdiff_t capacity): + // Allocate enough space to store N nodes + buffer_(static_cast(::operator new(capacity * sizeof(node_type))), + operator_deleter(capacity * sizeof(node_type))), + first_free_(buffer_.get()), + capacity_(capacity) + { + // Node constructors are noexcept, so we don't need to handle + // the cleanup of list nodes that would have been required if + // exceptions could have been thrown + + auto ptr = buffer_.get(); + for (std::ptrdiff_t n = 0 ; n < capacity - 1 ; ++n, ++ptr) { + // Each node is initialized with a "next" field pointing to the next + // node in memory: this allows to create a list of free nodes laid + // out contiguously in memory in one pass + ::new (ptr) node_type(ptr + 1); + } + // Initialize the last node, guard with nullptr + ::new (ptr) node_type(nullptr); + } + + //////////////////////////////////////////////////////////// + // Destructor + + ~fixed_size_list_node_pool() + { + // Destroy the nodes + node_type* ptr = buffer_.get(); + for (std::ptrdiff_t n = 0 ; n < capacity_ ; ++n, ++ptr) { + detail::destroy_at(ptr); + } + } + + //////////////////////////////////////////////////////////// + // Node providing/retrieval + + auto next_free_node() + -> node_type* + { + // Retrieve next free node + CPPSORT_ASSERT(first_free_ != nullptr); + node_type* new_node = first_free_; + first_free_ = first_free_->next; + return new_node; + } + + auto retrieve_nodes(node_type* first, node_type* last) + -> void + { + // Get back a range of nodes linked together, this function + // allows to retrieve any number of nodes in O(1), assuming + // that last is accessible repeatedly iterating over next + // from first + last->next = first_free_; + first_free_ = first; + } + + //////////////////////////////////////////////////////////// + // Danger Zone + + // reset_nodes relinks the first n nodes of the buffer in a + // rather brutal way: it relinks everywhere next pointer of + // the n first elements of the buffer to the next element in + // the buffer. + // + // The only real use case is when reusing the same node pool + // with several lists: once a list is destroyed, if it used + // no more than n elements, relink those n elements, which + // can greatly improve the cache-friendliness of the next + // node allocations. + // + // Using this function incorrectly *will* fuck everything + // and its invariants up. + + auto reset_nodes(std::ptrdiff_t until_n) + -> void + { + auto ptr = buffer_.get(); + for (std::ptrdiff_t n = 0 ; n < until_n - 1 ; ++n, ++ptr) { + ptr->next = ptr + 1; + } + if (until_n == capacity_) { + ptr->next = nullptr; + } else { + ptr->next = ptr + 1; + } + first_free_ = buffer_.get(); + } + + private: + + //////////////////////////////////////////////////////////// + // Data members + + // Backing storage + std::unique_ptr buffer_; + + // First free node of the pool + node_type* first_free_; + + // Number of nodes the pool holds + std::ptrdiff_t capacity_; }; //////////////////////////////////////////////////////////// // List iterator // - // Iterator wrapping a fixed_size_list_node: it uses the - // node's next & prev pointers to advance through the list + // Iterator wrapping a list node: it uses the node's + // next & prev pointers to advance through the list - template + template struct fixed_size_list_iterator { public: @@ -69,16 +235,19 @@ namespace detail //////////////////////////////////////////////////////////// // Public types + using node_type = NodeType; using iterator_category = std::bidirectional_iterator_tag; - using value_type = T; + using value_type = typename node_type::value_type; using difference_type = std::ptrdiff_t; - using pointer = T*; - using reference = T&; + using pointer = value_type*; + using reference = value_type&; //////////////////////////////////////////////////////////// // Constructors - constexpr explicit fixed_size_list_iterator(fixed_size_list_node* ptr) noexcept: + fixed_size_list_iterator() = default; + + constexpr explicit fixed_size_list_iterator(NodeType* ptr) noexcept: ptr_(ptr) {} @@ -86,7 +255,7 @@ namespace detail // Members access constexpr auto base() const - -> fixed_size_list_node* + -> node_type* { return ptr_; } @@ -158,44 +327,37 @@ namespace detail private: - fixed_size_list_node* ptr_; + node_type* ptr_ = nullptr; }; //////////////////////////////////////////////////////////// // Fixed size list // - // This is an immovable double linked list data structure that - // can be constructed with a max capacity (N thereafter): it - // allocates a contiguous block of memory for N nodes and a - // sentinel node and never performs any other allocation through - // the course of its lifetime. It is meant to be used from - // algorithms which know how many elements will be needed in a - // list and allow to reuse the list nodes: the contiguous node - // storage and the node reuse are expected to make the code - // using it faster than the equivalent code using std::list. - // - // The list itself is a doubly linked list of immovables nodes - // that starts at begin() and ends at the sentinel node end(). - // It can mostly be used like the standard library std::list. - // The sentinel nodes is always the last node of the backing - // storage. - // - // Internally the free nodes are tracked in a singly linked list - // which reuses the nodes internal pointers: that singly linked - // list of free nodes starts at first_free_ and ends at the - // sentinel node. It is worth noting that the sentinel node's - // prev pointer points at the previous element in the doubly - // linked list, not at the previous node in the free nodes list. + // This is a non-copyable doubly linked list data structure that + // can be constructed with a reference to a fixed-size node + // pool. The list handles the lifetimes of the node's values, + // but the lifetime of the nodes themselves is handled by the + // pool. // - // This list implementation does not check for overflow: when - // trying to insert too many nodes there is a precondition that - // there are still free nodes left in the list. + // The list is implemented with a sentinel node to simplify many + // of the operations. This sentinel node is store in the list + // itself instead of being allocated from the node pool. Its + // next pointer points to the first element of the list, its + // prev pointer points to the last. When the list is empty, the + // sentinel node points to itself. // - // This implementation does not provide all the functions to - // match std::list, but only the ones actually used by the - // library's algorithms, growing as needed - - template + // The interface is supposed to be as close as possible from + // that of std::list, except in the following areas: + // - Construction from a node pool + // - Templated of the node type + // - No tracking of the size + // - No copy semantics + // - empty -> is_empty + // A lot of the capabilities of std::list are still missing, + // they will be added as needed whenever new library components + // require them + + template struct fixed_size_list { public: @@ -203,46 +365,74 @@ namespace detail //////////////////////////////////////////////////////////// // Public types - using value_type = T; + using node_type = NodeType; + using node_value_destructor_t = void(*)(node_type*); + + using value_type = typename node_type::value_type; using size_type = std::size_t; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; using difference_type = std::ptrdiff_t; - using iterator = fixed_size_list_iterator; + using iterator = fixed_size_list_iterator; // TODO: const_iterator - using node_type = fixed_size_list_node; //////////////////////////////////////////////////////////// // Constructors - // Make fixed_size_list immovable + // Make fixed_size_list non-copyable fixed_size_list(const fixed_size_list&) = delete; - fixed_size_list(fixed_size_list&&) = delete; fixed_size_list& operator=(const fixed_size_list&) = delete; - fixed_size_list& operator=(fixed_size_list&&) = delete; - explicit fixed_size_list(std::ptrdiff_t capacity): - // Allocate enough space to store N nodes plus a - // sentinel node (where N = capacity) - buffer_(static_cast(::operator new((capacity + 1) * sizeof(node_type))), - operator_deleter((capacity + 1) * sizeof(node_type))), - sentinel_node_(buffer_.get() + capacity), - first_free_(buffer_.get()) + explicit constexpr fixed_size_list(fixed_size_list_node_pool& node_pool) noexcept: + node_pool_(&node_pool), + sentinel_node_(&sentinel_node_, &sentinel_node_), + node_destructor_(destroy_node_contents) + {} + + constexpr fixed_size_list(fixed_size_list_node_pool& node_pool, + node_value_destructor_t node_destructor) noexcept: + node_pool_(&node_pool), + sentinel_node_(&sentinel_node_, &sentinel_node_), + node_destructor_(node_destructor) + {} + + constexpr fixed_size_list(fixed_size_list&& other) noexcept: + node_pool_(other.node_pool_), + sentinel_node_(std::exchange(other.sentinel_node_.prev, &other.sentinel_node_), + std::exchange(other.sentinel_node_.next, &other.sentinel_node_)), + node_destructor_(other.node_destructor_) { - // fixed_size_list_node constructors are noexcept, so we don't - // need to handle the cleanup of list nodes that would have been - // required if exceptions could have been thrown + if (sentinel_node_.prev == &other.sentinel_node_) { + sentinel_node_.prev = &sentinel_node_; + } else { + sentinel_node_.prev->next = &sentinel_node_; + } + if (sentinel_node_.next == &other.sentinel_node_) { + sentinel_node_.next = &sentinel_node_; + } else { + sentinel_node_.next->prev = &sentinel_node_; + } + } - for (auto ptr = buffer_.get() ; ptr != sentinel_node_ ; ++ptr) { - // Each node is initialized with a "next" field pointing to the next - // node in memory: this allows to create a list of free nodes layed - // out contiguously in memory in one pass - ::new (ptr) node_type(ptr + 1); + auto operator=(fixed_size_list&& other) noexcept + -> fixed_size_list& + { + node_pool_ = other.node_pool_; + if (other.sentinel_node_.prev != &other.sentinel_node_) { + sentinel_node_.prev = std::exchange(other.sentinel_node_.prev, &other.sentinel_node_); + sentinel_node_.prev->next = &sentinel_node_; + } else { + sentinel_node_.prev = &sentinel_node_; } - // Initialize sentinel node - ::new (sentinel_node_) node_type(sentinel_node_, sentinel_node_); + if (other.sentinel_node_.next != &other.sentinel_node_) { + sentinel_node_.next = std::exchange(other.sentinel_node_.next, &other.sentinel_node_); + sentinel_node_.next->prev = &sentinel_node_; + } else { + sentinel_node_.next = &sentinel_node_; + } + return *this; } //////////////////////////////////////////////////////////// @@ -250,63 +440,301 @@ namespace detail ~fixed_size_list() { - // Destroy the constructed values - for (node_type* ptr = sentinel_node_ ; ptr != sentinel_node_ ; ptr = ptr->next) { - ptr->value.~T(); + node_type* ptr = sentinel_node_.next; + if (ptr != &sentinel_node_) { + // Destroy the node's values + do { + auto next_ptr = ptr->next; + node_destructor_(ptr); + ptr = next_ptr; + } while (ptr != &sentinel_node_); + + // Let the pool know that the nodes are free again + node_pool_->retrieve_nodes(sentinel_node_.next, sentinel_node_.prev); } - // Destroy the nodes - for (node_type* ptr = buffer_.get() ; ptr != sentinel_node_ ; ++ptr) { - ptr->~node_type(); - } - sentinel_node_->~node_type(); } //////////////////////////////////////////////////////////// - // Iterator access + // Element access + + auto front() + -> reference + { + return sentinel_node_.next->value; + } + + auto back() + -> reference + { + return sentinel_node_.prev->value; + } + + auto node_pool() + -> fixed_size_list_node_pool& + { + return *node_pool_; + } - auto begin() const + //////////////////////////////////////////////////////////// + // Iterators + + auto begin() -> iterator { - return iterator(sentinel_node_->next); + return iterator(sentinel_node_.next); } - auto end() const + auto end() -> iterator { - return iterator(sentinel_node_); + return iterator(&sentinel_node_); + } + + //////////////////////////////////////////////////////////// + // Capacity + + [[nodiscard]] auto is_empty() const noexcept + -> bool + { + return &sentinel_node_ == sentinel_node_.next; } //////////////////////////////////////////////////////////// // Modifiers - auto insert(iterator pos, const T& value) + auto insert(iterator pos, const value_type& value) -> iterator { - node_type* new_node = next_free_node_(); - ::new (&new_node->value) T(value); - link_node_before_(new_node, pos.base()); - return iterator(new_node); + return iterator(insert_node_(pos.base(), value)); } - auto insert(iterator pos, T&& value) + auto insert(iterator pos, value_type&& value) -> iterator { - node_type* new_node = next_free_node_(); - ::new (&new_node->value) T(std::move(value)); - link_node_before_(new_node, pos.base()); - return iterator(new_node); + return iterator(insert_node_(pos.base(), std::move(value))); + } + + auto push_back(const value_type& value) + -> void + { + insert_node_(&sentinel_node_, value); + } + + auto push_back(value_type&& value) + -> void + { + insert_node_(&sentinel_node_, std::move(value)); + } + + template + auto push_back(Callable&& setter) + -> void + { + insert_node_(&sentinel_node_, std::forward(setter)); + } + + auto push_front(const value_type& value) + -> void + { + insert_node_(sentinel_node_.next, value); + } + + auto push_front(value_type&& value) + -> void + { + insert_node_(sentinel_node_.next, std::move(value)); } - auto push_back(const T& value) + template + auto push_front(Callable&& setter) -> void { - insert(end(), value); + insert_node_(sentinel_node_.next, std::forward(setter)); + } + + auto extract(node_type* node) + -> node_type* + { + CPPSORT_ASSERT(node != &sentinel_node_); + CPPSORT_ASSERT(not is_empty()); + node->prev->next = node->next; + node->next->prev = node->prev; + return node; + } + + auto extract(iterator pos) + -> node_type* + { + return extract(pos.base()); } - auto push_back(T&& value) + auto extract_back() + -> node_type* + { + return extract(sentinel_node_.prev); + } + + auto extract_front() + -> node_type* + { + return extract(sentinel_node_.next); + } + + auto set_node_destructor(node_value_destructor_t node_destructor) -> void { - insert(end(), std::move(value)); + node_destructor_ = std::move(node_destructor); + } + + //////////////////////////////////////////////////////////// + // Operations + + template + auto merge(fixed_size_list& other, Compare compare, Projection projection) + -> void + { + merge(begin(), end(), other, std::move(compare), std::move(projection)); + } + + template + auto merge(iterator first, iterator last, fixed_size_list& other, + Compare compare, Projection projection) + -> void + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (is_empty()) { + splice(last, other); + return; + } + if (other.is_empty()) { + return; + } + + auto other_it = other.begin(); + auto other_end = other.end(); + + while (other_it != other_end) { + if (comp(proj(*other_it), proj(*first))) { + // The following loop finds a series of nodes to splice + // into the current list, which is faster than splicing + // elements one by one + auto insert_begin = other_it; + do { + ++other_it; + } while (other_it != other_end && comp(proj(*other_it), proj(*first))); + fast_splice_(first, insert_begin, other_it); + } else { + ++first; + } + + if (first == last) { + fast_splice_(last, other_it, other_end); + // Reset the other list's sentinel node, + // fast_splice_ does no do it + other.sentinel_node_.next = &other.sentinel_node_; + other.sentinel_node_.prev = &other.sentinel_node_; + return; + } + } + + // Reset the other list's sentinel node, + // fast_splice_ does no do it + other.sentinel_node_.next = &other.sentinel_node_; + other.sentinel_node_.prev = &other.sentinel_node_; + } + + template + auto merge_unsafe(fixed_size_list& other, Compare compare, Projection projection) + -> void + { + merge_unsafe(begin(), end(), other, std::move(compare), std::move(projection)); + } + + template + auto merge_unsafe(iterator first, iterator last, fixed_size_list& other, + Compare compare, Projection projection) + -> void + { + // WARNING: this variant of merge considers that only the next pointer + // of the nodes in other is correct, as well as the prev + // pointer of node.end() - it doesn't set the prev pointer + // of the merged nodes either + + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (is_empty()) { + splice(last, other); + return; + } + if (other.is_empty()) { + return; + } + + auto other_it = other.begin(); + auto other_end = other.end(); + + while (other_it != other_end) { + if (comp(proj(*other_it), proj(*first))) { + // The following loop finds a series of nodes to splice + // into the current list, which is faster than splicing + // elements one by one + iterator insert_first = other_it; + iterator insert_last; + do { + insert_last = other_it; + ++other_it; + } while (other_it != other_end && comp(proj(*other_it), proj(*first))); + first.base()->prev->next = insert_first.base(); + insert_last.base()->next = first.base(); + } else { + ++first; + } + + if (first == last) { + last.base()->prev->next = other_it.base(); + other_end.base()->prev->next = last.base(); + // Reset the other list's sentinel node, + // fast_splice_ does no do it + other.sentinel_node_.next = &other.sentinel_node_; + other.sentinel_node_.prev = &other.sentinel_node_; + return; + } + } + + // Reset the other list's sentinel node, + // fast_splice_ does no do it + other.sentinel_node_.next = &other.sentinel_node_; + other.sentinel_node_.prev = &other.sentinel_node_; + } + + auto splice(iterator pos, fixed_size_list& other) + -> void + { + if (other.is_empty()) { + return; + } + splice(pos, other, other.begin(), other.end()); + } + + auto splice(iterator pos, fixed_size_list&, iterator first, iterator last) + -> void + { + CPPSORT_ASSERT(first.base() != last.base()); + + auto last_1 = last.base()->prev; + + // Relink nodes in the other list + first.base()->prev->next = last.base(); + last.base()->prev = first.base()->prev; + + // Add the range of elements to this list + pos.base()->prev->next = first.base(); + first.base()->prev = pos.base()->prev; + pos.base()->prev = last_1; + last_1->next = pos.base(); } private: @@ -314,13 +742,31 @@ namespace detail //////////////////////////////////////////////////////////// // Helper functions - auto next_free_node_() + auto insert_node_(node_type* pos, const value_type& value) -> node_type* { - // Retrieve next free node - CPPSORT_ASSERT(first_free_ != sentinel_node_); - node_type* new_node = first_free_; - first_free_ = first_free_->next; + node_type* new_node = node_pool_->next_free_node(); + ::new (&new_node->value) value_type(value); + link_node_before_(new_node, pos); + return new_node; + } + + auto insert_node_(node_type* pos, value_type&& value) + -> node_type* + { + node_type* new_node = node_pool_->next_free_node(); + ::new (&new_node->value) value_type(std::move(value)); + link_node_before_(new_node, pos); + return new_node; + } + + template + auto insert_node_(node_type* pos, Callable setter) + -> node_type* + { + node_type* new_node = node_pool_->next_free_node(); + setter(new_node); + link_node_before_(new_node, pos); return new_node; } @@ -334,18 +780,37 @@ namespace detail node->next->prev = node; } + auto fast_splice_(iterator pos, iterator first, iterator last) + -> void + { + CPPSORT_ASSERT(first.base() != last.base()); + + // This function is like splice(), but it doesn't relink + // nodes in the other list: when merging another list into + // the current one, we never use the relinked pointers in + // the other list, so merge() can just skip relinking them + // altogether and just reset the sentinel node at the end + // of the operation instead + + // Add the range of elements to this list + pos.base()->prev->next = first.base(); + first.base()->prev = pos.base()->prev; + pos.base()->prev = last.base()->prev; + last.base()->prev->next = pos.base(); + } + //////////////////////////////////////////////////////////// // Data members - // Backing storage - std::unique_ptr buffer_; + // Node pool + fixed_size_list_node_pool* node_pool_; - // Immovable sentinel node: its prev field points to the last - // element of the list, its next field to the first one - node_type* const sentinel_node_; + // Sentinel node: its prev field points to the last element + // of the list, its next field to the first one + node_type sentinel_node_; - // First free node of the list - node_type* first_free_; + // Function pointer to a node's value destructor + node_value_destructor_t node_destructor_; }; }} diff --git a/include/cpp-sort/detail/functional.h b/include/cpp-sort/detail/functional.h new file mode 100644 index 00000000..6ea2cfaa --- /dev/null +++ b/include/cpp-sort/detail/functional.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_FUNCTIONAL_H_ +#define CPPSORT_DETAIL_FUNCTIONAL_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +namespace cppsort +{ +namespace detail +{ + //////////////////////////////////////////////////////////// + // C++17 std::not_fn + + template + class not_fn_t + { + private: + + Predicate predicate; + + public: + + not_fn_t() = delete; + + explicit not_fn_t(Predicate predicate): + predicate(std::move(predicate)) + {} + + template + auto operator()(T1&& x, T2&& y) + -> bool + { + auto&& pred = utility::as_function(predicate); + return not pred(std::forward(x), std::forward(y)); + } + + template + auto operator()(T1&& x, T2&& y) const + -> bool + { + auto&& pred = utility::as_function(predicate); + return not pred(std::forward(x), std::forward(y)); + } + }; + + template + auto not_fn(Predicate&& pred) + -> not_fn_t> + { + return not_fn_t>(std::forward(pred)); + } + + //////////////////////////////////////////////////////////// + // invert + + template + class invert_t + { + private: + + Predicate predicate; + + public: + + invert_t() = delete; + + explicit invert_t(Predicate predicate): + predicate(std::move(predicate)) + {} + + template + auto operator()(T1&& x, T2&& y) + -> bool + { + auto&& pred = utility::as_function(predicate); + return pred(std::forward(y), std::forward(x)); + } + + template + auto operator()(T1&& x, T2&& y) const + -> bool + { + auto&& pred = utility::as_function(predicate); + return pred(std::forward(y), std::forward(x)); + } + }; + + template + auto invert(Predicate&& pred) + -> invert_t> + { + return invert_t>(std::forward(pred)); + } + + //////////////////////////////////////////////////////////// + // indirect + + template + class indirect_t + { + private: + + Projection projection; + + public: + + indirect_t() = delete; + + explicit indirect_t(Projection projection): + projection(std::move(projection)) + {} + + template + auto operator()(T&& indirect_value) + -> decltype(utility::as_function(projection)(*indirect_value)) + { + auto&& proj = utility::as_function(projection); + return proj(*indirect_value); + } + + template + auto operator()(T&& indirect_value) const + -> decltype(utility::as_function(projection)(*indirect_value)) + { + auto&& proj = utility::as_function(projection); + return proj(*indirect_value); + } + }; + + template + auto indirect(Projection&& proj) + -> indirect_t> + { + return indirect_t>(std::forward(proj)); + } +}} + +#endif // CPPSORT_DETAIL_FUNCTIONAL_H_ diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index 1c1bd0c6..411a01e2 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -148,7 +148,7 @@ namespace grail p1 = std::prev(middle), p2 = std::prev(last); - while (p1 >= first) { + while (p1 > first) { if (p2 < middle || compare(proj(*p1), proj(*p2)) > 0) { iter_swap(p0, p1); --p1; @@ -158,13 +158,28 @@ namespace grail } --p0; } + // Same as the previous loop, just avoids decrementing p1, + // which can make it end up before the origin, causing + // issues with some kinds of iterators + if (p1 == first) { + while (not (p2 < middle || compare(proj(*p1), proj(*p2)) > 0)) { + iter_swap(p0, p2); + --p2; + --p0; + } + iter_swap(p0, p1); + --p0; + } - if (p2 != p0 && p2 >= middle) { - do { + if (p2 != p0) { + while (p2 > middle) { iter_swap(p0, p2); --p0; --p2; - } while (p2 != middle); + } + if (p2 == middle){ + iter_swap(p0, p2); + } } } @@ -208,10 +223,9 @@ namespace grail } template - auto smart_merge_without_buffer(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last, int left_over_frag, - Compare compare, Projection projection) - -> std::pair + auto smart_merge_without_buffer(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, + int left_over_frag, Compare compare, Projection projection) + -> std::pair, int> { auto&& proj = utility::as_function(projection); @@ -273,7 +287,7 @@ namespace grail RandomAccessIterator last, int left_over_frag, difference_type_t block_len, Compare compare, Projection projection) - -> std::pair + -> std::pair, int> { using utility::iter_move; auto&& proj = utility::as_function(projection); @@ -309,11 +323,15 @@ namespace grail // nblock2 are regular blocks from stream A. llast is length of last (irregular) block from stream B, that should go before nblock2 blocks. // llast=0 requires nblock2=0 (no irregular blocks). llast>0, nblock2=0 is possible. template - auto merge_buffers_left_with_extra_buffer(RandomAccessIterator keys, RandomAccessIterator midkey, - RandomAccessIterator arr, int nblock, int lblock, int nblock2, - int llast, Compare compare, Projection projection) + auto merge_buffers_left_with_extra_buffer(RandomAccessIterator keys, RandomAccessIterator midkey, RandomAccessIterator arr, + difference_type_t nblock, + difference_type_t lblock, + difference_type_t nblock2, + difference_type_t llast, + Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; auto&& proj = utility::as_function(projection); if (nblock == 0) { @@ -322,10 +340,10 @@ namespace grail return; } - int lrest = lblock; + auto lrest = lblock; int frest = compare(proj(*keys), proj(*midkey)) < 0 ? 0 : 1; auto pidx = arr + lblock; - for (int cidx = 1 ; cidx < nblock ; (void) ++cidx, pidx += lblock) { + for (difference_type cidx = 1 ; cidx < nblock ; (void) ++cidx, pidx += lblock) { auto prest = pidx - lrest; int fnext = compare(proj(keys[cidx]), proj(*midkey)) < 0 ? 0 : 1; if (fnext == frest) { @@ -363,11 +381,15 @@ namespace grail // that should go before nblock2 blocks. // llast=0 requires nblock2=0 (no irregular blocks). llast>0, nblock2=0 is possible. template - auto merge_buffers_left(RandomAccessIterator keys, RandomAccessIterator midkey, - RandomAccessIterator arr, int nblock, int lblock, bool havebuf, - int nblock2, int llast, Compare compare, Projection projection) + auto merge_buffers_left(RandomAccessIterator keys, RandomAccessIterator midkey, RandomAccessIterator arr, + difference_type_t nblock, + difference_type_t lblock, bool havebuf, + difference_type_t nblock2, + difference_type_t llast, + Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; auto&& proj = utility::as_function(projection); auto&& midkey_proj = proj(*midkey); @@ -381,10 +403,10 @@ namespace grail return; } - int lrest = lblock; + auto lrest = lblock; int frest = compare(proj(*keys), midkey_proj) < 0 ? 0 : 1; auto pidx = arr + lblock; - for (int cidx = 1 ; cidx < nblock ; (void) ++cidx, pidx += lblock) { + for (difference_type cidx = 1 ; cidx < nblock ; (void) ++cidx, pidx += lblock) { auto prest = pidx - lrest; int fnext = compare(proj(keys[cidx]), midkey_proj) < 0 ? 0 : 1; if(fnext == frest) { @@ -438,25 +460,27 @@ namespace grail template auto build_blocks(RandomAccessIterator first, RandomAccessIterator last, - int K, BufferIterator extbuf, int LExtBuf, + difference_type_t K, + BufferIterator extbuf, difference_type_t LExtBuf, Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; using utility::iter_move; using utility::iter_swap; auto&& proj = utility::as_function(projection); auto size = last - first; - int kbuf = std::min(K, LExtBuf); + auto kbuf = std::min(K, LExtBuf); while (kbuf & (kbuf - 1)) { kbuf &= kbuf - 1; // max power or 2 - just in case } - int h; + difference_type h; if (kbuf) { detail::move(first - kbuf, first, extbuf); - for (int m = 1 ; m < size ; m += 2) { - int u = 0; + for (difference_type m = 1 ; m < size ; m += 2) { + difference_type u = 0; if (compare(proj(first[m-1]), proj(first[m])) > 0) { u = 1; } @@ -475,7 +499,7 @@ namespace grail merge_left_with_extra_buffer(p0, p0+h, p0+(h+h), p0-h, compare, projection); p0 += 2 * h; } - int rest = last - p0; + auto rest = last - p0; if (rest > h) { merge_left_with_extra_buffer(p0, p0+h, last, p0-h, compare, projection); } else { @@ -488,8 +512,8 @@ namespace grail } detail::move(extbuf, extbuf + kbuf, last); } else { - for (int m = 1 ; m < size ; m += 2) { - int u = 0; + for (difference_type m = 1 ; m < size ; m += 2) { + difference_type u = 0; if (compare(proj(first[m-1]), proj(first[m])) > 0) { u = 1; } @@ -505,12 +529,14 @@ namespace grail } for (; h < K ; h *= 2) { auto p0 = first; - auto p1 = last - 2 * h; - while (p0 <= p1) { - merge_left(p0, p0+h, p0+(h+h), p0-h, compare, projection); - p0 += 2 * h; + if (2 * h <= (last - p0)) { + auto p1 = last - 2 * h; + while (p0 <= p1) { + merge_left(p0, p0+h, p0+(h+h), p0-h, compare, projection); + p0 += 2 * h; + } } - int rest = last - p0; + auto rest = last - p0; if (rest > h) { merge_left(p0, p0+h, last, p0-h, compare, projection); } else { @@ -519,7 +545,7 @@ namespace grail first -= h; last -= h; } - int restk = size % (2 * K); + auto restk = size % (2 * K); auto p = last - restk; if (restk <= K) { detail::rotate(p, last, last+K); @@ -536,17 +562,21 @@ namespace grail // LL and nkeys are powers of 2. (2*LL/lblock) keys are guarantied template - auto combine_blocks(RandomAccessIterator keys, RandomAccessIterator arr, int len, int LL, - int lblock, bool havebuf, BufferIterator xbuf, bool usexbuf, + auto combine_blocks(RandomAccessIterator keys, RandomAccessIterator arr, + difference_type_t len, + difference_type_t LL, + difference_type_t lblock, + bool havebuf, BufferIterator xbuf, bool usexbuf, Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; using utility::iter_move; using utility::iter_swap; auto&& proj = utility::as_function(projection); - int M = len / (2 * LL); - int lrest = len % (2 * LL); + auto M = len / (2 * LL); + auto lrest = len % (2 * LL); if (lrest <= LL) { len -= lrest; lrest = 0; @@ -554,16 +584,16 @@ namespace grail if (usexbuf) { detail::move(arr - lblock, arr, xbuf); } - for (int b = 0 ; b <= M ; ++b) { + for (difference_type b = 0 ; b <= M ; ++b) { if (b == M && lrest == 0) break; auto arr1 = arr + b * 2 * LL; - int NBlk = (b == M ? lrest : 2 * LL) / lblock; + auto NBlk = (b == M ? lrest : 2 * LL) / lblock; insertion_sort(keys, keys + (NBlk + (b == M ? 1 : 0)), compare.base(), projection); - int midkey = LL / lblock; - for (int u = 1 ; u < NBlk ; ++u) { - int p = u - 1; - for (int v = u ; v < NBlk ; ++v) { + auto midkey = LL / lblock; + for (difference_type u = 1 ; u < NBlk ; ++u) { + auto p = u - 1; + for (auto v = u ; v < NBlk ; ++v) { int kc = compare(proj(arr1[p*lblock]), proj(arr1[v*lblock])); if (kc > 0 || (kc == 0 && compare(proj(keys[p]), proj(keys[v])) > 0)) { p = v; @@ -577,8 +607,8 @@ namespace grail } } } - int nbl2 = 0; - int llast = 0; + difference_type nbl2 = 0; + difference_type llast = 0; if (b == M) { llast = lrest % lblock; } @@ -597,7 +627,7 @@ namespace grail } } if (usexbuf) { - for (int p = len ; --p >= 0;) { + for (auto p = len ; --p >= 0;) { arr[p] = iter_move(arr + (p - lblock)); } detail::move(xbuf, xbuf + lblock, arr - lblock); @@ -610,25 +640,29 @@ namespace grail template auto lazy_stable_sort(RandomAccessIterator first, RandomAccessIterator last, - Compare compare, Projection projection) + Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; using utility::iter_swap; auto&& proj = utility::as_function(projection); - for (auto it = std::next(first) ; it < last ; it += 2) { - if (compare(proj(*std::prev(it)), proj(*it)) > 0) { - iter_swap(std::prev(it), it); + auto size = last - first; + auto end_loop = size % 2 == 0 ? last : std::prev(last); + for (auto it = first ; it != end_loop ; it += 2) { + if (compare(proj(*it), proj(*std::next(it))) > 0) { + iter_swap(it, std::next(it)); } } - auto size = last - first; - for (int h = 2 ; h < size ; h *= 2) { + for (difference_type h = 2 ; h < size ; h *= 2) { auto p0 = first; - auto p1 = last - 2 * h; - while (p0 <= p1) { - merge_without_buffer(p0, p0 + h, p0 + (h + h), compare, projection); - p0 += 2 * h; + if (2 * h <= size) { + auto p1 = last - 2 * h; + while (p0 <= p1) { + merge_without_buffer(p0, p0 + h, p0 + (h + h), compare, projection); + p0 += 2 * h; + } } int rest = last - p0; if (rest > h) { @@ -644,18 +678,20 @@ namespace grail Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; + auto size = last - first; if (size < 16) { insertion_sort(first, last, compare.base(), std::move(projection)); return; } - int lblock = 1; + difference_type lblock = 1; while (lblock * lblock < size) { lblock *= 2; } - int nkeys = (size - 1) / lblock + 1; - int findkeys = find_keys(first, last, nkeys + lblock, compare, projection); + auto nkeys = (size - 1) / lblock + 1; + auto findkeys = find_keys(first, last, nkeys + lblock, compare, projection); bool havebuf = true; if (findkeys < nkeys + lblock) { if (findkeys < 4) { @@ -670,7 +706,7 @@ namespace grail lblock = 0; } auto ptr = first + (lblock + nkeys); - int cbuf = havebuf ? lblock : nkeys; + auto cbuf = havebuf ? lblock : nkeys; if (havebuf) { build_blocks(ptr, last, cbuf, extbuf, LExtBuf, compare, projection); } else { @@ -680,14 +716,14 @@ namespace grail // 2*cbuf are built while ((last - ptr) > (cbuf *= 2)) { - int lb = lblock; + auto lb = lblock; bool chavebuf = havebuf; if (not havebuf) { if (nkeys > 4 && nkeys / 8 * nkeys >= cbuf) { lb = nkeys / 2; chavebuf = true; } else { - int nk = 1; + difference_type nk = 1; long long s = (long long) cbuf * findkeys / 2; while (nk < nkeys && s != 0) { nk *= 2; @@ -711,11 +747,9 @@ namespace grail Compare compare, Projection projection) -> void { - using rvalue_reference = remove_cvref_t>; - // Allocate temporary buffer auto size = last - first; - typename BufferProvider::template buffer buffer(size); + typename BufferProvider::template buffer> buffer(size); using compare_t = std::remove_reference_t; common_sort(std::move(first), std::move(last), buffer.begin(), buffer.size(), diff --git a/include/cpp-sort/detail/heapsort.h b/include/cpp-sort/detail/heapsort.h index 51d60168..bcfa2ad9 100644 --- a/include/cpp-sort/detail/heapsort.h +++ b/include/cpp-sort/detail/heapsort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -99,6 +99,35 @@ namespace detail } } + template + auto push_heap(RandomAccessIterator first, RandomAccessIterator last, + Compare compare, Projection projection, + difference_type_t len) + { + using utility::iter_move; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (len > 1) { + len = (len - 2) / 2; + auto ptr = first + len; + if (comp(proj(*ptr), proj(*--last))) { + auto t = iter_move(last); + auto&& proj_t = proj(t); + do { + *last = iter_move(ptr); + last = ptr; + if (len == 0) { + break; + } + len = (len - 1) / 2; + ptr = first + len; + } while (comp(proj(*ptr), proj_t)); + *last = std::move(t); + } + } + } + template auto pop_heap(RandomAccessIterator first, RandomAccessIterator last, Compare compare, Projection projection, diff --git a/include/cpp-sort/detail/indiesort.h b/include/cpp-sort/detail/indiesort.h index 16a37faf..f64c628e 100644 --- a/include/cpp-sort/detail/indiesort.h +++ b/include/cpp-sort/detail/indiesort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -122,7 +122,7 @@ namespace detail }); if (size < 2) { - exit_function.release(); + exit_function.deactivate(); } // Sort the iterators on pointed values diff --git a/include/cpp-sort/detail/indirect_compare.h b/include/cpp-sort/detail/indirect_compare.h deleted file mode 100644 index 07e17358..00000000 --- a/include/cpp-sort/detail/indirect_compare.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2015-2020 Morwenn - * SPDX-License-Identifier: MIT - */ -#ifndef CPPSORT_DETAIL_INDIRECT_COMPARE_H_ -#define CPPSORT_DETAIL_INDIRECT_COMPARE_H_ - -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include - -namespace cppsort -{ -namespace detail -{ - template - class indirect_compare - { - private: - - // Pack compare and projection for EBCO - std::tuple data; - - public: - - indirect_compare(Compare compare, Projection projection): - data(std::move(compare), std::move(projection)) - {} - - template - auto operator()(Iterator lhs, Iterator rhs) - -> bool - { - auto&& comp = utility::as_function(std::get<0>(data)); - auto&& proj = utility::as_function(std::get<1>(data)); - return comp(proj(*lhs), proj(*rhs)); - } - }; - - template - auto make_indirect_compare(Compare compare, Projection projection={}) - -> indirect_compare - { - return { compare, projection }; - } -}} - -#endif // CPPSORT_DETAIL_INDIRECT_COMPARE_H_ diff --git a/include/cpp-sort/detail/inplace_merge.h b/include/cpp-sort/detail/inplace_merge.h index 8c4d86b6..e9721227 100644 --- a/include/cpp-sort/detail/inplace_merge.h +++ b/include/cpp-sort/detail/inplace_merge.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_INPLACE_MERGE_H_ @@ -92,7 +92,6 @@ namespace detail std::forward_iterator_tag) -> void { - using rvalue_reference = remove_cvref_t>; auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -106,7 +105,7 @@ namespace detail auto n0 = std::distance(first, middle); auto n1 = std::distance(middle, last); - auto buffer = temporary_buffer(std::max(n0, n1)); + auto buffer = temporary_buffer>((std::max)(n0, n1)); recmerge(std::move(first), n0, std::move(middle), n1, buffer.data(), buffer.size(), std::move(compare), std::move(projection)); @@ -121,8 +120,7 @@ namespace detail std::bidirectional_iterator_tag) -> void { - using rvalue_reference = remove_cvref_t>; - temporary_buffer buffer(std::min(len1, len2)); + temporary_buffer> buffer((std::min)(len1, len2)); using category = iterator_category_t; inplace_merge(std::move(first), std::move(middle), std::move(last), @@ -137,7 +135,6 @@ namespace detail std::bidirectional_iterator_tag) -> void { - using rvalue_reference = remove_cvref_t>; using category = iterator_category_t; auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -152,7 +149,7 @@ namespace detail auto len1 = std::distance(first, middle); auto len2 = std::distance(middle, last); - temporary_buffer buffer(std::min(len1, len2)); + temporary_buffer> buffer((std::min)(len1, len2)); inplace_merge(std::move(first), std::move(middle), std::move(last), std::move(compare), std::move(projection), len1, len2, buffer.data(), buffer.size(), diff --git a/include/cpp-sort/detail/iterator_traits.h b/include/cpp-sort/detail/iterator_traits.h index b5dcd36e..72dbad07 100644 --- a/include/cpp-sort/detail/iterator_traits.h +++ b/include/cpp-sort/detail/iterator_traits.h @@ -38,17 +38,15 @@ namespace detail template using iterator_category_t = typename std::iterator_traits::iterator_category; - // // Addition used by proxy iterators from P0022 - // - template using rvalue_reference_t = utility::rvalue_reference_t; - // - // Handy addition from time to time - // + // Additional common type to use instead of value_t + template + using rvalue_type_t = remove_cvref_t>; + // Handy addition from time to time template using projected_t = remove_cvref_t())>>; }} diff --git a/include/cpp-sort/detail/longest_non_descending_subsequence.h b/include/cpp-sort/detail/longest_non_descending_subsequence.h new file mode 100644 index 00000000..0e9e56a7 --- /dev/null +++ b/include/cpp-sort/detail/longest_non_descending_subsequence.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_LONGEST_NON_DESCENDING_SUBSEQUENCE_H_ +#define CPPSORT_DETAIL_LONGEST_NON_DESCENDING_SUBSEQUENCE_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include "functional.h" +#include "iterator_traits.h" +#include "upper_bound.h" + +namespace cppsort +{ +namespace detail +{ + // Longest non-decreasing subsequence, computed with an altered + // patience sorting algorithm - returns a pair containing the + // size of the LNDS and the size of the collection + + template< + bool RecomputeSize, + typename ForwardIterator, + typename Compare, + typename Projection + > + auto longest_non_descending_subsequence(ForwardIterator first, ForwardIterator last, + difference_type_t size, + Compare compare, Projection projection) + -> std::pair, difference_type_t> + { + constexpr bool is_random_access = std::is_base_of< + std::random_access_iterator_tag, + iterator_category_t + >::value; + + if (first == last) { + return { 0, 0 }; + } + if (std::next(first) == last) { + return { 1, 1 }; + } + + // The size is only needed at the end to actually compute Rem, but + // we can compute it as-we-go when it is not known in order to avoid + // making two passes over the sequence - when the sequence is made + // of random-access iterators, we only compute it once + if (RecomputeSize && is_random_access) { + size = std::distance(first, last); + } + + auto&& proj = utility::as_function(projection); + + // Top (smaller) elements in patience sorting stacks + std::vector stack_tops; + + while (first != last) { + auto it = detail::upper_bound( + stack_tops.begin(), stack_tops.end(), + proj(*first), compare, indirect(projection)); + + if (it == stack_tops.end()) { + // The element is bigger than everything else, + // create a new "stack" to put it + stack_tops.emplace_back(first); + } else { + // The element is strictly smaller than the top + // of a given stack, replace the stack top + *it = first; + } + ++first; + + if (RecomputeSize && not is_random_access) { + // Compute the size as-we-go if iterators are not random-access + ++size; + } + } + + return { stack_tops.size(), size }; + } +}} + +#endif // CPPSORT_DETAIL_LONGEST_NON_DESCENDING_SUBSEQUENCE_H_ diff --git a/include/cpp-sort/detail/melsort.h b/include/cpp-sort/detail/melsort.h new file mode 100644 index 00000000..0c2967f6 --- /dev/null +++ b/include/cpp-sort/detail/melsort.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2018-2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_MELSORT_H_ +#define CPPSORT_DETAIL_MELSORT_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include "fixed_size_list.h" +#include "functional.h" +#include "iterator_traits.h" +#include "lower_bound.h" +#include "merge_move.h" +#include "move.h" +#include "type_traits.h" + +namespace cppsort +{ +namespace detail +{ + template + auto merge_encroaching_lists(std::vector>& lists, + ForwardIterator first, bool extract_edges, + Compare compare, Projection projection) + { + // This part of the algorithm is an optimization which is not described in + // the original paper, but somewhat hinted at nonetheless: the heads of the + // encroaching lists form a sorted collection, and the tails form another + // collection sorted in descending order. When those collections are big + // enough, it is more efficient to split them in their own list and merge + // them back with the other lists later. + // + // Since the biggest of the minimums is not bigger than the smallest of the + // maximums, we can just add the minimums to the new list, followed directly + // by the maximum in reverse order, without having to perform a merge at all. + // + // WARNING: when making the edges list, only the next pointer of its nodes is + // correctly set, as well as the prev pointer of the sentinel node. + // All other prev pointers in the list aren't guaranteed to be valid. + // This is cursed but saves some operations. + + fixed_size_list edges(lists.front().node_pool()); + + if (extract_edges) { + auto insert_node = edges.end().base(); + // Add minimums of encroaching lists + for (auto& list : lists) { + insert_node->next = list.extract_front(); + insert_node = insert_node->next; + } + // Only the last node can have a single element + // and end up empty after the previous step + if (lists.back().is_empty()) { + lists.pop_back(); + } + // Add maximums of encroaching lists + for (auto it = lists.rbegin() ; it != lists.rend() ; ++it) { + auto& list = *it; + insert_node->next = list.extract_back(); + insert_node = insert_node->next; + } + insert_node->next = edges.end().base(); + // Proper link back from the sentinel node + insert_node->next->prev = insert_node; + + // Remove the empty lists to help with the merges + lists.erase(std::remove_if(lists.begin(), lists.end(), [](auto& list) { + return list.is_empty(); + }), lists.end()); + } + + // Merge lists pairwise, picking a list from each half + // of the collection instead of adjacent ones + while (lists.size() > 2) { + if (lists.size() % 2 != 0) { + auto last_it = std::prev(lists.end()); + auto last_1_it = std::prev(last_it); + last_1_it->merge(*last_it, compare, projection); + lists.pop_back(); + } + + auto first_it = lists.begin(); + auto half_it = first_it + lists.size() / 2; + while (half_it != lists.end()) { + first_it->merge(*half_it, compare, projection); + ++first_it; + ++half_it; + } + + lists.erase(lists.begin() + lists.size() / 2, lists.end()); + } + + // If the edges were split into a single sorted list, + // add it back to the collection of encroaching lists + if (extract_edges) { + if (lists.empty()) { + lists.push_back(std::move(edges)); + } else { + lists.back().merge_unsafe(edges, compare, projection); + } + } + + // Merge remaining list(s) back into the original collection + if (lists.size() == 2) { + detail::merge_move( + lists.front().begin(), lists.front().end(), + lists.back().begin(), lists.back().end(), + first, compare, projection, projection + ); + } else if (lists.size() == 1) { + detail::move(lists.front().begin(), lists.front().end(), first); + } + } + + template + auto melsort(ForwardIterator first, ForwardIterator last, + difference_type_t size, + Compare compare, Projection projection) + -> void + { + using utility::iter_move; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (first == last || std::next(first) == last) { + return; + } + + // Encroaching lists + using node_type = list_node>; + fixed_size_list_node_pool node_pool(size); + std::vector> lists; + // Ensure that there is always one list and that the last list + // always has at least one element, this simplifies the rest + // of the computations + lists.emplace_back(node_pool); + lists.back().push_back(iter_move(first)); + + //////////////////////////////////////////////////////////// + // Create encroaching lists + + for (auto it = std::next(first) ; it != last ; ++it) { + auto&& value = proj(*it); + + // The heads of the lists form an ascending collection while the heads + // form a descending collection, plus the we have the guarantee that the + // last lists contains the biggest of the heads and the smallest of the + // tails. Since the biggest of the heads is smaller than the smallest of + // the tails, we only need to compare the element to insert to the head + // and tail of the last list to know whether we need to add it to the + // heads, to the tails, or to its own new list. + // + // The search is done with lower_bound to ensure that the element will + // be added to the oldest suitable list, which is a necessary condition + // to keep the guarantees provided by the encroaching lists collection. + + auto& last_list = lists.back(); + if (not comp(value, proj(last_list.back()))) { + // Element belongs to the tails (bigger elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, invert(compare), + [&proj](auto& list) -> decltype(auto) { return proj(list.back()); } + ); + insertion_point->push_back(iter_move(it)); + } else if (not comp(proj(last_list.front()), value)) { + // Element belongs to the heads (smaller elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, compare, + [&proj](auto& list) -> decltype(auto) { return proj(list.front()); } + ); + insertion_point->push_front(iter_move(it)); + } else { + // Element does not belong to the existing encroaching lists, + // create a new list for it + lists.emplace_back(node_pool); + lists.back().push_back(iter_move(it)); + } + } + + //////////////////////////////////////////////////////////// + // Merge encroaching lists + + // This heuristic is selected so that the edge extraction + // optimization only occurs when the number of encroaching + // lists is greater than the average size of the encroaching + // lists to merge + bool bad_distribution = lists.size() > std::sqrt(size); + merge_encroaching_lists(lists, first, bad_distribution, + std::move(compare), std::move(projection)); + } +}} + +#endif // CPPSORT_DETAIL_MELSORT_H_ diff --git a/include/cpp-sort/detail/memory.h b/include/cpp-sort/detail/memory.h index 81dd053a..a1dbed26 100644 --- a/include/cpp-sort/detail/memory.h +++ b/include/cpp-sort/detail/memory.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -27,6 +27,18 @@ namespace cppsort { namespace detail { + //////////////////////////////////////////////////////////// + // C++17 std::destroy_at + + template + auto destroy_at(T* ptr) + -> void + { + // TODO: implement if needed + static_assert(not std::is_array::value, "destroy_at() does no handle arrays"); + ptr->~T(); + } + //////////////////////////////////////////////////////////// // Deleter for ::operator new(std::size_t) @@ -90,7 +102,7 @@ namespace detail -> void { for (std::size_t i = 0 ; i < size ; ++i) { - pointer->~T(); + detail::destroy_at(pointer); ++pointer; } } diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index b4336621..5a9fffc6 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -15,13 +15,14 @@ #include #include #include -#include #include #include "config.h" #include "fixed_size_list.h" +#include "functional.h" #include "iterator_traits.h" #include "memory.h" #include "move.h" +#include "scope_exit.h" #include "swap_if.h" #include "swap_ranges.h" #include "type_traits.h" @@ -254,13 +255,10 @@ namespace detail //////////////////////////////////////////////////////////// // Merge-insertion sort - template< - typename RandomAccessIterator, - typename Compare, - typename Projection - > + template auto merge_insertion_sort_impl(group_iterator first, group_iterator last, + fixed_size_list_node_pool& node_pool, Compare compare, Projection projection) -> void { @@ -306,18 +304,30 @@ namespace detail merge_insertion_sort_impl( make_group_iterator(first, 2), make_group_iterator(end, 2), - compare, projection + node_pool, compare, projection ); //////////////////////////////////////////////////////////// // Separate main chain and pend elements - using list_t = fixed_size_list>; + using list_t = fixed_size_list>>; + + // Reusing the node pool allows to halve the number of nodes + // allocated by the algorithm, but we need to reset the links + // between the nodes so that the future allocations remain + // somewhat cache-friendly - it is theoretically more work, + // but benchmarks proved that it made a huge difference + auto node_pool_reset = make_scope_exit([&node_pool, size, group_size=first.size()] { + if (group_size > 1) { + node_pool.reset_nodes(size); + } + }); + node_pool_reset.deactivate(); // The first pend element is always part of the main chain, // so we can safely initialize the list with the first two // elements of the sequence - list_t chain(size); + list_t chain(node_pool); chain.push_back(first); chain.push_back(std::next(first)); @@ -340,7 +350,7 @@ namespace detail //////////////////////////////////////////////////////////// // Binary insertion into the main chain - auto current_it = first + 2; + auto current_it = first; auto current_pend = pend.begin(); for (int k = 0 ; ; ++k) { @@ -358,37 +368,33 @@ namespace detail auto it = current_it + dist * 2; auto pe = current_pend + dist; - do { + while (true) { --pe; - it -= 2; auto insertion_point = detail::upper_bound( chain.begin(), *pe, proj(*it), - [&](auto&& lhs, auto&& rhs) { - return comp(lhs, proj(*rhs)); - }, - utility::identity{} + comp, indirect(proj) ); chain.insert(insertion_point, it); - } while (pe != current_pend); + + if (pe == current_pend) break; + it -= 2; + } current_it += dist * 2; current_pend += dist; } - // If there are pend elements left, insert them into - // the main chain, the order of insertion does not - // matter so forward traversal is ok + // If there are pend elements left, insert them into the main + // chain, the order of insertion does not matter so forward + // traversal is ok while (current_pend != pend.end()) { + current_it += 2; auto insertion_point = detail::upper_bound( chain.begin(), *current_pend, proj(*current_it), - [&](auto&& lhs, auto&& rhs) { - return comp(lhs, proj(*rhs)); - }, - utility::identity{} + comp, indirect(proj) ); chain.insert(insertion_point, current_it); - current_it += 2; ++current_pend; } @@ -398,21 +404,24 @@ namespace detail // Number of sub-iterators auto full_size = size * first.size(); - using rvalue_reference = remove_cvref_t>; - std::unique_ptr cache( - static_cast(::operator new(full_size * sizeof(rvalue_reference))), - operator_deleter(full_size * sizeof(rvalue_reference)) + using rvalue_type = rvalue_type_t; + std::unique_ptr cache( + static_cast(::operator new(full_size * sizeof(rvalue_type))), + operator_deleter(full_size * sizeof(rvalue_type)) ); - destruct_n d(0); - std::unique_ptr&> h2(cache.get(), d); + destruct_n d(0); + std::unique_ptr&> h2(cache.get(), d); - rvalue_reference* buff_it = cache.get(); + rvalue_type* buff_it = cache.get(); for (auto&& it: chain) { auto begin = it.base(); auto end = begin + it.size(); buff_it = uninitialized_move(begin, end, buff_it, d); } detail::move(cache.get(), cache.get() + full_size, first.base()); + + // Everything else worked, it's now safe to reset the node pool + node_pool_reset.activate(); } template< @@ -424,9 +433,14 @@ namespace detail Compare compare, Projection projection) -> void { + // Make a node pool big enough to hold all the values + using node_type = list_node>; + fixed_size_list_node_pool node_pool(last - first); + merge_insertion_sort_impl( make_group_iterator(std::move(first), 1), make_group_iterator(std::move(last), 1), + node_pool, std::move(compare), std::move(projection) ); } diff --git a/include/cpp-sort/detail/merge_sort.h b/include/cpp-sort/detail/merge_sort.h index 0fd066d9..90452ff0 100644 --- a/include/cpp-sort/detail/merge_sort.h +++ b/include/cpp-sort/detail/merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_MERGE_SORT_H_ @@ -22,15 +22,12 @@ namespace cppsort { namespace detail { - template - using buffer_ptr = temporary_buffer>; - template auto merge_sort_impl(ForwardIterator first, difference_type_t size, - buffer_ptr>&& buffer, + temporary_buffer>&& buffer, Compare compare, Projection projection, std::forward_iterator_tag tag) - -> buffer_ptr> + -> temporary_buffer> { auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -79,10 +76,10 @@ namespace detail template auto merge_sort_impl(BidirectionalIterator first, BidirectionalIterator last, difference_type_t size, - buffer_ptr>&& buffer, + temporary_buffer>&& buffer, Compare compare, Projection projection, std::bidirectional_iterator_tag tag) - -> buffer_ptr> + -> temporary_buffer> { auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -142,7 +139,7 @@ namespace detail return; } - buffer_ptr> buffer(nullptr); + temporary_buffer> buffer(nullptr); merge_sort_impl(std::move(first), size, std::move(buffer), std::move(compare), std::move(projection), tag); } @@ -160,7 +157,7 @@ namespace detail return; } - buffer_ptr> buffer(nullptr); + temporary_buffer> buffer(nullptr); merge_sort_impl(std::move(first), std::move(last), size, std::move(buffer), std::move(compare), std::move(projection), tag); } diff --git a/include/cpp-sort/detail/pdqsort.h b/include/cpp-sort/detail/pdqsort.h index fec53531..d2823e3a 100644 --- a/include/cpp-sort/detail/pdqsort.h +++ b/include/cpp-sort/detail/pdqsort.h @@ -6,7 +6,7 @@ /* pdqsort.h - Pattern-defeating quicksort. - Copyright (c) 2015-2017 Orson Peters + Copyright (c) 2021 Orson Peters This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. @@ -157,7 +157,7 @@ namespace detail template auto swap_offsets(RandomAccessIterator first, RandomAccessIterator last, unsigned char* offsets_l, unsigned char* offsets_r, - int num, bool use_swaps) + std::size_t num, bool use_swaps) -> void { using utility::iter_move; @@ -166,7 +166,7 @@ namespace detail if (use_swaps) { // This case is needed for the descending distribution, where we need // to have proper swapping for pdqsort to remain O(n). - for (int i = 0 ; i < num ; ++i) { + for (std::size_t i = 0 ; i < num ; ++i) { iter_swap(first + offsets_l[i], last - offsets_r[i]); } } else if (num > 0) { @@ -174,7 +174,7 @@ namespace detail RandomAccessIterator r = last - offsets_r[0]; auto tmp = iter_move(l); *l = iter_move(r); - for (int i = 1 ; i < num ; ++i) { + for (std::size_t i = 1 ; i < num ; ++i) { l = first + offsets_l[i]; *r = iter_move(l); r = last - offsets_r[i]; @@ -221,125 +221,113 @@ namespace detail if (!already_partitioned) { iter_swap(first, last); ++first; - } - // The following branchless partitioning is derived from "BlockQuicksort: How Branch - // Mispredictions don't affect Quicksort" by Stefan Edelkamp and Armin Weiss. + // The following branchless partitioning is derived from "BlockQuicksort: How Branch + // Mispredictions don't affect Quicksort" by Stefan Edelkamp and Armin Weiss, but + // heavily micro-optimized. #ifdef __MINGW32__ - unsigned char offsets_l_storage[block_size + cacheline_size]; - unsigned char offsets_r_storage[block_size + cacheline_size]; - unsigned char* offsets_l = align_cacheline(offsets_l_storage); - unsigned char* offsets_r = align_cacheline(offsets_r_storage); + unsigned char offsets_l_storage[block_size + cacheline_size]; + unsigned char offsets_r_storage[block_size + cacheline_size]; + unsigned char* offsets_l = align_cacheline(offsets_l_storage); + unsigned char* offsets_r = align_cacheline(offsets_r_storage); #else - alignas(cacheline_size) unsigned char offsets_l_storage[block_size]; - alignas(cacheline_size) unsigned char offsets_r_storage[block_size]; - unsigned char* offsets_l = offsets_l_storage; - unsigned char* offsets_r = offsets_r_storage; + alignas(cacheline_size) unsigned char offsets_l_storage[block_size]; + alignas(cacheline_size) unsigned char offsets_r_storage[block_size]; + unsigned char* offsets_l = offsets_l_storage; + unsigned char* offsets_r = offsets_r_storage; #endif - int num_l, num_r, start_l, start_r; - num_l = num_r = start_l = start_r = 0; - - while (last - first > 2 * block_size) { - // Fill up offset blocks with elements that are on the wrong side. - if (num_l == 0) { - start_l = 0; - RandomAccessIterator it = first; - for (unsigned char i = 0 ; i < block_size ;) { - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - offsets_l[num_l] = i++; num_l += !comp(proj(*it), pivot_proj); ++it; - } - } - if (num_r == 0) { - start_r = 0; - RandomAccessIterator it = last; - for (unsigned char i = 0 ; i < block_size ;) { - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); - offsets_r[num_r] = ++i; num_r += comp(proj(*--it), pivot_proj); + + auto offsets_l_base = first; + auto offsets_r_base = last; + std::size_t num_l, num_r, start_l, start_r; + num_l = num_r = start_l = start_r = 0; + + while (first < last) { + // Fill up offset blocks with elements that are on the wrong side. + // First we determine how much elements are considered for each offset block. + std::size_t num_unknown = last - first; + std::size_t left_split = num_l == 0 ? (num_r == 0 ? num_unknown / 2 : num_unknown) : 0; + std::size_t right_split = num_r == 0 ? (num_unknown - left_split) : 0; + + // Fill the offset blocks. + if (left_split >= block_size) { + for (std::size_t i = 0 ; i < block_size ;) { + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + offsets_l[num_l] = i++; num_l += not comp(proj(*first), pivot_proj); ++first; + } + } else { + for (std::size_t i = 0 ; i < left_split ;) { + offsets_l[num_l] = i++; + num_l += not comp(proj(*first), pivot_proj); + ++first; + } } - } - // Swap elements and update block sizes and first/last boundaries. - int num = std::min(num_l, num_r); - swap_offsets(first, last, offsets_l + start_l, offsets_r + start_r, - num, num_l == num_r); - num_l -= num; num_r -= num; - start_l += num; start_r += num; - if (num_l == 0) first += block_size; - if (num_r == 0) last -= block_size; - } + if (right_split >= block_size) { + for (std::size_t i = 0 ; i < block_size ;) { + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + offsets_r[num_r] = ++i; num_r += comp(proj(*--last), pivot_proj); + } + } else { + for (std::size_t i = 0 ; i < right_split ;) { + offsets_r[num_r] = ++i; + num_r += comp(proj(*--last), pivot_proj); + } + } - int l_size = 0, r_size = 0; - int unknown_left = int(last - first) - ((num_r || num_l) ? block_size : 0); - if (num_r) { - // Handle leftover block by assigning the unknown elements to the other block. - l_size = unknown_left; - r_size = block_size; - } else if (num_l) { - l_size = block_size; - r_size = unknown_left; - } else { - // No leftover block, split the unknown elements in two blocks. - l_size = unknown_left / 2; - r_size = unknown_left - l_size; - } + // Swap elements and update block sizes and first/last boundaries. + std::size_t num = (std::min)(num_l, num_r); + swap_offsets(offsets_l_base, offsets_r_base, + offsets_l + start_l, offsets_r + start_r, + num, num_l == num_r); + num_l -= num; + num_r -= num; + start_l += num; + start_r += num; + + if (num_l == 0) { + start_l = 0; + offsets_l_base = first; + } - // Fill offset buffers if needed. - if (unknown_left && !num_l) { - start_l = 0; - RandomAccessIterator it = first; - for (unsigned char i = 0 ; i < l_size ;) { - offsets_l[num_l] = i++; - num_l += !comp(proj(*it), pivot_proj); - ++it; - } - } - if (unknown_left && !num_r) { - start_r = 0; - RandomAccessIterator it = last; - for (unsigned char i = 0 ; i < r_size ;) { - offsets_r[num_r] = ++i; - num_r += comp(proj(*--it), pivot_proj); + if (num_r == 0) { + start_r = 0; + offsets_r_base = last; + } } - } - int num = std::min(num_l, num_r); - swap_offsets(first, last, offsets_l + start_l, offsets_r + start_r, num, num_l == num_r); - num_l -= num; num_r -= num; - start_l += num; start_r += num; - if (num_l == 0) first += l_size; - if (num_r == 0) last -= r_size; - - // We have now fully identified [first, last)'s proper position. Swap the last elements. - if (num_l) { - offsets_l += start_l; - while (num_l--) { - iter_swap(first + offsets_l[num_l], --last); + // We have now fully identified [first, last)'s proper position. Swap the last elements. + if (num_l) { + offsets_l += start_l; + while (num_l--) { + iter_swap(offsets_l_base + offsets_l[num_l], --last); + } + first = last; } - first = last; - } - if (num_r) { - offsets_r += start_r; - while (num_r--) { - iter_swap(last - offsets_r[num_r], first); - ++first; + if (num_r) { + offsets_r += start_r; + while (num_r--) { + iter_swap(offsets_r_base - offsets_r[num_r], first); + ++first; + } + last = first; } - last = first; } // Put the pivot in the right place. - RandomAccessIterator pivot_pos = first - 1; + auto pivot_pos = first - 1; *begin = iter_move(pivot_pos); *pivot_pos = std::move(pivot); diff --git a/include/cpp-sort/detail/recmerge_forward.h b/include/cpp-sort/detail/recmerge_forward.h index e51e5983..333711e7 100644 --- a/include/cpp-sort/detail/recmerge_forward.h +++ b/include/cpp-sort/detail/recmerge_forward.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -101,16 +101,16 @@ namespace detail std::forward_iterator_tag tag) -> void { - using rvalue_reference = remove_cvref_t>; + using rvalue_type = rvalue_type_t; using difference_type = difference_type_t; if (n0 == 0 || n1 == 0) return; if (n0 <= buff_size) { - destruct_n d(0); + destruct_n d(0); std::unique_ptr< - rvalue_reference, - destruct_n& + rvalue_type, + destruct_n& > h2(buffer, d); auto buff_ptr = uninitialized_move(f0, f1, buffer, d); diff --git a/include/cpp-sort/detail/scope_exit.h b/include/cpp-sort/detail/scope_exit.h index d0542987..4c1aaa38 100644 --- a/include/cpp-sort/detail/scope_exit.h +++ b/include/cpp-sort/detail/scope_exit.h @@ -1,22 +1,77 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SCOPE_EXIT_H_ #define CPPSORT_DETAIL_SCOPE_EXIT_H_ -#ifdef __cpp_lib_uncaught_exceptions - //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// #include #include +#include namespace cppsort { namespace detail { + template + struct scope_exit + { + private: + + EF exit_function; + bool execute_on_destruction = true; + + public: + + template + explicit scope_exit(EFP&& func) + noexcept(std::is_nothrow_constructible::value || + std::is_nothrow_constructible::value): + exit_function(std::forward(func)) + {} + + scope_exit(scope_exit&& rhs) + noexcept(std::is_nothrow_move_constructible::value || + std::is_nothrow_copy_constructible::value): + exit_function(std::forward(rhs.exit_function)) + {} + + ~scope_exit() + noexcept(noexcept(exit_function())) + { + if (execute_on_destruction) { + exit_function(); + } + } + + auto activate() noexcept + -> void + { + execute_on_destruction = true; + } + + auto deactivate() noexcept + -> void + { + execute_on_destruction = false; + } + + scope_exit(const scope_exit&) = delete; + scope_exit& operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + }; + + template + auto make_scope_exit(EF&& function) + -> scope_exit + { + return scope_exit(std::forward(function)); + } + +#ifdef __cpp_lib_uncaught_exceptions template struct scope_success { @@ -49,7 +104,13 @@ namespace detail } } - auto release() noexcept + auto activate() noexcept + -> void + { + execute_on_destruction = true; + } + + auto deactivate() noexcept -> void { execute_on_destruction = false; @@ -66,8 +127,7 @@ namespace detail { return scope_success(std::forward(function)); } -}} - #endif // __cpp_lib_uncaught_exceptions +}} #endif // CPPSORT_DETAIL_SCOPE_EXIT_H_ diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h new file mode 100644 index 00000000..d467fbc6 --- /dev/null +++ b/include/cpp-sort/detail/slabsort.h @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_SLABSORT_H_ +#define CPPSORT_DETAIL_SLABSORT_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include "bitops.h" +#include "fixed_size_list.h" +#include "functional.h" +#include "iterator_traits.h" +#include "melsort.h" +#include "memory.h" +#include "stable_partition.h" +#include "nth_element.h" + +namespace cppsort +{ +namespace detail +{ + // Basically list_node, except that the internal union contains + // either a value or an iterator pointing to that value. This + // allows to perform operations indirectly in a first time, then + // to move the values inside the list at some point. + + template + struct slabsort_list_node + { + using value_type = rvalue_type_t; + + constexpr explicit slabsort_list_node(slabsort_list_node* next) noexcept: + next(next) + {} + + constexpr slabsort_list_node(slabsort_list_node* prev, slabsort_list_node* next) noexcept: + prev(prev), + next(next) + {} + + slabsort_list_node(const slabsort_list_node&) = delete; + slabsort_list_node(slabsort_list_node&&) = delete; + slabsort_list_node& operator=(const slabsort_list_node&) = delete; + slabsort_list_node& operator=(slabsort_list_node&&) = delete; + + ~slabsort_list_node() {} + + union { + value_type value; + Iterator it; + }; + + slabsort_list_node* prev; + slabsort_list_node* next; + }; + + template + auto slabsort_get_median(RandomAccessIterator first, RandomAccessIterator last, + RandomAccessIterator* iterators_buffer, + Compare compare, Projection projection) + -> RandomAccessIterator + { + using difference_type = difference_type_t; + auto size = last - first; + + //////////////////////////////////////////////////////////// + // Bind index to iterator + + destruct_n d(0); + std::unique_ptr&> h2(iterators_buffer, d); + + // Associate iterators to their position + auto ptr = iterators_buffer; + for (difference_type count = 0 ; count != size ; ++count) { + ::new(ptr) RandomAccessIterator(first); + ++d; + ++first; + ++ptr; + } + + //////////////////////////////////////////////////////////// + // Run nth_element stably and indirectly + + return *nth_element( + iterators_buffer, iterators_buffer + size, size / 2, size, + std::move(compare), indirect(std::move(projection)) + ); + } + + template + auto slabsort_partition(RandomAccessIterator first, RandomAccessIterator last, + RandomAccessIterator* iterators_buffer, + Compare compare, Projection projection) + -> void + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + // Note: the partition algorithm is supposed to partition stably in + // order to preserve the presortedness that is suitable for + // melsort, however we don't fully partition stably here: the + // last element and pivot will not be ordered stably, but that + // difference should not have any noticeable impact on the + // adaptivity to presortedness + + auto pivot = slabsort_get_median(first, last, iterators_buffer, compare, projection); + auto last_1 = std::prev(last); + + // Put the pivot at position std::prev(last) and partition + iter_swap(pivot, last_1); + auto&& pivot1 = proj(*last_1); + auto middle1 = detail::stable_partition( + first, last_1, + [&](auto&& elem) { return comp(proj(elem), pivot1); } + ); + + // Put the pivot back in its final position + iter_swap(middle1, last_1); + } + + template + auto try_melsort(RandomAccessIterator first, RandomAccessIterator last, + difference_type_t p, + fixed_size_list_node_pool>& node_pool, + Compare compare, Projection projection) + -> bool + { + using rvalue_type = rvalue_type_t; + using node_type = slabsort_list_node; + using utility::iter_move; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (first == last || std::next(first) == last) { + return true; + } + + // Encroaching lists + std::vector> lists; + lists.emplace_back(node_pool, destroy_node_contents); + lists.back().push_back([&first](node_type* node) { + ::new (&node->it) RandomAccessIterator(first); + }); + + //////////////////////////////////////////////////////////// + // Create encroaching lists + + for (auto it = std::next(first) ; it != last ; ++it) { + auto&& value = proj(*it); + + auto& last_list = lists.back(); + if (not comp(value, proj(*std::prev(last_list.end()).base()->it))) { + // Element belongs to the tails (bigger elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, invert(compare), + [&proj](auto& list) -> decltype(auto) { + return proj(*std::prev(list.end()).base()->it); + } + ); + insertion_point->push_back([&it](node_type* node) { + ::new (&node->it) RandomAccessIterator(it); + }); + } else if (not comp(proj(*last_list.begin().base()->it), value)) { + // Element belongs to the heads (smaller elements) + auto insertion_point = detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, compare, + [&proj](auto& list) -> decltype(auto) { + return proj(*list.begin().base()->it); + } + ); + insertion_point->push_front([&it](node_type* node) { + ::new (&node->it) RandomAccessIterator(it); + }); + } else { + // Element does not belong to the existing encroaching lists, + // create a new list for it + lists.emplace_back(node_pool, destroy_node_contents); + lists.back().push_back([&it](node_type* node) { + ::new (&node->it) RandomAccessIterator(it); + }); + } + + // Too many encroaching lists have been created (Enc > p), + // give up, the elements of the collection remain in their + // order of creation + using difference_type = difference_type_t; + if (difference_type(lists.size()) >= p) { + return false; + } + } + + //////////////////////////////////////////////////////////// + // Reify encroaching lists + + for (auto& list : lists) { + // TODO: handle destruction when an exception is thrown during the loop + for (auto it = list.begin(), end = list.end() ; it != end ; ++it) { + auto node = it.base(); + auto value_it = node->it; + detail::destroy_at(&node->it); + ::new(&node->value) rvalue_type(iter_move(value_it)); + } + list.set_node_destructor(destroy_node_contents); + } + + //////////////////////////////////////////////////////////// + // Merge encroaching lists + + // We only ever reach this point in the algorithm when only + // a few encroaching lists have been created, therefore the + // edges extraction optimization can never be worth it + merge_encroaching_lists(lists, first, false, std::move(compare), std::move(projection)); + return true; + } + + template + auto slabsort_impl(RandomAccessIterator first, RandomAccessIterator last, + difference_type_t size, + difference_type_t original_p, + difference_type_t current_p, + RandomAccessIterator* iterators_buffer, + fixed_size_list_node_pool>& node_pool, + Compare compare, Projection projection) + -> void + { + if (size < 2) { + return; + } + + slabsort_partition(first, last, iterators_buffer, compare, projection); + auto left_size = (last - first) / 2; + auto right_size = size - left_size; + auto middle = first + left_size; + if (current_p > 2) { + // Partition further until the partitions are small enough + slabsort_impl(first, middle, left_size, original_p, current_p / 2, + iterators_buffer, node_pool, compare, projection); + slabsort_impl(middle, last, right_size, original_p, current_p / 2, + iterators_buffer, node_pool, compare, projection); + } else { + // The partitions are small enough, try to use melsort on them, + // if too many encroaching lists are created, cancel and recurse + bool done = try_melsort(first, middle, original_p, node_pool, compare, projection); + if (not done) { + slabsort_impl(first, middle, left_size, + original_p * original_p, original_p * original_p, + iterators_buffer, node_pool, compare, projection); + } + done = try_melsort(middle, last, original_p, node_pool, compare, projection); + if (not done) { + slabsort_impl(middle, last, right_size, + original_p * original_p, original_p * original_p, + iterators_buffer, node_pool, compare, projection); + } + } + } + + template + auto slabsort(RandomAccessIterator first, RandomAccessIterator last, + Compare compare, Projection projection) + -> void + { + // Node pool used by all try_melsort invocations + auto size = last - first; + using node_type = slabsort_list_node; + fixed_size_list_node_pool node_pool(size); + + // Take advantage of existing presortedness once before the partitioning + // partly gets rid of it, this makes a difference for collections with + // a few bigs runs + bool done = try_melsort(first, last, 2 * detail::log2(size), + node_pool, compare, projection); + if (done) { + return; + } + + // Allocate a buffer that will be used for median finding + std::unique_ptr iterators_buffer( + static_cast(::operator new(size * sizeof(RandomAccessIterator))), + operator_deleter(size * sizeof(RandomAccessIterator)) + ); + + difference_type_t original_p = 2; + return slabsort_impl( + first, last, size, original_p, original_p, + iterators_buffer.get(), node_pool, + std::move(compare), std::move(projection) + ); + } +}} + +#endif // CPPSORT_DETAIL_SLABSORT_H_ diff --git a/include/cpp-sort/detail/spinsort.h b/include/cpp-sort/detail/spinsort.h index b947075f..59bd8c4c 100644 --- a/include/cpp-sort/detail/spinsort.h +++ b/include/cpp-sort/detail/spinsort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -32,6 +32,7 @@ #include "boost_common/range.h" #include "bitops.h" #include "config.h" +#include "functional.h" #include "insertion_sort.h" #include "is_sorted_until.h" #include "iterator_traits.h" @@ -48,48 +49,6 @@ namespace detail { using boost_common::range; - //////////////////////////////////////////////////////////// - // Equivalent to C++17 std::not_fn - - template - class not_fn_t - { - private: - - Predicate predicate; - - public: - - not_fn_t() = delete; - - explicit not_fn_t(Predicate predicate): - predicate(std::move(predicate)) - {} - - template - auto operator()(T1&& x, T2&& y) - -> bool - { - auto&& pred = utility::as_function(predicate); - return not pred(std::forward(x), std::forward(y)); - } - - template - auto operator()(T1&& x, T2&& y) const - -> bool - { - auto&& pred = utility::as_function(predicate); - return not pred(std::forward(x), std::forward(y)); - } - }; - - template - auto not_fn(Predicate&& pred) - -> not_fn_t> - { - return not_fn_t>(std::forward(pred)); - } - template auto sort_range_sort(const range& rng_data, const range& rng_aux, Compare compare, Projection projection) @@ -206,7 +165,7 @@ namespace detail return false; } - it = detail::is_sorted_until(rng_data.first, rng_data.last, spin_detail::not_fn(compare), projection); + it = detail::is_sorted_until(rng_data.first, rng_data.last, detail::not_fn(compare), projection); if (std::size_t(rng_data.last - it) >= min_insert_partial_sort) { return false; } @@ -333,17 +292,16 @@ namespace detail { private: - using value_t = value_type_t; using range_it = range; - using rvalue_reference = remove_cvref_t>; - using range_buf = range; + using rvalue_type = rvalue_type_t; + using range_buf = range; // When the number of elements to sort is smaller than Sort_min, are sorted // by the insertion sort algorithm static constexpr std::uint32_t Sort_min = 36; // Pointer to the auxiliary memory - std::unique_ptr ptr; + std::unique_ptr ptr; // Number of elements in the auxiliary memory std::size_t nptr; @@ -380,13 +338,13 @@ namespace detail nptr = (nelem + 1) >> 1; std::size_t nelem_1 = nptr; std::size_t nelem_2 = nelem - nelem_1; - ptr.reset(static_cast( - ::operator new(nptr * sizeof(rvalue_reference)) + ptr.reset(static_cast( + ::operator new(nptr * sizeof(rvalue_type)) )); range_buf range_aux(ptr.get(), (ptr.get() + nptr)); - destruct_n d(0); - std::unique_ptr&> h2(ptr.get(), d); + destruct_n d(0); + std::unique_ptr&> h2(ptr.get(), d); //--------------------------------------------------------------------- // Process diff --git a/include/cpp-sort/detail/stable_partition.h b/include/cpp-sort/detail/stable_partition.h new file mode 100644 index 00000000..67e54f57 --- /dev/null +++ b/include/cpp-sort/detail/stable_partition.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ + +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CPPSORT_DETAIL_STABLE_PARTITION_H_ +#define CPPSORT_DETAIL_STABLE_PARTITION_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include "iterator_traits.h" +#include "memory.h" +#include "rotate.h" + +namespace cppsort +{ +namespace detail +{ + template + auto stable_partition(BidirectionalIterator first, BidirectionalIterator last, Predicate pred, + difference_type_t len, + temporary_buffer>& buffer) + -> BidirectionalIterator + { + using rvalue_type = rvalue_type_t; + using utility::iter_move; + using utility::iter_swap; + + // *first is known to be false + // *last is known to be true + // len >= 2 + if (len == 2) { + iter_swap(first, last); + return last; + } + if (len == 3) { + auto m = first; + if (pred(*++m)) { + iter_swap(first, m); + iter_swap(m, last); + return last; + } + iter_swap(m, last); + iter_swap(first, m); + return m; + } + if (len <= buffer.size()) { + // The buffer is big enough to use + destruct_n d(0); + std::unique_ptr&> h(buffer.data(), d); + // Move the falses into the temporary buffer, and the trues to the front of the line + // Update first to always point to the end of the trues + auto ptr = buffer.data(); + ::new (ptr) rvalue_type(iter_move(first)); + ++d; + ++ptr; + auto it = first; + while (++it != last) { + if (pred(*it)) { + *first = iter_move(it); + ++first; + } else { + ::new (ptr) rvalue_type(iter_move(it)); + ++d; + ++ptr; + } + } + // move *last, known to be true + *first = iter_move(it); + it = ++first; + // All trues now at start of range, all falses in buffer + // Move falses back into range, but don't mess up first which points to first false + for (auto t2 = buffer.data() ; t2 < ptr ; ++t2, (void) ++it) + *it = iter_move(t2); + // h destructs moved-from values out of the temp buffer, but doesn't deallocate buffer + return first; + } + // Else not enough buffer, do in place + // len >= 4 + auto m = first; + auto len2 = len / 2; // len2 >= 2 + std::advance(m, len2); + // recurse on [first, m-1], except reduce m-1 until *(m-1) is true, *first know to be false + // F????????????????T + // f m l + auto m1 = m; + auto first_false = first; + auto len_half = len2; + while (not pred(*--m1)) { + if (m1 == first) { + goto first_half_done; + } + --len_half; + } + // F???TFFF?????????T + // f m1 m l + first_false = stable_partition(first, m1, pred, len_half, buffer); + first_half_done: + // TTTFFFFF?????????T + // f ff m l + // recurse on [m, last], except increase m until *(m) is false, *last know to be true + m1 = m; + auto second_false = last; + ++second_false; + len_half = len - len2; + while (pred(*m1)) { + if (++m1 == last) { + goto second_half_done; + } + --len_half; + } + // TTTFFFFFTTTF?????T + // f ff m m1 l + second_false = stable_partition(m1, last, std::move(pred), len_half, buffer); + second_half_done: + // TTTFFFFFTTTTTFFFFF + // f ff m sf l + return detail::rotate(first_false, m, second_false); + // TTTTTTTTFFFFFFFFFF + // | + } + + template + auto stable_partition(BidirectionalIterator first, BidirectionalIterator last, Predicate predicate) + -> BidirectionalIterator + { + using difference_type = difference_type_t; + auto&& pred = utility::as_function(predicate); + + // might want to make this a function of trivial assignment + constexpr difference_type alloc_limit = 4; + + // Either prove all true and return first or point to first false + while (true) { + if (first == last) { + return first; + } + if (not pred(*first)) { + break; + } + ++first; + } + // first points to first false, everything prior to first is already set. + // Either prove [first, last) is all false and return first, or point last to last true + do { + if (first == --last) { + return first; + } + } while (not pred(*last)); + // We now have a reduced range [first, last] + // *first is known to be false + // *last is known to be true + // len >= 2 + auto len = std::distance(first, last) + 1; + temporary_buffer> buffer(nullptr); + if (len >= alloc_limit) { + buffer.try_grow(len); + } + return stable_partition(first, last, std::move(pred), len, buffer); + } +}} + +#endif // CPPSORT_DETAIL_STABLE_PARTITION_H_ diff --git a/include/cpp-sort/detail/swap_if.h b/include/cpp-sort/detail/swap_if.h index ecb47b87..1a1cb511 100644 --- a/include/cpp-sort/detail/swap_if.h +++ b/include/cpp-sort/detail/swap_if.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SWAP_IF_H_ @@ -51,7 +51,7 @@ namespace detail -> std::enable_if_t::value> { Integer dx = x; - x = std::min(x, y); + x = (std::min)(x, y); y ^= dx ^ x; } @@ -60,8 +60,8 @@ namespace detail -> std::enable_if_t::value> { Float dx = x; - x = std::min(x, y); - y = std::max(dx, y); + x = (std::min)(x, y); + y = (std::max)(dx, y); } template @@ -69,7 +69,7 @@ namespace detail -> std::enable_if_t::value> { Integer dx = x; - x = std::max(x, y); + x = (std::max)(x, y); y ^= dx ^ x; } @@ -78,8 +78,8 @@ namespace detail -> std::enable_if_t::value> { Float dx = x; - x = std::max(x, y); - y = std::min(dx, y); + x = (std::max)(x, y); + y = (std::min)(dx, y); } #if CPPSORT_STD_IDENTITY_AVAILABLE diff --git a/include/cpp-sort/detail/swap_ranges.h b/include/cpp-sort/detail/swap_ranges.h index abbee439..9056283f 100644 --- a/include/cpp-sort/detail/swap_ranges.h +++ b/include/cpp-sort/detail/swap_ranges.h @@ -16,8 +16,6 @@ #include "move.h" #include "type_traits.h" -#include - namespace cppsort { namespace detail diff --git a/include/cpp-sort/detail/timsort.h b/include/cpp-sort/detail/timsort.h index a3b21c0f..a38587f7 100644 --- a/include/cpp-sort/detail/timsort.h +++ b/include/cpp-sort/detail/timsort.h @@ -73,8 +73,7 @@ namespace detail class TimSort { using iterator = RandomAccessIterator; - using value_type = value_type_t; - using rvalue_reference = remove_cvref_t>; + using rvalue_type = rvalue_type_t; using difference_type = difference_type_t; static constexpr int min_merge = 32; @@ -83,7 +82,7 @@ namespace detail difference_type minGallop_ = min_gallop; // Buffer used for merges - std::unique_ptr buffer; + std::unique_ptr buffer; std::ptrdiff_t buffer_size = 0; // Silence GCC -Winline warning @@ -114,7 +113,7 @@ namespace detail difference_type runLen = countRunAndMakeAscending(cur, hi, compare, projection); if (runLen < minRun) { - difference_type const force = std::min(nRemaining, minRun); + difference_type const force = (std::min)(nRemaining, minRun); binarySort(cur, cur + force, cur + runLen, compare, projection); runLen = force; } @@ -417,9 +416,9 @@ namespace detail // easily avoidable out-of-memory errors and make sized // deallocation work properly buffer.reset(nullptr); - buffer.get_deleter() = operator_deleter(new_size * sizeof(rvalue_reference)); - buffer.reset(static_cast( - ::operator new(new_size * sizeof(rvalue_reference)) + buffer.get_deleter() = operator_deleter(new_size * sizeof(rvalue_type)); + buffer.reset(static_cast( + ::operator new(new_size * sizeof(rvalue_type)) )); buffer_size = new_size; } @@ -447,8 +446,8 @@ namespace detail } resize_buffer(len1); - destruct_n d(0); - std::unique_ptr&> h2(buffer.get(), d); + destruct_n d(0); + std::unique_ptr&> h2(buffer.get(), d); uninitialized_move(base1, base1 + len1, buffer.get(), d); auto cursor1 = buffer.get(); @@ -591,8 +590,8 @@ namespace detail } resize_buffer(len2); - destruct_n d(0); - std::unique_ptr&> h2(buffer.get(), d); + destruct_n d(0); + std::unique_ptr&> h2(buffer.get(), d); uninitialized_move(base2, base2 + len2, buffer.get(), d); auto cursor1 = base1 + len1; diff --git a/include/cpp-sort/fwd.h b/include/cpp-sort/fwd.h index e374058e..953a808a 100644 --- a/include/cpp-sort/fwd.h +++ b/include/cpp-sort/fwd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_FWD_H_ @@ -26,6 +26,7 @@ namespace cppsort template struct block_sorter; + struct cartesian_tree_sorter; struct counting_sorter; struct default_sorter; struct drop_merge_sorter; @@ -35,6 +36,7 @@ namespace cppsort struct heap_sorter; struct insertion_sorter; struct integer_spread_sorter; + struct mel_sorter; struct merge_insertion_sorter; struct merge_sorter; struct pdq_sorter; @@ -43,6 +45,7 @@ namespace cppsort struct quick_sorter; struct selection_sorter; struct ska_sorter; + struct slab_sorter; struct smooth_sorter; struct spin_sorter; struct split_sorter; diff --git a/include/cpp-sort/probes.h b/include/cpp-sort/probes.h index 8b9a9ace..e58dd791 100644 --- a/include/cpp-sort/probes.h +++ b/include/cpp-sort/probes.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_H_ @@ -19,5 +19,6 @@ #include #include #include +#include #endif // CPPSORT_PROBES_H_ diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index 79cbbf66..69cc87c6 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_DIS_H_ @@ -48,7 +48,7 @@ namespace probe difference_type dist = 1; // Distance between it1 and it2 for (auto it2 = std::next(it1) ; it2 != last ; ++it2) { if (comp(proj(*it2), value)) { - max_dist = std::max(max_dist, dist); + max_dist = (std::max)(max_dist, dist); } ++dist; } @@ -93,6 +93,13 @@ namespace probe return dis_probe_algo(first, last, std::distance(first, last), std::move(compare), std::move(projection)); } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } }; } diff --git a/include/cpp-sort/probes/enc.h b/include/cpp-sort/probes/enc.h index dbc1e5c1..ee6e442a 100644 --- a/include/cpp-sort/probes/enc.h +++ b/include/cpp-sort/probes/enc.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_ENC_H_ @@ -18,7 +18,9 @@ #include #include #include +#include "../detail/functional.h" #include "../detail/iterator_traits.h" +#include "../detail/lower_bound.h" namespace cppsort { @@ -43,55 +45,54 @@ namespace probe auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); - // Head an tail of encroaching lists + if (first == last || std::next(first) == last) { + return 0; + } + + // Heads an tails of encroaching lists std::vector> lists; + lists.emplace_back(first, first); + ++first; + + //////////////////////////////////////////////////////////// + // Create encroaching lists while (first != last) { auto&& value = proj(*first); - // Binary search for an encroaching list where - // value <= list.first or value >= list.second - - // Whether the found value is smaller than the head - // of the found encroaching list or greater than its - // tail - bool value_is_smaller = true; - - auto size = lists.size(); - auto res_it = lists.begin(); - while (size > 0) { - auto it = res_it; - std::advance(it, size / 2); - if (not comp(proj(*it->first), value)) { - size /= 2; - value_is_smaller = true; - } else if (not comp(value, proj(*it->second))) { - size /= 2; - value_is_smaller = false; - } else { - res_it = ++it; - size -= size / 2 + 1; - } - } - - if (res_it != lists.end()) { - // Change the head or tail of the found encroaching list - // if any has been found - if (value_is_smaller) { - res_it->first = first; - } else { - res_it->second = first; - } + auto& last_list = lists.back(); + if (not comp(value, proj(*last_list.second))) { + // Element belongs to the tails (bigger elements) + auto insertion_point = cppsort::detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, + cppsort::detail::invert(compare), + [&proj](auto& list) -> decltype(auto) { return proj(*list.second); } + ); + insertion_point->second = first; + } else if (not comp(proj(*last_list.first), value)) { + // Element belongs to the heads (smaller elements) + auto insertion_point = cppsort::detail::lower_bound( + lists.begin(), std::prev(lists.end()), value, compare, + [&proj](auto& list) -> decltype(auto) { return proj(*list.first); } + ); + insertion_point->first = first; } else { - // Create a new encroaching list if the element - // didn't fit in any of the existing ones + // Element does not belong to the existing encroaching lists, + // create a new list for it lists.emplace_back(first, first); } ++first; } - return lists.size() ? lists.size() - 1 : 0; + return lists.size() - 1; + } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : (n + 1) / 2 - 1; } }; } diff --git a/include/cpp-sort/probes/exc.h b/include/cpp-sort/probes/exc.h index 5edde18e..81f55de9 100644 --- a/include/cpp-sort/probes/exc.h +++ b/include/cpp-sort/probes/exc.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_EXC_H_ @@ -19,7 +19,7 @@ #include #include #include -#include "../detail/indirect_compare.h" +#include "../detail/functional.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -54,10 +54,9 @@ namespace probe } // Sort the iterators on pointed values - pdqsort( - iterators.begin(), iterators.end(), - cppsort::detail::make_indirect_compare(compare, projection), - utility::identity{} + cppsort::detail::pdqsort( + iterators.begin(), iterators.end(), compare, + cppsort::detail::indirect(projection) ); //////////////////////////////////////////////////////////// @@ -142,6 +141,13 @@ namespace probe return exc_probe_algo(first, last, std::distance(first, last), std::move(compare), std::move(projection)); } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } }; } diff --git a/include/cpp-sort/probes/ham.h b/include/cpp-sort/probes/ham.h index e6a12a10..cf7bb923 100644 --- a/include/cpp-sort/probes/ham.h +++ b/include/cpp-sort/probes/ham.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_HAM_H_ @@ -19,7 +19,7 @@ #include #include #include -#include "../detail/indirect_compare.h" +#include "../detail/functional.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -55,9 +55,8 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( - iterators.begin(), iterators.end(), - cppsort::detail::make_indirect_compare(compare, projection), - utility::identity{} + iterators.begin(), iterators.end(), compare, + cppsort::detail::indirect(projection) ); //////////////////////////////////////////////////////////// @@ -108,6 +107,13 @@ namespace probe return ham_probe_algo(first, last, std::distance(first, last), std::move(compare), std::move(projection)); } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n; + } }; } diff --git a/include/cpp-sort/probes/inv.h b/include/cpp-sort/probes/inv.h index b9f84282..8158dd03 100644 --- a/include/cpp-sort/probes/inv.h +++ b/include/cpp-sort/probes/inv.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_INV_H_ @@ -19,7 +19,7 @@ #include #include #include "../detail/count_inversions.h" -#include "../detail/indirect_compare.h" +#include "../detail/functional.h" #include "../detail/iterator_traits.h" namespace cppsort @@ -50,8 +50,8 @@ namespace probe return cppsort::detail::count_inversions( iterators.get(), iterators.get() + size, buffer.get(), - cppsort::detail::indirect_compare(std::move(compare), - std::move(projection)) + std::move(compare), + cppsort::detail::indirect(std::move(projection)) ); } @@ -89,6 +89,13 @@ namespace probe return inv_probe_algo(first, last, std::distance(first, last), std::move(compare), std::move(projection)); } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n * (n - 1) / 2; + } }; } diff --git a/include/cpp-sort/probes/max.h b/include/cpp-sort/probes/max.h index 7df24f76..2f7583e3 100644 --- a/include/cpp-sort/probes/max.h +++ b/include/cpp-sort/probes/max.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_MAX_H_ @@ -21,7 +21,7 @@ #include #include #include "../detail/equal_range.h" -#include "../detail/indirect_compare.h" +#include "../detail/functional.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" @@ -56,9 +56,8 @@ namespace probe // Sort the iterators on pointed values cppsort::detail::pdqsort( - iterators.begin(), iterators.end(), - cppsort::detail::make_indirect_compare(compare, projection), - utility::identity{} + iterators.begin(), iterators.end(), compare, + cppsort::detail::indirect(projection) ); //////////////////////////////////////////////////////////// @@ -70,18 +69,17 @@ namespace probe for (auto it = first ; it != last ; ++it) { // Find the range where *first belongs once sorted auto rng = cppsort::detail::equal_range( - iterators.begin(), iterators.end(), proj(*it), compare, - [&proj](const auto& iterator) { - return proj(*iterator); - }); + iterators.begin(), iterators.end(), proj(*it), + compare, cppsort::detail::indirect(projection) + ); auto pos_min = std::distance(iterators.begin(), rng.first); auto pos_max = std::distance(iterators.begin(), rng.second); // If *first isn't into one of its sorted positions, computed the closest if (it_pos < pos_min) { - max_dist = std::max(pos_min - it_pos, max_dist); + max_dist = (std::max)(pos_min - it_pos, max_dist); } else if (it_pos >= pos_max) { - max_dist = std::max(it_pos - pos_max + 1, max_dist); + max_dist = (std::max)(it_pos - pos_max + 1, max_dist); } ++it_pos; @@ -123,6 +121,13 @@ namespace probe return max_probe_algo(first, last, std::distance(first, last), std::move(compare), std::move(projection)); } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } }; } diff --git a/include/cpp-sort/probes/mono.h b/include/cpp-sort/probes/mono.h index e7add06a..82b2daf4 100644 --- a/include/cpp-sort/probes/mono.h +++ b/include/cpp-sort/probes/mono.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_MONO_H_ @@ -97,6 +97,13 @@ namespace probe } return count; } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : (n + 1) / 2 - 1; + } }; } diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index bb8f2811..78107d9e 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_OSC_H_ @@ -58,8 +58,8 @@ namespace probe while (next != last) { - if (comp(std::min(proj(*current), proj(*next)), value) && - comp(value, std::max(proj(*current), proj(*next)))) + if (comp((std::min)(proj(*current), proj(*next)), value) && + comp(value, (std::max)(proj(*current), proj(*next)))) { ++count; } @@ -70,6 +70,13 @@ namespace probe } return count; } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : (n * (n - 2) - 1) / 2; + } }; } diff --git a/include/cpp-sort/probes/par.h b/include/cpp-sort/probes/par.h index 1281aa13..dae6d1a2 100644 --- a/include/cpp-sort/probes/par.h +++ b/include/cpp-sort/probes/par.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_PAR_H_ @@ -51,6 +51,13 @@ namespace probe } return res; } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } }; } diff --git a/include/cpp-sort/probes/rem.h b/include/cpp-sort/probes/rem.h index e2dd1602..d390b25c 100644 --- a/include/cpp-sort/probes/rem.h +++ b/include/cpp-sort/probes/rem.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_REM_H_ @@ -11,15 +11,13 @@ #include #include #include -#include +#include #include #include -#include #include #include #include -#include "../detail/iterator_traits.h" -#include "../detail/upper_bound.h" +#include "../detail/longest_non_descending_subsequence.h" namespace cppsort { @@ -27,76 +25,6 @@ namespace probe { namespace detail { - template< - bool RecomputeSize, - typename ForwardIterator, - typename Compare, - typename Projection - > - auto rem_probe_algo(ForwardIterator first, ForwardIterator last, - cppsort::detail::difference_type_t size, - Compare compare, Projection projection) - -> ::cppsort::detail::difference_type_t - { - constexpr bool is_random_access = std::is_base_of< - std::random_access_iterator_tag, - cppsort::detail::iterator_category_t - >::value; - - // Compute Rem as n - longest non-decreasing subsequence, - // where the LNDS is computed with an altered patience - // sorting algorithm - - if (first == last || std::next(first) == last) { - return 0; - } - - // The size is only needed at the end to actually compute Rem, but - // we can compute it as-we-go when it is not known in order to avoid - // making two passes over the sequence - when the sequence is made - // of random-access iterators, we only compute it once - if (RecomputeSize && is_random_access) { - size = std::distance(first, last); - } - - auto&& comp = utility::as_function(compare); - auto&& proj = utility::as_function(projection); - - // Top (smaller) elements in patience sorting stacks - std::vector stack_tops; - - auto deref_compare = [&](const auto& lhs, auto rhs_it) mutable { - return comp(lhs, *rhs_it); - }; - auto deref_proj = [&](const auto& value) mutable -> decltype(auto) { - return proj(value); - }; - - while (first != last) { - auto it = cppsort::detail::upper_bound( - stack_tops.begin(), stack_tops.end(), - proj(*first), deref_compare, deref_proj); - - if (it == stack_tops.end()) { - // The element is bigger than everything else, - // create a new "stack" to put it - stack_tops.emplace_back(first); - } else { - // The element is strictly smaller than the top - // of a given stack, replace the stack top - *it = first; - } - ++first; - - if (RecomputeSize && not is_random_access) { - // Compute the size as-we-go if iterators are not random-access - ++size; - } - } - - return stack_tops.size() ? size - stack_tops.size() : 0; - } - struct rem_impl { template< @@ -124,9 +52,13 @@ namespace probe // with the assumption that it's better than O(n) - which is at least // consistent as far as the standard library is concerned. We also // handle C arrays whose size is known and part of the type. - return rem_probe_algo(std::begin(iterable), std::end(iterable), - utility::size(iterable), - std::move(compare), std::move(projection)); + auto res = cppsort::detail::longest_non_descending_subsequence( + std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection) + ); + auto lnds_size = res.second - res.first; + return lnds_size >= 0 ? lnds_size : 0; } template< @@ -144,7 +76,18 @@ namespace probe // We give 0 as a "dummy" value since it will be recomputed, but it // is also used by the non-random-access iterators version as the // initial value used for the size count - return rem_probe_algo(first, last, 0, std::move(compare), std::move(projection)); + auto res = cppsort::detail::longest_non_descending_subsequence( + first, last, 0, std::move(compare), std::move(projection) + ); + auto lnds_size = res.second - res.first; + return lnds_size >= 0 ? lnds_size : 0; + } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; } }; } diff --git a/include/cpp-sort/probes/runs.h b/include/cpp-sort/probes/runs.h index 6b625a72..444b7e58 100644 --- a/include/cpp-sort/probes/runs.h +++ b/include/cpp-sort/probes/runs.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_PROBES_RUNS_H_ @@ -63,6 +63,13 @@ namespace probe } return count; } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } }; } diff --git a/include/cpp-sort/probes/sus.h b/include/cpp-sort/probes/sus.h new file mode 100644 index 00000000..18d8e1de --- /dev/null +++ b/include/cpp-sort/probes/sus.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_PROBES_SUS_H_ +#define CPPSORT_PROBES_SUS_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/functional.h" +#include "../detail/longest_non_descending_subsequence.h" + +namespace cppsort +{ +namespace probe +{ + namespace detail + { + struct sus_impl + { + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> decltype(auto) + { + // We don't need the size information, so we can avoid + // computing it altogether + auto res = cppsort::detail::longest_non_descending_subsequence( + first, last, + 0, // Dummy value, not useful here + cppsort::detail::not_fn(compare), std::move(projection) + ); + return res.first > 0 ? res.first - 1 : 0; + } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } + }; + } + + namespace + { + constexpr auto&& sus = utility::static_const< + sorter_facade + >::value; + } +}} + +#endif // CPPSORT_PROBES_SUS_H_ diff --git a/include/cpp-sort/sorter_facade.h b/include/cpp-sort/sorter_facade.h index 8da56705..991e172a 100644 --- a/include/cpp-sort/sorter_facade.h +++ b/include/cpp-sort/sorter_facade.h @@ -22,6 +22,20 @@ namespace cppsort { namespace detail { + template + struct invoker + { + // This function is used to create function pointers from + // stateless sorters + + template + static constexpr auto invoke(Args... args) + -> Ret + { + return Sorter{}(std::forward(args)...); + } + }; + // Helper class to allow to convert a sorter_facade into a variety // of function pointers, but only if the wrapped sorter is empty: // conversion to function pointer does not make sense when state @@ -35,125 +49,69 @@ namespace cppsort { protected: - // Function pointer types, a type alias is required - // for the function pointer conversion operator syntax - // to be valid - - template - using fptr_t = detail::invoke_result_t(*)(Iterable&); - - template - using fptr_rvalue_t = detail::invoke_result_t(*)(Iterable&&); - - template - using fptr_func_t = detail::invoke_result_t(*)(Iterable&, Func); - - template - using fptr_rvalue_func_t = detail::invoke_result_t(*)(Iterable&&, Func); - - template - using fptr_func2_t - = detail::invoke_result_t(*)(Iterable&, Func1, Func2); - - template - using fptr_rvalue_func2_t - = detail::invoke_result_t(*)(Iterable&&, Func1, Func2); - - template - using fptr_it_t = detail::invoke_result_t(*)(Iterator, Iterator); - - template - using fptr_func_it_t - = detail::invoke_result_t(*)(Iterator, Iterator, Func); + // A type alias is required for the function pointer + // conversion operator syntax to be valid - template - using fptr_func2_it_t - = detail::invoke_result_t(*)(Iterator, Iterator, Func1, Func2); + template + using fptr_t = Ret(*)(Args...); public: //////////////////////////////////////////////////////////// // Conversion to function pointers - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_t() const + template + constexpr operator fptr_t() const { - return [](Iterable& iterable) { - return Sorter{}(iterable); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_rvalue_t() const + template + constexpr operator fptr_t() const { - return [](Iterable&& iterable) { - return Sorter{}(std::move(iterable)); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_func_t() const + template + constexpr operator fptr_t() const { - return [](Iterable& iterable, Func func) { - return Sorter{}(iterable, func); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_rvalue_func_t() const + template + constexpr operator fptr_t() const { - return [](Iterable&& iterable, Func func) { - return Sorter{}(std::move(iterable), func); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_func2_t() const + template + constexpr operator fptr_t() const { - return [](Iterable& iterable, Func1 func1, Func2 func2) { - return Sorter{}(iterable, func1, func2); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_rvalue_func2_t() const + template + constexpr operator fptr_t() const { - return [](Iterable&& iterable, Func1 func1, Func2 func2) { - return Sorter{}(std::move(iterable), func1, func2); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_it_t() const + template + constexpr operator fptr_t() const { - return [](Iterator first, Iterator last) { - return Sorter{}(first, last); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_func_it_t() const + template + constexpr operator fptr_t() const { - return [](Iterator first, Iterator last, Func func) { - return Sorter{}(first, last, func); - }; + return invoker::template invoke; } - template - CPPSORT_CONSTEXPR_AFTER_CXX14 - operator fptr_func2_it_t() const + template + constexpr operator fptr_t() const { - return [](Iterator first, Iterator last, Func1 func1, Func2 func2) { - return Sorter{}(first, last, func1, func2); - }; + return invoker::template invoke; } }; @@ -187,14 +145,14 @@ namespace cppsort // Non-comparison overloads template - auto operator()(Iterator first, Iterator last) const + constexpr auto operator()(Iterator first, Iterator last) const -> decltype(Sorter::operator()(std::move(first), std::move(last))) { return Sorter::operator()(std::move(first), std::move(last)); } template - auto operator()(Iterable&& iterable) const + constexpr auto operator()(Iterable&& iterable) const -> std::enable_if_t< detail::has_sort::value, decltype(Sorter::operator()(std::forward(iterable))) @@ -204,7 +162,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable) const + constexpr auto operator()(Iterable&& iterable) const -> std::enable_if_t< not detail::has_sort::value, decltype(Sorter::operator()(std::begin(iterable), std::end(iterable))) @@ -217,7 +175,7 @@ namespace cppsort // Comparison overloads template - auto operator()(Iterator first, Iterator last, Compare compare) const + constexpr auto operator()(Iterator first, Iterator last, Compare compare) const -> std::enable_if_t< detail::has_comparison_sort_iterator< Sorter, @@ -233,7 +191,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Compare compare) const + constexpr auto operator()(Iterable&& iterable, Compare compare) const -> std::enable_if_t< detail::has_comparison_sort< Sorter, @@ -249,7 +207,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Compare compare) const + constexpr auto operator()(Iterable&& iterable, Compare compare) const -> std::enable_if_t< not detail::has_comparison_sort< Sorter, @@ -273,7 +231,7 @@ namespace cppsort // Projection overloads template - auto operator()(Iterator first, Iterator last, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, Projection projection) const -> std::enable_if_t< not detail::has_comparison_sort_iterator< Sorter, @@ -294,7 +252,7 @@ namespace cppsort } template - auto operator()(Iterator first, Iterator last, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, Projection projection) const -> std::enable_if_t< not detail::has_projection_sort_iterator< Sorter, @@ -316,7 +274,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Projection projection) const -> std::enable_if_t< not detail::has_comparison_sort< Sorter, @@ -337,7 +295,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Projection projection) const -> std::enable_if_t< not detail::has_comparison_sort< Sorter, @@ -364,7 +322,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Projection projection) const -> std::enable_if_t< not detail::has_comparison_sort< Sorter, @@ -401,7 +359,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Projection projection) const -> std::enable_if_t< not detail::has_projection_sort< Sorter, @@ -437,7 +395,7 @@ namespace cppsort // std::less<> overloads template - auto operator()(Iterator first, Iterator last, std::less<>) const + constexpr auto operator()(Iterator first, Iterator last, std::less<>) const -> std::enable_if_t< not detail::has_comparison_sort_iterator>::value, decltype(Sorter::operator()(std::move(first), std::move(last))) @@ -447,7 +405,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<>) const + constexpr auto operator()(Iterable&& iterable, std::less<>) const -> std::enable_if_t< not detail::has_comparison_sort_iterator< Sorter, @@ -462,7 +420,7 @@ namespace cppsort #ifdef __cpp_lib_ranges template - auto operator()(Iterator first, Iterator last, std::ranges::less) const + constexpr auto operator()(Iterator first, Iterator last, std::ranges::less) const -> std::enable_if_t< not detail::has_comparison_sort_iterator::value, decltype(Sorter::operator()(std::move(first), std::move(last))) @@ -472,7 +430,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::ranges::less) const + constexpr auto operator()(Iterable&& iterable, std::ranges::less) const -> std::enable_if_t< not detail::has_comparison_sort_iterator< Sorter, @@ -490,7 +448,7 @@ namespace cppsort // utility::identity overloads template - auto operator()(Iterator first, Iterator last, utility::identity) const + constexpr auto operator()(Iterator first, Iterator last, utility::identity) const -> std::enable_if_t< not detail::has_projection_sort_iterator::value && not detail::has_comparison_projection_sort_iterator< @@ -506,7 +464,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, utility::identity) const + constexpr auto operator()(Iterable&& iterable, utility::identity) const -> std::enable_if_t< not detail::has_projection_sort_iterator< Sorter, @@ -527,7 +485,7 @@ namespace cppsort #if CPPSORT_STD_IDENTITY_AVAILABLE template - auto operator()(Iterator first, Iterator last, std::identity) const + constexpr auto operator()(Iterator first, Iterator last, std::identity) const -> std::enable_if_t< not detail::has_projection_sort_iterator::value && not detail::has_comparison_projection_sort_iterator< @@ -543,7 +501,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::identity) const + constexpr auto operator()(Iterable&& iterable, std::identity) const -> std::enable_if_t< not detail::has_projection_sort_iterator< Sorter, @@ -567,8 +525,7 @@ namespace cppsort // Fused comparison-projection overloads template - auto operator()(Iterator first, Iterator last, - Compare compare, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, Compare compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, @@ -587,7 +544,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Compare compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort< Sorter, @@ -606,7 +563,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Compare compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -634,7 +591,7 @@ namespace cppsort // std::less<> and utility::identity overloads template - auto operator()(Iterator first, Iterator last, std::less<>, utility::identity) const + constexpr auto operator()(Iterator first, Iterator last, std::less<>, utility::identity) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -649,7 +606,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<>, utility::identity) const + constexpr auto operator()(Iterable&& iterable, std::less<>, utility::identity) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -665,7 +622,7 @@ namespace cppsort #if CPPSORT_STD_IDENTITY_AVAILABLE template - auto operator()(Iterator first, Iterator last, std::less<>, std::identity) const + constexpr auto operator()(Iterator first, Iterator last, std::less<>, std::identity) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -680,7 +637,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<>, std::identity) const + constexpr auto operator()(Iterable&& iterable, std::less<>, std::identity) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -696,7 +653,7 @@ namespace cppsort #endif template - auto operator()(Iterator first, Iterator last, std::less<> compare, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, std::less<> compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, @@ -713,7 +670,7 @@ namespace cppsort } template - auto operator()(Iterator first, Iterator last, std::less<>, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, std::less<>, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -735,7 +692,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort< Sorter, @@ -752,7 +709,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -775,7 +732,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<>, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::less<>, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -803,7 +760,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<>, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::less<>, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -837,7 +794,7 @@ namespace cppsort #ifdef __cpp_lib_ranges template - auto operator()(Iterator first, Iterator last, std::ranges::less, std::identity) const + constexpr auto operator()(Iterator first, Iterator last, std::ranges::less, std::identity) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -852,7 +809,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::ranges::less, std::identity) const + constexpr auto operator()(Iterable&& iterable, std::ranges::less, std::identity) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -867,7 +824,7 @@ namespace cppsort } template - auto operator()(Iterator first, Iterator last, std::ranges::less compare, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, std::ranges::less compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, @@ -884,7 +841,7 @@ namespace cppsort } template - auto operator()(Iterator first, Iterator last, std::ranges::less, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, std::ranges::less, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -906,7 +863,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort< Sorter, @@ -923,7 +880,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -946,7 +903,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -974,7 +931,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const + constexpr auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -1011,7 +968,7 @@ namespace cppsort // Embed projection in comparison template - auto operator()(Iterator first, Iterator last, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, Projection projection) const -> std::enable_if_t< not detail::has_projection_sort_iterator::value && not detail::has_comparison_projection_sort_iterator< @@ -1036,8 +993,7 @@ namespace cppsort } template - auto operator()(Iterator first, Iterator last, - Compare compare, Projection projection) const + constexpr auto operator()(Iterator first, Iterator last, Compare compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -1064,7 +1020,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Projection projection) const -> std::enable_if_t< not detail::has_projection_sort_iterator< Sorter, @@ -1094,7 +1050,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Projection projection) const -> std::enable_if_t< not detail::has_projection_sort_iterator< Sorter, @@ -1134,7 +1090,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Compare compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, @@ -1161,7 +1117,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, Compare compare, Projection projection) const + constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, diff --git a/include/cpp-sort/sorters.h b/include/cpp-sort/sorters.h index 24b65119..3c9b7b4f 100644 --- a/include/cpp-sort/sorters.h +++ b/include/cpp-sort/sorters.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_H_ @@ -9,12 +9,14 @@ // Headers //////////////////////////////////////////////////////////// #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/include/cpp-sort/sorters/cartesian_tree_sorter.h b/include/cpp-sort/sorters/cartesian_tree_sorter.h new file mode 100644 index 00000000..0199086d --- /dev/null +++ b/include/cpp-sort/sorters/cartesian_tree_sorter.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_SORTERS_CARTESIAN_TREE_SORTER_H_ +#define CPPSORT_SORTERS_CARTESIAN_TREE_SORTER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/cartesian_tree_sort.h" +#include "../detail/iterator_traits.h" + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // Sorter + + namespace detail + { + struct cartesian_tree_sorter_impl + { + template< + typename RandomAccessIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + static_assert( + std::is_base_of< + std::random_access_iterator_tag, + iterator_category_t + >::value, + "cartesian_tree_sorter requires at least random-access iterators" + ); + + cartesian_tree_sort(std::move(first), std::move(last), + std::move(compare), std::move(projection)); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::random_access_iterator_tag; + using is_always_stable = std::false_type; + }; + } + + struct cartesian_tree_sorter: + sorter_facade + {}; + + //////////////////////////////////////////////////////////// + // Sort function + + namespace + { + constexpr auto&& cartesian_tree_sort + = utility::static_const::value; + } +} + +#endif // CPPSORT_SORTERS_CARTESIAN_TREE_SORTER_H_ diff --git a/include/cpp-sort/sorters/mel_sorter.h b/include/cpp-sort/sorters/mel_sorter.h new file mode 100644 index 00000000..51746c75 --- /dev/null +++ b/include/cpp-sort/sorters/mel_sorter.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018-2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_SORTERS_MEL_SORTER_H_ +#define CPPSORT_SORTERS_MEL_SORTER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/iterator_traits.h" +#include "../detail/melsort.h" + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // Sorter + + namespace detail + { + struct mel_sorter_impl + { + template< + typename ForwardIterable, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_v + > + > + auto operator()(ForwardIterable&& iterable, + Compare compare={}, Projection projection={}) const + -> void + { + static_assert( + std::is_base_of< + std::forward_iterator_tag, + iterator_category_t + >::value, + "mel_sorter requires at least forward iterators" + ); + + melsort(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection)); + } + + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + static_assert( + std::is_base_of< + std::forward_iterator_tag, + iterator_category_t + >::value, + "mel_sorter requires at least forward iterators" + ); + + auto dist = std::distance(first, last); + melsort(std::move(first), std::move(last), dist, + std::move(compare), std::move(projection)); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::forward_iterator_tag; + using is_always_stable = std::false_type; + }; + } + + struct mel_sorter: + sorter_facade + {}; + + //////////////////////////////////////////////////////////// + // Sort function + + namespace + { + constexpr auto&& mel_sort + = utility::static_const::value; + } +} + +#ifdef CPPSORT_ADAPTERS_CONTAINER_AWARE_ADAPTER_DONE_ +#include "../detail/container_aware/mel_sort.h" +#endif + +#endif // CPPSORT_SORTERS_MEL_SORTER_H_ diff --git a/include/cpp-sort/sorters/slab_sorter.h b/include/cpp-sort/sorters/slab_sorter.h new file mode 100644 index 00000000..833b289b --- /dev/null +++ b/include/cpp-sort/sorters/slab_sorter.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_SORTERS_SLAB_SORTER_H_ +#define CPPSORT_SORTERS_SLAB_SORTER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/iterator_traits.h" +#include "../detail/slabsort.h" + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // Sorter + + namespace detail + { + struct slab_sorter_impl + { + template< + typename RandomAccessIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + // TODO: make it work for bidirectional iterators + static_assert( + std::is_base_of< + std::random_access_iterator_tag, + iterator_category_t + >::value, + "slab_sorter requires at least random-access iterators" + ); + + slabsort(std::move(first), std::move(last), + std::move(compare), std::move(projection)); + } + + //////////////////////////////////////////////////////////// + // Sorter traits + + using iterator_category = std::random_access_iterator_tag; + using is_always_stable = std::false_type; + }; + } + + struct slab_sorter: + sorter_facade + {}; + + //////////////////////////////////////////////////////////// + // Sort function + + namespace + { + constexpr auto&& slab_sort + = utility::static_const::value; + } +} + +#endif // CPPSORT_SORTERS_SLAB_SORTER_H_ diff --git a/include/cpp-sort/utility/branchless_traits.h b/include/cpp-sort/utility/branchless_traits.h index 2f1cc9c7..0443dacb 100644 --- a/include/cpp-sort/utility/branchless_traits.h +++ b/include/cpp-sort/utility/branchless_traits.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_UTILITY_BRANCHLESS_TRAITS_H_ @@ -99,7 +99,7 @@ namespace utility std::is_member_object_pointer {}; -#if defined(__GLIBCXX__) +#if defined(__GLIBCXX__) || defined(_MSC_VER) template struct is_probably_branchless_projection_impl, U>: std::is_member_object_pointer diff --git a/include/cpp-sort/utility/buffer.h b/include/cpp-sort/utility/buffer.h index ab1eddd2..a88c73da 100644 --- a/include/cpp-sort/utility/buffer.h +++ b/include/cpp-sort/utility/buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_UTILITY_BUFFER_H_ @@ -53,44 +53,44 @@ namespace utility } constexpr auto begin() - -> decltype(_memory.begin()) + -> decltype(_memory.data()) { - return _memory.begin(); + return _memory.data(); } constexpr auto begin() const - -> decltype(_memory.begin()) + -> decltype(_memory.data()) { - return _memory.begin(); + return _memory.data(); } constexpr auto cbegin() const - -> decltype(_memory.cbegin()) + -> decltype(_memory.data()) { - return _memory.cbegin(); + return _memory.data(); } constexpr auto end() - -> decltype(_memory.end()) + -> decltype(_memory.data() + _memory.size()) { - return _memory.end(); + return _memory.data() + _memory.size(); } constexpr auto end() const - -> decltype(_memory.end()) + -> decltype(_memory.data() + _memory.size()) { - return _memory.end(); + return _memory.data() + _memory.size(); } constexpr auto cend() const - -> decltype(_memory.cend()) + -> decltype(_memory.data() + _memory.size()) { - return _memory.cend(); + return _memory.data() + _memory.size(); } }; }; -#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 7000 +#if defined(_MSC_VER) || (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 7000) template<> struct fixed_buffer<0> { @@ -252,7 +252,9 @@ namespace utility detail::dynamic_buffer_impl { explicit buffer(std::size_t size): - detail::dynamic_buffer_impl(SizePolicy{}(size)) + detail::dynamic_buffer_impl( + static_cast(SizePolicy{}(size)) + ) {} }; }; diff --git a/include/cpp-sort/utility/iter_move.h b/include/cpp-sort/utility/iter_move.h index 4c21f707..8dcb6ae3 100644 --- a/include/cpp-sort/utility/iter_move.h +++ b/include/cpp-sort/utility/iter_move.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_UTILITY_ITER_MOVE_H_ @@ -44,7 +44,7 @@ namespace utility cppsort::detail::is_detected_v > > - auto iter_swap(Iterator lhs, Iterator rhs) + constexpr auto iter_swap(Iterator lhs, Iterator rhs) -> void { auto tmp = iter_move(lhs); @@ -59,7 +59,7 @@ namespace utility >, typename = void // dummy parameter for ODR > - auto iter_swap(Iterator lhs, Iterator rhs) + constexpr auto iter_swap(Iterator lhs, Iterator rhs) -> void { // While this overload is not strictly needed, it @@ -72,7 +72,7 @@ namespace utility } template - auto iter_move(Iterator it) + constexpr auto iter_move(Iterator it) noexcept(noexcept(detail::iter_move_t(std::move(*it)))) -> detail::iter_move_t { diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index b71358d1..7c7035fa 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 1 -#define CPPSORT_VERSION_MINOR 9 +#define CPPSORT_VERSION_MINOR 10 #define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_ diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 69ec9913..2c80e535 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -4,7 +4,9 @@ include(cpp-sort-utils) include(DownloadProject) +######################################## # Test suite options + option(USE_VALGRIND "Whether to run the tests with Valgrind (deprecated, use CPPSORT_USE_VALGRIND)" OFF) option(ENABLE_COVERAGE "Whether to produce code coverage (deprecated, use CPPSORT_ENABLE_COVERAGE)" OFF) set(SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize (deprecated, use CPPSORT_SANITIZE)") @@ -17,7 +19,9 @@ if (CPPSORT_ENABLE_COVERAGE) set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) endif() -# Find & configure Catch2 for the tests +######################################## +# Find or download Catch2 + message(STATUS "Looking for Catch2 2.6.0+") find_package(Catch2 2.6.0 QUIET) if (TARGET Catch2::Catch2) @@ -35,6 +39,9 @@ else() endif() include(Catch) +######################################## +# Configure runtime tests + macro(configure_tests target) # Make testing tools easily available to tests # regardless of the directory of the test @@ -93,6 +100,9 @@ macro(configure_tests target) endif() endmacro() +######################################## +# Main tests + add_executable(main-tests # General tests main.cpp @@ -104,11 +114,14 @@ add_executable(main-tests every_sorter_move_only.cpp every_sorter_no_post_iterator.cpp every_sorter_non_const_compare.cpp + every_sorter_rvalue_projection.cpp every_sorter_span.cpp + every_sorter_throwing_moves.cpp is_stable.cpp rebind_iterator_category.cpp sort_array.cpp sorter_facade.cpp + sorter_facade_constexpr.cpp sorter_facade_defaults.cpp sorter_facade_iterable.cpp stable_sort_array.cpp @@ -118,7 +131,7 @@ add_executable(main-tests adapters/container_aware_adapter_forward_list.cpp adapters/container_aware_adapter_list.cpp adapters/counting_adapter.cpp - adapters/every_adapter_fptr.cpp + $<$>:adapters/every_adapter_fptr.cpp> adapters/every_adapter_internal_compare.cpp adapters/every_adapter_non_const_compare.cpp adapters/every_adapter_stateful_sorter.cpp @@ -150,7 +163,6 @@ add_executable(main-tests # Distributions tests distributions/all_equal.cpp distributions/alternating.cpp - distributions/alternating_16_values.cpp distributions/ascending.cpp distributions/ascending_sawtooth.cpp distributions/descending.cpp @@ -174,13 +186,15 @@ add_executable(main-tests probes/par.cpp probes/rem.cpp probes/runs.cpp + probes/sus.cpp probes/relations.cpp + probes/every_probe_common.cpp probes/every_probe_move_compare_projection.cpp # Sorters tests sorters/counting_sorter.cpp sorters/default_sorter.cpp - sorters/default_sorter_fptr.cpp + $<$>:sorters/default_sorter_fptr.cpp> sorters/default_sorter_projection.cpp sorters/merge_insertion_sorter_projection.cpp sorters/merge_sorter.cpp @@ -205,6 +219,9 @@ add_executable(main-tests ) configure_tests(main-tests) +######################################## +# Heap memory exhaustion tests + if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") add_executable(heap-memory-exhaustion-tests # These tests are in a separate executable because we replace @@ -219,13 +236,17 @@ if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") configure_tests(heap-memory-exhaustion-tests) endif() +######################################## # Configure coverage + if (CPPSORT_ENABLE_COVERAGE) list(APPEND LCOV_REMOVE_PATTERNS "'/usr/*'") coverage_evaluate() endif() +######################################## # Configure Valgrind + if (CPPSORT_USE_VALGRIND) find_program(MEMORYCHECK_COMMAND valgrind REQUIRED) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") @@ -234,6 +255,9 @@ if (CPPSORT_USE_VALGRIND) endif() endif() +######################################## +# Discover tests + include(CTest) string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) diff --git a/testsuite/adapters/container_aware_adapter_forward_list.cpp b/testsuite/adapters/container_aware_adapter_forward_list.cpp index 3b152ea9..9e5da81e 100644 --- a/testsuite/adapters/container_aware_adapter_forward_list.cpp +++ b/testsuite/adapters/container_aware_adapter_forward_list.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,32 @@ TEST_CASE( "container_aware_adapter and std::forward_list", CHECK( std::is_sorted(std::begin(vec_copy), std::end(vec_copy)) ); } + SECTION( "mel_sorter" ) + { + cppsort::container_aware_adapter< + cppsort::mel_sorter + > sorter; + std::forward_list collection(vec.begin(), vec.end()); + + collection = std::forward_list(vec.begin(), vec.end()); + sorter(collection, std::greater<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + + collection = std::forward_list(vec.begin(), vec.end()); + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + + collection = std::forward_list(vec.begin(), vec.end()); + sorter(collection, std::greater<>{}, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + + // Make sure that the generic overload is also called when needed + + auto vec_copy = vec; + sorter(vec_copy); + CHECK( std::is_sorted(vec_copy.begin(), vec_copy.end()) ); + } + SECTION( "selection_sorter" ) { cppsort::container_aware_adapter< diff --git a/testsuite/adapters/container_aware_adapter_list.cpp b/testsuite/adapters/container_aware_adapter_list.cpp index 1b53365c..81e91ee3 100644 --- a/testsuite/adapters/container_aware_adapter_list.cpp +++ b/testsuite/adapters/container_aware_adapter_list.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,32 @@ TEST_CASE( "container_aware_adapter and std::list", CHECK( std::is_sorted(std::begin(vec_copy), std::end(vec_copy)) ); } + SECTION( "mel_sorter" ) + { + cppsort::container_aware_adapter< + cppsort::mel_sorter + > sorter; + std::list collection(vec.begin(), vec.end()); + + collection = std::list(vec.begin(), vec.end()); + sorter(collection, std::greater<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + + collection = std::list(vec.begin(), vec.end()); + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + + collection = std::list(vec.begin(), vec.end()); + sorter(collection, std::greater<>{}, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + + // Make sure that the generic overload is also called when needed + + auto vec_copy = vec; + sorter(vec_copy); + CHECK( std::is_sorted(vec_copy.begin(), vec_copy.end()) ); + } + SECTION( "selection_sorter" ) { cppsort::container_aware_adapter< diff --git a/testsuite/adapters/every_adapter_fptr.cpp b/testsuite/adapters/every_adapter_fptr.cpp index 10cf16da..b9839a82 100644 --- a/testsuite/adapters/every_adapter_fptr.cpp +++ b/testsuite/adapters/every_adapter_fptr.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -34,8 +34,8 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::container_aware_adapter< cppsort::selection_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; - void(*sort_it2)(std::list&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it2)(std::list&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -49,7 +49,7 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::counting_adapter< cppsort::selection_sorter >; - std::size_t(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr std::size_t(*sort_it)(std::vector&, std::greater<>) = sorter{}; std::size_t res = sort_it(collection, std::greater<>{}); CHECK( res == 2080 ); @@ -62,7 +62,7 @@ TEST_CASE( "function pointer test for every adapter", cppsort::merge_sorter, cppsort::poplar_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -73,7 +73,7 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::indirect_adapter< cppsort::poplar_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -84,9 +84,9 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::out_of_place_adapter< cppsort::poplar_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; - void(*sort_it2)(std::list&, std::greater<>) = sorter{}; - void(*sort_it3)(std::forward_list&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it2)(std::list&, std::greater<>) = sorter{}; + constexpr void(*sort_it3)(std::forward_list&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -103,7 +103,7 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::schwartz_adapter< cppsort::poplar_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -114,7 +114,7 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::schwartz_adapter< cppsort::small_array_adapter >; - void(*sort_it)(std::array&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::array&, std::greater<>) = sorter{}; std::array arr = {{ 4, 3, 2, 5, 6, 1 }}; sort_it(arr, std::greater<>{}); @@ -126,8 +126,8 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::self_sort_adapter< cppsort::poplar_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; - void(*sort_it2)(std::list&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it2)(std::list&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -141,8 +141,8 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::stable_adapter< cppsort::self_sort_adapter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; - void(*sort_it2)(std::list&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it2)(std::list&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -158,9 +158,9 @@ TEST_CASE( "function pointer test for every adapter", using sorter2 = small_array_adapter; using sorter1 = small_array_adapter; using sorter3 = small_array_adapter; - void(*sort_it1)(std::array&, std::greater<>) = sorter2{}; - void(*sort_it2)(std::array&, std::greater<>) = sorter1{}; - void(*sort_it3)(std::array&, std::greater<>) = sorter3{}; + constexpr void(*sort_it1)(std::array&, std::greater<>) = sorter2{}; + constexpr void(*sort_it2)(std::array&, std::greater<>) = sorter1{}; + constexpr void(*sort_it3)(std::array&, std::greater<>) = sorter3{}; std::array arr = {{ 4, 3, 2, 5, 6, 1 }}; @@ -182,7 +182,7 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::stable_adapter< cppsort::poplar_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); @@ -193,7 +193,7 @@ TEST_CASE( "function pointer test for every adapter", using sorter = cppsort::verge_adapter< cppsort::poplar_sorter >; - void(*sort_it)(std::vector&, std::greater<>) = sorter{}; + constexpr void(*sort_it)(std::vector&, std::greater<>) = sorter{}; sort_it(collection, std::greater<>{}); CHECK( std::is_sorted(std::begin(collection), std::end(collection), std::greater<>{}) ); diff --git a/testsuite/adapters/indirect_adapter_every_sorter.cpp b/testsuite/adapters/indirect_adapter_every_sorter.cpp index 7e8295bf..1f2013aa 100644 --- a/testsuite/adapters/indirect_adapter_every_sorter.cpp +++ b/testsuite/adapters/indirect_adapter_every_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -13,11 +13,13 @@ TEMPLATE_TEST_CASE( "every random-access sorter with indirect adapter", "[indirect_adapter]", cppsort::block_sorter<>, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -26,6 +28,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with indirect adapter", "[indire cppsort::quick_sorter, cppsort::selection_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, @@ -47,6 +50,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with indirect adapter", "[indire TEMPLATE_TEST_CASE( "every bidirectional sorter with indirect_adapter", "[indirect_adapter]", cppsort::drop_merge_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, // Check extended support cppsort::quick_merge_sorter, @@ -64,6 +68,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with indirect_adapter", "[indire } TEMPLATE_TEST_CASE( "every forward sorter with with indirect_adapter", "[indirect_adapter]", + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, // Check extended support cppsort::quick_merge_sorter, diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index 838e2fe7..a2713d54 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -25,11 +25,13 @@ using wrapper = generic_wrapper; TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapter", "[schwartz_adapter]", cppsort::block_sorter>, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -37,6 +39,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapt cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, @@ -56,6 +59,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapt TEMPLATE_TEST_CASE( "every bidirectional sorter with Schwartzian transform adapter", "[schwartz_adapter]", cppsort::drop_merge_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, @@ -73,6 +77,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with Schwartzian transform adapt } TEMPLATE_TEST_CASE( "every forward sorter with Schwartzian transform adapter", "[schwartz_adapter]", + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index 63bf43ad..bda60ffa 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -25,11 +25,13 @@ using wrapper = generic_wrapper; TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse iterators", "[schwartz_adapter][reverse_iterator]", cppsort::block_sorter>, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -37,6 +39,7 @@ TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/adapters/stable_adapter_every_sorter.cpp b/testsuite/adapters/stable_adapter_every_sorter.cpp index a1757675..6e06e382 100644 --- a/testsuite/adapters/stable_adapter_every_sorter.cpp +++ b/testsuite/adapters/stable_adapter_every_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -28,11 +28,13 @@ using wrapper = generic_stable_wrapper; TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_adapter]", cppsort::block_sorter>, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -40,6 +42,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_a cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, @@ -71,6 +74,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_a TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_adapter]", cppsort::drop_merge_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, @@ -99,6 +103,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_a } TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_adapter]", + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, diff --git a/testsuite/adapters/verge_adapter_every_sorter.cpp b/testsuite/adapters/verge_adapter_every_sorter.cpp index 1a754640..88047c29 100644 --- a/testsuite/adapters/verge_adapter_every_sorter.cpp +++ b/testsuite/adapters/verge_adapter_every_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -13,11 +13,13 @@ TEMPLATE_TEST_CASE( "every sorter with verge_adapter", "[verge_adapter]", cppsort::block_sorter>, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -25,6 +27,7 @@ TEMPLATE_TEST_CASE( "every sorter with verge_adapter", "[verge_adapter]", cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::ska_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, diff --git a/testsuite/distributions/all_equal.cpp b/testsuite/distributions/all_equal.cpp index 12bd5a5d..db9f54c0 100644 --- a/testsuite/distributions/all_equal.cpp +++ b/testsuite/distributions/all_equal.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test random-access sorters with all_equal distribution", "[ cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/alternating.cpp b/testsuite/distributions/alternating.cpp index 8ccf5a00..2a24f9d8 100644 --- a/testsuite/distributions/alternating.cpp +++ b/testsuite/distributions/alternating.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test sorter with alternating distribution", "[distributions cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/alternating_16_values.cpp b/testsuite/distributions/alternating_16_values.cpp deleted file mode 100644 index 31a2a29d..00000000 --- a/testsuite/distributions/alternating_16_values.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2017-2020 Morwenn - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include -#include -#include -#include -#include - -TEMPLATE_TEST_CASE( "test sorter with alternating_16_values distribution", "[distributions]", - cppsort::block_sorter<>, - cppsort::block_sorter< - cppsort::utility::dynamic_buffer - >, - cppsort::drop_merge_sorter, - cppsort::grail_sorter<>, - cppsort::grail_sorter< - cppsort::utility::dynamic_buffer - >, - cppsort::heap_sorter, - cppsort::merge_sorter, - cppsort::pdq_sorter, - cppsort::poplar_sorter, - cppsort::quick_merge_sorter, - cppsort::quick_sorter, - cppsort::ska_sorter, - cppsort::smooth_sorter, - cppsort::spin_sorter, - cppsort::split_sorter, - cppsort::spread_sorter, - cppsort::std_sorter, - cppsort::tim_sorter, - cppsort::verge_sorter ) -{ - std::vector collection; - collection.reserve(10'000); - auto distribution = dist::alternating_16_values{}; - distribution(std::back_inserter(collection), 10'000); - - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); -} diff --git a/testsuite/distributions/ascending.cpp b/testsuite/distributions/ascending.cpp index e8ce4e7f..14a30e58 100644 --- a/testsuite/distributions/ascending.cpp +++ b/testsuite/distributions/ascending.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -21,18 +21,21 @@ TEMPLATE_TEST_CASE( "test sorter with ascending distribution", "[distributions]" // that could specifically appear with an ascending distribution, // so here is the dedicated test (see issue #103) cppsort::counting_sorter, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/ascending_sawtooth.cpp b/testsuite/distributions/ascending_sawtooth.cpp index 11369533..7e10b3be 100644 --- a/testsuite/distributions/ascending_sawtooth.cpp +++ b/testsuite/distributions/ascending_sawtooth.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -17,18 +17,21 @@ TEMPLATE_TEST_CASE( "test random-access sorters with ascending_sawtooth distribu cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/descending.cpp b/testsuite/distributions/descending.cpp index f8f9201d..982085e0 100644 --- a/testsuite/distributions/descending.cpp +++ b/testsuite/distributions/descending.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test sorter with descending distribution", "[distributions] cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/descending_sawtooth.cpp b/testsuite/distributions/descending_sawtooth.cpp index ba94b869..a5ef7705 100644 --- a/testsuite/distributions/descending_sawtooth.cpp +++ b/testsuite/distributions/descending_sawtooth.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -17,18 +17,21 @@ TEMPLATE_TEST_CASE( "test random-access sorters with descending_sawtooth distrib cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/median_of_3_killer.cpp b/testsuite/distributions/median_of_3_killer.cpp index 2380ad9d..57d21909 100644 --- a/testsuite/distributions/median_of_3_killer.cpp +++ b/testsuite/distributions/median_of_3_killer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test random-access sorters with median_of_3_killer distribu cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/pipe_organ.cpp b/testsuite/distributions/pipe_organ.cpp index fed87ea6..0327e010 100644 --- a/testsuite/distributions/pipe_organ.cpp +++ b/testsuite/distributions/pipe_organ.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test sorter with pipe_organ distribution", "[distributions] cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/push_front.cpp b/testsuite/distributions/push_front.cpp index c3fa6c7a..8c6db10a 100644 --- a/testsuite/distributions/push_front.cpp +++ b/testsuite/distributions/push_front.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test sorter with push_front distribution", "[distributions] cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/push_middle.cpp b/testsuite/distributions/push_middle.cpp index 1e92183a..e7b8da17 100644 --- a/testsuite/distributions/push_middle.cpp +++ b/testsuite/distributions/push_middle.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test sorter with push_middle distribution", "[distributions cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/shuffled.cpp b/testsuite/distributions/shuffled.cpp index 424625f1..876d2539 100644 --- a/testsuite/distributions/shuffled.cpp +++ b/testsuite/distributions/shuffled.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test sorter with shuffled distribution", "[distributions]", cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/distributions/shuffled_16_values.cpp b/testsuite/distributions/shuffled_16_values.cpp index 87f02422..1be5d50f 100644 --- a/testsuite/distributions/shuffled_16_values.cpp +++ b/testsuite/distributions/shuffled_16_values.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,18 +16,21 @@ TEMPLATE_TEST_CASE( "test sorter with shuffled_16_values distribution", "[distri cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< cppsort::utility::dynamic_buffer >, cppsort::heap_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, cppsort::poplar_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/every_instantiated_sorter.cpp b/testsuite/every_instantiated_sorter.cpp index 138d6623..0b1f4750 100644 --- a/testsuite/every_instantiated_sorter.cpp +++ b/testsuite/every_instantiated_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -34,6 +34,12 @@ TEST_CASE( "test every instantiated sorter", "[sorters]" ) CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); } + SECTION( "cartesian_tree_sort" ) + { + cppsort::cartesian_tree_sort(collection); + CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + } + SECTION( "counting_sort" ) { cppsort::counting_sort(collection); @@ -64,6 +70,12 @@ TEST_CASE( "test every instantiated sorter", "[sorters]" ) CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); } + SECTION( "mel_sorter" ) + { + cppsort::mel_sort(collection); + CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + } + SECTION( "merge_insertion_sorter" ) { cppsort::merge_insertion_sort(collection); @@ -118,6 +130,12 @@ TEST_CASE( "test every instantiated sorter", "[sorters]" ) CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); } + SECTION( "slab_sorter" ) + { + cppsort::slab_sort(collection); + CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + } + SECTION( "smooth_sorter" ) { cppsort::smooth_sort(collection); diff --git a/testsuite/every_sorter.cpp b/testsuite/every_sorter.cpp index 9124c6ee..099dde05 100644 --- a/testsuite/every_sorter.cpp +++ b/testsuite/every_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -14,11 +14,12 @@ #include #include -TEMPLATE_TEST_CASE( "test every random-access sorter with vector", "[sorters]", +TEMPLATE_TEST_CASE( "test every random-access sorter", "[sorters]", cppsort::block_sorter<>, cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::counting_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, @@ -27,6 +28,7 @@ TEMPLATE_TEST_CASE( "test every random-access sorter with vector", "[sorters]", >, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -35,6 +37,7 @@ TEMPLATE_TEST_CASE( "test every random-access sorter with vector", "[sorters]", cppsort::quick_sorter, cppsort::selection_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, @@ -47,83 +50,68 @@ TEMPLATE_TEST_CASE( "test every random-access sorter with vector", "[sorters]", // already tested in-depth somewhere else and needs specific // tests, so it's not included here. - std::vector collection; collection.reserve(491); auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 491, -125); - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); -} + SECTION( "with std::vector" ) + { + std::vector collection; collection.reserve(491); + distribution(std::back_inserter(collection), 491, -125); -TEMPLATE_TEST_CASE( "test every random-access sorter with deque", "[sorters]", - cppsort::block_sorter<>, - cppsort::block_sorter< - cppsort::utility::dynamic_buffer - >, - cppsort::counting_sorter, - cppsort::drop_merge_sorter, - cppsort::grail_sorter<>, - cppsort::grail_sorter< - cppsort::utility::dynamic_buffer - >, - cppsort::heap_sorter, - cppsort::insertion_sorter, - cppsort::merge_insertion_sorter, - cppsort::merge_sorter, - cppsort::pdq_sorter, - cppsort::poplar_sorter, - cppsort::quick_merge_sorter, - cppsort::quick_sorter, - cppsort::selection_sorter, - cppsort::ska_sorter, - cppsort::smooth_sorter, - cppsort::spin_sorter, - cppsort::spread_sorter, - cppsort::std_sorter, - cppsort::tim_sorter, - cppsort::verge_sorter ) -{ - std::deque collection; - auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 491, -125); + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + SECTION( "with std::deque" ) + { + std::deque collection; + distribution(std::back_inserter(collection), 491, -125); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } -TEMPLATE_TEST_CASE( "test every bidirectional sorter with list", "[sorters]", +TEMPLATE_TEST_CASE( "test every bidirectional sorter", "[sorters]", cppsort::counting_sorter, cppsort::drop_merge_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, cppsort::verge_sorter ) { - std::list collection; - auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 491, -125); + SECTION( "with std::list" ) + { + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 491, -125); - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } -TEMPLATE_TEST_CASE( "test every forward sorter with forward_list", "[sorters]", +TEMPLATE_TEST_CASE( "test every forward sorter", "[sorters]", cppsort::counting_sorter, + cppsort::mel_sorter, cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter ) { - std::forward_list collection; - auto distribution = dist::shuffled{}; - distribution(std::front_inserter(collection), 491, -125); + SECTION( "wwith std::forward_list" ) + { + std::forward_list collection; + auto distribution = dist::shuffled{}; + distribution(std::front_inserter(collection), 491, -125); - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } diff --git a/testsuite/every_sorter_internal_compare.cpp b/testsuite/every_sorter_internal_compare.cpp index 83f4e26b..1aeb52c5 100644 --- a/testsuite/every_sorter_internal_compare.cpp +++ b/testsuite/every_sorter_internal_compare.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2019 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -13,10 +13,12 @@ TEMPLATE_TEST_CASE( "test every sorter with a pointer to member function comparison", "[sorters][as_function]", cppsort::block_sorter<>, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -24,6 +26,7 @@ TEMPLATE_TEST_CASE( "test every sorter with a pointer to member function compari cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/every_sorter_long_string.cpp b/testsuite/every_sorter_long_string.cpp index 38d94440..efc0b57e 100644 --- a/testsuite/every_sorter_long_string.cpp +++ b/testsuite/every_sorter_long_string.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -45,6 +45,7 @@ TEMPLATE_TEST_CASE( "test every sorter with long std::string", "[sorters]", cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, @@ -53,6 +54,7 @@ TEMPLATE_TEST_CASE( "test every sorter with long std::string", "[sorters]", >, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -60,6 +62,7 @@ TEMPLATE_TEST_CASE( "test every sorter with long std::string", "[sorters]", cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::ska_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, diff --git a/testsuite/every_sorter_move_compare_projection.cpp b/testsuite/every_sorter_move_compare_projection.cpp index f2d90024..95aad895 100644 --- a/testsuite/every_sorter_move_compare_projection.cpp +++ b/testsuite/every_sorter_move_compare_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -17,6 +17,7 @@ TEMPLATE_TEST_CASE( "every sorter with comparison function altered by move", "[s cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< @@ -24,6 +25,7 @@ TEMPLATE_TEST_CASE( "every sorter with comparison function altered by move", "[s >, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -31,6 +33,7 @@ TEMPLATE_TEST_CASE( "every sorter with comparison function altered by move", "[s cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, @@ -48,11 +51,12 @@ TEMPLATE_TEST_CASE( "every sorter with comparison function altered by move", "[s CHECK( std::is_sorted(collection.begin(), collection.end()) ); } -TEMPLATE_TEST_CASE( "every sorter with projection function altered by move", "[sorters]", +TEMPLATE_TEST_CASE( "every sorter with projection function altered by move", "[sorters][projection]", cppsort::block_sorter<>, cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::grail_sorter< @@ -60,6 +64,7 @@ TEMPLATE_TEST_CASE( "every sorter with projection function altered by move", "[s >, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, diff --git a/testsuite/every_sorter_move_only.cpp b/testsuite/every_sorter_move_only.cpp index ec90121c..ff26c48d 100644 --- a/testsuite/every_sorter_move_only.cpp +++ b/testsuite/every_sorter_move_only.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -14,11 +14,13 @@ TEMPLATE_TEST_CASE( "test every sorter with move-only types", "[sorters]", cppsort::block_sorter>, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -26,6 +28,7 @@ TEMPLATE_TEST_CASE( "test every sorter with move-only types", "[sorters]", cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/every_sorter_no_post_iterator.cpp b/testsuite/every_sorter_no_post_iterator.cpp index baf602e5..f9231477 100644 --- a/testsuite/every_sorter_no_post_iterator.cpp +++ b/testsuite/every_sorter_no_post_iterator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -15,12 +15,14 @@ TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", cppsort::block_sorter>, + cppsort::cartesian_tree_sorter, cppsort::counting_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -29,6 +31,7 @@ TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", cppsort::quick_sorter, cppsort::selection_sorter, cppsort::ska_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/every_sorter_non_const_compare.cpp b/testsuite/every_sorter_non_const_compare.cpp index 610705b6..5719cd3d 100644 --- a/testsuite/every_sorter_non_const_compare.cpp +++ b/testsuite/every_sorter_non_const_compare.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -12,11 +12,13 @@ TEMPLATE_TEST_CASE( "test extended compatibility with LWG 3031", "[sorters]", cppsort::block_sorter>, + cppsort::cartesian_tree_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -24,6 +26,7 @@ TEMPLATE_TEST_CASE( "test extended compatibility with LWG 3031", "[sorters]", cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, cppsort::split_sorter, diff --git a/testsuite/every_sorter_rvalue_projection.cpp b/testsuite/every_sorter_rvalue_projection.cpp new file mode 100644 index 00000000..4dd79539 --- /dev/null +++ b/testsuite/every_sorter_rvalue_projection.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "random-access sorters with a projection returning an rvalue", "[sorters][projection]", + cppsort::block_sorter<>, + cppsort::cartesian_tree_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter, + cppsort::ska_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::verge_sorter ) +{ + // This test is meant to check that sorters can correctly handle + // projections that return an rvalue, we use std::negate as + // the projection for simplicity + + auto distribution = dist::shuffled{}; + + SECTION( "std::vector" ) + { + std::vector collection; + distribution(std::back_inserter(collection), 50); + + TestType sorter; + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + } + + SECTION( "std::deque" ) + { + std::deque collection; + distribution(std::back_inserter(collection), 50); + + TestType sorter; + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + } +} + +TEMPLATE_TEST_CASE( "bidirectional sorters with a projection returning an rvalue", "[sorters][projection]", + cppsort::drop_merge_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::verge_sorter ) +{ + SECTION( "std::list" ) + { + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 50); + + TestType sorter; + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + } +} + +TEMPLATE_TEST_CASE( "forward sorters with a projection returning an rvalue", "[sorters][projection]", + cppsort::mel_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter ) +{ + SECTION( "std::forward_list" ) + { + std::forward_list collection; + auto distribution = dist::shuffled{}; + distribution(std::front_inserter(collection), 50); + + TestType sorter; + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); + } +} diff --git a/testsuite/every_sorter_span.cpp b/testsuite/every_sorter_span.cpp index 5c38d2a3..69bfc9eb 100644 --- a/testsuite/every_sorter_span.cpp +++ b/testsuite/every_sorter_span.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -17,6 +17,7 @@ TEMPLATE_TEST_CASE( "test every sorter with temporary span", "[sorters][span]", cppsort::block_sorter< cppsort::utility::dynamic_buffer >, + cppsort::cartesian_tree_sorter, cppsort::counting_sorter, cppsort::default_sorter, cppsort::drop_merge_sorter, @@ -26,6 +27,7 @@ TEMPLATE_TEST_CASE( "test every sorter with temporary span", "[sorters][span]", >, cppsort::heap_sorter, cppsort::insertion_sorter, + cppsort::mel_sorter, cppsort::merge_insertion_sorter, cppsort::merge_sorter, cppsort::pdq_sorter, @@ -33,6 +35,7 @@ TEMPLATE_TEST_CASE( "test every sorter with temporary span", "[sorters][span]", cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::ska_sorter, cppsort::smooth_sorter, cppsort::spin_sorter, diff --git a/testsuite/every_sorter_throwing_moves.cpp b/testsuite/every_sorter_throwing_moves.cpp new file mode 100644 index 00000000..9c6ef6fb --- /dev/null +++ b/testsuite/every_sorter_throwing_moves.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + constexpr int throwing_move_value = 150; + + struct throw_on_move_error: + std::logic_error + { + using std::logic_error::logic_error; + }; + + int correctly_constructed = 0; + int correctly_destructed = 0; + int moves_count = 0; + + struct throw_on_move + { + int value = 0; + + throw_on_move() = default; + + throw_on_move(int value): + value(value) + { + ++correctly_constructed; + } + + throw_on_move(const throw_on_move&) = delete; + throw_on_move& operator=(throw_on_move&) = delete; + + throw_on_move(throw_on_move&& other): + value(other.value) + { + if (++moves_count == throwing_move_value) { + throw throw_on_move_error("move constructor threw"); + } + ++correctly_constructed; + } + + auto operator=(throw_on_move&& other) + -> throw_on_move& + { + value = other.value; + if (++moves_count == throwing_move_value) { + throw throw_on_move_error("move operator threw"); + } + return *this; + } + + ~throw_on_move() + { + ++correctly_destructed; + } + + friend auto operator<(const throw_on_move& lhs, const throw_on_move& rhs) + -> bool + { + return lhs.value < rhs.value; + } + }; +} + +TEMPLATE_TEST_CASE( "random-access sorters against throwing move operations", "[sorters][throwing_moves]", + cppsort::block_sorter<>, + cppsort::block_sorter< + cppsort::utility::dynamic_buffer + >, + cppsort::cartesian_tree_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::grail_sorter< + cppsort::utility::dynamic_buffer + >, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::verge_sorter ) +{ + auto distribution = dist::shuffled{}; + // Initialize counters + correctly_constructed = 0; + correctly_destructed = 0; + moves_count = 0; + + SECTION( "with std::vector" ) + { + try { + std::vector collection; collection.reserve(250); + distribution(std::back_inserter(collection), 250); + + TestType sorter; + sorter(collection); + + INFO( "the sorter did not throw" ); + } catch (const throw_on_move_error&) { + INFO( "the sorter did throw" ); + CHECK( correctly_constructed == correctly_destructed ); + } + } + + SECTION( "with std::deque" ) + { + try { + std::deque collection; + distribution(std::back_inserter(collection), 250); + + TestType sorter; + sorter(collection); + + INFO( "the sorter did not throw" ); + } catch (const throw_on_move_error&) { + INFO( "the sorter did throw" ); + CHECK( correctly_constructed == correctly_destructed ); + } + } +} + +TEMPLATE_TEST_CASE( "bidirectional sorters against throwing move operations", "[sorters][throwing_moves]", + cppsort::drop_merge_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::verge_sorter ) +{ + auto distribution = dist::shuffled{}; + // Initialize counters + correctly_constructed = 0; + correctly_destructed = 0; + moves_count = 0; + + SECTION( "with std::list" ) + { + try { + std::list collection; + distribution(std::back_inserter(collection), 250); + + TestType sorter; + sorter(collection); + + INFO( "the sorter did not throw" ); + } catch (const throw_on_move_error&) { + INFO( "the sorter did throw" ); + CHECK( correctly_constructed == correctly_destructed ); + } + } +} + +TEMPLATE_TEST_CASE( "forward sorters against throwing move operations", "[sorters][throwing_moves]", + cppsort::mel_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter ) +{ + auto distribution = dist::shuffled{}; + // Initialize counters + correctly_constructed = 0; + correctly_destructed = 0; + moves_count = 0; + + SECTION( "with std::forward_list" ) + { + try { + std::forward_list collection; + distribution(std::front_inserter(collection), 250); + + TestType sorter; + sorter(collection); + + INFO( "the sorter did not throw" ); + } catch (const throw_on_move_error&) { + INFO( "the sorter did throw" ); + CHECK( correctly_constructed == correctly_destructed ); + } + } +} diff --git a/testsuite/probes/dis.cpp b/testsuite/probes/dis.cpp index cfe8db6d..cb5f26a9 100644 --- a/testsuite/probes/dis.cpp +++ b/testsuite/probes/dis.cpp @@ -1,31 +1,26 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include #include #include #include +#include #include TEST_CASE( "presortedness measure: dis", "[probe][dis]" ) { + using cppsort::probe::dis; + SECTION( "simple test" ) { std::forward_list li = { 47, 53, 46, 41, 59, 81, 74, 97, 100, 45 }; - CHECK( cppsort::probe::dis(li) == 9 ); - CHECK( cppsort::probe::dis(std::begin(li), std::end(li)) == 9 ); + CHECK( dis(li) == 9 ); + CHECK( dis(li.begin(), li.end()) == 9 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::dis(tricky, &internal_compare::compare_to) == 9 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::dis(li) == 0 ); - CHECK( cppsort::probe::dis(std::begin(li), std::end(li)) == 0 ); + CHECK( dis(tricky, &internal_compare::compare_to) == 9 ); } SECTION( "upper bound" ) @@ -34,7 +29,9 @@ TEST_CASE( "presortedness measure: dis", "[probe][dis]" ) // the input sequence minus one std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::dis(li) == 10 ); - CHECK( cppsort::probe::dis(std::begin(li), std::end(li)) == 10 ); + auto max_n = dis.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 10 ); + CHECK( dis(li) == max_n ); + CHECK( dis(li.begin(), li.end()) == max_n ); } } diff --git a/testsuite/probes/enc.cpp b/testsuite/probes/enc.cpp index b10b342f..4dc0643c 100644 --- a/testsuite/probes/enc.cpp +++ b/testsuite/probes/enc.cpp @@ -1,31 +1,26 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include #include #include #include +#include #include TEST_CASE( "presortedness measure: enc", "[probe][enc]" ) { + using cppsort::probe::enc; + SECTION( "simple test" ) { std::forward_list li = { 4, 6, 5, 2, 9, 1, 3, 8, 0, 7 }; - CHECK( cppsort::probe::enc(li) == 2 ); - CHECK( cppsort::probe::enc(std::begin(li), std::end(li)) == 2 ); + CHECK( enc(li) == 2 ); + CHECK( enc(li.begin(), li.end()) == 2 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::enc(tricky, &internal_compare::compare_to) == 2 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::enc(li) == 0 ); - CHECK( cppsort::probe::enc(std::begin(li), std::end(li)) == 0 ); + CHECK( enc(tricky, &internal_compare::compare_to) == 2 ); } SECTION( "upper bound" ) @@ -33,8 +28,10 @@ TEST_CASE( "presortedness measure: enc", "[probe][enc]" ) // The upper bound should correspond to half the size // of the input sequence minus one - std::forward_list li = { 0, 9, 1, 8, 2, 7, 3, 6, 4, 5 }; - CHECK( cppsort::probe::enc(li) == 4 ); - CHECK( cppsort::probe::enc(std::begin(li), std::end(li)) == 4 ); + std::forward_list li = { 10, 0, 9, 1, 8, 2, 7, 3, 6, 4, 5 }; + auto max_n = enc.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 5 ); + CHECK( enc(li) == max_n ); + CHECK( enc(li.begin(), li.end()) == max_n ); } } diff --git a/testsuite/probes/every_probe_common.cpp b/testsuite/probes/every_probe_common.cpp new file mode 100644 index 00000000..532cea8d --- /dev/null +++ b/testsuite/probes/every_probe_common.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include + +// +// Common tests for measures of presortedness +// + +TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", + decltype(cppsort::probe::dis), + decltype(cppsort::probe::enc), + decltype(cppsort::probe::exc), + decltype(cppsort::probe::ham), + decltype(cppsort::probe::inv), + decltype(cppsort::probe::max), + decltype(cppsort::probe::mono), + decltype(cppsort::probe::osc), + decltype(cppsort::probe::par), + decltype(cppsort::probe::rem), + decltype(cppsort::probe::runs), + decltype(cppsort::probe::sus) ) +{ + // Ensure that all measures of presortedness return 0 when + // given a collection where all elements are equal + + std::vector collection(50, 5); + std::decay_t mop; + auto presortedness = mop(collection); + CHECK( presortedness == 0 ); +} + +TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]", + decltype(cppsort::probe::dis), + decltype(cppsort::probe::enc), + decltype(cppsort::probe::exc), + decltype(cppsort::probe::ham), + decltype(cppsort::probe::inv), + decltype(cppsort::probe::max), + decltype(cppsort::probe::mono), + decltype(cppsort::probe::osc), + decltype(cppsort::probe::par), + decltype(cppsort::probe::rem), + decltype(cppsort::probe::runs), + decltype(cppsort::probe::sus) ) +{ + // Ensure that all measures of presortedness return 0 when + // given a collection where all elements are sorted + + std::vector collection(50); + std::iota(collection.begin(), collection.end(), 0); + std::decay_t mop; + auto presortedness = mop(collection); + CHECK( presortedness == 0 ); +} diff --git a/testsuite/probes/every_probe_move_compare_projection.cpp b/testsuite/probes/every_probe_move_compare_projection.cpp index 8ad3288f..a1a80603 100644 --- a/testsuite/probes/every_probe_move_compare_projection.cpp +++ b/testsuite/probes/every_probe_move_compare_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -20,7 +20,8 @@ TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[pr decltype(cppsort::probe::osc), decltype(cppsort::probe::par), decltype(cppsort::probe::rem), - decltype(cppsort::probe::runs) ) + decltype(cppsort::probe::runs), + decltype(cppsort::probe::sus) ) { std::vector collection; collection.reserve(491); auto distribution = dist::shuffled{}; @@ -42,7 +43,8 @@ TEMPLATE_TEST_CASE( "every probe with projection function altered by move", "[pr decltype(cppsort::probe::osc), decltype(cppsort::probe::par), decltype(cppsort::probe::rem), - decltype(cppsort::probe::runs) ) + decltype(cppsort::probe::runs), + decltype(cppsort::probe::sus) ) { std::vector collection; collection.reserve(491); auto distribution = dist::shuffled{}; diff --git a/testsuite/probes/exc.cpp b/testsuite/probes/exc.cpp index f247ff8d..05003d6f 100644 --- a/testsuite/probes/exc.cpp +++ b/testsuite/probes/exc.cpp @@ -1,36 +1,33 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include #include #include +#include #include #include TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) { + using cppsort::probe::exc; + SECTION( "simple test" ) { std::forward_list li = { 74, 59, 62, 23, 86, 69, 18, 52, 77, 68 }; - CHECK( cppsort::probe::exc(li) == 7 ); - CHECK( cppsort::probe::exc(std::begin(li), std::end(li)) == 7 ); + CHECK( exc(li) == 7 ); + CHECK( exc(li.begin(), li.end()) == 7 ); std::forward_list li2 = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::exc(li2) == 5 ); - CHECK( cppsort::probe::exc(std::begin(li2), std::end(li2)) == 5 ); + CHECK( exc(li2) == 5 ); + CHECK( exc(li2.begin(), li2.end()) == 5 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::exc(tricky, &internal_compare::compare_to) == 7 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::exc(li) == 0 ); - CHECK( cppsort::probe::exc(std::begin(li), std::end(li)) == 0 ); + CHECK( exc(tricky, &internal_compare::compare_to) == 7 ); } SECTION( "upper bound" ) @@ -39,8 +36,10 @@ TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) // the input sequence minus one std::forward_list li = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - CHECK( cppsort::probe::exc(li) == 10 ); - CHECK( cppsort::probe::exc(std::begin(li), std::end(li)) == 10 ); + auto max_n = exc.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 10 ); + CHECK( exc(li) == max_n ); + CHECK( exc(li.begin(), li.end()) == max_n ); } SECTION( "regressions" ) @@ -50,7 +49,7 @@ TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) auto distribution = dist::ascending_sawtooth{}; distribution(std::back_inserter(collection), 100); - std::sort(std::begin(collection), std::end(collection)); - CHECK( cppsort::probe::exc(collection) == 0 ); + std::sort(collection.begin(), collection.end()); + CHECK( exc(collection) == 0 ); } } diff --git a/testsuite/probes/ham.cpp b/testsuite/probes/ham.cpp index 11b3c1cc..bb87e640 100644 --- a/testsuite/probes/ham.cpp +++ b/testsuite/probes/ham.cpp @@ -1,32 +1,29 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include #include #include +#include #include #include TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) { + using cppsort::probe::ham; + SECTION( "simple test" ) { std::forward_list li = { 34, 43, 96, 42, 44, 48, 57, 42, 68, 69 }; - CHECK( cppsort::probe::ham(li) == 6 ); - CHECK( cppsort::probe::ham(std::begin(li), std::end(li)) == 6 ); + CHECK( ham(li) == 6 ); + CHECK( ham(li.begin(), li.end()) == 6 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::ham(tricky, &internal_compare::compare_to) == 6 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::ham(li) == 0 ); - CHECK( cppsort::probe::ham(std::begin(li), std::end(li)) == 0 ); + CHECK( ham(tricky, &internal_compare::compare_to) == 6 ); } SECTION( "upper bound" ) @@ -35,8 +32,10 @@ TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) // the input sequence std::forward_list li = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - CHECK( cppsort::probe::ham(li) == 11 ); - CHECK( cppsort::probe::ham(std::begin(li), std::end(li)) == 11 ); + auto max_n = ham.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 11 ); + CHECK( ham(li) == max_n ); + CHECK( ham(li.begin(), li.end()) == max_n ); } SECTION( "regressions" ) @@ -46,7 +45,7 @@ TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) auto distribution = dist::ascending_sawtooth{}; distribution(std::back_inserter(collection), 100); - std::sort(std::begin(collection), std::end(collection)); - CHECK( cppsort::probe::ham(collection) == 0 ); + std::sort(collection.begin(), collection.end()); + CHECK( ham(collection) == 0 ); } } diff --git a/testsuite/probes/inv.cpp b/testsuite/probes/inv.cpp index cbb69b23..8894361a 100644 --- a/testsuite/probes/inv.cpp +++ b/testsuite/probes/inv.cpp @@ -1,31 +1,26 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include #include #include #include +#include #include TEST_CASE( "presortedness measure: inv", "[probe][inv]" ) { + using cppsort::probe::inv; + SECTION( "simple test" ) { const std::forward_list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; - CHECK( cppsort::probe::inv(li) == 19 ); - CHECK( cppsort::probe::inv(std::begin(li), std::end(li)) == 19 ); + CHECK( inv(li) == 19 ); + CHECK( inv(li.begin(), li.end()) == 19 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::inv(tricky, &internal_compare::compare_to) == 19 ); - } - - SECTION( "lower bound" ) - { - const std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::inv(li) == 0 ); - CHECK( cppsort::probe::inv(std::begin(li), std::end(li)) == 0 ); + CHECK( inv(tricky, &internal_compare::compare_to) == 19 ); } SECTION( "upper bound" ) @@ -34,7 +29,9 @@ TEST_CASE( "presortedness measure: inv", "[probe][inv]" ) // size * (size - 1) / 2 const std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::inv(li) == 55 ); - CHECK( cppsort::probe::inv(std::begin(li), std::end(li)) == 55 ); + auto max_n = inv.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 55 ); + CHECK( inv(li) == max_n ); + CHECK( inv(li.begin(), li.end()) == max_n ); } } diff --git a/testsuite/probes/max.cpp b/testsuite/probes/max.cpp index b02ed489..24cd41b0 100644 --- a/testsuite/probes/max.cpp +++ b/testsuite/probes/max.cpp @@ -1,32 +1,29 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include #include #include +#include #include #include TEST_CASE( "presortedness measure: max", "[probe][max]" ) { + using cppsort::probe::max; + SECTION( "simple test" ) { std::forward_list li = { 12, 28, 17, 59, 13, 10, 39, 21, 31, 30 }; - CHECK( cppsort::probe::max(li) == 6 ); - CHECK( cppsort::probe::max(std::begin(li), std::end(li)) == 6 ); + CHECK( (max)(li) == 6 ); + CHECK( (max)(li.begin(), li.end()) == 6 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::max(tricky, &internal_compare::compare_to) == 6 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::max(li) == 0 ); - CHECK( cppsort::probe::max(std::begin(li), std::end(li)) == 0 ); + CHECK( (max)(tricky, &internal_compare::compare_to) == 6 ); } SECTION( "upper bound" ) @@ -35,8 +32,10 @@ TEST_CASE( "presortedness measure: max", "[probe][max]" ) // the input sequence minus one std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::max(li) == 10 ); - CHECK( cppsort::probe::max(std::begin(li), std::end(li)) == 10 ); + auto max_n = (max).max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 10 ); + CHECK( (max)(li) == max_n ); + CHECK( (max)(li.begin(), li.end()) == max_n ); } SECTION( "regressions" ) @@ -46,7 +45,7 @@ TEST_CASE( "presortedness measure: max", "[probe][max]" ) auto distribution = dist::ascending_sawtooth{}; distribution(std::back_inserter(collection), 100); - std::sort(std::begin(collection), std::end(collection)); - CHECK( cppsort::probe::max(collection) == 0 ); + std::sort(collection.begin(), collection.end()); + CHECK( (max)(collection) == 0 ); } } diff --git a/testsuite/probes/mono.cpp b/testsuite/probes/mono.cpp index b5ed6547..4539f58c 100644 --- a/testsuite/probes/mono.cpp +++ b/testsuite/probes/mono.cpp @@ -1,37 +1,32 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include #include #include #include +#include #include TEST_CASE( "presortedness measure: mono", "[probe][mono]" ) { + using cppsort::probe::mono; + SECTION( "simple test" ) { const std::forward_list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; - CHECK( cppsort::probe::mono(li) == 2 ); - CHECK( cppsort::probe::mono(std::begin(li), std::end(li)) == 2 ); + CHECK( mono(li) == 2 ); + CHECK( mono(li.begin(), li.end()) == 2 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::mono(tricky, &internal_compare::compare_to) == 2 ); + CHECK( mono(tricky, &internal_compare::compare_to) == 2 ); } SECTION( "lower bound" ) { - const std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::mono(li) == 0 ); - CHECK( cppsort::probe::mono(std::begin(li), std::end(li)) == 0 ); - const std::forward_list li1 = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::mono(li1) == 0 ); - - const std::forward_list li2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - CHECK( cppsort::probe::mono(li2) == 0 ); + CHECK( mono(li1) == 0 ); } SECTION( "upper bound" ) @@ -40,19 +35,21 @@ TEST_CASE( "presortedness measure: mono", "[probe][mono]" ) // size / 2 const std::forward_list li = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; - CHECK( cppsort::probe::mono(li) == 5 ); - CHECK( cppsort::probe::mono(std::begin(li), std::end(li)) == 5 ); + auto max_n = mono.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 5 ); + CHECK( mono(li) == max_n ); + CHECK( mono(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( cppsort::probe::mono(li) == 1 ); + CHECK( mono(li) == 1 ); const std::forward_list li1 = { 6, 5, 4, 3, 2, 2, 2, 2 }; - CHECK( cppsort::probe::mono(li1) == 0 ); + CHECK( mono(li1) == 0 ); const std::forward_list li2 = { 1, 1, 2, 8, 3, 3, 2, 1, 1, 5, 6 }; - CHECK( cppsort::probe::mono(li2) == 2 ); + CHECK( mono(li2) == 2 ); } } diff --git a/testsuite/probes/osc.cpp b/testsuite/probes/osc.cpp index 310319b7..7a173361 100644 --- a/testsuite/probes/osc.cpp +++ b/testsuite/probes/osc.cpp @@ -1,44 +1,41 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include #include #include #include +#include #include TEST_CASE( "presortedness measure: osc", "[probe][osc]" ) { + using cppsort::probe::osc; + SECTION( "simple test" ) { - // Example from the paper Adaptative Heapsort + // Example from the paper Adaptive Heapsort // by Levcopoulos and Petersson std::forward_list li = { 6, 3, 9, 8, 4, 7, 1, 11 }; - CHECK( cppsort::probe::osc(li) == 17 ); - CHECK( cppsort::probe::osc(std::begin(li), std::end(li)) == 17 ); + CHECK( osc(li) == 17 ); + CHECK( osc(li.begin(), li.end()) == 17 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::osc(tricky, &internal_compare::compare_to) == 17 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::osc(li) == 0 ); - CHECK( cppsort::probe::osc(std::begin(li), std::end(li)) == 0 ); + CHECK( osc(tricky, &internal_compare::compare_to) == 17 ); } SECTION( "upper bound" ) { - // Example from the paper Adaptative Heapsort + // Example from the paper Adaptive Heapsort // by Levcopoulos and Petersson, the upper bound // should be (size * (size - 2) - 1) / 2 std::forward_list li = { 8, 5, 10, 3, 12, 1, 13, 2, 11, 4, 9, 6, 7 }; - CHECK( cppsort::probe::osc(li) == 71 ); - CHECK( cppsort::probe::osc(std::begin(li), std::end(li)) == 71 ); + auto max_n = osc.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 71 ); + CHECK( osc(li) == max_n ); + CHECK( osc(li.begin(), li.end()) == max_n ); } } diff --git a/testsuite/probes/par.cpp b/testsuite/probes/par.cpp index e78addec..6198df2c 100644 --- a/testsuite/probes/par.cpp +++ b/testsuite/probes/par.cpp @@ -1,8 +1,7 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include @@ -10,21 +9,16 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) { + using cppsort::probe::par; + SECTION( "simple test" ) { const std::vector vec = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; - CHECK( cppsort::probe::par(vec) == 7 ); - CHECK( cppsort::probe::par(std::begin(vec), std::end(vec)) == 7 ); + CHECK( par(vec) == 7 ); + CHECK( par(vec.begin(), vec.end()) == 7 ); std::vector> tricky(vec.begin(), vec.end()); - CHECK( cppsort::probe::par(tricky, &internal_compare::compare_to) == 7 ); - } - - SECTION( "lower bound" ) - { - const std::vector vec = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::par(vec) == 0 ); - CHECK( cppsort::probe::par(std::begin(vec), std::end(vec)) == 0 ); + CHECK( par(tricky, &internal_compare::compare_to) == 7 ); } SECTION( "upper bound" ) @@ -33,7 +27,9 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) // the input sequence minus one const std::vector vec = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; - CHECK( cppsort::probe::par(vec) == 10 ); - CHECK( cppsort::probe::par(std::begin(vec), std::end(vec)) == 10 ); + auto max_n = par.max_for_size(vec.end() - vec.begin()); + CHECK( max_n == 10 ); + CHECK( par(vec) == max_n ); + CHECK( par(vec.begin(), vec.end()) == max_n ); } } diff --git a/testsuite/probes/relations.cpp b/testsuite/probes/relations.cpp index 935c7b30..e5254d72 100644 --- a/testsuite/probes/relations.cpp +++ b/testsuite/probes/relations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -23,20 +23,23 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) // tests check that these relations are respected in // the library + auto enc = cppsort::probe::enc(sequence); auto exc = cppsort::probe::exc(sequence); auto ham = cppsort::probe::ham(sequence); auto inv = cppsort::probe::inv(sequence); auto max = cppsort::probe::max(sequence); auto mono = cppsort::probe::mono(sequence); + auto osc = cppsort::probe::osc(sequence); auto par = cppsort::probe::par(sequence); auto rem = cppsort::probe::rem(sequence); auto runs = cppsort::probe::runs(sequence); + auto sus = cppsort::probe::sus(sequence); // Measures of Presortedness and Optimal Sorting Algorithms // by Heikki Mannila CHECK( exc <= inv ); - // A framework for adaptative sorting + // A framework for adaptive sorting // by Ola Petersson and Alistair Moffat CHECK( runs <= rem + 1 ); @@ -50,6 +53,26 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) // by Vladimir Estivill-Castro and Derick Wood CHECK( par <= inv ); CHECK( rem <= size * (1 - 1 / (par + 1)) ); + CHECK( inv <= size * par / 2 ); + + // Encroaching lists as a measure of presortedness + // by Steven S. Skiena + CHECK( enc <= runs ); + CHECK( (2 * std::sqrt(enc) + 1) <= inv ); + CHECK( enc <= std::min(rem + 1, size - rem) ); + CHECK( 2 * enc <= exc ); + + // Sorting Shuffled Monotone Sequences + // by Christos Levcopoulos and Ola Petersson + CHECK( sus <= runs ); + CHECK( sus <= max ); + CHECK( enc <= sus ); + + // Heapsort - Adapted for Presorted Files + // by Christos Levcopoulos and Ola Petersson + CHECK( osc <= 4 * inv ); + CHECK( osc <= 2 * size * runs + size ); + CHECK( osc <= size * par ); // Intuitive result: a descending run can be seen as several // ascending runs diff --git a/testsuite/probes/rem.cpp b/testsuite/probes/rem.cpp index ec8e6ad2..f29d011e 100644 --- a/testsuite/probes/rem.cpp +++ b/testsuite/probes/rem.cpp @@ -1,37 +1,32 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include #include #include #include +#include #include TEST_CASE( "presortedness measure: rem", "[probe][rem]" ) { + using cppsort::probe::rem; + SECTION( "simple test" ) { // Forward iterators std::forward_list li = { 6, 9, 79, 41, 44, 49, 11, 16, 69, 15 }; - CHECK( cppsort::probe::rem(li) == 4 ); - CHECK( cppsort::probe::rem(std::begin(li), std::end(li)) == 4 ); + CHECK( rem(li) == 4 ); + CHECK( rem(li.begin(), li.end()) == 4 ); // Random-access iterators - std::vector vec(std::begin(li), std::end(li)); - CHECK( cppsort::probe::rem(vec) == 4 ); - CHECK( cppsort::probe::rem(std::begin(vec), std::end(vec)) == 4 ); + std::vector vec(li.begin(), li.end()); + CHECK( rem(vec) == 4 ); + CHECK( rem(vec.begin(), vec.end()) == 4 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::rem(tricky, &internal_compare::compare_to) == 4 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::rem(li) == 0 ); - CHECK( cppsort::probe::rem(std::begin(li), std::end(li)) == 0 ); + CHECK( rem(tricky, &internal_compare::compare_to) == 4 ); } SECTION( "upper bound" ) @@ -40,18 +35,9 @@ TEST_CASE( "presortedness measure: rem", "[probe][rem]" ) // the input sequence minus one std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::rem(li) == 10 ); - CHECK( cppsort::probe::rem(std::begin(li), std::end(li)) == 10 ); - } - - SECTION( "uniform distribution" ) - { - // Check that we are taking the longest non-decreasing - // subsequence into account, and not the longest increasing - // subsequence - - std::forward_list li = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }; - CHECK( cppsort::probe::rem(li) == 0 ); - CHECK( cppsort::probe::rem(std::begin(li), std::end(li)) == 0 ); + auto max_n = rem.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 10 ); + CHECK( rem(li) == max_n ); + CHECK( rem(li.begin(), li.end()) == max_n ); } } diff --git a/testsuite/probes/runs.cpp b/testsuite/probes/runs.cpp index bb8ff750..e2742965 100644 --- a/testsuite/probes/runs.cpp +++ b/testsuite/probes/runs.cpp @@ -1,40 +1,32 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include #include #include #include +#include #include TEST_CASE( "presortedness measure: runs", "[probe][runs]" ) { + using cppsort::probe::runs; + SECTION( "simple tests" ) { std::forward_list li = { 40, 49, 58, 99, 60, 70, 12, 87, 9, 8, 82, 91, 99, 67, 82, 92 }; - CHECK( cppsort::probe::runs(li) == 5 ); - CHECK( cppsort::probe::runs(std::begin(li), std::end(li)) == 5 ); + CHECK( runs(li) == 5 ); + CHECK( runs(li.begin(), li.end()) == 5 ); // From Right invariant metrics and measures of // presortedness by Estivill-Castro, Mannila and Wood std::forward_list li2 = { 4, 2, 6, 5, 3, 1, 9, 7, 10, 8 }; - CHECK( cppsort::probe::runs(li2) == 6 ); - CHECK( cppsort::probe::runs(std::begin(li2), std::end(li2)) == 6 ); + CHECK( runs(li2) == 6 ); + CHECK( runs(li2.begin(), li2.end()) == 6 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::runs(tricky, &internal_compare::compare_to) == 5 ); - } - - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::runs(li) == 0 ); - CHECK( cppsort::probe::runs(std::begin(li), std::end(li)) == 0 ); - - std::forward_list li1 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - CHECK( cppsort::probe::runs(li1) == 0 ); + CHECK( runs(tricky, &internal_compare::compare_to) == 5 ); } SECTION( "upper bound" ) @@ -43,7 +35,9 @@ TEST_CASE( "presortedness measure: runs", "[probe][runs]" ) // the input sequence minus one std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::runs(li) == 10 ); - CHECK( cppsort::probe::runs(std::begin(li), std::end(li)) == 10 ); + auto max_n = runs.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 10 ); + CHECK( runs(li) == max_n ); + CHECK( runs(li.begin(), li.end()) == max_n ); } } diff --git a/testsuite/probes/sus.cpp b/testsuite/probes/sus.cpp new file mode 100644 index 00000000..9ddbebfa --- /dev/null +++ b/testsuite/probes/sus.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include + +TEST_CASE( "presortedness measure: sus", "[probe][sus]" ) +{ + using cppsort::probe::sus; + + SECTION( "simple test" ) + { + std::forward_list li = { 6, 9, 79, 41, 44, 49, 11, 16, 69, 15 }; + CHECK( sus(li) == 3 ); + CHECK( sus(li.begin(), li.end()) == 3 ); + + std::vector> tricky(li.begin(), li.end()); + CHECK( sus(tricky, &internal_compare::compare_to) == 3 ); + } + + SECTION( "upper bound" ) + { + // The upper bound should correspond to the size of + // the input sequence minus one + + std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + auto max_n = sus.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 10 ); + CHECK( sus(li) == max_n ); + CHECK( sus(li.begin(), li.end()) == max_n ); + } +} diff --git a/testsuite/sorter_facade_constexpr.cpp b/testsuite/sorter_facade_constexpr.cpp new file mode 100644 index 00000000..463394fe --- /dev/null +++ b/testsuite/sorter_facade_constexpr.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + struct constexpr_insertion_sorter_impl + { + template< + typename RandomAccessIterator, + typename Compare = std::less<>, + typename Projection = cppsort::utility::identity, + typename = std::enable_if_t< + cppsort::is_projection_iterator_v + > + > + constexpr auto operator()(RandomAccessIterator first, RandomAccessIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + if (first == last) return; + + using cppsort::utility::iter_move; + auto&& comp = cppsort::utility::as_function(compare); + auto&& proj = cppsort::utility::as_function(projection); + + for (auto cur = first + 1 ; cur != last ; ++cur) { + auto sift = cur; + auto sift_1 = sift - 1; + + // Compare first so we can avoid 2 moves for + // an element already positioned correctly. + if (comp(proj(*sift), proj(*sift_1))) { + auto tmp = iter_move(sift); + auto&& tmp_proj = proj(tmp); + + do { + *sift = iter_move(sift_1); + } while (--sift != first && comp(tmp_proj, proj(*--sift_1))); + *sift = std::move(tmp); + } + } + } + + using iterator_category = std::random_access_iterator_tag; + using is_always_stable = std::true_type; + }; + + struct constexpr_insertion_sorter: + cppsort::sorter_facade + {}; + + constexpr auto test_sorter() + -> bool + { + constexpr std::size_t size = 13; + int collection[size] = { 15, 6, 0, 2, 2, 3, 8, 12, 10, 5, 9, 7, 10 }; + constexpr_insertion_sorter sorter; + sorter(collection, collection + size); + return helpers::is_sorted(collection, collection + size); + } +} + +TEST_CASE( "test basic constexpr support", "[sorter_facade][constexpr]" ) +{ + // Check that sorter_facade can be useful in a constexpr constext + // if the sorter implementation is constexpr-friendly itself + constexpr bool is_sorted = test_sorter(); + CHECK( is_sorted ); +} diff --git a/testsuite/sorters/default_sorter_fptr.cpp b/testsuite/sorters/default_sorter_fptr.cpp index 9f7534b9..82453043 100644 --- a/testsuite/sorters/default_sorter_fptr.cpp +++ b/testsuite/sorters/default_sorter_fptr.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -25,7 +25,7 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterable" ) { - void(*sorter)(std::vector&) = cppsort::default_sorter(); + constexpr void(*sorter)(std::vector&) = cppsort::default_sorter(); sorter(vec); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); @@ -33,7 +33,7 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterable and compare" ) { - void(*sorter)(std::vector&, std::greater<>) = cppsort::default_sorter(); + constexpr void(*sorter)(std::vector&, std::greater<>) = cppsort::default_sorter(); sorter(vec, std::greater<>{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); @@ -41,7 +41,7 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterable and projection" ) { - void(*sorter)(std::vector&, decltype(projection)) = cppsort::default_sorter(); + constexpr void(*sorter)(std::vector&, decltype(projection)) = cppsort::default_sorter(); sorter(vec, projection); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); @@ -49,9 +49,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterable, compare and projection" ) { - void(*sorter)(std::vector&, - std::greater<>, - decltype(projection)) + constexpr void(*sorter)(std::vector&, + std::greater<>, + decltype(projection)) = cppsort::default_sorter(); sorter(vec, std::greater<>{}, projection); @@ -60,8 +60,8 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterators" ) { - void(*sorter)(std::vector::iterator, - std::vector::iterator) + constexpr void(*sorter)(std::vector::iterator, + std::vector::iterator) = cppsort::default_sorter(); sorter(std::begin(vec), std::end(vec)); @@ -70,9 +70,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterators and compare" ) { - void(*sorter)(std::vector::iterator, - std::vector::iterator, - std::greater<>) + constexpr void(*sorter)(std::vector::iterator, + std::vector::iterator, + std::greater<>) = cppsort::default_sorter(); sorter(std::begin(vec), std::end(vec), std::greater<>{}); @@ -81,9 +81,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterators and projection" ) { - void(*sorter)(std::vector::iterator, - std::vector::iterator, - decltype(projection)) + constexpr void(*sorter)(std::vector::iterator, + std::vector::iterator, + decltype(projection)) = cppsort::default_sorter(); sorter(std::begin(vec), std::end(vec), projection); @@ -92,10 +92,10 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with random-access iterators, compare and projection" ) { - void(*sorter)(std::vector::iterator, - std::vector::iterator, - std::greater<>, - decltype(projection)) + constexpr void(*sorter)(std::vector::iterator, + std::vector::iterator, + std::greater<>, + decltype(projection)) = cppsort::default_sorter(); sorter(std::begin(vec), std::end(vec), std::greater<>{}, projection); @@ -104,8 +104,8 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with bidirectional iterators" ) { - void(*sorter)(std::list::iterator, - std::list::iterator) + constexpr void(*sorter)(std::list::iterator, + std::list::iterator) = cppsort::default_sorter(); std::list li(std::begin(vec), std::end(vec)); @@ -115,9 +115,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with bidirectional iterators and compare" ) { - void(*sorter)(std::list::iterator, - std::list::iterator, - std::greater<>) + constexpr void(*sorter)(std::list::iterator, + std::list::iterator, + std::greater<>) = cppsort::default_sorter(); std::list li(std::begin(vec), std::end(vec)); @@ -127,9 +127,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with bidirectional iterators and projection" ) { - void(*sorter)(std::list::iterator, - std::list::iterator, - decltype(projection)) + constexpr void(*sorter)(std::list::iterator, + std::list::iterator, + decltype(projection)) = cppsort::default_sorter(); std::list li(std::begin(vec), std::end(vec)); @@ -139,9 +139,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with bidirectional iterators, compare and projection" ) { - void(*sorter)(std::list::iterator, - std::list::iterator, - std::greater<>, + constexpr void(*sorter)(std::list::iterator, + std::list::iterator, + std::greater<>, decltype(projection)) = cppsort::default_sorter(); @@ -152,8 +152,8 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with forward iterators" ) { - void(*sorter)(std::forward_list::iterator, - std::forward_list::iterator) + constexpr void(*sorter)(std::forward_list::iterator, + std::forward_list::iterator) = cppsort::default_sorter(); std::forward_list li(std::begin(vec), std::end(vec)); @@ -163,9 +163,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with forward iterators and compare" ) { - void(*sorter)(std::forward_list::iterator, - std::forward_list::iterator, - std::greater<>) + constexpr void(*sorter)(std::forward_list::iterator, + std::forward_list::iterator, + std::greater<>) = cppsort::default_sorter(); std::forward_list li(std::begin(vec), std::end(vec)); @@ -175,9 +175,9 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with forward iterators and projection" ) { - void(*sorter)(std::forward_list::iterator, - std::forward_list::iterator, - decltype(projection)) + constexpr void(*sorter)(std::forward_list::iterator, + std::forward_list::iterator, + decltype(projection)) = cppsort::default_sorter(); std::forward_list li(std::begin(vec), std::end(vec)); @@ -187,10 +187,10 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with forward iterators and projection" ) { - void(*sorter)(std::forward_list::iterator, - std::forward_list::iterator, - std::greater<>, - decltype(projection)) + constexpr void(*sorter)(std::forward_list::iterator, + std::forward_list::iterator, + std::greater<>, + decltype(projection)) = cppsort::default_sorter(); std::forward_list li(std::begin(vec), std::end(vec)); @@ -200,7 +200,7 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with self-sortable iterable" ) { - void(*sorter)(std::list&) = cppsort::default_sorter(); + constexpr void(*sorter)(std::list&) = cppsort::default_sorter(); std::list li(std::begin(vec), std::end(vec)); sorter(li); @@ -209,7 +209,7 @@ TEST_CASE( "default sorter function pointer tests", SECTION( "sort with self-sortable iterable and compare" ) { - void(*sorter)(std::forward_list&, std::greater<>) = cppsort::default_sorter(); + constexpr void(*sorter)(std::forward_list&, std::greater<>) = cppsort::default_sorter(); std::forward_list li(std::begin(vec), std::end(vec)); sorter(li, std::greater<>{}); diff --git a/testsuite/testing-tools/algorithm.h b/testsuite/testing-tools/algorithm.h index 1f098307..ea7a0fdc 100644 --- a/testsuite/testing-tools/algorithm.h +++ b/testsuite/testing-tools/algorithm.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_TESTSUITE_ALGORITHM_H_ @@ -9,7 +9,6 @@ // Headers //////////////////////////////////////////////////////////// #include -#include #include #include @@ -20,17 +19,23 @@ namespace helpers typename Compare = std::less<>, typename Projection = cppsort::utility::identity > - auto is_sorted(Iterator first, Iterator last, - Compare compare={}, Projection projection={}) + constexpr auto is_sorted(Iterator first, Iterator last, + Compare compare={}, Projection projection={}) -> bool { auto&& comp = cppsort::utility::as_function(compare); auto&& proj = cppsort::utility::as_function(projection); - for (auto it = std::next(first) ; it != last ; ++it) { - if (comp(proj(*it), proj(*first))) { + if (first == last) { + return true; + } + + auto next = first; + while (++next != last) { + if (comp(proj(*next), proj(*first))) { return false; } + ++first; } return true; } diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index c2ecca36..61d1d011 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_TESTSUITE_DISTRIBUTIONS_H_ @@ -22,12 +22,12 @@ namespace dist struct distribution { template - using fptr_t = void(*)(OutputIterator, std::size_t); + using fptr_t = void(*)(OutputIterator, long long int); template operator fptr_t() const { - return [](OutputIterator out, std::size_t size) { + return [](OutputIterator out, long long int size) { return Derived{}(out, size); }; } @@ -59,7 +59,7 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { // Pseudo-random number generator @@ -68,7 +68,7 @@ namespace dist std::vector vec; vec.reserve(size); - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { vec.emplace_back(i % 16); } std::shuffle(std::begin(vec), std::end(vec), engine); @@ -80,10 +80,10 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { *out++ = 0; } } @@ -93,10 +93,10 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { *out++ = i; } } @@ -106,7 +106,7 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { while (size--) { @@ -119,13 +119,13 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - for (std::size_t i = 0 ; i < size / 2 ; ++i) { + for (long long int i = 0 ; i < size / 2 ; ++i) { *out++ = i; } - for (std::size_t i = size / 2 ; i < size ; ++i) { + for (long long int i = size / 2 ; i < size ; ++i) { *out++ = size - i; } } @@ -135,11 +135,11 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { if (size > 0) { - for (std::size_t i = 0 ; i < size - 1 ; ++i) { + for (long long int i = 0 ; i < size - 1 ; ++i) { *out++ = i; } *out = 0; @@ -151,11 +151,11 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { if (size > 0) { - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { if (i != size / 2) { *out++ = i; } @@ -169,11 +169,11 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - std::size_t limit = size / cppsort::detail::log2(size) * 0.9; - for (std::size_t i = 0 ; i < size ; ++i) { + long long int limit = size / cppsort::detail::log2(size) * 0.9; + for (long long int i = 0 ; i < size ; ++i) { *out++ = i % limit; } } @@ -183,10 +183,10 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - std::size_t limit = size / cppsort::detail::log2(size) * 0.9; + long long int limit = size / cppsort::detail::log2(size) * 0.9; while (size--) { *out++ = size % limit; } @@ -197,36 +197,23 @@ namespace dist distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - for (std::size_t i = 0 ; i < size ; ++i) { + for (long long int i = 0 ; i < size ; ++i) { *out++ = (i % 2) ? i : -i; } } }; - struct alternating_16_values: - distribution - { - template - auto operator()(OutputIterator out, std::size_t size) const - -> void - { - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = (i % 2) ? i % 16 : -(i % 16); - } - } - }; - struct descending_plateau: distribution { template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - std::size_t i = size; + long long int i = size; while (i > 2 * size / 3) { *out++ = i; --i; @@ -250,18 +237,18 @@ namespace dist // implementations with common pivot selection methods go quadratic template - auto operator()(OutputIterator out, std::size_t size) const + auto operator()(OutputIterator out, long long int size) const -> void { - std::size_t j = size / 2; - for (std::size_t i = 1 ; i < j + 1 ; ++i) { + long long int j = size / 2; + for (long long int i = 1 ; i < j + 1 ; ++i) { if (i % 2 != 0) { *out++ = i; } else { *out++ = j + i - 1; } } - for (std::size_t i = 1 ; i < j + 1 ; ++i) { + for (long long int i = 1 ; i < j + 1 ; ++i) { *out++ = 2 * i; } } diff --git a/testsuite/utility/adapter_storage.cpp b/testsuite/utility/adapter_storage.cpp index 4bae9649..145fbd29 100644 --- a/testsuite/utility/adapter_storage.cpp +++ b/testsuite/utility/adapter_storage.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -103,8 +103,7 @@ TEST_CASE( "test correct adapter_storage behavior", "[adapter_storage]" ) cppsort::selection_sorter original_sorter{}; auto adapted_sorter = dummy_adapter(original_sorter); - void(*my_sort)(std::vector&) = adapted_sorter; - my_sort(arr); + adapted_sorter(arr); CHECK( std::is_sorted(std::begin(arr), std::end(arr)) ); } diff --git a/tools/mops-partial-ordering.tex b/tools/mops-partial-ordering.tex new file mode 100644 index 00000000..6aea5af3 --- /dev/null +++ b/tools/mops-partial-ordering.tex @@ -0,0 +1,74 @@ +% Copyright (c) 2021 Morwenn +% SPDX-License-Identifier: MIT + +\documentclass{minimal} +\usepackage{bm, pgf, tikz} +\usetikzlibrary{arrows, automata, backgrounds, positioning} + +\begin{document} + \begin{tikzpicture}[ + background rectangle/.style={fill=white}, + show background rectangle, + auto, + node distance = 1.3cm, + semithick + ] + + \tikzstyle{every state}=[ + draw=none, + shape=rectangle, + fill=white + ] + + % A framework for adaptive sorting + % by O. Pertersson and A. Moffat + % + % Max=Dis equivalence comes from: + % NeatSort - A practical adaptive algorithm + % by M. La Rocca and D. Cantone + \node[state] (reg) {$Reg$}; + \node[state] (loc) [below of=reg] {$Loc$}; + \node[state] (hist) [left=2.2cm of loc] {$Hist$}; + \node[state] (sms) [right=2.2cm of loc] {$SMS$}; + \node[state] (block) [below of=hist] {$Block$}; + \node[state] (osc) [below of=loc] {$\bm{Osc}$}; + \node[state] (enc) [below of=sms] {$\bm{Enc}$}; + \node[state] (rem) [below of=block] {$\bm{Rem}$}; + \node[state] (inv) [below of=osc] {$\bm{Inv}~$$\equiv$$~DS$}; + \node[state] (sus) [below of=enc] {$\bm{SUS}$}; + \node[state] (exc) [below of=rem] {$\bm{Exc}~$$\equiv$$~\bm{Ham}$}; + \node[state] (max) [below of=inv] {$\bm{Max}~$$\equiv$$~\bm{Dis}~$$\equiv$$~\bm{Par}$}; + \node[state] (runs) [below of=sus] {$\bm{Runs}$}; + \node[state] (m01) [below of=max] {$m_{01}$}; + \node[state] (m0) [below of=m01] {$m_{0}$}; + \path[-] (reg) edge node {} (hist); + \path[-] (reg) edge node {} (loc); + \path[-] (reg) edge node {} (sms); + \path[-] (hist) edge node {} (block); + \path[-] (hist) edge node {} (inv); + \path[-] (loc) edge node {} (block); + \path[-] (loc) edge node {} (osc); + \path[-] (sms) edge node {} (enc); + \path[-] (block) edge node {} (rem); + \path[-] (osc) edge node {} (inv); + \path[-] (osc) edge node {} (runs); + \path[-] (enc) edge node {} (sus); + \path[-] (rem) edge node {} (exc); + \path[-] (inv) edge node {} (max); + \path[-] (sus) edge node {} (max); + \path[-] (sus) edge node {} (runs); + \path[-] (exc) edge node {} (m01); + \path[-] (max) edge node {} (m01); + \path[-] (runs) edge node {} (m01); + \path[-] (m01) edge node {} (m0); + + % Sort Race + % by H. Zhang, B. Meng and Y. Liang + \node[state] (mono) [right=1.4cm of sus] {$\bm{Mono}$}; + \path[-] (mono) edge node {} (runs); + + % See the Original Research page of the docs + \path[-] (enc) edge node {} (mono); + + \end{tikzpicture} +\end{document} diff --git a/tools/release-checklist.md b/tools/release-checklist.md index 57ab4a79..94e70cd1 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -31,7 +31,6 @@ development phase: - [ ] Add the Zenodo badge to the release notes. - [ ] Close the new version's milestone. -- [ ] Push the new version to Bincrafters. - [ ] Add the new version to Conan Center Index. - [ ] Brag about it where relevant. - [ ] Merge master into 2.0.0-develop branch. diff --git a/tools/rename-library.py b/tools/rename-library.py index 72591ddd..6ecbb5ea 100644 --- a/tools/rename-library.py +++ b/tools/rename-library.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020 Morwenn +# Copyright (c) 2020-2021 Morwenn # SPDX-License-Identifier: MIT """ @@ -14,6 +14,7 @@ import fileinput import fnmatch import os +import shutil import sys import pygit2 @@ -72,6 +73,8 @@ def main(): # sure that the .gitignore is valid when modifying said files old_dirname = os.path.join(repo_root, 'include', 'cpp-sort') new_dirname = os.path.join(repo_root, 'include', REPLACEMENT_LIST['cpp-sort']) + if os.path.isdir(new_dirname): + shutil.rmtree(new_dirname) os.rename(old_dirname, new_dirname) if __name__ == '__main__': diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 42418799..c1adf6d3 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -48,7 +48,7 @@ struct shuffled_string: template void test(const char* name) { - const int size = 412; + const int size = 5000; std::vector collection; auto distribution = shuffled_string{}; @@ -59,6 +59,7 @@ void test(const char* name) auto sorter = Sorter{}; sorter(collection); + auto copy2 = collection; // Collect basic data auto first_unsorted_it = std::is_sorted_until(std::begin(collection), std::end(collection)); @@ -72,13 +73,13 @@ void test(const char* name) std::cout << "position of the first unsorted element: " << std::distance(std::begin(collection), first_unsorted_it) << std::endl; + } else { + std::cout << "is it the same as the one sorted with quicksort? "; + std::cout << (collection == copy) << std::endl; + std::cout << "were some elements altered? "; + cppsort::quick_sort(std::begin(collection), std::end(collection)); + std::cout << (collection != copy) << std::endl; } - std::cout << "is it the same as the one sorted with std::sort? "; - std::cout << (collection == copy) << std::endl; - std::cout << "were some elements altered? "; - auto copy2 = collection; - cppsort::quick_sort(std::begin(collection), std::end(collection)); - std::cout << (collection != copy) << std::endl; // Measures of presortedness std::cout << '\n' @@ -93,6 +94,7 @@ void test(const char* name) << "par: " << cppsort::probe::par(copy2) << std::endl << "rem: " << cppsort::probe::rem(copy2) << std::endl << "runs: " << cppsort::probe::runs(copy2) << std::endl + << "sus: " << cppsort::probe::sus(copy2) << std::endl << '\n'; if (size < 40) {