From 78ca2b084e244ac10511f2fac4ab0f35d70c3d7f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Jan 2021 16:32:03 +0100 Subject: [PATCH 01/88] conanfile.py: move checks to validate() --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 09aec752..ccbc240b 100644 --- a/conanfile.py +++ b/conanfile.py @@ -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) From f7f041855f2efe958cf16868a71fa5bbc30f3fba Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Jan 2021 17:37:32 +0100 Subject: [PATCH 02/88] Fix CPPSORT_STD_IDENTITY_AVAILABLE Its definition was broken for non-Clang non-GCC compilers. --- include/cpp-sort/detail/config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 72d01d48..6a311f34 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -46,9 +46,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 From 6178e0d621317cfcf6b158657b524a482af6a4f9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 31 Jan 2021 11:35:21 +0100 Subject: [PATCH 03/88] rename-library.py: remove new dir if it already exists --- tools/rename-library.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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__': From a8c7cdb2e681586fc5094aef244faabe17cf74e1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 15 Feb 2021 16:28:41 +0100 Subject: [PATCH 04/88] Slightly improve test_failing_sorter.cpp --- tools/test_failing_sorter.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 42418799..1bce0c53 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -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' From 7fbc7c6dc5aab19c84629264ce4dbaad8ee41b5f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 15 Feb 2021 16:58:51 +0100 Subject: [PATCH 05/88] Add header compilation tests Add some basic tests to ensure that every public-facing header can be included without triggering any error. --- cmake/header-test.cpp.in | 8 +++ testsuite/CMakeLists.txt | 121 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 cmake/header-test.cpp.in diff --git a/cmake/header-test.cpp.in b/cmake/header-test.cpp.in new file mode 100644 index 00000000..001312a4 --- /dev/null +++ b/cmake/header-test.cpp.in @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ + +#include + +int main() {} diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 69ec9913..3bd85196 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,108 @@ else() endif() include(Catch) +######################################## +# Compilation tests + +# These tests ensure that every public-facing header +# can be included individually without causing any +# compile-time error + +foreach(HEADER_FILE + adapters/container_aware_adapter.h + adapters/counting_adapter.h + adapters/hybrid_adapter.h + adapters/indirect_adapter.h + adapters/out_of_place_adapter.h + adapters/schwartz_adapter.h + adapters/self_sort_adapter.h + adapters/small_array_adapter.h + adapters/stable_adapter.h + adapters/verge_adapter.h + adapters.h + comparators/case_insensitive_less.h + comparators/natural_less.h + comparators/partial_greater.h + comparators/partial_less.h + comparators/projection_compare.h + comparators/total_greater.h + comparators/total_less.h + comparators/weak_greater.h + comparators/weak_less.h + fixed/low_comparisons_sorter.h + fixed/low_moves_sorter.h + fixed/sorting_network_sorter.h + fixed_sorters.h + fwd.h + probes/dis.h + probes/enc.h + probes/exc.h + probes/ham.h + probes/inv.h + probes/max.h + probes/mono.h + probes/osc.h + probes/par.h + probes/rem.h + probes/runs.h + probes.h + refined.h + sort.h + sorters/block_sorter.h + sorters/counting_sorter.h + sorters/default_sorter.h + sorters/drop_merge_sorter.h + sorters/grail_sorter.h + sorters/heap_sorter.h + sorters/insertion_sorter.h + sorters/merge_insertion_sorter.h + sorters/merge_sorter.h + sorters/pdq_sorter.h + sorters/poplar_sorter.h + sorters/quick_merge_sorter.h + sorters/quick_sorter.h + sorters/selection_sorter.h + sorters/ska_sorter.h + sorters/smooth_sorter.h + sorters/spin_sorter.h + sorters/split_sorter.h + sorters/spread_sorter/float_spread_sorter.h + sorters/spread_sorter/integer_spread_sorter.h + sorters/spread_sorter/string_spread_sorter.h + sorters/spread_sorter.h + sorters/std_sorter.h + sorters/tim_sorter.h + sorters/verge_sorter.h + sorters.h + sorter_facade.h + sorter_traits.h + stable_sort.h + utility/adapter_storage.h + utility/as_function.h + utility/branchless_traits.h + utility/buffer.h + utility/functional.h + utility/iter_move.h + utility/make_integer_range.h + utility/size.h + utility/static_const.h + version.h +) + # Create standalone test file + string(REPLACE "/" "-" TEST_FILE "${HEADER_FILE}") + get_filename_component(TEST_FILE ${TEST_FILE} NAME_WLE) + set(TEST_FILE "test-${TEST_FILE}") + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/header-test.cpp.in "${TEST_FILE}.cpp" @ONLY) + + # Compile standalone test file + add_executable(${TEST_FILE} "${TEST_FILE}.cpp") + target_link_libraries(${TEST_FILE} PRIVATE cpp-sort::cpp-sort) + target_compile_definitions(${TEST_FILE} PRIVATE CPPSORT_DISABLE_DEPRECATION_WARNINGS) +endforeach() + +######################################## +# Configure runtime tests + macro(configure_tests target) # Make testing tools easily available to tests # regardless of the directory of the test @@ -93,6 +199,9 @@ macro(configure_tests target) endif() endmacro() +######################################## +# Main tests + add_executable(main-tests # General tests main.cpp @@ -205,6 +314,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 +331,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 +350,9 @@ if (CPPSORT_USE_VALGRIND) endif() endif() +######################################## +# Discover tests + include(CTest) string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) From 00b34c3b8c55a4870b309688cec2a5708578842f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 15 Feb 2021 21:19:12 +0100 Subject: [PATCH 06/88] Make sorter conversion to function pointer unconditionally constexpr That feature used to only be available in C++17, when the conversion of lambdas to function pointers was made constepxr. The new design doesn't use lambdas anymore, which allows the conversion to always be constexpr. This commit also simplifies the implementation, making it less redundant and easier to extend. Not using lambdas also means easier to understand error messages and less potential generic lambda bloat. --- docs/Changelog.md | 4 - docs/Sorter-facade.md | 26 +++-- include/cpp-sort/detail/config.h | 13 --- include/cpp-sort/sorter_facade.h | 132 ++++++++-------------- testsuite/adapters/every_adapter_fptr.cpp | 40 +++---- testsuite/sorters/default_sorter_fptr.cpp | 88 +++++++-------- 6 files changed, 126 insertions(+), 177 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 672604bc..3c651dd8 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -50,10 +50,6 @@ 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. - - This feature is made available through the check `__cpp_constexpr >= 201603`. - **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. diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index 70e0ca2b..b9268b22 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,12 +43,12 @@ 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()`. - -Since C++17, these function pointer conversion operators are also `constexpr`. +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()`. *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: @@ -72,9 +72,9 @@ auto operator()(Iterator first, Iterator last, -> /* 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 @@ -119,7 +119,7 @@ auto operator()(Iterable&& iterable, Compare compare, Projection projection) con -> /* 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. @@ -169,12 +169,20 @@ 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. + + + [selection-sort]: https://en.wikipedia.org/wiki/Selection_sort + [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/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 6a311f34..27e7534b 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 diff --git a/include/cpp-sort/sorter_facade.h b/include/cpp-sort/sorter_facade.h index 8da56705..29d87017 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; } }; 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/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<>{}); From a9340acd906f5faf74ea9f5ed269d31545bb72f7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 24 Sep 2019 20:48:21 +0200 Subject: [PATCH 07/88] Preliminary CMake configuration for MSVC support [ci skip] --- CMakeLists.txt | 5 +++++ cmake/cpp-sort-utils.cmake | 18 +++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b000a91b..d58cb3c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/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() From dbae915ec36a7beb0589827bebf432794561eeb6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Feb 2021 14:10:39 +0100 Subject: [PATCH 08/88] Silence a narrowing conversion warning --- include/cpp-sort/detail/block_sort.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/block_sort.h b/include/cpp-sort/detail/block_sort.h index 74f15646..c228f8f9 100644 --- a/include/cpp-sort/detail/block_sort.h +++ b/include/cpp-sort/detail/block_sort.h @@ -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 From 48776c31c00f0b0030598dce3deeb9154bf97724 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Feb 2021 14:54:29 +0100 Subject: [PATCH 09/88] Add explicit destructor to fixed_size_list_node --- include/cpp-sort/detail/fixed_size_list.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/fixed_size_list.h b/include/cpp-sort/detail/fixed_size_list.h index 52fb9424..3254d853 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_ @@ -47,6 +47,9 @@ namespace detail fixed_size_list_node& operator=(const fixed_size_list_node&) = delete; fixed_size_list_node& operator=(fixed_size_list_node&&) = delete; + // The list takes care of managing the lifetime of value + ~fixed_size_list_node() {} + // Inhibit construction of the node with a value union { T value; }; From 8479a93ff041d95bde16cf63ba8d44717e6ba4b4 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Feb 2021 15:54:01 +0100 Subject: [PATCH 10/88] Ensure that buffered_inplace_merge is always fed pointers --- .../cpp-sort/detail/buffered_inplace_merge.h | 7 ++--- include/cpp-sort/utility/buffer.h | 28 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/include/cpp-sort/detail/buffered_inplace_merge.h b/include/cpp-sort/detail/buffered_inplace_merge.h index e1e9ad66..1847419f 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 */ @@ -130,14 +130,13 @@ namespace detail } }; - 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) + remove_cvref_t>* buff) -> void { using utility::iter_move; diff --git a/include/cpp-sort/utility/buffer.h b/include/cpp-sort/utility/buffer.h index ab1eddd2..03438d3d 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> { From 3851215a6dfba20c636f656618c812c942dec4c0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Feb 2021 18:35:02 +0100 Subject: [PATCH 11/88] Fix potential integer conversion issues in grailsort --- include/cpp-sort/detail/grail_sort.h | 106 +++++++++++++++------------ 1 file changed, 61 insertions(+), 45 deletions(-) diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index 1c1bd0c6..3ff9260f 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -208,10 +208,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 +272,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 +308,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 +325,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 +366,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 +388,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 +445,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 +484,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 +497,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; } @@ -510,7 +519,7 @@ namespace grail 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 +528,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 +545,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 +567,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 +590,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 +610,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); @@ -613,6 +626,7 @@ namespace grail Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; using utility::iter_swap; auto&& proj = utility::as_function(projection); @@ -623,7 +637,7 @@ namespace grail } 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) { @@ -644,18 +658,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 +686,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 +696,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; From 7f5591e9b3b255265e13b5d34b53e0631b94cd68 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 3 Feb 2021 10:35:20 +0100 Subject: [PATCH 12/88] Silence conversion warning in dynamic_buffer --- include/cpp-sort/utility/buffer.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/utility/buffer.h b/include/cpp-sort/utility/buffer.h index 03438d3d..a88c73da 100644 --- a/include/cpp-sort/utility/buffer.h +++ b/include/cpp-sort/utility/buffer.h @@ -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)) + ) {} }; }; From aa18800029e4c805ec8641152983c9e64b106f36 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 3 Feb 2021 11:56:47 +0100 Subject: [PATCH 13/88] Change distributions intput size from size_t to long long int Using signed integers ensures that we won't accidentally generate big positive numbers instead of negative ones (which was most likely the case for the alternating distributions). --- benchmarks/benchmarking-tools/distributions.h | 82 +++++++++---------- testsuite/testing-tools/distributions.h | 64 +++++++-------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/benchmarks/benchmarking-tools/distributions.h b/benchmarks/benchmarking-tools/distributions.h index bf528a77..d3d6db71 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); } } @@ -291,11 +291,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 % 16 : -(i % 16)); } } @@ -307,12 +307,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 i = size; + long long int i = size; while (i > 2 * size / 3) { *out++ = proj(i); --i; @@ -343,7 +343,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 +351,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 +369,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/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index c2ecca36..aa111d7f 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,10 +197,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 % 2) ? i : -i; } } @@ -210,10 +210,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 % 2) ? i % 16 : -(i % 16); } } @@ -223,10 +223,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 i = size; + long long int i = size; while (i > 2 * size / 3) { *out++ = i; --i; @@ -250,18 +250,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; } } From eefd52740d7c5827a591450887187a21f67f9a47 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 3 Feb 2021 16:34:25 +0100 Subject: [PATCH 14/88] Don't test conversions from sorter to function pointer on MSVC While everything else compiles fine, the conversion from a sorter to a function pointer seems broken with MSVC. It is reasonable to have this single feature not working. --- testsuite/CMakeLists.txt | 4 ++-- testsuite/utility/adapter_storage.cpp | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 3bd85196..a732206c 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -227,7 +227,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 @@ -289,7 +289,7 @@ add_executable(main-tests # 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 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)) ); } From c3cde9fd42aa1176bee3c77980892460de32de1c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 6 Feb 2021 13:15:47 +0100 Subject: [PATCH 15/88] Guard std::min and std::max against common macros --- include/cpp-sort/detail/block_sort.h | 6 +++--- include/cpp-sort/detail/inplace_merge.h | 8 ++++---- include/cpp-sort/detail/pdqsort.h | 4 ++-- include/cpp-sort/detail/swap_if.h | 14 +++++++------- include/cpp-sort/detail/timsort.h | 2 +- include/cpp-sort/probes/dis.h | 4 ++-- include/cpp-sort/probes/max.h | 6 +++--- include/cpp-sort/probes/osc.h | 6 +++--- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/cpp-sort/detail/block_sort.h b/include/cpp-sort/detail/block_sort.h index c228f8f9..5741e220 100644 --- a/include/cpp-sort/detail/block_sort.h +++ b/include/cpp-sort/detail/block_sort.h @@ -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/inplace_merge.h b/include/cpp-sort/detail/inplace_merge.h index 8c4d86b6..d1781004 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_ @@ -106,7 +106,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)); @@ -122,7 +122,7 @@ namespace detail -> 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), @@ -152,7 +152,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/pdqsort.h b/include/cpp-sort/detail/pdqsort.h index fec53531..7459c497 100644 --- a/include/cpp-sort/detail/pdqsort.h +++ b/include/cpp-sort/detail/pdqsort.h @@ -271,7 +271,7 @@ namespace detail } // Swap elements and update block sizes and first/last boundaries. - int num = std::min(num_l, num_r); + 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; @@ -314,7 +314,7 @@ namespace detail } } - int num = std::min(num_l, num_r); + 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; 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/timsort.h b/include/cpp-sort/detail/timsort.h index a3b21c0f..7418f1f7 100644 --- a/include/cpp-sort/detail/timsort.h +++ b/include/cpp-sort/detail/timsort.h @@ -114,7 +114,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; } diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index 79cbbf66..735c61d1 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; } diff --git a/include/cpp-sort/probes/max.h b/include/cpp-sort/probes/max.h index 7df24f76..aef3b685 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_ @@ -79,9 +79,9 @@ namespace probe // 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; diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index bb8f2811..6ec8ca6c 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; } From a5db222014be3b114b70d507888824b05c5245f0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 8 Feb 2021 16:49:01 +0100 Subject: [PATCH 16/88] Fix invalid iterators in grailsort strategy 3 (#183) Grailsort creates invalid pointers here and there that are never dereferenced, so generally don't cause any issues. However, forming invalid iterators like that might cause issues, and MSVC debug iterators do trip on grailsort. This commit only fixes "strategy 3": when the algorithm can't find at least 4 unique keys in the collection. --- include/cpp-sort/detail/grail_sort.h | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index 3ff9260f..d224ca26 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -623,26 +623,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 (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) { From e0b4f3b9ce192a531bf696a8c6d4e4ea174c1697 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 14 Feb 2021 20:38:36 +0100 Subject: [PATCH 17/88] Fix invalid iterators in grailsort strategy 2 (#183) --- include/cpp-sort/detail/grail_sort.h | 35 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index d224ca26..ef8e98d6 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -34,7 +34,7 @@ #include "three_way_compare.h" #include "type_traits.h" #include "upper_bound.h" - +#include namespace cppsort { namespace detail @@ -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); + } } } @@ -514,10 +529,12 @@ 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; + } } auto rest = last - p0; if (rest > h) { From 3d2db924ed1538bdccee993eee0437b00540ac4a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 15 Feb 2021 14:58:42 +0100 Subject: [PATCH 18/88] Fix invalid iterators in merge_insertion_sort (#182) --- include/cpp-sort/detail/merge_insertion_sort.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index b4336621..ec2f2843 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -376,10 +376,17 @@ namespace detail 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 + // + // current_it is guaranteed to be at least first + 2, so this is + // safe - incrementing it at the end of the loop might cause the + // iterator to go out of bounds, so we decrement it here so that + // we can increment it at the beginning of the loop + current_it -= 2; 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) { @@ -388,7 +395,6 @@ namespace detail utility::identity{} ); chain.insert(insertion_point, current_it); - current_it += 2; ++current_pend; } From d6dd8f2c7d8c2d31e5d36b912ca118ad80244361 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 16 Feb 2021 16:34:43 +0100 Subject: [PATCH 19/88] Add Visual Studio to Windows Builds workflow --- .../{build-windows.yml => build-mingw.yml} | 6 +-- .github/workflows/build-msvc.yml | 54 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) rename .github/workflows/{build-windows.yml => build-mingw.yml} (91%) create mode 100644 .github/workflows/build-msvc.yml 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}} From 8dbc9c31399fd0e801de5e071bc5b46e1c0640d6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Feb 2021 12:10:47 +0100 Subject: [PATCH 20/88] Fix detection of std::identity on GCC --- include/cpp-sort/detail/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 27e7534b..4fd795bc 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -24,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 From 6455546edb204204092f06195896a94cc4cda1ef Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Feb 2021 12:53:50 +0100 Subject: [PATCH 21/88] Mark MSVC STL std::_Mem_fn as likely branchless (#177) --- docs/Changelog.md | 4 ++-- include/cpp-sort/utility/branchless_traits.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 3c651dd8..ac9d8ecd 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -74,9 +74,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 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 From 782a05c0436ce3b0be4441b5d138b8585b5fbf14 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Feb 2021 14:41:46 +0100 Subject: [PATCH 22/88] Update documentation to mention MSVC support --- README.md | 7 ++----- docs/Sorter-facade.md | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3c941297..99bf4297 100644 --- a/README.md +++ b/README.md @@ -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. 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/docs/Sorter-facade.md b/docs/Sorter-facade.md index b9268b22..0544dac9 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -45,6 +45,8 @@ 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 `typedef`s needed for the syntax to be valid. In these signatures, `Ret` is the [`std::result_of_t`][std-result-of] of the sorter called with the parameters. The actual implementation is more verbose and redundant, but it allows to transform a sorter into a function pointer corresponding to any valid overload of `operator()`. +***WARNING:** conversion to function pointers does not work with MSVC (issue #185).* + *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). From 75e049914f3df634949533a63b1cb0690980e724 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Feb 2021 14:43:38 +0100 Subject: [PATCH 23/88] Mention that MSVC support is only for /permissive- --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99bf4297..7125ca63 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ or more recent versions of these compilers. So far, the library should work with following compilers: * 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. A few features are unavailable. +* 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++. From 92bae8fc06fd7b1a056a03094ab61ccc349a4f35 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Feb 2021 14:47:19 +0100 Subject: [PATCH 24/88] Don't include in grail_sort.h --- include/cpp-sort/detail/grail_sort.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index ef8e98d6..fcd8f430 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -34,7 +34,7 @@ #include "three_way_compare.h" #include "type_traits.h" #include "upper_bound.h" -#include + namespace cppsort { namespace detail From d1a6a52e2baf66a10507056efc5389eafcabb53b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Feb 2021 14:48:37 +0100 Subject: [PATCH 25/88] Don't include in swap_ranges.h --- include/cpp-sort/detail/swap_ranges.h | 2 -- 1 file changed, 2 deletions(-) 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 From cbadd8858305b5ac5bd8cb9ffaee11d7b6cdf59a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 18 Feb 2021 19:22:59 +0100 Subject: [PATCH 26/88] Test measures of presortedness with all_equal distribution --- testsuite/CMakeLists.txt | 1 + testsuite/probes/every_probe_all_equal.cpp | 35 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 testsuite/probes/every_probe_all_equal.cpp diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index a732206c..9ded8ac2 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -284,6 +284,7 @@ add_executable(main-tests probes/rem.cpp probes/runs.cpp probes/relations.cpp + probes/every_probe_all_equal.cpp probes/every_probe_move_compare_projection.cpp # Sorters tests diff --git a/testsuite/probes/every_probe_all_equal.cpp b/testsuite/probes/every_probe_all_equal.cpp new file mode 100644 index 00000000..62ca9fcb --- /dev/null +++ b/testsuite/probes/every_probe_all_equal.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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) ) +{ + // Ensure that all measures of presortedness return 0 when + // given a collection where all elements are equal + + std::vector collection(50, 5); + cppsort::detail::remove_cvref_t mop; + auto presortedness = mop(collection); + CHECK( presortedness == 0 ); +} From ea8bbd2854b21116cbdad091f37f5c06e53c4beb Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 18 Feb 2021 23:08:21 +0100 Subject: [PATCH 27/88] Move invert and not_fn to a new header --- .../cpp-sort/detail/buffered_inplace_merge.h | 35 +----- include/cpp-sort/detail/functional.h | 104 ++++++++++++++++++ include/cpp-sort/detail/spinsort.h | 47 +------- 3 files changed, 109 insertions(+), 77 deletions(-) create mode 100644 include/cpp-sort/detail/functional.h diff --git a/include/cpp-sort/detail/buffered_inplace_merge.h b/include/cpp-sort/detail/buffered_inplace_merge.h index 1847419f..e88dfc34 100644 --- a/include/cpp-sort/detail/buffered_inplace_merge.h +++ b/include/cpp-sort/detail/buffered_inplace_merge.h @@ -23,6 +23,7 @@ #include #include #include "config.h" +#include "functional.h" #include "iterator_traits.h" #include "memory.h" #include "move.h" @@ -98,38 +99,6 @@ 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 auto buffered_inplace_merge(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last, @@ -154,7 +123,7 @@ namespace detail 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/functional.h b/include/cpp-sort/detail/functional.h new file mode 100644 index 00000000..5859005c --- /dev/null +++ b/include/cpp-sort/detail/functional.h @@ -0,0 +1,104 @@ +/* + * 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)); + } +}} + +#endif // CPPSORT_DETAIL_FUNCTIONAL_H_ diff --git a/include/cpp-sort/detail/spinsort.h b/include/cpp-sort/detail/spinsort.h index b947075f..6534b8ee 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; } From 958ac9aa967390b3532bcb6f1c414a1b6029e2f2 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 18 Feb 2021 23:54:45 +0100 Subject: [PATCH 28/88] Add measure of presortedness: sus (issue #62) Add the measure of presortedness SUS(X) first described in Sorting Shuffled Monotone Sequences by Levcopoulos and Petersson. SUS(X) is the minimum number of non-decreasing subsequences (of possibly non adjacent elements) into which X can be partitioned. It happens to be equal to the size of the longest decreasing subsequence of X minus 1, which is why we reuse the algorithm used to compute Rem(X) to compute it. --- docs/Measures-of-presortedness.md | 16 ++- .../longest_non_descending_subsequence.h | 100 ++++++++++++++++++ include/cpp-sort/probes.h | 3 +- include/cpp-sort/probes/rem.h | 94 +++------------- include/cpp-sort/probes/sus.h | 63 +++++++++++ testsuite/CMakeLists.txt | 2 + testsuite/probes/every_probe_all_equal.cpp | 3 +- .../every_probe_move_compare_projection.cpp | 8 +- testsuite/probes/relations.cpp | 11 +- testsuite/probes/sus.cpp | 39 +++++++ tools/test_failing_sorter.cpp | 3 +- 11 files changed, 254 insertions(+), 88 deletions(-) create mode 100644 include/cpp-sort/detail/longest_non_descending_subsequence.h create mode 100644 include/cpp-sort/probes/sus.h create mode 100644 testsuite/probes/sus.cpp diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index aa8280ff..7ee3d496 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -180,7 +180,7 @@ Its worst case returns (*n* - 1) when the last element of *X* is smaller than t #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 *n* minus the size of the [longest non-decreasing 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. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -197,3 +197,17 @@ Computes the number of non-decreasing runs in *X* minus one. Its worst case retu | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n | 1 | Forward | + +### *SUS* + +```cpp +#include +``` + +Computes the 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](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) of *X*. Its worst case returns *n* - 1 when *X* is sorted in reverse order. + +*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 | 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..4f7d3aa7 --- /dev/null +++ b/include/cpp-sort/detail/longest_non_descending_subsequence.h @@ -0,0 +1,100 @@ +/* + * 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 "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&& 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 = 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 }; + } +}} + +#endif // CPPSORT_DETAIL_LONGEST_NON_DESCENDING_SUBSEQUENCE_H_ 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/rem.h b/include/cpp-sort/probes/rem.h index e2dd1602..7013ace7 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,11 @@ 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; } }; } diff --git a/include/cpp-sort/probes/sus.h b/include/cpp-sort/probes/sus.h new file mode 100644 index 00000000..ba044390 --- /dev/null +++ b/include/cpp-sort/probes/sus.h @@ -0,0 +1,63 @@ +/* + * 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; + } + }; + } + + namespace + { + constexpr auto&& sus = utility::static_const< + sorter_facade + >::value; + } +}} + +#endif // CPPSORT_PROBES_SUS_H_ diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 9ded8ac2..70d99f5b 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -83,6 +83,7 @@ foreach(HEADER_FILE probes/par.h probes/rem.h probes/runs.h + probes/sus.h probes.h refined.h sort.h @@ -283,6 +284,7 @@ add_executable(main-tests probes/par.cpp probes/rem.cpp probes/runs.cpp + probes/sus.cpp probes/relations.cpp probes/every_probe_all_equal.cpp probes/every_probe_move_compare_projection.cpp diff --git a/testsuite/probes/every_probe_all_equal.cpp b/testsuite/probes/every_probe_all_equal.cpp index 62ca9fcb..dc1a9cb7 100644 --- a/testsuite/probes/every_probe_all_equal.cpp +++ b/testsuite/probes/every_probe_all_equal.cpp @@ -23,7 +23,8 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", decltype(cppsort::probe::osc), decltype(cppsort::probe::par), decltype(cppsort::probe::rem), - decltype(cppsort::probe::runs) ) + 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 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/relations.cpp b/testsuite/probes/relations.cpp index 935c7b30..ff324d1d 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,6 +23,7 @@ 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); @@ -31,12 +32,13 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) 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 ); @@ -51,6 +53,11 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) CHECK( par <= inv ); CHECK( rem <= size * (1 - 1 / (par + 1)) ); + // Sorting Shuffled Monotone Sequences + // by Christos Levcopoulos and Ola Petersson + CHECK( sus <= runs ); + CHECK( enc <= sus ); + // Intuitive result: a descending run can be seen as several // ascending runs CHECK( mono <= runs ); diff --git a/testsuite/probes/sus.cpp b/testsuite/probes/sus.cpp new file mode 100644 index 00000000..aef6d1d2 --- /dev/null +++ b/testsuite/probes/sus.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include + +TEST_CASE( "presortedness measure: sus", "[probe][sus]" ) +{ + SECTION( "simple test" ) + { + std::forward_list li = { 6, 9, 79, 41, 44, 49, 11, 16, 69, 15 }; + CHECK( cppsort::probe::sus(li) == 3 ); + CHECK( cppsort::probe::sus(li.begin(), li.end()) == 3 ); + + std::vector> tricky(li.begin(), li.end()); + CHECK( cppsort::probe::sus(tricky, &internal_compare::compare_to) == 3 ); + } + + SECTION( "lower bound" ) + { + std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + CHECK( cppsort::probe::sus(li) == 0 ); + CHECK( cppsort::probe::sus(li.begin(), li.end()) == 0 ); + } + + 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 }; + CHECK( cppsort::probe::sus(li) == 10 ); + CHECK( cppsort::probe::sus(li.begin(), li.end()) == 10 ); + } +} diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 1bce0c53..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{}; @@ -94,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) { From b6a2c6e341f8c083f7862f325ec8604a571e6599 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 19 Feb 2021 11:15:44 +0100 Subject: [PATCH 29/88] the the --- docs/Measures-of-presortedness.md | 2 +- docs/Original-research.md | 2 +- docs/Sorters.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 7ee3d496..e84f4c64 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -204,7 +204,7 @@ Computes the number of non-decreasing runs in *X* minus one. Its worst case retu #include ``` -Computes the 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](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) of *X*. Its worst case returns *n* - 1 when *X* is sorted in reverse order. +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](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) of *X*. Its worst case returns *n* - 1 when *X* is sorted in reverse order. *SUS* stands for *Shuffled Up-Sequences* and was introduced in *Sorting Shuffled Monotone Sequences* by Levcopoulos and Petersson. diff --git a/docs/Original-research.md b/docs/Original-research.md index 4274439d..36940622 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 diff --git a/docs/Sorters.md b/docs/Sorters.md index 7bb3e92f..6d6809a3 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -353,7 +353,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`. From d277014dd2ddb2b4c36f69a665b7fcfbffeba22e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 19 Feb 2021 11:34:56 +0100 Subject: [PATCH 30/88] Cleanup the documentation for measures of presortedness --- docs/Measures-of-presortedness.md | 60 +++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index e84f4c64..6e852d7c 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -1,6 +1,6 @@ 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. -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): +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: > @@ -50,7 +50,9 @@ 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. + +**Worst case:** *n* - 1 when *X* is sorted in reverse order. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -66,7 +68,9 @@ 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*). + +**Worst case:** *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). | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -78,7 +82,9 @@ Computes the number of encroaching lists that can be extracted from *X* minus on #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 *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. + +**Worst case:** *n* - 1 when every element in *X* is one element away from its sorted position. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -92,7 +98,9 @@ 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. + +**Worst case:** *n* when every element in *X* is one element away from its sorted position. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -104,7 +112,9 @@ Computes the number of elements in *X* that are not in their sorted position, wh #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). + +**Worst case:** returns *n* * (*n* - 1) / 2 when *X* is sorted in reverse order. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -116,7 +126,9 @@ Computes the number of inversions in *X*, where an inversion corresponds to a pa #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. + +**Worst case:** *n* - 1 when *X* is sorted in reverse order. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -130,11 +142,13 @@ 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 Zhang, Meng and 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 + +**Worst case:** *n* / 2 - 1 when *X* is a sequence of elements that are alternatively greater then lesser than their previous neighbour. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -148,7 +162,9 @@ 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*. + +**Worst case:** (*n* * (*n* - 2) - 1) / 2 when the values in *X* are strongly oscillating. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -168,7 +184,7 @@ The following definition is also given to determine whether a sequence is *p*-so > *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. +**Worst case:** (*n* - 1) when the last element of *X* is smaller than the first one. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -180,7 +196,9 @@ Its worst case returns (*n* - 1) when the last element of *X* is smaller than t #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 non-decreasing 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 *n* minus the size of the [longest non-decreasing subsequence][longest-increasing-subsequence] of *X*. + +**Worst case:** *n* - 1 when *X* is sorted in reverse order. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -192,7 +210,9 @@ Computes the minimum number of elements that must be removed from *X* to obtain #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. + +**Worst case:** *n* - 1 when *X* is sorted in reverse order. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -204,10 +224,20 @@ Computes the number of non-decreasing runs in *X* minus one. Its worst case retu #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](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) of *X*. Its worst case returns *n* - 1 when *X* is sorted in reverse order. +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. +**Worst case:** *n* - 1 when *X* is sorted in reverse order. + | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | + +*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 + [sort-race]: https://arxiv.org/ftp/arxiv/papers/1609/1609.04471.pdf From 3f2892f5b7a4771bfb45304576b9c6029faaea6b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 19 Feb 2021 12:33:56 +0100 Subject: [PATCH 31/88] Use template test case for probes common tests --- testsuite/CMakeLists.txt | 2 +- testsuite/probes/dis.cpp | 9 +--- testsuite/probes/enc.cpp | 9 +--- testsuite/probes/every_probe_all_equal.cpp | 36 ------------- testsuite/probes/every_probe_common.cpp | 60 ++++++++++++++++++++++ testsuite/probes/exc.cpp | 9 +--- testsuite/probes/ham.cpp | 9 +--- testsuite/probes/inv.cpp | 9 +--- testsuite/probes/max.cpp | 9 +--- testsuite/probes/mono.cpp | 9 +--- testsuite/probes/osc.cpp | 9 +--- testsuite/probes/par.cpp | 9 +--- testsuite/probes/rem.cpp | 20 +------- testsuite/probes/runs.cpp | 12 +---- testsuite/probes/sus.cpp | 7 --- 15 files changed, 72 insertions(+), 146 deletions(-) delete mode 100644 testsuite/probes/every_probe_all_equal.cpp create mode 100644 testsuite/probes/every_probe_common.cpp diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 70d99f5b..dc42b592 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -286,7 +286,7 @@ add_executable(main-tests probes/runs.cpp probes/sus.cpp probes/relations.cpp - probes/every_probe_all_equal.cpp + probes/every_probe_common.cpp probes/every_probe_move_compare_projection.cpp # Sorters tests diff --git a/testsuite/probes/dis.cpp b/testsuite/probes/dis.cpp index cfe8db6d..4c2a1c36 100644 --- a/testsuite/probes/dis.cpp +++ b/testsuite/probes/dis.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -21,13 +21,6 @@ TEST_CASE( "presortedness measure: dis", "[probe][dis]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of diff --git a/testsuite/probes/enc.cpp b/testsuite/probes/enc.cpp index b10b342f..2b305292 100644 --- a/testsuite/probes/enc.cpp +++ b/testsuite/probes/enc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -21,13 +21,6 @@ TEST_CASE( "presortedness measure: enc", "[probe][enc]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to half the size diff --git a/testsuite/probes/every_probe_all_equal.cpp b/testsuite/probes/every_probe_all_equal.cpp deleted file mode 100644 index dc1a9cb7..00000000 --- a/testsuite/probes/every_probe_all_equal.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 Morwenn - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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); - cppsort::detail::remove_cvref_t mop; - auto presortedness = mop(collection); - CHECK( presortedness == 0 ); -} 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/exc.cpp b/testsuite/probes/exc.cpp index f247ff8d..6363313e 100644 --- a/testsuite/probes/exc.cpp +++ b/testsuite/probes/exc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -26,13 +26,6 @@ TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of diff --git a/testsuite/probes/ham.cpp b/testsuite/probes/ham.cpp index 11b3c1cc..b7dbc97d 100644 --- a/testsuite/probes/ham.cpp +++ b/testsuite/probes/ham.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -22,13 +22,6 @@ TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of diff --git a/testsuite/probes/inv.cpp b/testsuite/probes/inv.cpp index cbb69b23..1a136122 100644 --- a/testsuite/probes/inv.cpp +++ b/testsuite/probes/inv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -21,13 +21,6 @@ TEST_CASE( "presortedness measure: inv", "[probe][inv]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to: diff --git a/testsuite/probes/max.cpp b/testsuite/probes/max.cpp index b02ed489..04f5439c 100644 --- a/testsuite/probes/max.cpp +++ b/testsuite/probes/max.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -22,13 +22,6 @@ TEST_CASE( "presortedness measure: max", "[probe][max]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of diff --git a/testsuite/probes/mono.cpp b/testsuite/probes/mono.cpp index b5ed6547..960bcac5 100644 --- a/testsuite/probes/mono.cpp +++ b/testsuite/probes/mono.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -23,15 +23,8 @@ TEST_CASE( "presortedness measure: mono", "[probe][mono]" ) 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 ); } SECTION( "upper bound" ) diff --git a/testsuite/probes/osc.cpp b/testsuite/probes/osc.cpp index 310319b7..e7a61896 100644 --- a/testsuite/probes/osc.cpp +++ b/testsuite/probes/osc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -24,13 +24,6 @@ TEST_CASE( "presortedness measure: osc", "[probe][osc]" ) 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 ); - } - SECTION( "upper bound" ) { // Example from the paper Adaptative Heapsort diff --git a/testsuite/probes/par.cpp b/testsuite/probes/par.cpp index e78addec..86350f75 100644 --- a/testsuite/probes/par.cpp +++ b/testsuite/probes/par.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -20,13 +20,6 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of diff --git a/testsuite/probes/rem.cpp b/testsuite/probes/rem.cpp index ec8e6ad2..d132a93b 100644 --- a/testsuite/probes/rem.cpp +++ b/testsuite/probes/rem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -27,13 +27,6 @@ TEST_CASE( "presortedness measure: rem", "[probe][rem]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of @@ -43,15 +36,4 @@ TEST_CASE( "presortedness measure: rem", "[probe][rem]" ) 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 ); - } } diff --git a/testsuite/probes/runs.cpp b/testsuite/probes/runs.cpp index bb8ff750..20e202e5 100644 --- a/testsuite/probes/runs.cpp +++ b/testsuite/probes/runs.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -27,16 +27,6 @@ TEST_CASE( "presortedness measure: runs", "[probe][runs]" ) 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 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of diff --git a/testsuite/probes/sus.cpp b/testsuite/probes/sus.cpp index aef6d1d2..aecace08 100644 --- a/testsuite/probes/sus.cpp +++ b/testsuite/probes/sus.cpp @@ -20,13 +20,6 @@ TEST_CASE( "presortedness measure: sus", "[probe][sus]" ) CHECK( cppsort::probe::sus(tricky, &internal_compare::compare_to) == 3 ); } - SECTION( "lower bound" ) - { - std::forward_list li = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - CHECK( cppsort::probe::sus(li) == 0 ); - CHECK( cppsort::probe::sus(li.begin(), li.end()) == 0 ); - } - SECTION( "upper bound" ) { // The upper bound should correspond to the size of From f71c6eeb18c7f4c8f4563fdaaf76aa7fc4bf06d2 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 19 Feb 2021 14:50:20 +0100 Subject: [PATCH 32/88] Cleanup measure of presortedness tests --- testsuite/probes/dis.cpp | 5 ++--- testsuite/probes/enc.cpp | 5 ++--- testsuite/probes/exc.cpp | 9 +++++---- testsuite/probes/ham.cpp | 7 ++++--- testsuite/probes/inv.cpp | 5 ++--- testsuite/probes/max.cpp | 7 ++++--- testsuite/probes/mono.cpp | 5 ++--- testsuite/probes/osc.cpp | 9 ++++----- testsuite/probes/par.cpp | 5 ++--- testsuite/probes/rem.cpp | 9 ++++----- testsuite/probes/runs.cpp | 7 +++---- 11 files changed, 34 insertions(+), 39 deletions(-) diff --git a/testsuite/probes/dis.cpp b/testsuite/probes/dis.cpp index 4c2a1c36..37055ac3 100644 --- a/testsuite/probes/dis.cpp +++ b/testsuite/probes/dis.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include #include @@ -15,7 +14,7 @@ TEST_CASE( "presortedness measure: dis", "[probe][dis]" ) { 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( cppsort::probe::dis(li.begin(), li.end()) == 9 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::dis(tricky, &internal_compare::compare_to) == 9 ); @@ -28,6 +27,6 @@ TEST_CASE( "presortedness measure: dis", "[probe][dis]" ) 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 ); + CHECK( cppsort::probe::dis(li.begin(), li.end()) == 10 ); } } diff --git a/testsuite/probes/enc.cpp b/testsuite/probes/enc.cpp index 2b305292..ca941261 100644 --- a/testsuite/probes/enc.cpp +++ b/testsuite/probes/enc.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include #include @@ -15,7 +14,7 @@ TEST_CASE( "presortedness measure: enc", "[probe][enc]" ) { 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( cppsort::probe::enc(li.begin(), li.end()) == 2 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::enc(tricky, &internal_compare::compare_to) == 2 ); @@ -28,6 +27,6 @@ TEST_CASE( "presortedness measure: enc", "[probe][enc]" ) 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 ); + CHECK( cppsort::probe::enc(li.begin(), li.end()) == 4 ); } } diff --git a/testsuite/probes/exc.cpp b/testsuite/probes/exc.cpp index 6363313e..e61a4a89 100644 --- a/testsuite/probes/exc.cpp +++ b/testsuite/probes/exc.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include @@ -16,11 +17,11 @@ TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) { 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( cppsort::probe::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( cppsort::probe::exc(li2.begin(), li2.end()) == 5 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::exc(tricky, &internal_compare::compare_to) == 7 ); @@ -33,7 +34,7 @@ TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) 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 ); + CHECK( cppsort::probe::exc(li.begin(), li.end()) == 10 ); } SECTION( "regressions" ) @@ -43,7 +44,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)); + std::sort(collection.begin(), collection.end()); CHECK( cppsort::probe::exc(collection) == 0 ); } } diff --git a/testsuite/probes/ham.cpp b/testsuite/probes/ham.cpp index b7dbc97d..16310d70 100644 --- a/testsuite/probes/ham.cpp +++ b/testsuite/probes/ham.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include @@ -16,7 +17,7 @@ TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) { 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( cppsort::probe::ham(li.begin(), li.end()) == 6 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::ham(tricky, &internal_compare::compare_to) == 6 ); @@ -29,7 +30,7 @@ TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) 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 ); + CHECK( cppsort::probe::ham(li.begin(), li.end()) == 11 ); } SECTION( "regressions" ) @@ -39,7 +40,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)); + std::sort(collection.begin(), collection.end()); CHECK( cppsort::probe::ham(collection) == 0 ); } } diff --git a/testsuite/probes/inv.cpp b/testsuite/probes/inv.cpp index 1a136122..94baf21c 100644 --- a/testsuite/probes/inv.cpp +++ b/testsuite/probes/inv.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include #include @@ -15,7 +14,7 @@ TEST_CASE( "presortedness measure: inv", "[probe][inv]" ) { 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( cppsort::probe::inv(li.begin(), li.end()) == 19 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::inv(tricky, &internal_compare::compare_to) == 19 ); @@ -28,6 +27,6 @@ TEST_CASE( "presortedness measure: inv", "[probe][inv]" ) 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 ); + CHECK( cppsort::probe::inv(li.begin(), li.end()) == 55 ); } } diff --git a/testsuite/probes/max.cpp b/testsuite/probes/max.cpp index 04f5439c..57aa1ddb 100644 --- a/testsuite/probes/max.cpp +++ b/testsuite/probes/max.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include @@ -16,7 +17,7 @@ TEST_CASE( "presortedness measure: max", "[probe][max]" ) { 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( cppsort::probe::max(li.begin(), li.end()) == 6 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::max(tricky, &internal_compare::compare_to) == 6 ); @@ -29,7 +30,7 @@ TEST_CASE( "presortedness measure: max", "[probe][max]" ) 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 ); + CHECK( cppsort::probe::max(li.begin(), li.end()) == 10 ); } SECTION( "regressions" ) @@ -39,7 +40,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)); + std::sort(collection.begin(), collection.end()); CHECK( cppsort::probe::max(collection) == 0 ); } } diff --git a/testsuite/probes/mono.cpp b/testsuite/probes/mono.cpp index 960bcac5..d0333dbb 100644 --- a/testsuite/probes/mono.cpp +++ b/testsuite/probes/mono.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include #include @@ -15,7 +14,7 @@ TEST_CASE( "presortedness measure: mono", "[probe][mono]" ) { 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( cppsort::probe::mono(li.begin(), li.end()) == 2 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::mono(tricky, &internal_compare::compare_to) == 2 ); @@ -34,7 +33,7 @@ TEST_CASE( "presortedness measure: mono", "[probe][mono]" ) 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 ); + CHECK( cppsort::probe::mono(li.begin(), li.end()) == 5 ); } SECTION( "equal neighbours in the sequence" ) diff --git a/testsuite/probes/osc.cpp b/testsuite/probes/osc.cpp index e7a61896..b49986bd 100644 --- a/testsuite/probes/osc.cpp +++ b/testsuite/probes/osc.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include #include @@ -13,12 +12,12 @@ TEST_CASE( "presortedness measure: osc", "[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( cppsort::probe::osc(li.begin(), li.end()) == 17 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::osc(tricky, &internal_compare::compare_to) == 17 ); @@ -26,12 +25,12 @@ TEST_CASE( "presortedness measure: osc", "[probe][osc]" ) 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 ); + CHECK( cppsort::probe::osc(li.begin(), li.end()) == 71 ); } } diff --git a/testsuite/probes/par.cpp b/testsuite/probes/par.cpp index 86350f75..8d323a86 100644 --- a/testsuite/probes/par.cpp +++ b/testsuite/probes/par.cpp @@ -2,7 +2,6 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include @@ -14,7 +13,7 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) { 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( cppsort::probe::par(vec.begin(), vec.end()) == 7 ); std::vector> tricky(vec.begin(), vec.end()); CHECK( cppsort::probe::par(tricky, &internal_compare::compare_to) == 7 ); @@ -27,6 +26,6 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) 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 ); + CHECK( cppsort::probe::par(vec.begin(), vec.end()) == 10 ); } } diff --git a/testsuite/probes/rem.cpp b/testsuite/probes/rem.cpp index d132a93b..784dc4ce 100644 --- a/testsuite/probes/rem.cpp +++ b/testsuite/probes/rem.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include #include @@ -16,12 +15,12 @@ TEST_CASE( "presortedness measure: rem", "[probe][rem]" ) // 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( cppsort::probe::rem(li.begin(), li.end()) == 4 ); // Random-access iterators - std::vector vec(std::begin(li), std::end(li)); + std::vector vec(li.begin(), li.end()); CHECK( cppsort::probe::rem(vec) == 4 ); - CHECK( cppsort::probe::rem(std::begin(vec), std::end(vec)) == 4 ); + CHECK( cppsort::probe::rem(vec.begin(), vec.end()) == 4 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::rem(tricky, &internal_compare::compare_to) == 4 ); @@ -34,6 +33,6 @@ TEST_CASE( "presortedness measure: rem", "[probe][rem]" ) 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 ); + CHECK( cppsort::probe::rem(li.begin(), li.end()) == 10 ); } } diff --git a/testsuite/probes/runs.cpp b/testsuite/probes/runs.cpp index 20e202e5..7a51dab5 100644 --- a/testsuite/probes/runs.cpp +++ b/testsuite/probes/runs.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include #include @@ -15,13 +14,13 @@ TEST_CASE( "presortedness measure: runs", "[probe][runs]" ) { 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( cppsort::probe::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( cppsort::probe::runs(li2.begin(), li2.end()) == 6 ); std::vector> tricky(li.begin(), li.end()); CHECK( cppsort::probe::runs(tricky, &internal_compare::compare_to) == 5 ); @@ -34,6 +33,6 @@ TEST_CASE( "presortedness measure: runs", "[probe][runs]" ) 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 ); + CHECK( cppsort::probe::runs(li.begin(), li.end()) == 10 ); } } From c4741e6c8d8c685ba938d0f3c556f6945dd223d0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 20 Feb 2021 12:33:01 +0100 Subject: [PATCH 33/88] litterature -> literature --- docs/Original-research.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Original-research.md b/docs/Original-research.md index 36940622..0f92ed4d 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -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. From b29040120db5383244e739a6e374ac6320d77ba6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 20 Feb 2021 12:37:07 +0100 Subject: [PATCH 34/88] Add max_for_size to measures of presortedness --- docs/Measures-of-presortedness.md | 82 +++++++++++++++++++------------ include/cpp-sort/probes/dis.h | 7 +++ include/cpp-sort/probes/enc.h | 9 +++- include/cpp-sort/probes/exc.h | 9 +++- include/cpp-sort/probes/ham.h | 9 +++- include/cpp-sort/probes/inv.h | 9 +++- include/cpp-sort/probes/max.h | 7 +++ include/cpp-sort/probes/mono.h | 9 +++- include/cpp-sort/probes/osc.h | 7 +++ include/cpp-sort/probes/par.h | 9 +++- include/cpp-sort/probes/rem.h | 7 +++ include/cpp-sort/probes/runs.h | 9 +++- include/cpp-sort/probes/sus.h | 7 +++ testsuite/probes/dis.cpp | 15 ++++-- testsuite/probes/enc.cpp | 15 ++++-- testsuite/probes/exc.cpp | 21 +++++--- testsuite/probes/ham.cpp | 17 ++++--- testsuite/probes/inv.cpp | 15 ++++-- testsuite/probes/max.cpp | 17 ++++--- testsuite/probes/mono.cpp | 23 +++++---- testsuite/probes/osc.cpp | 15 ++++-- testsuite/probes/par.cpp | 14 ++++-- testsuite/probes/rem.cpp | 19 ++++--- testsuite/probes/runs.cpp | 19 ++++--- testsuite/probes/sus.cpp | 15 ++++-- 25 files changed, 274 insertions(+), 111 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 6e852d7c..394e7f4e 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -1,5 +1,7 @@ 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. +## 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 +14,9 @@ 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. ---- +## Measures of presortedness in cpp-sort -In **cpp-sort**, measures of presortedness are implemented as instances of some specific function objects; they take an iterable or a pair of iterators and return how much disorder there is in the sequence according to the measure. Just like sorters, measures of presortedness can handle custom comparison and projection functions, and with the same degree of freedom when it comes to how they can be called: +In **cpp-sort**, measures of presortedness are implemented as instances of some specific function objects. They take an iterable or a pair of iterators and return how much disorder there is in the sequence according to the measure. 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 +38,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* @@ -52,12 +72,12 @@ Measures of presortedness are pretty formalized, so the names of the functions i Computes the maximum distance determined by an inversion. -**Worst case:** *n* - 1 when *X* is sorted in reverse order. - | 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. @@ -70,26 +90,26 @@ Computes the maximum distance determined by an inversion. Computes the number of encroaching lists that can be extracted from *X* minus one (see Skiena's *Encroaching lists as a measure of presortedness*). -**Worst case:** *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). - | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: |*X*| / 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. - -**Worst case:** *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* @@ -100,12 +120,12 @@ Computes the minimum number of exchanges required to sort *X*, which corresponds 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. -**Worst case:** *n* when every element in *X* is one element away from its sorted position. - | 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 @@ -114,12 +134,12 @@ Computes the number of elements in *X* that are not in their sorted position, wh 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). -**Worst case:** returns *n* * (*n* - 1) / 2 when *X* is sorted in reverse order. - | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n log n | n | Forward | +`max_for_size`: |*X*| * (|*X*| - 1) / 2 when *X* is sorted in reverse order. + ### *Max* ```cpp @@ -128,12 +148,12 @@ Computes the number of inversions in *X*, where an inversion corresponds to a pa Computes the maximum distance an element in *X* must travel to find its sorted position. -**Worst case:** *n* - 1 when *X* is sorted in reverse order. - | 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* @@ -148,12 +168,12 @@ The measure of presortedness is slightly different from its original description * It subtracts 1 from the number of runs, thus returning 0 when *X* is sorted * It explicitly handles non-increasing and non-decreasing runs, not only the strictly increasing or decreasing ones -**Worst case:** *n* / 2 - 1 when *X* is a sequence of elements that are alternatively greater then lesser than their previous neighbour. - | 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* @@ -164,12 +184,12 @@ The measure of presortedness is slightly different from its original description Computes the *Oscillation* measure described by Levcopoulos and Petersson in *Adaptive Heapsort*. -**Worst case:** (*n* * (*n* - 2) - 1) / 2 when the values in *X* are strongly oscillating. - | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n² | 1 | Forward | +`max_for_size`: (|*X*| * (|*X*| - 2) - 1) / 2 when the values in *X* are strongly oscillating. + ### *Par* ```cpp @@ -182,28 +202,28 @@ 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*. - -**Worst case:** (*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 non-decreasing subsequence][longest-increasing-subsequence] of *X*. - -**Worst case:** *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 @@ -212,12 +232,12 @@ Computes the minimum number of elements that must be removed from *X* to obtain Computes the number of non-decreasing runs in *X* minus one. -**Worst case:** *n* - 1 when *X* is sorted in reverse order. - | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n | 1 | Forward | +`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. + ### *SUS* ```cpp @@ -228,12 +248,12 @@ Computes the minimum number of non-decreasing subsequences (of possibly not adja *SUS* stands for *Shuffled Up-Sequences* and was introduced in *Sorting Shuffled Monotone Sequences* by Levcopoulos and Petersson. -**Worst case:** *n* - 1 when *X* is sorted in reverse order. - | 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* diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index 735c61d1..69cc87c6 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -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..323a46eb 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_ @@ -93,6 +93,13 @@ namespace probe return lists.size() ? lists.size() - 1 : 0; } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n / 2 - 1; + } }; } diff --git a/include/cpp-sort/probes/exc.h b/include/cpp-sort/probes/exc.h index 5edde18e..750656a4 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_ @@ -142,6 +142,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..5f304f9e 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_ @@ -108,6 +108,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..d8500a59 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_ @@ -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 aef3b685..eabe01f2 100644 --- a/include/cpp-sort/probes/max.h +++ b/include/cpp-sort/probes/max.h @@ -123,6 +123,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 6ec8ca6c..78107d9e 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -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 7013ace7..d390b25c 100644 --- a/include/cpp-sort/probes/rem.h +++ b/include/cpp-sort/probes/rem.h @@ -82,6 +82,13 @@ namespace probe 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 index ba044390..18d8e1de 100644 --- a/include/cpp-sort/probes/sus.h +++ b/include/cpp-sort/probes/sus.h @@ -49,6 +49,13 @@ namespace probe ); return res.first > 0 ? res.first - 1 : 0; } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } }; } diff --git a/testsuite/probes/dis.cpp b/testsuite/probes/dis.cpp index 37055ac3..cb5f26a9 100644 --- a/testsuite/probes/dis.cpp +++ b/testsuite/probes/dis.cpp @@ -6,18 +6,21 @@ #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(li.begin(), li.end()) == 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 ); + CHECK( dis(tricky, &internal_compare::compare_to) == 9 ); } SECTION( "upper bound" ) @@ -26,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(li.begin(), li.end()) == 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 ca941261..b115c645 100644 --- a/testsuite/probes/enc.cpp +++ b/testsuite/probes/enc.cpp @@ -6,18 +6,21 @@ #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(li.begin(), li.end()) == 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 ); + CHECK( enc(tricky, &internal_compare::compare_to) == 2 ); } SECTION( "upper bound" ) @@ -26,7 +29,9 @@ TEST_CASE( "presortedness measure: enc", "[probe][enc]" ) // 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(li.begin(), li.end()) == 4 ); + auto max_n = enc.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 4 ); + CHECK( enc(li) == max_n ); + CHECK( enc(li.begin(), li.end()) == max_n ); } } diff --git a/testsuite/probes/exc.cpp b/testsuite/probes/exc.cpp index e61a4a89..05003d6f 100644 --- a/testsuite/probes/exc.cpp +++ b/testsuite/probes/exc.cpp @@ -8,23 +8,26 @@ #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(li.begin(), li.end()) == 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(li2.begin(), li2.end()) == 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 ); + CHECK( exc(tricky, &internal_compare::compare_to) == 7 ); } SECTION( "upper bound" ) @@ -33,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(li.begin(), li.end()) == 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" ) @@ -45,6 +50,6 @@ TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) distribution(std::back_inserter(collection), 100); std::sort(collection.begin(), collection.end()); - CHECK( cppsort::probe::exc(collection) == 0 ); + CHECK( exc(collection) == 0 ); } } diff --git a/testsuite/probes/ham.cpp b/testsuite/probes/ham.cpp index 16310d70..bb87e640 100644 --- a/testsuite/probes/ham.cpp +++ b/testsuite/probes/ham.cpp @@ -8,19 +8,22 @@ #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(li.begin(), li.end()) == 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 ); + CHECK( ham(tricky, &internal_compare::compare_to) == 6 ); } SECTION( "upper bound" ) @@ -29,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(li.begin(), li.end()) == 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" ) @@ -41,6 +46,6 @@ TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) distribution(std::back_inserter(collection), 100); std::sort(collection.begin(), collection.end()); - CHECK( cppsort::probe::ham(collection) == 0 ); + CHECK( ham(collection) == 0 ); } } diff --git a/testsuite/probes/inv.cpp b/testsuite/probes/inv.cpp index 94baf21c..8894361a 100644 --- a/testsuite/probes/inv.cpp +++ b/testsuite/probes/inv.cpp @@ -6,18 +6,21 @@ #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(li.begin(), li.end()) == 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 ); + CHECK( inv(tricky, &internal_compare::compare_to) == 19 ); } SECTION( "upper bound" ) @@ -26,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(li.begin(), li.end()) == 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 57aa1ddb..24cd41b0 100644 --- a/testsuite/probes/max.cpp +++ b/testsuite/probes/max.cpp @@ -8,19 +8,22 @@ #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(li.begin(), li.end()) == 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 ); + CHECK( (max)(tricky, &internal_compare::compare_to) == 6 ); } SECTION( "upper bound" ) @@ -29,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(li.begin(), li.end()) == 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" ) @@ -41,6 +46,6 @@ TEST_CASE( "presortedness measure: max", "[probe][max]" ) distribution(std::back_inserter(collection), 100); std::sort(collection.begin(), collection.end()); - CHECK( cppsort::probe::max(collection) == 0 ); + CHECK( (max)(collection) == 0 ); } } diff --git a/testsuite/probes/mono.cpp b/testsuite/probes/mono.cpp index d0333dbb..4539f58c 100644 --- a/testsuite/probes/mono.cpp +++ b/testsuite/probes/mono.cpp @@ -6,24 +6,27 @@ #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(li.begin(), li.end()) == 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 li1 = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::mono(li1) == 0 ); + CHECK( mono(li1) == 0 ); } SECTION( "upper bound" ) @@ -32,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(li.begin(), li.end()) == 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 b49986bd..7a173361 100644 --- a/testsuite/probes/osc.cpp +++ b/testsuite/probes/osc.cpp @@ -6,21 +6,24 @@ #include #include #include +#include #include TEST_CASE( "presortedness measure: osc", "[probe][osc]" ) { + using cppsort::probe::osc; + SECTION( "simple test" ) { // 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(li.begin(), li.end()) == 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 ); + CHECK( osc(tricky, &internal_compare::compare_to) == 17 ); } SECTION( "upper bound" ) @@ -30,7 +33,9 @@ TEST_CASE( "presortedness measure: osc", "[probe][osc]" ) // 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(li.begin(), li.end()) == 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 8d323a86..6198df2c 100644 --- a/testsuite/probes/par.cpp +++ b/testsuite/probes/par.cpp @@ -9,14 +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(vec.begin(), vec.end()) == 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 ); + CHECK( par(tricky, &internal_compare::compare_to) == 7 ); } SECTION( "upper bound" ) @@ -25,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(vec.begin(), vec.end()) == 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/rem.cpp b/testsuite/probes/rem.cpp index 784dc4ce..f29d011e 100644 --- a/testsuite/probes/rem.cpp +++ b/testsuite/probes/rem.cpp @@ -6,24 +6,27 @@ #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(li.begin(), li.end()) == 4 ); + CHECK( rem(li) == 4 ); + CHECK( rem(li.begin(), li.end()) == 4 ); // Random-access iterators std::vector vec(li.begin(), li.end()); - CHECK( cppsort::probe::rem(vec) == 4 ); - CHECK( cppsort::probe::rem(vec.begin(), vec.end()) == 4 ); + 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 ); + CHECK( rem(tricky, &internal_compare::compare_to) == 4 ); } SECTION( "upper bound" ) @@ -32,7 +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(li.begin(), li.end()) == 10 ); + 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 7a51dab5..e2742965 100644 --- a/testsuite/probes/runs.cpp +++ b/testsuite/probes/runs.cpp @@ -6,24 +6,27 @@ #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(li.begin(), li.end()) == 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(li2.begin(), li2.end()) == 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 ); + CHECK( runs(tricky, &internal_compare::compare_to) == 5 ); } SECTION( "upper bound" ) @@ -32,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(li.begin(), li.end()) == 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 index aecace08..9ddbebfa 100644 --- a/testsuite/probes/sus.cpp +++ b/testsuite/probes/sus.cpp @@ -6,18 +6,21 @@ #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( cppsort::probe::sus(li) == 3 ); - CHECK( cppsort::probe::sus(li.begin(), li.end()) == 3 ); + CHECK( sus(li) == 3 ); + CHECK( sus(li.begin(), li.end()) == 3 ); std::vector> tricky(li.begin(), li.end()); - CHECK( cppsort::probe::sus(tricky, &internal_compare::compare_to) == 3 ); + CHECK( sus(tricky, &internal_compare::compare_to) == 3 ); } SECTION( "upper bound" ) @@ -26,7 +29,9 @@ TEST_CASE( "presortedness measure: sus", "[probe][sus]" ) // the input sequence minus one std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - CHECK( cppsort::probe::sus(li) == 10 ); - CHECK( cppsort::probe::sus(li.begin(), li.end()) == 10 ); + 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 ); } } From 6e225343b32e4c1d4e027b86a056944456d87f17 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 20 Feb 2021 15:10:26 +0100 Subject: [PATCH 35/88] Partial ordering of measures of presortedness --- docs/Measures-of-presortedness.md | 18 +++++++++++++++++- testsuite/probes/relations.cpp | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 394e7f4e..141342cb 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -1,4 +1,6 @@ -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. + +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 @@ -14,6 +16,20 @@ 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* being the algorithmically finest one. + +![Partial ordering of measure of presortedness](https://github.com/Morwenn/cpp-sort/wiki/images/mep-partial-ordering.png) + +Not all of the measures of presortedness in this graph are available in **cpp-sort**, and some of the measures of presortedness available in **cpp-sort** do not appear in this graph. + ## 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: diff --git a/testsuite/probes/relations.cpp b/testsuite/probes/relations.cpp index ff324d1d..1fefd60e 100644 --- a/testsuite/probes/relations.cpp +++ b/testsuite/probes/relations.cpp @@ -56,6 +56,7 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) // Sorting Shuffled Monotone Sequences // by Christos Levcopoulos and Ola Petersson CHECK( sus <= runs ); + CHECK( sus <= max ); CHECK( enc <= sus ); // Intuitive result: a descending run can be seen as several From 141ab3e3732b6cdb5c4ded35d0c2369cc92509ee Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 20 Feb 2021 15:11:44 +0100 Subject: [PATCH 36/88] Add LaTeX file to generate the MEP partial ordering graph --- tools/mep-partial-ordering.tex | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tools/mep-partial-ordering.tex diff --git a/tools/mep-partial-ordering.tex b/tools/mep-partial-ordering.tex new file mode 100644 index 00000000..2af662aa --- /dev/null +++ b/tools/mep-partial-ordering.tex @@ -0,0 +1,46 @@ +% Copyright (c) 2021 Morwenn +% SPDX-License-Identifier: MIT + +\documentclass{minimal} +\usepackage{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 + ] + + \node[state] (reg) {$Reg$}; + \node[state] (sms) [above of=reg] {$SMS$}; + \node[state] (enc) [above of=sms] {$Enc$}; + \node[state] (sus) [above of=enc] {$SUS$}; + \node[state] (runs) [above of=sus] {$Runs$}; + \node[state] (rem) [right=1.5cm of sms] {$Rem$}; + \node[state] (exc) [above of=rem] {$Exc$}; + \node[state] (osc) [left=1.5cm of sms] {$Osc$}; + \node[state] (inv) [above of=osc] {$Inv$}; + \node[state] (dis) [left= 0.9cm of runs] {$Dis=Max$}; + + \path[-] (reg) edge node {} (sms); + \path[-] (sms) edge node {} (enc); + \path[-] (enc) edge node {} (sus); + \path[-] (sus) edge node {} (runs); + \path[-] (reg) edge node {} (rem); + \path[-] (rem) edge node {} (exc); + \path[-] (reg) edge node {} (osc); + \path[-] (osc) edge node {} (inv); + \path[-] (inv) edge node {} (dis); + \path[-] (sus) edge node {} (dis); + \path[-] (osc) edge node {} (runs); + \end{tikzpicture} +\end{document} From 06c988f85c59643d39ea7037453d04e7744708bb Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 20 Feb 2021 19:49:29 +0100 Subject: [PATCH 37/88] Describe why Mono is where it is in the partial ordering of MEPs This commit also changes the graph of partial ordering of measures of presortedness to that found in A framework for adaptive sorting by O. Petersson and A. Moffat. It is completed with the addition of the two measures of presortedness Dis and Mono. --- docs/Measures-of-presortedness.md | 16 ++++--- docs/Original-research.md | 28 ++++++++++++- testsuite/probes/relations.cpp | 1 + tools/mep-partial-ordering.tex | 70 +++++++++++++++++++++---------- 4 files changed, 87 insertions(+), 28 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 141342cb..c8e55a66 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -22,13 +22,18 @@ La rocca & Cantone also define a partial order on measure of presortedness as fo 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*. +- *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* being the algorithmically finest one. +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 measure of presortedness](https://github.com/Morwenn/cpp-sort/wiki/images/mep-partial-ordering.png) +![Partial ordering of measures of presortedness](https://github.com/Morwenn/cpp-sort/wiki/images/mep-partial-ordering.png) -Not all of the measures of presortedness in this graph are available in **cpp-sort**, and some of the measures of presortedness available in **cpp-sort** do not appear in this graph. +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 @@ -180,7 +185,7 @@ Computes the maximum distance an element in *X* must travel to find its sorted p 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 Race*][sort-race] 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 increasing or decreasing ones @@ -276,4 +281,5 @@ Computes the minimum number of non-decreasing subsequences (of possibly not adja [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/Original-research.md b/docs/Original-research.md index 0f92ed4d..085aa171 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -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,40 @@ 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* + +The following relations have yet to be analyzed: +- ***Mono* ⊇ *Max*** +- ***Mono* ≡ *SUS*** + [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/testsuite/probes/relations.cpp b/testsuite/probes/relations.cpp index 1fefd60e..9fe8025e 100644 --- a/testsuite/probes/relations.cpp +++ b/testsuite/probes/relations.cpp @@ -52,6 +52,7 @@ 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 ); // Sorting Shuffled Monotone Sequences // by Christos Levcopoulos and Ola Petersson diff --git a/tools/mep-partial-ordering.tex b/tools/mep-partial-ordering.tex index 2af662aa..6aea5af3 100644 --- a/tools/mep-partial-ordering.tex +++ b/tools/mep-partial-ordering.tex @@ -2,13 +2,13 @@ % SPDX-License-Identifier: MIT \documentclass{minimal} -\usepackage{pgf, tikz} +\usepackage{bm, pgf, tikz} \usetikzlibrary{arrows, automata, backgrounds, positioning} \begin{document} \begin{tikzpicture}[ - background rectangle/.style={fill=white}, - show background rectangle, + background rectangle/.style={fill=white}, + show background rectangle, auto, node distance = 1.3cm, semithick @@ -20,27 +20,55 @@ fill=white ] - \node[state] (reg) {$Reg$}; - \node[state] (sms) [above of=reg] {$SMS$}; - \node[state] (enc) [above of=sms] {$Enc$}; - \node[state] (sus) [above of=enc] {$SUS$}; - \node[state] (runs) [above of=sus] {$Runs$}; - \node[state] (rem) [right=1.5cm of sms] {$Rem$}; - \node[state] (exc) [above of=rem] {$Exc$}; - \node[state] (osc) [left=1.5cm of sms] {$Osc$}; - \node[state] (inv) [above of=osc] {$Inv$}; - \node[state] (dis) [left= 0.9cm of runs] {$Dis=Max$}; - + % 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[-] (enc) edge node {} (sus); - \path[-] (sus) edge node {} (runs); - \path[-] (reg) edge node {} (rem); - \path[-] (rem) edge node {} (exc); - \path[-] (reg) edge node {} (osc); + \path[-] (block) edge node {} (rem); \path[-] (osc) edge node {} (inv); - \path[-] (inv) edge node {} (dis); - \path[-] (sus) edge node {} (dis); \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} From d628f954e4084be34b2ba64790e6b6b5c00a8bb0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 20 Feb 2021 19:58:35 +0100 Subject: [PATCH 38/88] Add missing docs/images --- .gitignore | 4 ++-- docs/images/sorting-network-23.png | Bin 0 -> 34737 bytes docs/images/sorting-network-24.png | Bin 0 -> 35585 bytes docs/images/sorting-network-29.png | Bin 0 -> 59790 bytes 4 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 docs/images/sorting-network-23.png create mode 100644 docs/images/sorting-network-24.png create mode 100644 docs/images/sorting-network-29.png diff --git a/.gitignore b/.gitignore index 5c76947e..73337f00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2020 Morwenn +# Copyright (c) 2015-2021 Morwenn # SPDX-License-Identifier: MIT # Usual build directory @@ -9,5 +9,5 @@ results # Files generated by project scripts *.csv -*.png +tools/*.pdf tools/*.txt diff --git a/docs/images/sorting-network-23.png b/docs/images/sorting-network-23.png new file mode 100644 index 0000000000000000000000000000000000000000..682b085186a523a8188588785a891397a3350eef GIT binary patch literal 34737 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¨|9X22L&+knd=ga7(vN?LQ#N{n*#rZ? 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