diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 00000000..10a117a3 --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,67 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: MacOS Builds + +on: + push: + paths: + - '.github/workflows/build-macos.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + pull_request: + paths: + - '.github/workflows/build-macos.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + +jobs: + build: + runs-on: macos-10.15 + + strategy: + fail-fast: false + matrix: + cxx: + - g++-9 + - $(brew --prefix llvm)/bin/clang++ # Clang 11 + config: + # Release build + - build_type: Release + # Debug builds + - build_type: Debug + sanitize: address + - build_type: Debug + sanitize: undefined + + steps: + - uses: actions/checkout@v2 + - uses: seanmiddleditch/gha-setup-ninja@master + + - name: Configure CMake + working-directory: ${{runner.workspace}} + run: | + export CXX=${{matrix.cxx}} + cmake -H${{github.event.repository.name}} -Bbuild \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ + -DCPPSORT_SANITIZE=${{matrix.config.sanitize}} \ + -GNinja \ + -DCPPSORT_BUILD_EXAMPLES=ON + + - name: Build the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.config.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.config.build_type}} diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml new file mode 100644 index 00000000..305d196a --- /dev/null +++ b/.github/workflows/build-ubuntu.yml @@ -0,0 +1,84 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: Ubuntu Builds + +on: + push: + paths: + - '.github/workflows/build-ubuntu.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + pull_request: + paths: + - '.github/workflows/build-ubuntu.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + +jobs: + build: + runs-on: ubuntu-16.04 + + strategy: + fail-fast: false + matrix: + cxx: + - g++-5 + - clang++-6.0 + config: + # Release build + - build_type: Release + # Debug builds + - build_type: Debug + valgrind: ON + - build_type: Debug + sanitize: address + - build_type: Debug + sanitize: undefined + + steps: + - uses: actions/checkout@v2 + + - name: Install Valgrind + if: ${{matrix.config.valgrind == 'ON'}} + run: sudo apt install -y valgrind + + - name: Configure CMake + working-directory: ${{runner.workspace}} + env: + CXX: ${{matrix.cxx}} + run: | + cmake -H${{github.event.repository.name}} -Bbuild \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ + -DCPPSORT_SANITIZE=${{matrix.config.sanitize}} \ + -DCPPSORT_USE_VALGRIND=${{matrix.config.valgrind}} \ + -G"Unix Makefiles" \ + -DCPPSORT_BUILD_EXAMPLES=ON + + - name: Build the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.config.build_type}} -j 2 + + - name: Run the test suite + if: ${{matrix.config.valgrind != 'ON'}} + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.config.build_type}} + + - name: Run the test suite with Memcheck + if: ${{matrix.config.valgrind == 'ON'}} + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: | + ctest -T memcheck -C ${{matrix.config.build_type}} -j 2 + find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 00000000..f5097cfa --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,54 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: Windows Builds + +on: + push: + paths: + - '.github/workflows/build-windows.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + pull_request: + paths: + - '.github/workflows/build-windows.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"MinGW Makefiles" ` + -DCPPSORT_BUILD_EXAMPLES=ON + + - name: Build the test suite + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml new file mode 100644 index 00000000..74d7571d --- /dev/null +++ b/.github/workflows/code-coverage.yml @@ -0,0 +1,59 @@ +# Copyright (c) 2020-2021 Morwenn +# SPDX-License-Identifier: MIT + +name: Coverage Upload to Codecov + +on: + push: + branches: + - master + - develop + - 2.0.0-develop + paths: + - '.github/workflows/code-coverage.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'codecov.yml' + - 'include/**' + - 'testsuite/**' + +env: + BUILD_TYPE: Debug + +jobs: + upload-coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout project + uses: actions/checkout@v2 + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}} + run: > + cmake -H${{github.event.repository.name}} -Bbuild + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" + -DCPPSORT_ENABLE_COVERAGE=true + -G"Unix Makefiles" + + - name: Build with coverage + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config $BUILD_TYPE -j 2 + + - name: Run the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: ctest -C Release --output-on-failure + + - name: Create coverage info + shell: bash + working-directory: ${{runner.workspace}}/build + run: make gcov + + - name: Upload coverage info + uses: codecov/codecov-action@v1.2.1 + with: + directory: ${{runner.workspace}}/build + functionalities: gcov diff --git a/.gitignore b/.gitignore index 0157b8fe..5c76947e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,13 @@ # Copyright (c) 2015-2020 Morwenn # SPDX-License-Identifier: MIT -# Project-specific directory +# Usual build directory build -# Compiled Object files -*.slo -*.lo -*.o -*.obj +# Benchmark results directories +results -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# Code::Blocks files -*.cbp -*.depend -*.layout -*.save-failed - -# VSCode files -.vscode - -# Static analyzer files -CppCheckResults.xml - -# Files generated by LaTeX -*.aux -*.log +# Files generated by project scripts +*.csv *.png -*.pdf -*.synctex.gz +tools/*.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 72c1689a..00000000 --- a/.travis.yml +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright (c) 2015-2020 Morwenn -# SPDX-License-Identifier: MIT - -language: cpp - -_packages: - - &clang clang-3.8 - - &gcc g++-5 - -_apt: &apt-common - sources: - - llvm-toolchain-trusty-3.8 - - ubuntu-toolchain-r-test - -cache: - ccache: true - directories: - - build - - .mtime_cache - -matrix: - include: - - # Linux clang - - os: linux - sudo: required - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - - valgrind - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - # Linux gcc - - os: linux - sudo: false - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - - valgrind - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - # OSX clang - - os: osx - osx_image: xcode9.4 - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Ninja" - addons: - homebrew: - update: true - packages: - - ccache - - cmake - - ninja - - valgrind - compiler: clang - - - os: osx - osx_image: xcode9.4 - env: BUILD_TYPE=Release CMAKE_GENERATOR="Ninja" - addons: - homebrew: - update: true - packages: - - ccache - - cmake - - ninja - compiler: clang - - # Windows GCC - - os: windows - language: sh - env: BUILD_TYPE=Debug CMAKE_GENERATOR="MinGW Makefiles" - compiler: gcc - - - os: windows - language: sh - env: BUILD_TYPE=Release CMAKE_GENERATOR="MinGW Makefiles" - compiler: gcc - - # Code coverage - - os: linux - sudo: false - if: branch in (master, develop) - env: BUILD_TYPE=Debug CMAKE_GENERATOR="Unix Makefiles" ENABLE_COVERAGE=true - addons: - apt: - <<: *apt-common - packages: - - *gcc - - lcov - compiler: gcc - -before_install: - - if [[ $TRAVIS_OS_NAME = "linux" && $CXX = "clang++" ]]; then - sudo ln -s $(which ccache) /usr/lib/ccache/clang++; - export CXXFLAGS="-Qunused-arguments"; - fi - -install: - - gem install mtime_cache - - if [[ $TRAVIS_OS_NAME = "osx" ]]; then - export PATH="/usr/local/opt/ccache/libexec:$PATH"; - fi - - if [[ $CXX = "g++" && $TRAVIS_OS_NAME != "osx" ]]; then export CXX="g++-5"; fi - - if [[ $CXX = "clang++" && $TRAVIS_OS_NAME != "osx" ]]; then export CXX="clang++-3.8"; fi - -script: - - cmake -H. -Bbuild - -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" - -DSANITIZE="${SANITIZE}" - -DUSE_VALGRIND=${USE_VALGRIND} - -DENABLE_COVERAGE=${ENABLE_COVERAGE} - -G"${CMAKE_GENERATOR}" - -DCMAKE_SH="CMAKE_SH-NOTFOUND" - -DBUILD_EXAMPLES=ON - - mtime_cache -g mtime_cache_globs.txt -c .mtime_cache/cache.json - - if [[ $TRAVIS_OS_NAME = "osx" ]]; then - cmake --build build --config ${BUILD_TYPE}; - else - cmake --build build --config ${BUILD_TYPE} -- -j2; - fi - - cd build - - if [[ $USE_VALGRIND = true ]]; then - travis_wait 50 ctest -T memcheck -C ${BUILD_TYPE} --output-on-failure -j 4; - else - travis_wait ctest -C ${BUILD_TYPE} --output-on-failure; - fi - - if [[ $TRAVIS_OS_NAME = "windows" ]]; then - cd ..; - fi - -after_success: - - if [[ $TRAVIS_OS_NAME = "windows" ]]; then - cd build; - fi - - if [[ $ENABLE_COVERAGE = true ]]; then - make gcov; - make lcov; - bash <(curl -s https://codecov.io/bash) -X gcov || echo "Codecov did not collect coverage reports"; - fi - -after_failure: - - if [[ $TRAVIS_OS_NAME = "windows" ]]; then - cd build; - fi - - if [[ $USE_VALGRIND = true ]]; then - find ./Testing/Temporary -type f -name "MemoryChecker.*.log" -size +1300c | xargs cat; - fi - -notifications: - email: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e233b99..b000a91b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,20 @@ -# Copyright (c) 2015-2020 Morwenn +# Copyright (c) 2015-2021 Morwenn # SPDX-License-Identifier: MIT cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 1.8.1 LANGUAGES CXX) +project(cpp-sort VERSION 1.9.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) # Project options -option(BUILD_TESTING "Build the cpp-sort test suite" ON) -option(BUILD_EXAMPLES "Build the cpp-sort examples" OFF) +option(BUILD_TESTING "Build the cpp-sort test suite (deprecated, use CPPSORT_BUILD_TESTING)" ON) +option(BUILD_EXAMPLES "Build the cpp-sort examples (deprecated, use CPPSORT_BUILD_EXAMPLES)" OFF) +option(CPPSORT_BUILD_TESTING "Build the cpp-sort test suite" ${BUILD_TESTING}) +option(CPPSORT_BUILD_EXAMPLES "Build the cpp-sort examples" ${BUILD_EXAMPLES}) # Create cpp-sort library and configure it add_library(cpp-sort INTERFACE) @@ -77,12 +79,12 @@ export( # Build tests and/or examples if this is the main project if (PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) - if (BUILD_TESTING) + if (CPPSORT_BUILD_TESTING) enable_testing() add_subdirectory(testsuite) endif() - if (BUILD_EXAMPLES) + if (CPPSORT_BUILD_EXAMPLES) add_subdirectory(examples) endif() endif() diff --git a/NOTICE.txt b/NOTICE.txt index 3e65e05c..57f2babe 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -213,6 +213,19 @@ In addition, certain files include the notices provided below. ---------------------- +/* Copyright Andrei Alexandrescu, 2016-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ +/* Copyright Danila Kutenin, 2020-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ + +---------------------- + # Distributed under the OSI-approved MIT License. See accompanying # file LICENSE or https://github.com/Crascit/DownloadProject for details. diff --git a/README.md b/README.md index 153ce318..3c941297 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--sort%2F1.8.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.8.1) -[![Conan Package](https://img.shields.io/badge/conan-1.8.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.8.1) -[![Build Status](https://travis-ci.org/Morwenn/cpp-sort.svg?branch=master)](https://travis-ci.org/Morwenn/cpp-sort) -[![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) -[![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/master/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) +[![Latest Release](https://img.shields.io/badge/release-1.9.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.9.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.9.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.9.0) +[![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/develop/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) > *It would be nice if only one or two of the sorting methods would dominate all of the others, > regardless of application or the computer being used. But in fact, each method has its own @@ -111,29 +109,33 @@ wiki page](https://github.com/Morwenn/cpp-sort/wiki/Benchmarks). # Compiler support & tooling +![Ubuntu builds status](https://github.com/Morwenn/cpp-sort/workflows/Ubuntu%20Builds/badge.svg?branch=develop) +![Windows builds status](https://github.com/Morwenn/cpp-sort/workflows/Windows%20Builds/badge.svg?branch=develop) +![MacOS builds status](https://github.com/Morwenn/cpp-sort/workflows/MacOS%20Builds/badge.svg?branch=develop) + **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 -* MinGW-w64 g++5.5 or more recent -* clang++3.8 or more recent -* AppleClang shipping with Xcode 9.4 (used to work with older versions but they aren't tested anymore) -* It is notably tested with both libstdc++ and libc++ +* g++5.5 or more recent. It it know 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. +* The versions of MinGW-w64 and AppleClang equivalent to the compilers mentioned above. +* Clang is notably tested with both libstdc++ and libc++. -The compilers listed above are the ones tested specifically, and the library is also tested with -the most recent versions of those compilers on a regular basis. All the other compiler versions -in-between are untested, but should also work. Feel free to open an issue if it isn't the case. +The compilers listed above are the ones used by the CI pipeline, and the library is also tested +with the most recent versions of those compilers on a regular basis. All the other compiler +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 it in the future. +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. -The main repository contains additional support for standard tooling such as CMake or Conan; -you can read more about those [in the wiki](https://github.com/Morwenn/cpp-sort/wiki/Tooling). +The main repository contains additional support for standard tooling such as CMake or Conan. +You can read more about those [in the wiki](https://github.com/Morwenn/cpp-sort/wiki/Tooling). # Thanks @@ -201,6 +203,10 @@ of the algorithm. * The algorithm used by `indirect_adapter` with forward or bidirectional iterators is a slightly modified version of Matthew Bentley's [indiesort](https://github.com/mattreecebentley/plf_indiesort). +* The implementation of the random-access overload of `nth_element` used by some of the algorithms +comes from Danila Kutenin's [miniselect library](https://github.com/danlark1/miniselect) and uses +Andrei Alexandrescu's [*AdaptiveQuickselect*](https://arxiv.org/abs/1606.00484) algorithm. + * The algorithms 0 to 16 used by `sorting_network_sorter` have been generated with Perl's [`Algorithm::Networksort` module](https://metacpan.org/pod/release/JGAMBLE/Algorithm-Networksort-1.30/lib/Algorithm/Networksort.pm). diff --git a/benchmarks/benchmarking-tools/distributions.h b/benchmarks/benchmarking-tools/distributions.h index 24ef901f..bf528a77 100644 --- a/benchmarks/benchmarking-tools/distributions.h +++ b/benchmarks/benchmarking-tools/distributions.h @@ -3,9 +3,8 @@ * SPDX-License-Identifier: MIT */ #include +#include #include -#include -#include #include #include #include @@ -21,350 +20,380 @@ thread_local std::mt19937_64 distributions_prng(std::time(nullptr)); // Distributions for benchmarks // // Distributions are function objects used to fill the -// collections to be sorter before benchmarking the sorting +// collections to be sorted before benchmarking the sorting // algorithms. These distributions allow to check how a // sorting algorithm behaves with common patterns found in -// actual data sets. -// +// real-life data sets, or with patterns known to adversely +// affect some classes of sorting algorithms. -template -struct base_distribution +namespace dist { - template - using fptr_t = void(*)(OutputIterator, std::size_t); + template + struct base_distribution + { + template + using fptr_t = void(*)(OutputIterator, std::size_t); - template - using fptr_proj_t = void(*)(OutputIterator, std::size_t, Projection); + template + using fptr_proj_t = void(*)(OutputIterator, std::size_t, Projection); - template - operator fptr_t() const - { - return [](OutputIterator out, std::size_t size) + template + operator fptr_t() const { - return Derived{}(out, size); - }; - } + return [](OutputIterator out, std::size_t size) + { + return Derived{}(out, size); + }; + } - template - operator fptr_proj_t() const - { - return [](OutputIterator out, std::size_t size, Projection projection) + template + operator fptr_proj_t() const { - return Derived{}(out, size, std::move(projection)); - }; - } -}; + return [](OutputIterator out, std::size_t size, Projection projection) + { + return Derived{}(out, size, std::move(projection)); + }; + } + }; -struct shuffled: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct shuffled: + base_distribution { - std::vector vec; - for (std::size_t i = 0 ; i < size ; ++i) { - vec.emplace_back(i); - } - std::shuffle(std::begin(vec), std::end(vec), distributions_prng); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + std::vector vec; + for (std::size_t i = 0 ; i < size ; ++i) { + vec.emplace_back(i); + } + std::shuffle(vec.begin(), vec.end(), distributions_prng); - auto&& proj = cppsort::utility::as_function(projection); - std::transform(std::begin(vec), std::end(vec), out, proj); - } + auto&& proj = cppsort::utility::as_function(projection); + std::transform(vec.begin(), vec.end(), out, proj); + } - static constexpr const char* output = "shuffled.txt"; -}; + static constexpr const char* output = "shuffled.txt"; + }; -struct shuffled_16_values: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct shuffled_16_values: + base_distribution { - std::vector vec; - for (std::size_t i = 0 ; i < size ; ++i) { - vec.emplace_back(i % 16); - } - std::shuffle(std::begin(vec), std::end(vec), distributions_prng); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + std::vector vec; + for (std::size_t i = 0 ; i < size ; ++i) { + vec.emplace_back(i % 16); + } + std::shuffle(vec.begin(), vec.end(), distributions_prng); - auto&& proj = cppsort::utility::as_function(projection); - std::transform(std::begin(vec), std::end(vec), out, proj); - } + auto&& proj = cppsort::utility::as_function(projection); + std::transform(vec.begin(), vec.end(), out, proj); + } - static constexpr const char* output = "shuffled.txt"; -}; + static constexpr const char* output = "shuffled.txt"; + }; -struct all_equal: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct all_equal: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj(0); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj(0); + } } - } - static constexpr const char* output = "all_equal.txt"; -}; + static constexpr const char* output = "all_equal.txt"; + }; -struct ascending: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct ascending: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj(i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj(i); + } } - } - static constexpr const char* output = "ascending.txt"; -}; + static constexpr const char* output = "ascending.txt"; + }; -struct descending: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct descending: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - while (size--) { - *out++ = proj(size); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + while (size--) { + *out++ = proj(size); + } } - } - static constexpr const char* output = "descending.txt"; -}; + static constexpr const char* output = "descending.txt"; + }; -struct pipe_organ: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct pipe_organ: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size / 2 ; ++i) { - *out++ = proj(i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size / 2 ; ++i) { + *out++ = proj(i); + } + for (std::size_t i = size / 2 ; i < size ; ++i) { + *out++ = proj(size - i); + } } - for (std::size_t i = size / 2 ; i < size ; ++i) { - *out++ = proj(size - i); + + static constexpr const char* output = "pipe_organ.txt"; + }; + + struct push_front: + base_distribution + { + template + auto operator()(OutputIterator out, std::size_t 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) { + *out++ = proj(i); + } + *out = proj(0); + } } - } - static constexpr const char* output = "pipe_organ.txt"; -}; + static constexpr const char* output = "push_front.txt"; + }; -struct push_front: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct push_middle: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - if (size > 0) { - for (std::size_t i = 0 ; i < size - 1 ; ++i) { - *out++ = proj(i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + if (size > 0) { + for (std::size_t i = 0 ; i < size ; ++i) { + if (i != size / 2) { + *out++ = proj(i); + } + } + *out = proj(size / 2); } - *out = proj(0); } - } - static constexpr const char* output = "push_front.txt"; -}; + static constexpr const char* output = "push_middle.txt"; + }; -struct push_middle: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct ascending_sawtooth: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - if (size > 0) { + template + auto operator()(OutputIterator out, std::size_t 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) { - if (i != size / 2) { - *out++ = proj(i); - } + *out++ = proj(i % limit); } - *out = proj(size / 2); } - } - static constexpr const char* output = "push_middle.txt"; -}; + static constexpr const char* output = "ascending_sawtooth.txt"; + }; -struct ascending_sawtooth: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct ascending_sawtooth_bad: + base_distribution { - 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) { - *out++ = proj(i % limit); + template + auto operator()(OutputIterator out, std::size_t 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) { + *out++ = proj(i % limit); + } } - } - static constexpr const char* output = "ascending_sawtooth.txt"; -}; + static constexpr const char* output = "ascending_sawtooth.txt"; + }; -struct ascending_sawtooth_bad: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct descending_sawtooth: + base_distribution { - 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) { - *out++ = proj(i % limit); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + std::size_t limit = size / cppsort::detail::log2(size) + 50; + while (size--) { + *out++ = proj(size % limit); + } } - } - static constexpr const char* output = "ascending_sawtooth.txt"; -}; + static constexpr const char* output = "descending_sawtooth.txt"; + }; -struct descending_sawtooth: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct descending_sawtooth_bad: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) + 50; - while (size--) { - *out++ = proj(size % limit); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + std::size_t limit = size / cppsort::detail::log2(size) - 50; + while (size--) { + *out++ = proj(size % limit); + } } - } - static constexpr const char* output = "descending_sawtooth.txt"; -}; + static constexpr const char* output = "descending_sawtooth.txt"; + }; -struct descending_sawtooth_bad: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct alternating: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) - 50; - while (size--) { - *out++ = proj(size % limit); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj((i % 2) ? i : -i); + } } - } - static constexpr const char* output = "descending_sawtooth.txt"; -}; + static constexpr const char* output = "alternating.txt"; + }; -struct alternating: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct alternating_16_values: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj((i % 2) ? i : -i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj((i % 2) ? i % 16 : -(i % 16)); + } } - } - static constexpr const char* output = "alternating.txt"; -}; + static constexpr const char* output = "alternating_16_values.txt"; + }; -struct alternating_16_values: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct descending_plateau: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj((i % 2) ? i % 16 : -(i % 16)); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + + std::size_t i = size; + while (i > 2 * size / 3) { + *out++ = proj(i); + --i; + } + while (i > size / 3) { + *out++ = proj(size / 2); + --i; + } + while (i > 0) { + *out++ = proj(i); + --i; + } } - } - static constexpr const char* output = "alternating_16_values.txt"; -}; + static constexpr const char* output = "descending_plateau.txt"; + }; -struct inversions: - base_distribution -{ - // Percentage of chances that an "out-of-place" value - // is produced for each position, the goal is to test - // Inv-adaptive algorithms - double factor; - - constexpr explicit inversions(double factor) noexcept: - factor(factor) - {} - - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct inversions: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - - // Generate a percentage of error - std::uniform_real_distribution percent_dis(0.0, 1.0); - // Generate a random value - std::uniform_int_distribution value_dis(0, size - 1); - - for (std::size_t i = 0 ; i < size ; ++i) { - if (percent_dis(distributions_prng) < factor) { - *out++ = value_dis(distributions_prng); - } else { - *out++ = i; + // Percentage of chances that an "out-of-place" value + // is produced for each position, the goal is to test + // Inv-adaptive algorithms + double factor; + + constexpr explicit inversions(double factor) noexcept: + factor(factor) + {} + + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + + // Generate a percentage of error + std::uniform_real_distribution percent_dis(0.0, 1.0); + // Generate a random value + std::uniform_int_distribution value_dis(0, size - 1); + + for (std::size_t i = 0 ; i < size ; ++i) { + if (percent_dis(distributions_prng) < factor) { + *out++ = value_dis(distributions_prng); + } else { + *out++ = i; + } } } - } - static constexpr const char* output = "inversions.txt"; -}; + static constexpr const char* output = "inversions.txt"; + }; -struct vergesort_killer: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct vergesort_killer: + base_distribution { - // WARNING: not for small collections, mostly because I'm lazy... - - const auto size_run = size / cppsort::detail::log2(size); - auto desc = descending{}; - auto killer = pipe_organ{}; - - auto size_output_left = size; - while (true) { - killer(out, size_run - 50, projection); - size_output_left -= size_run - 50; - if (size_output_left < size_run + 50) break; - desc(out, size_run + 50, projection); - size_output_left -= size_run + 50; - if (size_output_left < size_run - 50) break; - }; - - // Just in case - if (size_output_left) { - shuffled{}(out, size_output_left, projection); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + // WARNING: not for small collections, mostly because I'm lazy... + + const auto size_run = size / cppsort::detail::log2(size); + auto desc = descending{}; + auto killer = pipe_organ{}; + + auto size_output_left = size; + while (true) { + killer(out, size_run - 50, projection); + size_output_left -= size_run - 50; + if (size_output_left < size_run + 50) break; + desc(out, size_run + 50, projection); + size_output_left -= size_run + 50; + if (size_output_left < size_run - 50) break; + }; + + // Just in case + if (size_output_left) { + shuffled{}(out, size_output_left, projection); + } } - } - static constexpr const char* output = "vergesort_killer.txt"; -}; + static constexpr const char* output = "vergesort_killer.txt"; + }; +} diff --git a/benchmarks/errorbar-plot/bench.cpp b/benchmarks/errorbar-plot/bench.cpp index 613bb0a3..0604a614 100644 --- a/benchmarks/errorbar-plot/bench.cpp +++ b/benchmarks/errorbar-plot/bench.cpp @@ -40,7 +40,7 @@ std::pair sorts[] = { }; // Distribution to benchmark against -auto distribution = shuffled{}; +auto distribution = dist::shuffled{}; // Sizes of the collections to sort std::uint64_t size_min = 1u << 1; diff --git a/benchmarks/errorbar-plot/plot.py b/benchmarks/errorbar-plot/plot.py index a0af7170..90012710 100644 --- a/benchmarks/errorbar-plot/plot.py +++ b/benchmarks/errorbar-plot/plot.py @@ -59,7 +59,7 @@ def main(): ax.grid(True) ax.set_xlabel('Size') ax.set_ylabel('Time [s]') - ax.set_xscale('log', basex=2) + ax.set_xscale('log', base=2) ax.set_yscale('log') pyplot.title("Sorting std::vector") diff --git a/benchmarks/inversions/inv-bench.cpp b/benchmarks/inversions/inv-bench.cpp index 38d6be33..a1df50d6 100644 --- a/benchmarks/inversions/inv-bench.cpp +++ b/benchmarks/inversions/inv-bench.cpp @@ -81,7 +81,7 @@ int main(int argc, char* argv[]) for (int idx = 0 ; idx <= 100 ; ++idx) { double factor = 0.01 * idx; - auto dist = inversions(factor); + auto distribution = dist::inversions(factor); std::vector cycles; @@ -90,7 +90,7 @@ int main(int argc, char* argv[]) while (std::chrono::duration_cast(total_end - total_start) < max_run_time && cycles.size() < max_runs_per_size) { collection_t collection; - dist(std::back_inserter(collection), size); + distribution(std::back_inserter(collection), size); std::uint64_t start = rdtsc(); sort.second(collection); std::uint64_t end = rdtsc(); diff --git a/benchmarks/patterns/bars.py b/benchmarks/patterns/bars.py index 40fe89e7..7a76a215 100644 --- a/benchmarks/patterns/bars.py +++ b/benchmarks/patterns/bars.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015-2020 Morwenn +# Copyright (c) 2015-2021 Morwenn # SPDX-License-Identifier: MIT # Copyright (c) 2015 Orson Peters @@ -50,10 +50,18 @@ def main(): "ascending_sawtooth": "Ascending sawtooth", "descending_sawtooth": "Descending sawtooth", "alternating": "Alternating", - "alternating_16_values": "Alternating (16 values)" + "alternating_16_values": "Alternating (16 values)", } - sort_order = ["heap_sort", "pdq_sort", "quick_sort", "spread_sort", "std_sort", "verge_sort"] + # Algorithm results will be displayed in this order + algos = [ + "heap_sort", + "pdq_sort", + "quick_sort", + "spread_sort", + "std_sort", + "verge_sort", + ] root = pathlib.Path(args.root) for file in root.iterdir(): @@ -72,7 +80,7 @@ def main(): # Choose the colour palette and markers to use if args.use_alt_palette: # That one has the advantage of being infinite - palette = pyplot.cm.rainbow(numpy.linspace(0, 1, len(sort_order))) + palette = pyplot.cm.rainbow(numpy.linspace(0, 1, len(algos))) else: # Colorblind-friendly palette (https://gist.github.com/thriveth/8560036) palette = ['#377eb8', '#ff7f00', '#4daf4a', @@ -92,12 +100,9 @@ def main(): "Ascending sawtooth", "Descending sawtooth", "Alternating", - "Alternating (16 values)" + "Alternating (16 values)", ) - algos = tuple(data[size]["Shuffled"].keys()) - algos = tuple(sorted(algos, key=lambda a: sort_order.index(a))) - groupnames = distributions groupsize = len(algos) groups = [[data[size][distribution][algo] for algo in algos] for distribution in distributions] @@ -132,7 +137,7 @@ def main(): ax.autoscale_view() pyplot.ylim(pyplot.ylim()[0] + 1, pyplot.ylim()[1] - 1) - pyplot.title("Sorting $10^{}$ elements".format(round(math.log(size, 10)))) + pyplot.title("Sorting a std::vector with $10^{}$ elements".format(round(math.log(size, 10)))) pyplot.legend(loc="best") figure = pyplot.gcf() diff --git a/benchmarks/patterns/bench.cpp b/benchmarks/patterns/bench.cpp index c0dd2a9c..c9e697c2 100644 --- a/benchmarks/patterns/bench.cpp +++ b/benchmarks/patterns/bench.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -40,7 +40,7 @@ #include "rdtsc.h" // Type of data to sort during the benchmark -using value_t = int; +using value_t = double; // Type of collection to sort using collection_t = std::vector; @@ -60,18 +60,18 @@ int main() >; std::pair distributions[] = { - { "shuffled", shuffled() }, - { "shuffled_16_values", shuffled_16_values() }, - { "all_equal", all_equal() }, - { "ascending", ascending() }, - { "descending", descending() }, - { "pipe_organ", pipe_organ() }, - { "push_front", push_front() }, - { "push_middle", push_middle() }, - { "ascending_sawtooth", ascending_sawtooth() }, - { "descending_sawtooth", descending_sawtooth() }, - { "alternating", alternating() }, - { "alternating_16_values", alternating_16_values() } + { "shuffled", dist::shuffled() }, + { "shuffled_16_values", dist::shuffled_16_values() }, + { "all_equal", dist::all_equal() }, + { "ascending", dist::ascending() }, + { "descending", dist::descending() }, + { "pipe_organ", dist::pipe_organ() }, + { "push_front", dist::push_front() }, + { "push_middle", dist::push_middle() }, + { "ascending_sawtooth", dist::ascending_sawtooth() }, + { "descending_sawtooth", dist::descending_sawtooth() }, + { "alternating", dist::alternating() }, + { "alternating_16_values", dist::alternating_16_values() }, }; std::pair sorts[] = { @@ -80,7 +80,7 @@ int main() { "quick_sort", cppsort::quick_sort }, { "spread_sort", cppsort::spread_sort }, { "std_sort", cppsort::std_sort }, - { "verge_sort", cppsort::verge_sort } + { "verge_sort", cppsort::verge_sort }, }; std::size_t sizes[] = { 1'000'000 }; diff --git a/benchmarks/small-array/benchmark.cpp b/benchmarks/small-array/benchmark.cpp index 7227263b..99addbb8 100644 --- a/benchmarks/small-array/benchmark.cpp +++ b/benchmarks/small-array/benchmark.cpp @@ -45,7 +45,7 @@ template< typename Sorter, typename DistributionFunction > -auto time_it(Sorter sorter, DistributionFunction dist) +auto time_it(Sorter sorter, DistributionFunction distribution) -> double { // Seed the distribution manually to ensure that all algorithms @@ -54,13 +54,13 @@ auto time_it(Sorter sorter, DistributionFunction dist) std::vector cycles; - // Generate and sort arrays of size N thanks to dist + // Generate and sort arrays of size N thanks to distribution auto total_start = clock_type::now(); auto total_end = clock_type::now(); while (std::chrono::duration_cast(total_end - total_start) < max_run_time && cycles.size() < max_runs_per_size) { std::array arr; - dist(arr.begin(), N); + distribution(arr.begin(), N); std::uint64_t start = rdtsc(); sorter(arr); std::uint64_t end = rdtsc(); @@ -139,12 +139,12 @@ int main() std::cout << "SEED: " << seed << '\n'; time_distributions(); } diff --git a/codecov.yml b/codecov.yml index ef8acb94..84fab2a6 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,7 +3,7 @@ coverage: ignore: - - "testsuite/**/*" + - "testsuite" # This unrolled version of a merge-insertion sort derivative was tested # exhaustively for every permutation of an integer sequence of 9 elements, # thus we don't include it diff --git a/conanfile.py b/conanfile.py index 97e52c35..09aec752 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018-2020 Morwenn +# Copyright (c) 2018-2021 Morwenn # SPDX-License-Identifier: MIT from conans import CMake, ConanFile @@ -8,7 +8,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.8.1" + version = "1.9.0" description = "Additional sorting algorithms & related tools" topics = "conan", "cpp-sort", "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index d441b8ec..2e98c0f9 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -1,6 +1,6 @@ -*Note: this page is hardly ever updated and the graphs might not reflect the most recent algorithms or optimizations. It can be used as a quick guide but if you really need a fast algorithm for a specific use case, you better run your own benchmarks.* +*Note: this page only benchmarls sorting algorithms under specific conditions. It can be used as a quick guide but if you really need a fast algorithm for a specific use case, you better run your own benchmarks.* -*Last major update: 1.8.0 release.* +*Last meaningful update: 1.9.0 release.* Benchmarking is hard and I might not be doing it right. Moreover, benchmarking sorting algorithms highlights that the time needed to sort a collection of elements depends on several things: the type to sort, the size of the collection, the cost of comparing two values, the cost of moving an element, the patterns formed by the distribution of the values in the collection to sort, the type of the collection itself, etc. The aim of this page is to help you choose a sorting algorithm depending on your needs. You can find two main kinds of benchmarks: the ones that compare algorithms against shuffled collections of different sizes, and the ones that compare algorithms against different data patterns for a given collection size. @@ -19,7 +19,7 @@ Most sorting algorithms are designed to work with random-access iterators, so th Unstable sorts are the most common sorting algorithms, and unstable sorts on random-access iterators are generally the fastest comparison sorts. If you don't know what algorithm you know, it's probably one of these ones. ![Benchmark speed of unstable sorts with increasing size for std::vector](https://i.imgur.com/6Jfj768.png) -![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/HUiK2jf.png) +![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/C9GypoJ.png) The plots above show a few general tendencies: * `selection_sort` is O(n²) and doesn't scale. @@ -28,8 +28,8 @@ The plots above show a few general tendencies: The quicksort derivatives and the hybrid radix sorts are generally the fastest of the lot, yet `drop_merge_sort` seems to offer interesting speedups for `std::deque` despite not being designed to be the fastest on truly shuffled data. Part of the explanation is that it uses `pdq_sort` in a buffer underneath, which might be faster for `std::deque` than truly sorting in-place. -![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/LL7iCQd.png) -![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/4rkTNeq.png) +![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/te098uq.png) +![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/aRbP7wY.png) A few random takeways: * All the algorithms are more or less adaptive, not always for the same patterns. @@ -71,13 +71,13 @@ The analysis is pretty simple here: # Bidirectional iterables -Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`](http://en.cppreference.com/w/cpp/container/list/sort) which are aware of the data structure they are sorting. +Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`][std-list-sort] which are aware of the data structure they are sorting. ![Benchmark speed of sorts with increasing size for std::list](https://i.imgur.com/Z2BDhpz.png) For elements as small as `double`, there are two clear winners here: `drop_merge_sort` and `out_of_place_adapter(pdq_sort)`. Both have in common the fact that they move a part of the collection (or the whole collection) to a contiguous memory buffer and sort it there using `pdq_sort`. The only difference is that `drop_merge_sort` does that "accidentally" while `out_of_place_adapter` was specifically introduced to sort into a contiguous memory buffer and move back for speed. -![Benchmark sorts over different patterns for std::list](https://i.imgur.com/6EftqN7.png) +![Benchmark sorts over different patterns for std::list](https://i.imgur.com/RcmJ8gL.png) `out_of_place_adapter(pdq_sort)` was not included in this benchmark, because it adapts to patterns the same way `pdq_sort` does. Comments can be added for these results: * `std::list::sort` would require elements more expensive to move for node relinking to be faster than move-based algorithms. @@ -93,7 +93,7 @@ Even fewer sorters can handle forward iterators. `out_of_place_adapter(pdq_sort) ![Benchmark sorts over different patterns for std::forward_list](https://i.imgur.com/bWZRega.png) The results are roughly the same than with bidirectional iterables: -* `std::forward_list::sort` doesn't scale well unless moves are expensive. +* [`std::forward_list::sort`][std-forward-list-sort] doesn't scale well unless moves are expensive. * Sorting out-of-place is faster than anything else. * If no extra heap memory is available, `quick_merge_sort` is the only O(n log n) algorithm that can be used, and does a fine job despite never being excellent. @@ -114,7 +114,7 @@ Integer sorting is a rather specific scenario for which many solutions exist: co ## *Inv*-adaptive algorithms -Some sorting algorithms are specifically designed to be fast when there are only a few inversions in the collection, they are known as *Inv*-adaptive algorithms since the amount of work they perform is dependent on the result of the measure of presortedness *Inv(X)*. There are two such algorithms in **cpp-sort**: `drop_merge_sort` and `split_sort` (which probably makes them *Rem*-adaptive too). Both work by removing elements from the collections to leave a *longest ascending subsequence*, sorting the removed elements and merging the two sorted sequences back into one. +Some sorting algorithms are specifically designed to be fast when there are only a few inversions in the collection, they are known as *Inv*-adaptive algorithms since the amount of work they perform is dependent on the result of the measure of presortedness *Inv(X)*. There are two such algorithms in **cpp-sort**: `drop_merge_sort` and `split_sort`. Both work by removing elements from the collections to leave a *longest ascending subsequence*, sorting the removed elements and merging the two sorted sequences back into one (which probably makes them *Rem*-adaptive too). The following plot shows how fast those algorithms are depending on the percentage of inversions in the collection to sort. They are benchmarked against `pdq_sort` because it is the algorithm they use internally to sort the remaining unsorted elements prior to the merge, which makes it easy to compare the gains and overheads of those algorithms compared to a raw `pdq_sort`. @@ -134,6 +134,21 @@ The following example uses a collection of `std::array` whose first The improvements are not always as clear as in this benchmark, but it shows that `indirect_adapter` might be an interesting tool to have in your sorting toolbox in such a scenario. +## Sorting stably without heap memory + +Only a few algorithms allow to sort a collection stably without using extra heap memory: `grail_sort` and `block_sort` can accept a fixed-size buffer (possibly of size 0) while `merge_sort` has a fallback algorithm when no heap memory is available. + +![Benchmark speed of stable sorts with no heap memory with increasing size for std::vector](https://i.imgur.com/1a64irX.png) +![Benchmark speed of stable sorts with no heap memory with increasing size for std::deque](https://i.imgur.com/U5uD8Er.png) +![Detail of the previous benchmark](https://i.imgur.com/owUictQ.png) + +`merge_sort` is definitely losing this benchmark. Interestingly enough `block_sort` is way better with a fixed buffer of 512 elements while it hardly affects `grail_sort` at all. For `std::deque`, `grail_sort` is almost always the fastest no matter what. + +![Benchmark stable sorts with no heap memory over different patterns for std::vector](https://i.imgur.com/74YxCLI.png) +![Benchmark stable sorts with no heap memory over different patterns for std::deque](https://i.imgur.com/jqek5Ii.png) + +Here `merge_sort` still loses the battle, but it also displays an impressive enough adaptiveness to presortedness and patterns. + ## Small array sorters Some sorting algorithms are particularly suited to sort very small collections: the ones provided by ``, but also the very simple ones such as `insertion_sort` or `selection_sort`. Most other sorting algorithms fallback to one of these when sorting a small collection. @@ -149,10 +164,15 @@ The spikes in the otherwise smooth sorting networks curve when sorting arrays of # Measures of presortedness -This benchmark for [measures of presortedness](https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness) is small and only intends to show the cost that these tools might incur. It is not meant to be exhaustive in any way. +This benchmark for [measures of presortedness][measures-of-presortedness] is small and only intends to show the cost that these tools might incur. It is not meant to be exhaustive in any way. ![Benchmark speed of measures of presortedness for increasing size for std::vector](https://i.imgur.com/5XniqE1.png) While the graph reasonably shows the relative cost of the different measures of presortedness, there are a few hidden traps: * *Par(X)* seems to beat every other measure, but it is a highly adaptative O(n² log n) algorithm, whose theoretical worst case might be the worst of all measures of presortedness. * *Dis(X)* looks like a O(n) algorithm in this graph, but it is actually a O(n²) algorithm with extremely efficient short-circuits in most cases. Its worst case would put it closer from *Osc(X)*. + + + [measures-of-presortedness]: https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness + [std-forward-list-sort]: https://en.cppreference.com/w/cpp/container/list/sort + [std-list-sort]: https://en.cppreference.com/w/cpp/container/list/sort diff --git a/docs/Changelog.md b/docs/Changelog.md index b333faaa..672604bc 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,6 +1,4 @@ -This page describes the features that change in **cpp-sort** depending on the C++ version with which it is compiled (C++14 or later) as well as the support for miscellaneous compiler extensions; for a full changelog between actual releases, you can check the dedicated [releases page](https://github.com/Morwenn/cpp-sort/releases). - -*The notes in this page are only valid for the latest versions of the 1.x and 2.x branches. If you are using an older version of the library, some of them might not apply.* +This page describes the features that change in **cpp-sort** depending on the C++ version with which it is compiled (C++14 or later) as well as the support for miscellaneous compiler extensions; for a full changelog between actual releases, you can check the dedicated [releases page][cpp-sort-releases]. ## C++14 features @@ -11,10 +9,10 @@ While **cpp-sort** theoretically requires a fully C++14-compliant compiler, a fe ## C++17 features -When compiled with C++17, **cpp-sort** might gain a few additional features depending on the level of C++17 support provided by the compiler. The availability of most of the features depend on the presence of corresponding [feature-testing macros](https://wg21.link/SD6). The support for feature-testing macros being optional, it is possible that one of the features listed below isn't available even though the compiler is supposed to provide enough C++17 features to support it. If it is the case and it is a problem for you, don't hesitate to open an issue so that we can explicitly support the given compiler. +When compiled with C++17, **cpp-sort** might gain a few additional features depending on the level of C++17 support provided by the compiler. The availability of most of the features depends on the presence of corresponding [feature-testing macros][feature-test-macros]. The support for feature-testing macros being optional in C++17, it is possible that some of the features listed below aren't available even though the compiler is implements them. If it is the case and it is a problem for you, don't hesitate to open an issue so that we can explicitly support the given compiler. **New features:** -* `string_spread_sort` now accepts [`std::string_view`](https://en.cppreference.com/w/cpp/string/basic_string_view) and sometimes `std::wstring_view`. +* `string_spread_sort` now accepts [`std::string_view`][std-string-view] and sometimes `std::wstring_view`. This feature is made available through the check `__cplusplus > 201402L && __has_include()`. @@ -42,7 +40,7 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe This feature is made available through the check `__cpp_lib_uncaught_exceptions`. -* New [`function_constant`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) utility to micro-optimize function pointers and class member pointers. +* New [`function_constant`][cpp-sort-function-objects] utility to micro-optimize function pointers and class member pointers. ```cpp insertion_sort(collection, function_constant<&foo::bar>{}); @@ -61,18 +59,41 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe The C++17 traits are used as is when the feature-test macro `__cpp_lib_is_invocable` is defined. -## Other features +## C++20 features -**cpp-sort** tries to take advantage of more than just standard features when possible, and also to provide extended support for some compiler-specific extensions. Below is a list of the impact that non-standard features might have on the library: +When compiled with C++20, **cpp-sort** might gain a few additional features depending on the level of C++20 support provided by the compiler. The availability of those features depends on the presence of corresponding [feature-testing macros][feature-test-macros] when possible, even though some checks are more granular. Don't hesitate to open an issue if your compiler and standard library supports one of those features but it doesn't seem to work in **cpp-sort**. -**Extension-specific support:** -* 128-bit integers support: `ska_sorter` has dedicated support for 128-bit integers (`unsigned __int128` or `__uint128_t` and its signed counterpart), no matter whether the standard library is also instrumented for those types. This support should be available as long as `__SIZEOF_INT128__` is defined by the compiler. +**New features:** +* When available, [`std::identity`][std-identity] benefits from dedicated support wherever [`utility::identity`][cpp-sort-function-objects] is supported, with equivalent semantics. -**Performance improvements:** -* Additional allocators: `merge_insertion_sorter` can be somewhat more performant when libstdc++'s [`bitmap_allocator`](https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html) is available. +* When available, [`std::ranges::less`][std-ranges-less] and [`std::ranges::greater`][std-ranges-greater] benefit from dedicated support wherever [`std::less<>`][std-less-void] and [`std::greater<>`][std-greater-void] are supported, with equivalent semantics. - This improvement is made available through the check `__has_include()`, which means that it should be available for every compiler where `__has_include` and libstdc++ are available (old and new Clang, and more recent GCC). +## Other features + +**cpp-sort** tries to take advantage of more than just standard features when possible by using implementation-specific tweaks to improve the user experience. The following improvements might be available depending on the your standard implementation: - *Changed in version 1.7.0:* `merge_insertion_sorter` uses a custom list implementation which does not need to take advantage of `bitmap_allocator` anymore. +**Additional features:** +* 128-bit integers support: [`counting_sorter`][counting-sorter] and [`ska_sorter`][ska-sorter] have dedicated support for 128-bit integers (`unsigned __int128` or `__uint128_t` and its signed counterpart), no matter whether the standard library is also instrumented for those types. This support should be available as long as the macro `__SIZEOF_INT128__` is defined. +**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. + +* 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. + + + [branchless-traits]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits + [counting-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#counting_sorter + [cpp-sort-function-objects]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects + [cpp-sort-releases]: https://github.com/Morwenn/cpp-sort/releases + [feature-test-macros]: https://wg21.link/SD6 + [pdq-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#pdq_sorter + [ska-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#ska_sorter + [std-greater-void]: https://en.cppreference.com/w/cpp/utility/functional/greater_void + [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity + [std-less-void]: https://en.cppreference.com/w/cpp/utility/functional/less_void + [std-mem-fn]: https://en.cppreference.com/w/cpp/utility/functional/mem_fn + [std-ranges-greater]: https://en.cppreference.com/w/cpp/utility/functional/ranges/greater + [std-ranges-less]: https://en.cppreference.com/w/cpp/utility/functional/ranges/less + [std-string-view]: https://en.cppreference.com/w/cpp/string/basic_string_view) diff --git a/docs/Comparators-and-projections.md b/docs/Comparators-and-projections.md index 25919bca..18d3b76f 100644 --- a/docs/Comparators-and-projections.md +++ b/docs/Comparators-and-projections.md @@ -1,14 +1,14 @@ Most sorting algorithms in **cpp-sort** accept comparison and/or projection parameters. The library therefore considers these kinds of functions to be first-class citizens too and provides dedicated comparators, projections and tools to combine them and to solve common related problems. -All the functions and classes in **cpp-sort** that take comparison or projection functions as parameters expect [*Callable*][callable] parameters, which correspond to anything that can be used as the first parameter of [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke). This allows to pass entities such as pointers to members or pointer to member functions to the sorting algorithms; it should work out-of-the-box without any wrapping needed on the user side. +All the functions and classes in **cpp-sort** that take comparison or projection functions as parameters expect [*Callable*][callable] parameters, which correspond to anything that can be used as the first parameter of [`std::invoke`][std-invoke]. This allows to pass entities such as pointers to members or pointer to member functions to the sorting algorithms; it should work out-of-the-box without any wrapping needed on the user side. ### Related utilities Several of the [miscellaneous utilities][utilities] provided by the library are meant to interact with comparators and projections: -- [`as_comparison` and `as_projection`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_comparison-and-as_projection) are used to make it explicit whether an ambiguous function object should be used for comparison or for projection. -- [`as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function) can be used to turn any [*Callable*][callable] into an object invokable with regular parentheses. -- [`is_probably_branchless_comparison` and `is_probably_branchless_projection`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) are type traits that can be used to mark whether functions are likely to be branchless when called with a specific type. -- [`identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) is the default projection returning the argument it is passed without modifying it. +- [`as_comparison` and `as_projection`][as-comparison-as-projection] are used to make it explicit whether an ambiguous function object should be used for comparison or for projection. +- [`as_function`][as-function] can be used to turn any [*Callable*][callable] into an object invokable with regular parentheses. +- [`is_probably_branchless_comparison` and `is_probably_branchless_projection`][branchless-traits] are type traits that can be used to mark whether functions are likely to be branchless when called with a specific type. +- [`identity`][misc-function-objects] is the default projection returning the argument it is passed without modifying it. ### LWG3031 @@ -19,6 +19,11 @@ This additional guarantee is allowed by the resolution of [LWG3031][lwg3031]. Ho *New in version 1.7.0* + [as-comparison-as-projection]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_comparison-and-as_projection + [as-function]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function + [branchless-traits]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits [callable]: https://en.cppreference.com/w/cpp/named_req/Callable [lwg3031]: https://wg21.link/LWG3031 + [misc-function-objects]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects + [std-invoke]: https://en.cppreference.com/w/cpp/utility/functional/invoke [utilities]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities diff --git a/docs/Comparators.md b/docs/Comparators.md index 215c73a8..0139fb1a 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -1,9 +1,29 @@ -While comparators are not inherent to sorting *per se*, most sorting algorithms (at least in this library) are *comparison sorts*, hence providing some common useful comparators along with the sorters can be useful. Every comparator in this module satisfies the [`BinaryPredicate`](http://en.cppreference.com/w/cpp/concept/BinaryPredicate) library concept. +While comparators are not inherent to sorting *per se*, most sorting algorithms (at least in this library) are *comparison sorts*, hence providing some common useful comparators along with the sorters can be useful. Every comparator in this module satisfies the [`BinaryPredicate`][binary-predicate] library concept. Every non-refined comparator described below is also a [transparent comparator][transparent-comparator]. While this ability is not used by the library itself, it means that the comparators can be used with the standard library associative containers to compare heterogeneous objects without having to create temporaries. *Changed in version 1.5.0:* every non-refined comparator is now a [transparent comparator][transparent-comparator]. +### `projection_compare` + +```cpp +#include +``` + +The class template `projection_compare` can be used to embed a comparison and a projection in a single comparison object, allowing to provide projection support to algorithms that only support comparisons, such as standard library algorithms prior to C++20. Both the passed comparison and projection functions can be [*Callable*][callable]. + +It is accompanied by a `make_projection_compare` function template to avoid having to pass the template parameters by hand. + +**Example:** + +```cpp +// Sort a family from older to younger member +std::vector family = { /* ... */ }; +std::sort(family.begin(), family.end(), cppsort::make_projection_compare(std::greater<>{}, &Person::age)); +``` + +*New in version 1.9.0* + ### Total order comparators ```cpp @@ -25,7 +45,7 @@ The comparators `total_less` and `total_order` are [customization points][custom That said, the comparators are currently unable to discriminate between quiet and signaling NaNs, so they compare equivalent. When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). -Total order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](http://en.cppreference.com/w/cpp/types/is_integral). +Total order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_integral`][std-is-integral]. *Changed in version 1.5.0:* `total_greater` and `total_less` are respectively of type `total_greater_t` and `total_less_t`. @@ -47,7 +67,7 @@ The comparators `weak_less` and `weak_order` are [customization points][custom-p When it doesn't handle a type natively and ADL doesn't find any suitable `weak_less` function in a class namespace, `cppsort::weak_less` falls back to `cppsort::total_less` since a total order is also a weak order (it applies to the whole `weak_*` family of customization points). -Weak order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](http://en.cppreference.com/w/cpp/types/is_integral). +Weak order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_integral`][std-is-integral]. *Changed in version 1.5.0:* `weak_greater` and `weak_less` are respectively of type `weak_greater_t` and `weak_less_t`. @@ -62,7 +82,7 @@ The comparators `partial_less` and `partial_order` are [customization points][cu When it doesn't handle a type natively and ADL doesn't find any suitable `partial_less` function in a class namespace, `cppsort::partial_less` falls back to `cppsort::weak_less` since a weak order is also a partial order (it applies to the whole `partial_*` family of customization points). -Partial order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic). +Partial order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_arithmetic`][std-is-arithmetic]. *Changed in version 1.5.0:* `partial_greater` and `partial_less` are respectively of type `partial_greater_t` and `partial_less_t`. @@ -72,7 +92,7 @@ Partial order comparators are considered as [generating branchless code](https:/ #include ``` -The comparator `natural_less` is a [customization point][custom-point] that can be used to perform a [natural sort][natural-sort]. The function handles any two forward iterable sequences of `char` out of the box using [`std::isdigit`][is-digit] to identify digits (which includes `std::string`, `std::vector` and `char[]`). Other character types and locales are currently not handled and it is unlikely that the library will evolve more than switching to ``'s `std::isdigit` instead of ``'s one. +The comparator `natural_less` is a [customization point][custom-point] that can be used to perform a [natural sort][natural-sort]. The function handles any two forward iterable sequences of `char` out of the box using [`std::isdigit`][std-is-digit] to identify digits (which includes `std::string`, `std::vector` and `char[]`). Other character types and locales are currently not handled and it is unlikely that the library will evolve more than switching to ``'s `std::isdigit` instead of ``'s one. *Changed in version 1.5.0:* `natural_less` can compare heterogeneous types as long as they provide `begin` and `end` functions returning iterators to a sequence of `char`. @@ -111,16 +131,21 @@ The two-parameter version of the customization point calls the three-parameter o *Changed in version 1.5.0:* `case_insensitive_less` is an instance of type `case_insensitive_less_t`. + [binary-predicate]: https://en.cppreference.com/w/cpp/concept/BinaryPredicate + [branchless-traits]https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits + [callable]: https://en.cppreference.com/w/cpp/named_req/Callable [case-sensitivity]: https://en.wikipedia.org/wiki/Case_sensitivity [cppcon2015-compare]: https://github.com/CppCon/CppCon2015/tree/master/Presentations/Comparison%20is%20not%20simple%2C%20but%20it%20can%20be%20simpler%20-%20Lawrence%20Crowl%20-%20CppCon%202015 - [custom-point]: http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ - [is-digit]: http://en.cppreference.com/w/cpp/string/byte/isdigit + [custom-point]: https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ [natural-sort]: https://en.wikipedia.org/wiki/Natural_sort_order [P0100]: http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0100r1.html [partial-order]: https://en.wikipedia.org/wiki/Partially_ordered_set#Formal_definition [refining]: https://github.com/Morwenn/cpp-sort/wiki/Refined-functions - [std-locale]: http://en.cppreference.com/w/cpp/locale/locale - [to-lower]: http://en.cppreference.com/w/cpp/locale/ctype/tolower + [std-is-arithmetic]: https://en.cppreference.com/w/cpp/types/is_arithmetic + [std-is-digit]: https://en.cppreference.com/w/cpp/string/byte/isdigit + [std-is-integral]: https://en.cppreference.com/w/cpp/types/is_integral + [std-locale]: https://en.cppreference.com/w/cpp/locale/locale + [to-lower]: https://en.cppreference.com/w/cpp/locale/ctype/tolower [total-order]: https://en.wikipedia.org/wiki/Total_order [transparent-comparator]: https://stackoverflow.com/q/20317413/1364752 - [weak-order]: https://en.wikipedia.org/wiki/Weak_ordering \ No newline at end of file + [weak-order]: https://en.wikipedia.org/wiki/Weak_ordering diff --git a/docs/Home.md b/docs/Home.md index 184d9282..035189c4 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,4 +1,4 @@ -Welcome to the **cpp-sort** wiki! +Welcome to the **cpp-sort 1.9.0** documentation! You probably read the introduction in the README, so I won't repeat it here. This wiki contains documentation about the library: basic documentation about the many sorting tools and how to use them, documentation about the additional utilities provided by the library and even some detailed tutorials if you ever want to write your own sorters or sorter adapters. This main page explains a few general things that didn't quite fit in other parts of the documentation. @@ -22,7 +22,7 @@ If the library throws any other exception, it will likely come from user code. T **cpp-sort** strives to follow the standard library guidance when it comes to self-move and self-swap operations: when sorting a collection of `T`, the expectations are as follows: * The algorithms in the library should never perform a blind self-move operation since there is no guarantee about the result of such an operation. -* However the different algorithms expect `T` to satisfy the C++20 [`Swappable` concept][swappable] concept. This concepts requires that, for any value `x` of type `T`, the following code doesn't change the value of `x`: +* However the different algorithms expect `T` to satisfy the C++20 [`swappable` concept][swappable]. This concepts requires that, for any value `x` of type `T`, the following code doesn't change the value of `x`: ```cpp using std::swap; swap(x, x); @@ -31,6 +31,12 @@ If the library throws any other exception, it will likely come from user code. T *New in version 1.4.0* +### Determinism & reproducibility + +So far every algorithm in the library is deterministic: for a given input, one should always get the exact same sequence of operations performed. It was a deliberate choice not to use algorithms such as random pivot quicksort or random sampling algorithms. + +The library does not contain multithreaded algorithms either for now, further guaranteeing reproducibility. + ## Library information & configuration ### Versioning @@ -59,12 +65,16 @@ Some old components undergo deprecation before being removed in the following ma *New in version 1.8.0* -### Assertions +### Assertions & audits Some algorithms have assertions to guard against accidental logic issues (mostly in algorithms adapted from other projects), but they are disabled by default. You can enable these assertions by defining the preprocessor macro `CPPSORT_ENABLE_ASSERTIONS`. This new macro still honours `NDEBUG`, so assertions won't be enabled anyway if `NDEBUG` is defined. +A similar `CPPSORT_ENABLE_AUDITS` macro can be defined to enable audits: those are expensive assertions which are not enabled by `CPPSORT_ENABLE_ASSERTIONS` because they are too expensive, to the point that they might even change the complexity of some algorithms. + *New in version 1.6.0* +*New in version 1.9.0*: `CPPSORT_ENABLE_AUDITS` + ## Miscellaneous This wiki also includes a small section about the [[original research|Original research]] that happened during the conception of the library and the results of this research. While it is not needed to understand how the library works or how to use it, it may be of interest if you want to discover new things about sorting. @@ -75,4 +85,4 @@ If you ever feel that this wiki is incomplete, that it needs more examples or mo Hope you have fun! - [swappable]: http://eel.is/c++draft/concept.swappable \ No newline at end of file + [swappable]: https://en.cppreference.com/w/cpp/concepts/swappable diff --git a/docs/Library-nomenclature.md b/docs/Library-nomenclature.md index fcca86ea..f81f53ee 100644 --- a/docs/Library-nomenclature.md +++ b/docs/Library-nomenclature.md @@ -6,9 +6,9 @@ cppsort::utility::fixed_buffer<512> >; -* *Comparison function*: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke). The default comparison function used by the sorting algorithms is [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void). Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order. +* *Comparison function*: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke). The default comparison function used by the sorting algorithms is [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void). Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order. - cppsort::sort(collection, std::greater<>{}); + cppsort::heap_sort(collection, std::greater<>{}); Some algorithms don't accept such an additional parameter. It may be because they implement a non-comparison sort instead, a sorting algorithm that uses other properties of the elements to perform the sort rather than a comparison function (for example a [radix sort](https://en.wikipedia.org/wiki/Radix_sort)). @@ -16,7 +16,7 @@ * *Fixed-size sorter*: [[fixed-size sorters]] are a special breed of sorters designed to sort a fixed number of values. While they try their best to be full-fledge sorters, they are definitely not full-fledge sorters and probably don't blend as well as one would like into the library. Their main advantage is that they can be more performant than regular sorters in some specific scenarios. -* *Iterator category*: the C++ standard defines [several categories of iterators](http://en.cppreference.com/w/cpp/iterator) such as forward iterators, bidirectional iterators or random-access iterators. The standard library uses [iterator tags](http://en.cppreference.com/w/cpp/iterator/iterator_tags) to document the category of an iterator. These categories are important since algorithms are designed to work with some categories of iterators and not with other categories, and those in this library are not different: in-place sorting needs at least forward iterators. You can use the [`iterator_category`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#iterator_category) sorter trait to get the least constrained iterator category associated with a sorter. +* *Iterator category*: the C++ standard defines [several categories of iterators](https://en.cppreference.com/w/cpp/iterator) such as forward iterators, bidirectional iterators or random-access iterators. The standard library uses [iterator tags](https://en.cppreference.com/w/cpp/iterator/iterator_tags) to document the category of an iterator. These categories are important since algorithms are designed to work with some categories of iterators and not with other categories, and those in this library are not different: in-place sorting needs at least forward iterators. You can use the [`iterator_category`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#iterator_category) sorter trait to get the least constrained iterator category associated with a sorter. using category = cppsort::iterator_category; @@ -26,11 +26,11 @@ auto max_inversion = cppsort::probe::dis(collection); -* *Projection*: some sorters accept a projection as an additional parameter. A projection is a unary function that allows to "view" the values of a collection differently. For example it may allow to sort a collection of values on a specific field. The available sorting algorithms transform projections on the fly so that pointers to member data can also be used as projections. Projections were pioneered by the [Adobe Source Libraries](http://stlab.adobe.com/) and appear in the C++20 [Constrained algorithms](https://en.cppreference.com/w/cpp/algorithm/ranges). +* *Projection*: some sorters accept a projection as an additional parameter. A projection is a unary function that allows to "view" the values of a collection differently. For example it may allow to sort a collection of values on a specific field. The available sorting algorithms transform projections on the fly so that pointers to member data can also be used as projections. Projections were pioneered by the [Adobe Source Libraries](https://stlab.adobe.com/) and appear in the C++20 [Constrained algorithms](https://en.cppreference.com/w/cpp/algorithm/ranges). struct wrapper { int value; }; std::vector collection = { /* ... */ }; - cppsort::sort(collection, &wrapper::value); + cppsort::heap_sort(collection, &wrapper::value); Every *comparison sorter* is also a *projection sorter*, but there are also projection-only sorters, such as [`spread_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters). diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index f8c46e11..3f72ce17 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -52,7 +52,7 @@ auto as_comparison(Function&& func) `as_function` is an utility function borrowed from Eric Niebler's [Ranges v3](https://github.com/ericniebler/range-v3) library. This function takes a parameter and does what it can to return an object callable with the usual function call syntax. It is notably useful to transform a pointer to member data into a function taking an instance of the class and returning the member. -To be more specific, `as_function` returns the passed object as is if it is already callable with the usual function call syntax, and uses [`std::mem_fn`](http://en.cppreference.com/w/cpp/utility/functional/mem_fn) to wrap the passed object otherwise. It is worth noting that when the original object is returned, its potential to be called in a `constexpr` context remains the same, which makes `as_function` superior to `std::invoke` in this regard (prior to C++20). +To be more specific, `as_function` returns the passed object as is if it is already callable with the usual function call syntax, and uses [`std::mem_fn`](https://en.cppreference.com/w/cpp/utility/functional/mem_fn) to wrap the passed object otherwise. It is worth noting that when the original object is returned, its potential to be called in a `constexpr` context remains the same, which makes `as_function` superior to `std::invoke` in this regard (prior to C++20). ```cpp struct wrapper { int foo; }; @@ -79,8 +79,8 @@ constexpr bool is_probably_branchless_comparison_v ``` This trait tells whether the comparison function `Compare` is likely to generate branchless code when comparing two instances of `T`. By default it considers that the following comparison functions are likely to be branchless: -* `std::less<>` and `std::less` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) -* `std::greater<>` and `std::greater` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) +* `std::less<>`, `std::ranges::less` and `std::less` for any `T` which satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic) +* `std::greater<>`, `std::ranges::greater` and `std::greater` for any `T` which satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic) ```cpp template @@ -93,10 +93,15 @@ constexpr bool is_probably_branchless_projection_v This trait tells whether the projection function `Projection` is likely to generate branchless code when called with an instance of `T`. By default it considers that the following projection functions are likely to be branchless: * `cppsort::utility::identity` for any type -* Any type that satisfies [`std::is_member_function_pointer`](http://en.cppreference.com/w/cpp/types/is_member_function_pointer) provided it is called with an instance of the appropriate class +* `std::identity` for any type (when available) +* Any type that satisfies [`std::is_member_function_pointer`](https://en.cppreference.com/w/cpp/types/is_member_function_pointer) provided it is called with an instance of the appropriate class These traits can be specialized for user-defined types. If one of the traits is specialized to consider that a user-defined type is likely to be branchless with a comparison/projection function, cv-qualified and reference-qualified versions of the same user-defined type will also be considered to produce branchless code when compared/projected with the same function. +*Changed in version 1.9.0:* conditional support for [`std::ranges::less`](https://en.cppreference.com/w/cpp/utility/functional/ranges/less) and [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). + +*Changed in version 1.9.0:* conditional support for [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity). + ### Buffer providers ```cpp @@ -110,7 +115,7 @@ template struct fixed_buffer; ``` -This buffer provider allocates `N` elements on the stack. It uses [`std::array`](http://en.cppreference.com/w/cpp/container/array) behind the scenes, so it also allows buffers of size 0. The runtime size passed to the buffer at construction is discarded. +This buffer provider allocates `N` elements on the stack. It uses [`std::array`](https://en.cppreference.com/w/cpp/container/array) behind the scenes, so it also allows buffers of size 0. The runtime size passed to the buffer at construction is discarded. ```cpp template @@ -125,6 +130,8 @@ This buffer provider allocates on the heap a number of elements depending on a g #include ``` +***WARNING:** `utility::identity` is removed in version 2.0.0, use `std::identity` instead.* + This header provides the class `projection_base` and the mechanism used to compose projections with `operator|`. See [[Chainable projections]] for more information. Also available in this header, the struct `identity` is a function object that can type any value of any movable type and return it as is. It is used as a default for every projection parameter in the library so that sorters view the values as they are by default, without a modification. @@ -141,7 +148,7 @@ struct identity: }; ``` -It is equivalent to the proposed `std::identity` from the [Ranges TS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4560.pdf) and will probably be replaced by the standard function object the day it makes its way into the standard. +It is equivalent to the C++20 [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity). Wherever the documentation mentions special handling of `utility::identity`, the same support is provided for `std::identity` when it is available. This header also provides additional function objects implementing basic unary operations. These functions objects are designed to be used as *size policies* with `dynamic_buffer` and similar classes. The following function objects are available: * `half`: returns the passed value divided by 2. @@ -167,7 +174,7 @@ struct function_constant }; ``` -This utility is modeled after [`std::integral_constant`](http://en.cppreference.com/w/cpp/types/integral_constant), but is different in that it takes its parameter as `template`, and `operator()` calls the wrapped value instead of returning it. The goal is to store function pointers and pointer to members "for free": they are only "stored" as a template parameter, which allows `function_constant` to be an empty class. This has two main advantages: `function_constant` can benefit from *Empty Base Class Optimization* since it weights virtually nothing, and it won't need to be pushed on the stack when passed to a function, while the wrapped pointer would have been if passed unwrapped. Unless you are micro-optimizing some specific piece of code, you shouldn't need this class. +This utility is modeled after [`std::integral_constant`](https://en.cppreference.com/w/cpp/types/integral_constant), but is different in that it takes its parameter as `template`, and `operator()` calls the wrapped value instead of returning it. The goal is to store function pointers and pointer to members "for free": they are only "stored" as a template parameter, which allows `function_constant` to be an empty class. This has two main advantages: `function_constant` can benefit from *Empty Base Class Optimization* since it weights virtually nothing, and it won't need to be pushed on the stack when passed to a function, while the wrapped pointer would have been if passed unwrapped. Unless you are micro-optimizing some specific piece of code, you shouldn't need this class. `is_probably_branchless_comparison` and `is_probably_branchless_projection` will correspond to `std::true_type` if the wrapped `Function` also gives `std::true_type`. Moreover, you can even specialize these traits for specific `function_constant` instanciations if you need even more performance. @@ -175,6 +182,8 @@ This utility is modeled after [`std::integral_constant`](http://en.cppreference. *New in version 1.7.0:* `projection_base` and chainable projections. +*Changed in version 1.9.0:* `std::identity` is now also supported wherever the library has special behavior for `utility::identity`. + ### `iter_move` and `iter_swap` ```cpp @@ -203,7 +212,7 @@ auto iter_swap(Iterator lhs, Iterator rhs) ***WARNING:** `make_integer_range` and `make_index_range` are deprecated in version 1.8.0 and removed in version 2.0.0.* -The class template `make_integer_range` can be used wherever an [`std::integer_sequence`](http://en.cppreference.com/w/cpp/utility/integer_sequence) can be used. An `integer_range` takes a type template parameter that shall be an integer type, then three integer template parameters which correspond to the beginning of the range, the end of the range and the « step ». +The class template `make_integer_range` can be used wherever an [`std::integer_sequence`](https://en.cppreference.com/w/cpp/utility/integer_sequence) can be used. An `integer_range` takes a type template parameter that shall be an integer type, then three integer template parameters which correspond to the beginning of the range, the end of the range and the « step ». ```cpp template< @@ -232,7 +241,7 @@ using make_index_range = make_integer_range; #include ``` -`size` is a function that can be used to get the size of an iterable. It is equivalent to the C++17 function [`std::size`](http://en.cppreference.com/w/cpp/iterator/size) but has an additional tweak so that, if the iterable is not a fixed-size C array and doesn't have a `size` method, it calls `std::distance(std::begin(iter), std::end(iter))` on the iterable. Therefore, this function can also be used for `std::forward_list` as well as some implementations of ranges. +`size` is a function that can be used to get the size of an iterable. It is equivalent to the C++17 function [`std::size`](https://en.cppreference.com/w/cpp/iterator/size) but has an additional tweak so that, if the iterable is not a fixed-size C array and doesn't have a `size` method, it calls `std::distance(std::begin(iter), std::end(iter))` on the iterable. Therefore, this function can also be used for `std::forward_list` as well as some implementations of ranges. ### `static_const` @@ -250,6 +259,6 @@ namespace } ``` -You can read more about this instantiation pattern in [an article](http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) by Eric Niebler. +You can read more about this instantiation pattern in [an article](https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) by Eric Niebler. -*Warning: this header does not exist anymore in the C++17 branch; use [`inline` variables](http://en.cppreference.com/w/cpp/language/inline) instead.* +*Warning: this header does not exist anymore in the C++17 branch; use [`inline` variables](https://en.cppreference.com/w/cpp/language/inline) instead.* diff --git a/docs/Original-research.md b/docs/Original-research.md index ace9ab7a..4274439d 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -6,10 +6,10 @@ You can find some experiments and interesting pieces of code [in my Gist][morwen One of the main observations which naturally occured as long as I was putting together this library was about the best complexity tradeoffs between time and memory depending on the iterator categories of the different sorting algorithms (only taking comparison sorts into account): * 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). +* 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. -* 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. +* 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 Now, those observations/claims are there to be challenged: if you know of any stable comparison sorting algorithm that runs on bidirectional iterators in O(n log n) with O(1) extra memory, don't hesitate to be the ones challenging those claims :) @@ -190,7 +190,7 @@ Somehow Edelkamp and Weiß eventually [published a paper][quick-merge-sort-arxiv [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]: http://www.geocities.ws/p356spt/ + [exact-sort]: https://www.geocities.ws/p356spt/ [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 diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index a84b0402..33aee2a8 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -6,7 +6,7 @@ Sorter adapters are the main reason for using sorter function objects instead of In this documentation, we will call *adapted sorters* the sorters passed to the adapters and *resulting sorter* the sorter class that results from the adaption of a sorter by an adapter. If not specified, the stability and the iterator category of the *resulting sorter* is that of the *adapted sorter* provided there is a single *adapted sorter*. -In C++17, *sorter adapters* can be used in a function-like fashion thanks to `explicit` constructors (taking one or several sorters) by taking advantage of implicit [deduction guides](http://en.cppreference.com/w/cpp/language/class_template_argument_deduction). The following example illustrates how it simplifies their use: +In C++17, *sorter adapters* can be used in a function-like fashion thanks to `explicit` constructors (taking one or several sorters) by taking advantage of implicit [deduction guides][ctad]. The following example illustrates how it simplifies their use: ```cpp // C++14 @@ -22,7 +22,7 @@ Most of the library's *sorter adapters* can store the passed *sorters* in their * If the *sorter adapter* adapts a single *sorter*, then it has a member function called `get()` which returns a reference to the internal *sorter* whose reference and `const` qualifications match those of the *sorter adapter* instance. If the *sorter adapter* is empty and default-constructible, then a default-constructed instance of the type of the *original sorter* is returned instead. * If the *sorter adapter* is empty and default-constructible, then it can be converted to any function pointer whose signature matches that of its `operator()`. -It is worth noting that in the current state of things, sorters & adapters are expected to have a `const operator()`, and thus don't play nice with *mutable sorters*. There are plans to properly handle *mutable sorters* in the future: you can track [the corresponding issue](https://github.com/Morwenn/cpp-sort/issues/104). +It is worth noting that in the current state of things, sorters & adapters are expected to have a `const operator()`, and thus don't play nice with *mutable sorters*. There are plans to properly handle *mutable sorters* in the future: you can track [the corresponding issue][issue-104]. *Changed in version 1.5.0:* adapters can store the sorters they adapt, enabling the use of *stateful sorters*. The overall semantics of sorters and adapters have evolved accordingly. @@ -95,7 +95,7 @@ This adapter uses `cppsort::iterator_category` to check the iterator category of If `hybrid_adapter` is nested in another `hybrid_adapter`, those are flattened: for example `hybrid_adapter, D>` is flattened to `hybrid_adapter`. This unwrapping exists so that the iterator categories of the sorters in the inner `hybrid_adapter` are seen by the outer one, and not only the fused iterator category of the inner `hybrid_adapter`. -If `hybrid_adapter` is wrapped into [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter), it wraps every *adapted sorter* into `stable_adapter`, forwarding it to better get the specific behaviour f some sorters or adapters when wrapped into it. +If `hybrid_adapter` is wrapped into [`stable_adapter`][stable-adapter], it wraps every *adapted sorter* into `stable_adapter`, forwarding it to better get the specific behaviour f some sorters or adapters when wrapped into it. The *resulting sorter*'s `is_always_stable` is `std::true_type` if and only if every *adapted sorter*'s `is_always_stable` is `std::true_type`. `is_stable` is specialized so that it will return the stability of the called *adapted sorter* with the given parameters. The iterator category of the *resulting sorter* is the most permissive iterator category among the *adapted sorters*. @@ -107,7 +107,7 @@ The *resulting sorter*'s `is_always_stable` is `std::true_type` if and only if e #include ``` -This adapter implements an indirect sort: a sorting algorithm that actually sorts the iterators rather than the values themselves, then uses the sorted iterators to move the actual values to their final position in the original collection. The actual algorithm used is a [mountain sort](https://github.com/Morwenn/mountain-sort), whose goal is to sort a collection while performing a minimal number of *move operations* on the elements of the collection. This indirect adapter copies the iterators and sorts them with the given sorter before performing cycles in a way close to a [cycle sort](https://en.wikipedia.org/wiki/Cycle_sort) to actually move the elements. There are a few differences though: while the cycle sort always has a O(n²) complexity, the *resulting sorter* of `indirect_adapter` has the complexity of the *adapted sorter*. However, it stores n additional iterators as well as n additional booleans and performs up to (3/2)n move operations once the iterators have been sorted; these operations are not significant enough to change the complexity of the *adapted sorter*, but they do represent a rather big additional constant factor. +This adapter implements an indirect sort: a sorting algorithm that actually sorts the iterators rather than the values themselves, then uses the sorted iterators to move the actual values to their final position in the original collection. The actual algorithm used is a [mountain sort][mountain-sort], whose goal is to sort a collection while performing a minimal number of *move operations* on the elements of the collection. This indirect adapter copies the iterators and sorts them with the given sorter before performing cycles in a way close to a [cycle sort][cycle-sort] to actually move the elements. There are a few differences though: while the cycle sort always has a O(n²) complexity, the *resulting sorter* of `indirect_adapter` has the complexity of the *adapted sorter*. However, it stores n additional iterators as well as n additional booleans and performs up to (3/2)n move operations once the iterators have been sorted; these operations are not significant enough to change the complexity of the *adapted sorter*, but they do represent a rather big additional constant factor. Note that `indirect_adapter` provides a rather good exception guarantee: as long as the collection of iterators is being sorted, if an exception is thrown, the collection to sort will remain in its original state. However, it doesn't provide the *strong exception guarantee* since exceptions could still be thrown when the elements are moved to their sorted position. @@ -118,7 +118,7 @@ template class indirect_adapter; ``` -The *resulting sorter* accepts forward iterators, and the iterator category of the *adapted sorter* does not matter. Note that this algorithm performs even fewer move operations than [`low_moves_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters#low_moves_sorter), but at the cost of a higher constant factor that may not always be worth it for small collections. +The *resulting sorter* accepts forward iterators, and the iterator category of the *adapted sorter* does not matter. Note that this algorithm performs even fewer move operations than [`low_moves_sorter`][low-moves-sorter], but at the cost of a higher constant factor that may not always be worth it for small collections. *Changed in version 1.3.0:* `indirect_adapter` now returns the result of the *adapted sorter* in C++17 mode. @@ -151,7 +151,7 @@ The *resulting sorter* accepts forward iterators, and the iterator category of t #include ``` -This adapter implements a [Schwartzian transform](https://en.wikipedia.org/wiki/Schwartzian_transform) that helps to reduce the runtime cost when projections are expensive. A regular sorting algorithm generally projects elements on-the-fly during a comparison, that is, every time a sorting algorithm performs a comparison, it projects both operands before comparing the results, which is ok when the projection operation is cheap, but which might become a problem when the projection is more expensive. A sorter wrapped into `schwartz_adapter` will instead precompute the projection for every element in the collection to sort, then sort the original collection according to the projected elements. Compared to a raw sorter, it requires O(n) additional space to store the projected elements. +This adapter implements a [Schwartzian transform][schwartzian-transform] that helps to reduce the runtime cost when projections are expensive. A regular sorting algorithm generally projects elements on-the-fly during a comparison, that is, every time a sorting algorithm performs a comparison, it projects both operands before comparing the results, which is ok when the projection operation is cheap, but which might become a problem when the projection is more expensive. A sorter wrapped into `schwartz_adapter` will instead precompute the projection for every element in the collection to sort, then sort the original collection according to the projected elements. Compared to a raw sorter, it requires O(n) additional space to store the projected elements. `schwartz_adapter` returns the result of the *adapted sorter* if any. @@ -172,7 +172,7 @@ The mechanism used to synchronize the collection of projected objects with the o #include ``` -This adapter takes a sorter and, if the collection to sort has a suitable `sort` method, it is used to sort the collection. Otherwise, if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection. Otherwise, the *adapted sorter* is used instead to sort the collection. If `self_sort_adapter` is wrapped into [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter), if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection (the `sort` methods of `std::list` and `std::forward_list` are special-cased and called by this adapter too). Otherwise, the *adapted sorter* wrapped into `stable_adapter` is used instead to sort the collection. +This adapter takes a sorter and, if the collection to sort has a suitable `sort` method, it is used to sort the collection. Otherwise, if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection. Otherwise, the *adapted sorter* is used instead to sort the collection. If `self_sort_adapter` is wrapped into [`stable_adapter`][stable-adapter], if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection (the `sort` methods of `std::list` and `std::forward_list` are special-cased and called by this adapter too). Otherwise, the *adapted sorter* wrapped into `stable_adapter` is used instead to sort the collection. This sorter adapter allows to support out-of-the-box sorting for `std::list` and `std::forward_list` as well as other user-defined classes that implement a `sort` method with a compatible interface. @@ -183,7 +183,7 @@ template struct self_sort_adapter; ``` -Since it is impossible to guarantee the stability of the `sort` method of a given iterable, the *resulting sorter*'s `is_always_stable` is `std::false_type`. However, [`is_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_stable) will be `std::true_type` if a container's `stable_sort` is called or if a call to the *adapted sorter* is stable. A special case considers valid calls to `std::list::sort` and `std::forward_list::sort` to be stable. +Since it is impossible to guarantee the stability of the `sort` method of a given iterable, the *resulting sorter*'s `is_always_stable` is `std::false_type`. However, [`is_stable`][is-stable] will be `std::true_type` if a container's `stable_sort` is called or if a call to the *adapted sorter* is stable. A special case considers valid calls to `std::list::sort` and `std::forward_list::sort` to be stable. ### `small_array_adapter` @@ -191,7 +191,7 @@ Since it is impossible to guarantee the stability of the `sort` method of a give #include ``` -This adapter is not a regular sorter adapter, but a *fixed-size sorter adapter*. It wraps a [fixed-size sorter](https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters) and calls it whenever it is passed a fixed-size C array or an `std::array` with an appropriate size. +This adapter is not a regular sorter adapter, but a *fixed-size sorter adapter*. It wraps a [fixed-size sorter][fixed-size-sorter] and calls it whenever it is passed a fixed-size C array or an `std::array` with an appropriate size. ```cpp template< @@ -201,7 +201,7 @@ template< struct small_array_adapter; ``` -The `Indices` parameter may be either `void` or a specialization of the standard class template [`std::index_sequence`](http://en.cppreference.com/w/cpp/utility/integer_sequence). When it is `void`, `small_array_adapter` will try to call the underlying fixed-size sorter for C arrays or `std::array` instances of any size. If an `std::index_sequence` specialization is given instead, the adapter will try to call the underlying fixed-size sorter only if the size of the array to be sorted appears in the index sequence. If the template parameter `Indices` is omitted, this class will check whether the [`fixed_sorter_traits`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#fixed_sorter_traits) specialization for the given fixed-size sorter contains a type named `domain` and use it as indices; if such a type does not exist, `void` will be used as `Indices`. +The `Indices` parameter may be either `void` or a specialization of the standard class template [`std::index_sequence`][std-index-sequence]. When it is `void`, `small_array_adapter` will try to call the underlying fixed-size sorter for C arrays or `std::array` instances of any size. If an `std::index_sequence` specialization is given instead, the adapter will try to call the underlying fixed-size sorter only if the size of the array to be sorted appears in the index sequence. If the template parameter `Indices` is omitted, this class will check whether the [`fixed_sorter_traits`][fixed-sorter-traits] specialization for the given fixed-size sorter contains a type named `domain` and use it as indices; if such a type does not exist, `void` will be used as `Indices`. The `operator()` overloads are SFINAEd out if a collection not handled by the fixed-size sorter is passed (*e.g.* wrong type or array too big). These soft errors allow `small_array_adapter` to play nice with `hybrid_adapter`. For example, if one wants to call `low_moves_sorter` when a sorter is given an array of size 0 to 8 and `pdq_sorter` otherwise, they could easily create an appropriate sorter the following way: @@ -219,13 +219,19 @@ using sorter = cppsort::hybrid_adapter< *Warning: this adapter only supports default-constructible stateless sorters.* -### `stable_adapter` +### `stable_adapter`, `make_stable` and `stable_t` ```cpp #include ``` -This adapter takes a sorter and alters its behavior (if needed) to produce a stable sorter. It does so by associating every element of the collection to sort to its starting position and, whenever two elements compare equivalent, the algorithm uses the starting position of the compared elements to make sure that their relative starting positions are preserved. Compared to a raw sorter, it requires O(n) additional space to store the starting positions. +This adapter takes a sorter and alters its behavior (if needed) to produce a stable sorter. It does so by associating every element of the collection to sort to its starting position and, whenever two elements compare equivalent, the algorithm compares the starting positions of the elements to ensure that their relative starting positions are preserved. Compared to a raw sorter, it requires O(n) additional space to store the starting positions. + +If the *adapted sorter* already implements a stable sorting algorithm when called with a specific set of parameters (if [`is_stable`][is-stable] is `std::true_type` for the parameters), then the *resulting sorter* will call the *adapted sorter* directly. + +`stable_adapter` and its specializations might expose a `type` member type which aliases the *adapter sorter* or some intermediate sorter which is always stable, or the *resulting sorter* otherwise. Its goal is to provide the least nested type that is known to always be stable in order to sometimes skip some template nesting. + +The *resulting sorter* is always stable. `stable_adapter` returns the result of the *adapted sorter* if any. @@ -234,16 +240,18 @@ template struct stable_adapter; ``` -However, if the *adapted sorter* already implements a stable sorting algorithm when called with a specific set of parameters (if [`is_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_stable) is `std::true_type` for the parameters), then the *resulting sorter* will call the *adapted sorter* directly. The *resulting sorter* is always stable. +One can provide a dedicated stable algorithm by explicitly specializing `stable_adapter` to bypass the automatic transformation and allow optimizations, which can be useful when a pair of stable and unstable sorting algorithms are closely related. For example, while [`std_sorter`][std-sorter] calls [`std::sort`][std-sort], the explicit specialization `stable_adapter` calls [`std::stable_sort`][std-stable-sort] instead. In **cpp-sort**, `stable_adapter` has specializations for the following components: -One can provide a dedicated stable algorithm by explicitly specializing `stable_adapter` to bypass the automatic transformation and allow optimizations (this isn't clean *per se*, but...), which can be useful when a pair of stable and unstable sorting algorithms are closely related. For example, while [`std_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#std_sorter) calls [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort), the explicit specialization `stable_adapter` actually calls [`std::stable_sort`](http://en.cppreference.com/w/cpp/algorithm/stable_sort) instead. In **cpp-sort**, `stable_adapter` has specializations for the following sorters and adapters: +* [`default_sorter`][default-sorter] +* [`std_sorter`][std-sorter] +* [`verge_sorter`][verge-sorter] +* [`hybrid_adapter`][hybrid-adapter] +* [`self_sort_adapter`][self-sort-adapter] +* [`verge_adapter`][verge-adapter] -* [`default_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter) -* [`std_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#std_sorter) -* [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) -* [`self_sort_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter) +If such a user specialization is provided, it shall alias `is_always_stable` to `std::true_type` and provide a `type` member type which follows the rules mentioned earlier. -While `stable_adapter` is the "high-level" adapter whenever one wants a stable sorting algorithm, the header also provides `make_stable`, which directly exposes the raw mechanism used to transform an unstable sorter into a stable one without applying any of the short-circuits described above: +While `stable_adapter` is the "high-level" adapter to use whenever one wants a stable sorting algorithm, the header also provides `make_stable`, which directly exposes the raw mechanism used to transform an unstable sorter into a stable one without applying any of the short-circuits described above: ```cpp template @@ -252,13 +260,24 @@ struct make_stable; Contrary to `stable_adapter`, `make_stable` isn't meant to be specialized by end users. In C++17, `make_stable` like most of the other adapters can take advantage of deduction guides. +The header also provides the `stable_t` type alias, which either alias the passed sorter if it is always stable, or `stable_adapter` otherwise. + +```cpp +template +using stable_t = /* implementation-defined */; +``` + +*New in version 1.9.0:* `stable_t` + +It is roughly equivalent to `stable_adapter::type`, except that it doesn't instantiate `stable_adapter` when it doesn't need to. It can be used to reduce the template nesting and improve error messages in some places, and as such is often a better alternative to a raw `stable_adapter`. + ### `verge_adapter` ```cpp #include ``` -While the library already provides a `verge_sorter` built on top of `pdq_sorter`, the true power of vergesort is to add a fast *Runs*-adaptive layer on top of any sorting algorithm to make it handle data with big runs better while not being noticeably slower for the distributions that the vergesort layer can't handle. [This page](https://github.com/Morwenn/vergesort/blob/master/fallbacks.md) contains benchmarks of vergesort on top of several sorting algorithms, showing that it can be valuable tool to add on top of most sorting algorithms. +While the library already provides a `verge_sorter` built on top of `pdq_sorter`, the true power of vergesort is to add a fast *Runs*-adaptive layer on top of any sorting algorithm to make it handle data with big runs better while not being noticeably slower for the distributions that the vergesort layer can't handle. [This page][vergesort-fallbacks] contains benchmarks of vergesort on top of several sorting algorithms, showing that it can be valuable tool to add on top of most sorting algorithms. `verge_adapter` takes any sorter and uses it as a fallback sorting algorithm when it can't sort a collection on its own. The *resulting sorter* is always unstable, no matter the stability of the *adapted sorter*. It only accepts random-access iterables. @@ -266,3 +285,29 @@ While the library already provides a `verge_sorter` built on top of `pdq_sorter` template struct verge_adapter; ``` + +When wrapped into [`stable_adapter`][stable-adapter], it has a slightly different behaviour: it detects strictly descending runs instead of non-ascending ones, and wraps the fallback sorter with `stable_t`. The *resulting sorter* is stable, and faster than just using `make_stable`. + +*New in version 1.9.0:* explicit specialization for `stable_adapter`. + + + [ctad]: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction + [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort + [default-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter + [fixed-size-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters + [fixed-sorter-traits]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#fixed_sorter_traits + [hybrid-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter + [is-stable]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_stable + [issue-104]: https://github.com/Morwenn/cpp-sort/issues/104 + [low-moves-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters#low_moves_sorter + [mountain-sort]: https://github.com/Morwenn/mountain-sort + [schwartzian-transform]: https://en.wikipedia.org/wiki/Schwartzian_transform + [stable-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter + [self-sort-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter + [std-index-sequence]: https://en.cppreference.com/w/cpp/utility/integer_sequence + [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort + [std-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#std_sorter + [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/stable_sort + [verge-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#verge_adapter + [verge-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#verge_sorter + [vergesort-fallbacks]: https://github.com/Morwenn/vergesort/blob/master/fallbacks.md diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index c2614aeb..70e0ca2b 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -43,7 +43,7 @@ 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`](http://en.cppreference.com/w/cpp/types/result_of) of the parameters (well, it is what you would expect it to be). The actual implementation is more verbose and redundant, but it allows to transform a sorter into a function pointer corresponding to any valid overload of `operator()`. +Note that the function pointer conversion syntax above is made up, but it allows to clearly highlight what it does while hiding the 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`. @@ -72,7 +72,7 @@ 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<>`](http://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<>`](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. 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): @@ -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<>`](http://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<>`](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. 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,4 +169,12 @@ 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::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. + 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. diff --git a/docs/Sorter-traits.md b/docs/Sorter-traits.md index d707242e..c2f69e0c 100644 --- a/docs/Sorter-traits.md +++ b/docs/Sorter-traits.md @@ -127,7 +127,7 @@ template using iterator_category = typename sorter_traits::iterator_category; ``` -Some tools need to know which category of iterators a sorting algorithm can work with. Therefore, a well-defined sorter shall provide one of the standard library [iterator tags](http://en.cppreference.com/w/cpp/iterator/iterator_tags) in order to document that. +Some tools need to know which category of iterators a sorting algorithm can work with. Therefore, a well-defined sorter shall provide one of the standard library [iterator tags](https://en.cppreference.com/w/cpp/iterator/iterator_tags) in order to document that. When a sorter is adapted so that it may be used with several categories of iterators, the resulting sorter's iterator category will correspond to the most permissive among the original sorters. For example, if an [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) merges sorting algorithms with `std::forward_iterator_tag` and `std::random_access_iterator_tag`, the resulting sorter's category will be `std::forward_iterator_tag` since it is guaranteed to work with any iterable type which has *at least* forward iterators. @@ -142,7 +142,7 @@ constexpr bool is_always_stable_v = is_always_stable::value; ``` -This type trait is always either [`std::true_type`](http://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type` and tells whether a sorting algorithm is always [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) or not. This information may be useful in some contexts, most notably to make sure that [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) can use a stable sorter directly instead of artificially making it stable. +This type trait is always either [`std::true_type`](https://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type` and tells whether a sorting algorithm is always [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) or not. This information may be useful in some contexts, most notably to make sure that [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) can use a stable sorter directly instead of artificially making it stable. When a sorter adapter is used, the *resulting sorter* is stable if and only if its stability can be guaranteed and unstable otherwise, even when the *adapted sorter* may be stable (for example, [`self_sort_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter)'s `is_always_stable` is aliased to `std::false_type` since it is impossible to guarantee the stability of every `sort` method). @@ -208,6 +208,6 @@ struct fixed_sorter_traits; This class template can be specialized for any fixed-size sorter and exposes the following properties: -* `domain`: a specialization of [`std::index_sequence`](http://en.cppreference.com/w/cpp/utility/integer_sequence) containing all the sizes for which a specialization of the fixed-size sorter exists. If this trait isn't specified, it is assumed that the fixed-size sorter can be specialized for any value of `N`. +* `domain`: a specialization of [`std::index_sequence`](https://en.cppreference.com/w/cpp/utility/integer_sequence) containing all the sizes for which a specialization of the fixed-size sorter exists. If this trait isn't specified, it is assumed that the fixed-size sorter can be specialized for any value of `N`. * `iterator_category`: the category of iterators every specialization of the fixed-size sorter is guaranteed to work with. Individual specializations may work with less strict iterator categories. -* `is_always_stable`: an alias for [`std::true_type`](http://en.cppreference.com/w/cpp/types/integral_constant) if every specialization of the fixed-size sorter is guaranteed to always be stable, and `std::false_type` otherwise. \ No newline at end of file +* `is_always_stable`: an alias for [`std::true_type`](https://en.cppreference.com/w/cpp/types/integral_constant) if every specialization of the fixed-size sorter is guaranteed to always be stable, and `std::false_type` otherwise. \ No newline at end of file diff --git a/docs/Sorters.md b/docs/Sorters.md index 8b4302b9..7bb3e92f 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -153,13 +153,13 @@ None of the container-aware algorithms invalidates iterators. #include ``` -Implements the Ford-Johnson merge-insertion sort. I am no researcher and I couldn't find the algorithm's complexity on the internet, so you will have nice question marks instead. This algorithm isn't meant to actually be used and is mostly interesting from a computer science point of view: for really small collections, it has an optimal worst case for the number of comparisons performed; it has been proven that for some sizes, no algorithm can perform fewer comparisons. That said, the algorithm has a rather big memory overhead and performs many move operations; it is really too slow for any real world use. +Implements the Ford-Johnson merge-insertion sort. This algorithm isn't meant to actually be used and is mostly interesting from a computer science point of view: for really small collections, it has an optimal worst case for the number of comparisons performed. It has indeed been proved that for some sizes, no algorithm can perform fewer comparisons. That said, the algorithm has a rather big memory overhead and performs many move operations; it is really too slow for any real world use. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | | ? | n log n | n log n | n | No | Random-access | -*Until version 1.7.0:* it is worth noting that the algorithm uses GNU's [`bitmap_allocator`](https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html) when possible at some places instead of the default allocator. I noticed speed improvements up to 25%, but that still doesn't make the algorithm anywhere near fast. +*Until version 1.7.0:* the algorithm used GNU's [`bitmap_allocator`](https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html) with `std::list` when possible instead of the default allocator, leading to speed improvements up to 25%. *Changed in version 1.7.0:* the `std::list` used by the algorithm has been replaced with a custom list implementation whose speed does not depend on the availability of some allocator, and which should be faster than the previous implementation in any case (still not anywhere near fast). @@ -223,7 +223,7 @@ This sorter is a bit faster or a bit slower than `smooth_sorter` depending on th #include ``` -Implements a flavour of [QuickMergeSort](https://arxiv.org/abs/1307.3033). +Implements a flavour of [QuickMergesort][quick-mergesort]. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -231,14 +231,18 @@ Implements a flavour of [QuickMergeSort](https://arxiv.org/abs/1307.3033). | n | n log n | n log n | log² n | No | Bidirectional | | n | n log² n | n log² n | log² n | No | Forward | -QuickMergeSort is an algorithm that performs a quicksort-like partition and tries to use mergesort on the bigger partition, using the smaller one as a swap buffer used for the merge operation when possible. The flavour of QuickMergeSort used by `quick_merge_sorter` actually uses an equivalent of [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element) to partition the collection in 1/3-2/3 parts in order to maximize the size of the partition (2 thirds of the space) that can be merge-sorted using the other partition as a swap buffer. +QuickMergesort is an algorithm that performs a quicksort-like partition and tries to use mergesort on the bigger partition, using the smaller one as a swap buffer used for the merge operation when possible. The flavour of QuickMergesort used by `quick_merge_sorter` uses a [selection algorithm][selection-algorithm] to split the collection into partitions containing 2/3 and 1/3 of the elements respectively. This allows to use an internal mergesort of the biggest partition (2/3 of the elements) using the other partition (1/3 of the elements) as a swap buffer. -The change in time complexity for forward iterators is due to the partitioning algorithm being O(n log n) instead of O(n). The log n memory is due to top-down mergesort stack recursion in the random-access version, while the memory of the forward version use is dominated by the mutually recursive [introselect](https://en.wikipedia.org/wiki/Introselect) algorithm which is used to implement an `nth_element` equivalent for forward iterators. +The change in time complexity for forward iterators is due to the partitioning algorithm being O(n log n) instead of O(n). The space complexity is dominated by the stack recursion in the selection algorithms: +* log n for the random-access version, which uses Andrei Alexandrescu's [*AdaptiveQuickselect*][adaptive-quickselect]. +* log² n for the forward and bidirectional versions, which use the mutually recursive [introselect][introselect] algorithm. This sorter can't throw `std::bad_alloc`. *New in version 1.2.0* +*Changed in version 1.9.0:* the random-access version now runs in O(n log n) instead of accidentally running in O(n²). + ### `quick_sorter` ```cpp @@ -313,7 +317,7 @@ Implements a [spinsort](https://www.boost.org/doc/libs/1_72_0/libs/sort/doc/html #include ``` -Implements an in-place *SplitSort* as descirbed in *Splitsort—an adaptive sorting algorithm* by Levcopoulos and Petersson. This library implements the simpler "in-place" version of the algorithm described in the paper. +Implements an in-place *SplitSort* as descirbed in *Splitsort — an adaptive sorting algorithm* by Levcopoulos and Petersson. This library implements the simpler "in-place" version of the algorithm described in the paper. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -324,6 +328,8 @@ SplitSort is a [*Rem*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measure * While it uses O(n) extra memory to merge some elements, it can run perfectly fine with O(1) extra memory. * Benchmarks shows that drop-merge sort is better when few elements aren't in place, but SplitSort has a lower overhead on random data while still performing better than most general-purpose sorting algorithms when the data is already somewhat sorted. +This sorter can't throw `std::bad_alloc`. + *New in version 1.4.0* ### `std_sorter` @@ -332,7 +338,7 @@ SplitSort is a [*Rem*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measure #include ``` -Uses the standard library [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort) to sort a collection. While the complexity guarantees are only partial in the standard, here is what's expected: +Uses the standard library [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) to sort a collection. While the complexity guarantees are only partial in the standard, here is what's expected: | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -340,14 +346,14 @@ Uses the standard library [`std::sort`](http://en.cppreference.com/w/cpp/algorit \* *`std::sort` is mandated by the standard to be O(n log n), but the libc++ implementation of the algorithm - despite non-trivial optimizations - [is still O(n²)](https://bugs.llvm.org/show_bug.cgi?id=20837). If you are using another standard library implementation then `std_sorter` should be O(n log n) for randon-access iterators, as expected.* -The adapter [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) has an explicit specialization for `std_sorter` which calls [`std::stable_sort`](http://en.cppreference.com/w/cpp/algorithm/stable_sort) instead. Its complexity depends on whether it can allocate additional memory or not. While the complexity guarantees are only partial in the standard, here is what's expected: +The adapter [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) has an explicit specialization for `std_sorter` which calls [`std::stable_sort`](https://en.cppreference.com/w/cpp/algorithm/stable_sort) instead. Its complexity depends on whether it can allocate additional memory or not. While the complexity guarantees are only partial in the standard, here is what's expected: | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | | 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`](http://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 the 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`. @@ -382,14 +388,18 @@ Implements a [vergesort](https://github.com/Morwenn/vergesort) algorithm backed | n | n log n | n log n log log n | n | No | Bidirectional | | n | n log n | n log n | log² n | No | Bidirectional | -Vergesort is a [*Runs*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness#runs) algorithm (including descending runs) as long as the size of those runs is greater than *n / log n*; when the runs are smaller, it falls back to another sorting algorithm to sort them (pdqsort for random-access iterators, QuickMergeSort otherwise). +Vergesort is a [*Runs*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness#runs) algorithm (including descending runs) as long as the size of those runs is greater than *n / log n*; when the runs are smaller, it falls back to another sorting algorithm to sort them (pdqsort for random-access iterators, QuickMergesort otherwise). Vergesort's complexity is bound either by its optimization layer or by the fallback sorter's complexity: * When it doesn't find big runs, the complexity is bound by the fallback sorter: depending on the category of iterators you can refer to the tables of either `pdq_sorter` or `quick_merge_sorter`. * When it does find big runs, vergesort's complexity is bound by the merging phase of its optimization layer. In such a case, `inplace_merge` is used to merge the runs: it will use additional memory if any is available, in which case vergesort is O(n log n). If there isn't much extra memory available, it may still require O(log n) extra memory (and thus raise an `std::bad_alloc` if there isn't that much memory available) in which case the complexity falls to O(n log n log log n). It should not happen that much, and the additional *log log n* factor is likely irrelevant for most real-world applications. +When wrapped into [`stable_adapter`][stable-adapter], it has a slightly different behaviour: it detects strictly descending runs instead of non-ascending ones, and wraps the fallback sorter with `stable_t`. This make the specialization stable, and faster than just using `make_stable`. + *Changed in version 1.6.0:* when sorting a collection made of bidirectional iterators, `verge_sorter` falls back to `quick_merge_sorter` instead of `quick_sorter`. +*New in version 1.9.0:* explicit specialization for `stable_adapter`. + ## Type-specific sorters The following sorters are available but will only work for some specific types instead of using a user-provided comparison function. Some of them also accept projections as long as the result of the projection can be handled by the sorter. @@ -400,7 +410,7 @@ The following sorters are available but will only work for some specific types i #include ``` -`counting_sorter` implements a simple [counting sort](https://en.wikipedia.org/wiki/Counting_sort). This sorter also supports reverse sorting with `std::greater<>`. +`counting_sorter` implements a simple [counting sort](https://en.wikipedia.org/wiki/Counting_sort). This sorter also supports reverse sorting with `std::greater<>` or `std::ranges::greater`. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -412,6 +422,8 @@ This sorter works with any type satisfying the trait `std::is_integral` (as well *Changed in version 1.6.0:* support for `[un]signed __int128`. +*Changed in version 1.9.0:* conditional support for [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). + ### `ska_sorter` ```cpp @@ -451,7 +463,7 @@ It comes into three main flavours (available individually if needed): * `integer_spread_sorter` works with any type satisfying the trait `std::is_integral`. * `float_spread_sorter` works with any type satisfying the trait `std::numeric_limits::is_iec559` whose size is the same as `std::uint32_t` or `std::uin64_t`. -* `string_spread_sorter` works with `std::string` and `std::wstring` (if `wchar_t` is 2 bytes). This sorter also supports reverse sorting with `std::greater<>`. In C++17 it also works with `std::string_view` and `std::wstring_view` (if `wchar_t` is 2 bytes). +* `string_spread_sorter` works with `std::string` and `std::wstring` (if `wchar_t` is 2 bytes). This sorter also supports reverse sorting with `std::greater<>` and `std::ranges::greater`. In C++17 it also works with `std::string_view` and `std::wstring_view` (if `wchar_t` is 2 bytes). These sorters accept projections as long as their simplest form can handle the result of the projection. The three of them are aggregated into one main sorter the following way: @@ -464,3 +476,11 @@ struct spread_sorter: > {}; ``` + +*Changed in version 1.9.0:* conditional support for [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). + + + [adaptive-quickselect]: https://arxiv.org/abs/1606.00484 + [introselect]: https://en.wikipedia.org/wiki/Introselect + [quick-mergesort]: https://arxiv.org/abs/1307.3033 + [selection-algorithm]: https://en.wikipedia.org/wiki/Selection_algorithm diff --git a/docs/Sorting-functions.md b/docs/Sorting-functions.md index 0783d54f..2e154216 100644 --- a/docs/Sorting-functions.md +++ b/docs/Sorting-functions.md @@ -40,7 +40,7 @@ auto sort(Iterator first, Iterator last, Compare compare, Projection projection) -> void; ``` -These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using [`cppsort::default_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter) to perform the sort. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). +These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using [`cppsort::default_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter) to perform the sort. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). ### Overload calling a user-provided sorter @@ -82,7 +82,7 @@ auto sort(const Sorter& sorter, Iterator first, Iterator last, -> decltype(auto); ``` -These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using the given sorter. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). The returned value corresponds to the value returned by the sorter's `operator()` when it is given the other parameters. +These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using the given sorter. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). The returned value corresponds to the value returned by the sorter's `operator()` when it is given the other parameters. Note that there is some heavy SFINAE wizardry happening to ensure that none of the `sort` overloads are ambiguous. This magic has been stripped from the documentation for clarity but may contribute to highly unreadable error messages. However, there is still some ambiguity left: the overload resolution might fail if `sort` is given an object that satisfies both the `Compare` and `Projection` concepts. This issue can we worked around with [`as_comparison` and `as_projection`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_comparison-and-as_projection) diff --git a/docs/Tooling.md b/docs/Tooling.md index 58d3c644..12d93cce 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -25,13 +25,20 @@ target_link_libraries(my-target PRIVATE cpp-sort::cpp-sort) ### Building cpp-sort The project's CMake files do offer some options, but they are mainly used to configure the test suite and the examples: -* `BUILD_TESTING`: whether to build the test suite, defaults to `ON`. -* `BUILD_EXAMPLES`: whether to build the examples, defaults to `OFF`. -* `ENABLE_COVERAGE`: whether to produce code coverage information when building the test suite, defaults to `OFF`. -* `USE_VALGRIND`: whether to run the test suite through Valgrind, defaults to `OFF`. +* `CPPSORT_BUILD_TESTING`: whether to build the test suite, defaults to `ON`. +* `CPPSORT_BUILD_EXAMPLES`: whether to build the examples, defaults to `OFF`. +* `CPPSORT_ENABLE_COVERAGE`: whether to produce code coverage information when building the test suite, defaults to `OFF`. +* `CPPSORT_USE_VALGRIND`: whether to run the test suite through Valgrind, defaults to `OFF`. +* `CPPSORT_SANITIZE`: values to pass to the `-fsanitize` falgs of compilers that supports them, default to empty. + +The same options exist without the `CPPSORT_` prefix exist, but are deprecated. For compatibility reasons, the options with the `CPPSORT_` prefix default the values of the equivalent unprefixed options. *New in version 1.6.0:* added the option `BUILD_EXAMPLES`. +*New in version 1.9.0:* options with the `CPPSORT_` prefix. + +***WARNING:** options without a `CPPSORT_` prefixed are deprecated in version 1.9.0 and removed in version 2.0.0.* + [Catch2][catch2] 2.6.0 or greater is required to build the tests: if a suitable version has been installed on the system it will be used, otherwise the latest Catch2 release will be downloaded. *Changed in version 1.7.0:* if a suitable Catch2 version is found on the system, it will be used. @@ -44,10 +51,10 @@ The project's CMake files do offer some options, but they are mainly used to con conan search cpp-sort --remote=conan-center ``` -And then install any version to your local cache as follows (here with version 1.5.1): +And then install any version to your local cache as follows (here with version 1.9.0): ```sh -conan install cpp-sort/1.5.1 +conan install cpp-sort/1.9.0 ``` The packages downloaded from conan-center are minimal and only contain the files required to use **cpp-sort** as a library: the headers, CMake files and licensing information. If you need anything else you have to use the source available in this GitHub repository. @@ -61,4 +68,4 @@ Alternatively you can find the packages on [Bintray][bintray], generated with th [catch2]: https://github.com/catchorg/Catch2 [cmake]: https://cmake.org/ [conan]: https://conan.io/ - [conan-center]: https://bintray.com/conan/conan-center \ No newline at end of file + [conan-center]: https://bintray.com/conan/conan-center diff --git a/docs/Writing-a-bubble_sorter.md b/docs/Writing-a-bubble_sorter.md index 833f2859..4df61e4e 100644 --- a/docs/Writing-a-bubble_sorter.md +++ b/docs/Writing-a-bubble_sorter.md @@ -163,7 +163,7 @@ auto bubble_sort(BidirectionalIterator first, BidirectionalIterator last, } ``` -Note that in C++17, it is preferred to use directly [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke) to call the comparison function instead of transforming it with `as_function`. +Note that in C++17, it is preferred to use directly [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke) to call the comparison function instead of transforming it with `as_function`. ## Using `bubble_sorter` with forward iterators @@ -261,7 +261,7 @@ struct bubble_sorter_impl We can see several improvements compared to the previous version: first of all, we added an optional projection parameter which defauts to [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). This is a function object that returns a value as is so that the default behaviour of the algorithm is to run *as if* projections didn't exist. It is very likely to be optimized aways by the compiler anyway. -The second modification is one I wish we could do without (yet another thing that concepts would make more expressive): [`is_projection_iterator_v`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_projection-and-is_projection_iterator) is a trait that checks whether a projection function can be used on a dereferenced iterator. It also optionally checks that a given comparison function can be called with the result of two such projections. This trait exists to ensure that `cppsort::sort` won't call the functor when these conditions are not satisfied, which may be crucial when aggregating sorters with [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter). +The second modification is one I wish we could do without (yet another thing that concepts would make more expressive): [`is_projection_iterator_v`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_projection-and-is_projection_iterator) is a trait that checks whether a projection function can be used on a dereferenced iterator. It also optionally checks that a given comparison function can be called with the result of two such projections. This trait exists to ensure that a sorter's `operator()` won't be called when these conditions are not satisfied, which may be crucial when aggregating sorters with [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter). Now that you know how to handle projections in your algorithm, here is the interesting part: you generally don't need to manually handle projections. The class template `sorter_facade` generates overloads of `operator()` taking projection functions that bake the projection into the comparison and forward that mix to the sorter implementation. In our implementation of `bubble_sort`, we always use the projection inside the comparison, so handling the projections by hand isn't giving us any optimization opportunity; we might as well implement just the comparison and add the small required SFINAE check: @@ -413,7 +413,7 @@ namespace inline constexpr auto&& bubble_sort = bubble_sorter{}; ``` -The utility [`static_const`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#static_const) is a variable template used to avoid ODR problem. Understanding the details is a bit tough; you can read [Eric Niebler's original article](http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) about this pattern if you want to learn more about it. Basically it is a poor man's substitute to compensate the lack of `inline` variables pre-C++17. +The utility [`static_const`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#static_const) is a variable template used to avoid ODR problem. Understanding the details is a bit tough; you can read [Eric Niebler's original article](https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) about this pattern if you want to learn more about it. Basically it is a poor man's substitute to compensate the lack of `inline` variables pre-C++17. ## Better error messages diff --git a/docs/Writing-a-container-aware-algorithm.md b/docs/Writing-a-container-aware-algorithm.md index 4cd64e7d..af612235 100644 --- a/docs/Writing-a-container-aware-algorithm.md +++ b/docs/Writing-a-container-aware-algorithm.md @@ -1,4 +1,4 @@ -Sometimes, iterators are not enough and you want to use the full abilities of containers to sort them, like O(1) insertion for [`std::list`](http://en.cppreference.com/w/cpp/container/list) and [`std::forward_list`](http://en.cppreference.com/w/cpp/container/forward_list). **cpp-sort** makes it possible to enhance sorters so that they can recognize specific containers and use a dedicated altered version of the sorting algorithm to sort the container thanks to [`container_aware_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#container_aware_adapter). +Sometimes, iterators are not enough and you want to use the full abilities of containers to sort them, like O(1) insertion for [`std::list`](https://en.cppreference.com/w/cpp/container/list) and [`std::forward_list`](https://en.cppreference.com/w/cpp/container/forward_list). **cpp-sort** makes it possible to enhance sorters so that they can recognize specific containers and use a dedicated altered version of the sorting algorithm to sort the container thanks to [`container_aware_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#container_aware_adapter). Let's get straight to example and enhance `selection_sort` for a custom list class to take advantage of the O(1) insertion. Here is the list implementation: diff --git a/docs/Writing-a-sorter.md b/docs/Writing-a-sorter.md index a24864e0..53d0aa91 100644 --- a/docs/Writing-a-sorter.md +++ b/docs/Writing-a-sorter.md @@ -55,7 +55,7 @@ We just wrote what we call a *sorter implementation* and wrapped it into [`sorte Now, let's define a set of rules to apply when writing sorters. These rules don't *have* to be enforced, but enforcing them will ensure that a sorter will work smoothly with most tool available in this library. In this tutorial, every section will define a small set of rules instead of defining all of them at once without introducing the relevant concepts first. Fortunately, the simpler the sorter, the simpler the rules. -**Rule 1.1:** for any *sorter*, [`std::is_sorted`](http://en.cppreference.com/w/cpp/algorithm/is_sorted) called without a comparison function on the resulting range shall return `true` (note that this is not exactly true: floating point numbers are an example of types that will almost always cause problems). +**Rule 1.1:** for any *sorter*, [`std::is_sorted`](https://en.cppreference.com/w/cpp/algorithm/is_sorted) called without a comparison function on the resulting range shall return `true` (note that this is not exactly true: floating point numbers are an example of types that will almost always cause problems). **Rule 1.2:** a *sorter* shall be callable with either a pair of iterators or an iterable. @@ -67,7 +67,7 @@ Now, let's define a set of rules to apply when writing sorters. These rules don' ## Category of iterators -When writing a sorter, one of the most important things to consider is the [category of iterators](http://en.cppreference.com/w/cpp/iterator) it is meant to work with. It directly influences the kinds of collections that the sorter will be able to sort. Sorters implement in-place sorting algorithms, therefore they can only sort forward iterators or more specific types. **cpp-sort** does more than document the sorter category a sorter is supposed to work with: it actually embeds the information directly into the *sorter implementation* itself. If we take the `selection_sorter` from the previous section, we can document its properties as follows: +When writing a sorter, one of the most important things to consider is the [category of iterators](https://en.cppreference.com/w/cpp/iterator) it is meant to work with. It directly influences the kinds of collections that the sorter will be able to sort. Sorters implement in-place sorting algorithms, therefore they can only sort forward iterators or more specific types. **cpp-sort** does more than document the sorter category a sorter is supposed to work with: it actually embeds the information directly into the *sorter implementation* itself. If we take the `selection_sorter` from the previous section, we can document its properties as follows: ```cpp struct selection_sorter_impl @@ -83,7 +83,7 @@ struct selection_sorter_impl }; ``` -The standard library's [iterator tags](http://en.cppreference.com/w/cpp/iterator/iterator_tags) are used to document the iterator category supported by the sorter (stability is also documented but we'll come back to that later). It is a bit useful for error messages, but some other tools from the library rely of this information. For example [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) can take several sorters with different iterator categories and generate a new sorter that will call the appropriate sorter depending on the iterator category of the passed collection: +The standard library's [iterator tags](https://en.cppreference.com/w/cpp/iterator/iterator_tags) are used to document the iterator category supported by the sorter (stability is also documented but we'll come back to that later). It is a bit useful for error messages, but some other tools from the library rely of this information. For example [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) can take several sorters with different iterator categories and generate a new sorter that will call the appropriate sorter depending on the iterator category of the passed collection: ```cpp using sorter = cppsort::hybrid_adapter< @@ -121,7 +121,7 @@ As you can see, the iterator category supported by a given sorter is not only th ## Comparison sorters -Most sorting algorithms are [comparison sorts](https://en.wikipedia.org/wiki/Comparison_sort). It means that, to sort the elements of a collection, they repeatedly use a comparison function that returns whether two elements are already in order. The standard library's [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort) implicitly uses an ADL-found `operator<` to compare two elements, but it also provides an overload which takes a user-provided comparison function to compare two elements. **cpp-sort** loosely follows this design (it defaults to `std::less<>` instead of `operator<`) and allows its sorters to take an additional parameter for user-provided comparison functions. Let's write a *sorter implementation* to wrap the three-parameter overload of `std::sort`: +Most sorting algorithms are [comparison sorts](https://en.wikipedia.org/wiki/Comparison_sort). It means that, to sort the elements of a collection, they repeatedly use a comparison function that returns whether two elements are already in order. The standard library's [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) implicitly uses an ADL-found `operator<` to compare two elements, but it also provides an overload which takes a user-provided comparison function to compare two elements. **cpp-sort** loosely follows this design (it defaults to `std::less<>` instead of `operator<`) and allows its sorters to take an additional parameter for user-provided comparison functions. Let's write a *sorter implementation* to wrap the three-parameter overload of `std::sort`: ```cpp struct std_sorter_impl @@ -147,13 +147,13 @@ struct std_sorter: {}; ``` -Compared to the previous `selection_sorter_impl`, the only things we had to add was a template parameter defaulted to [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and a default-contructed (when not provided) parameter to `operator()`. As usual, [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) generates a bunch of additional features: it still adds the overload of `operator()` taking a single iterable, but also adds an overload taking an iterable and a comparison function. Basically, it ensures that you always only have to provide a single overload of `operator()`, and generates all the other ones as well as all the corresponding conversions to function pointers. +Compared to the previous `selection_sorter_impl`, the only things we had to add was a template parameter defaulted to [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and a default-contructed (when not provided) parameter to `operator()`. As usual, [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) generates a bunch of additional features: it still adds the overload of `operator()` taking a single iterable, but also adds an overload taking an iterable and a comparison function. Basically, it ensures that you always only have to provide a single overload of `operator()`, and generates all the other ones as well as all the corresponding conversions to function pointers. -This kind of comparison sorters help to compare things that don't have an overloaded `operator<` or to compare things differently. For example, passing [`std::greater<>`](http://en.cppreference.com/w/cpp/utility/functional/greater_void) to a sorter instead of `std::less<>` sorts a collection in descending order: +This kind of comparison sorters help to compare things that don't have an overloaded `operator<` or to compare things differently. For example, passing [`std::greater<>`](https://en.cppreference.com/w/cpp/utility/functional/greater_void) to a sorter instead of `std::less<>` sorts a collection in descending order: ```cpp // Sort collection in reverse order with std::sort -cppsort::sort(collection, std_sorter{}, std::greater<>{}); +cppsort::std_sort(collection, std::greater<>{}); ``` It is worth noting that every *comparison sorter* provided by the library transforms the comparison parameter with [`utility::as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function) before actually using it. It allows to use pointers to member functions of the `lhs.compare_to(rhs)` kind out-of-the-box. @@ -162,7 +162,7 @@ The rules for *comparison sorters* are but an extension to the rules defined for **Rule 3.1:** a *comparison sorter* is also a *sorter*, which means that it shall be called without a comparison function and shall obey all the rules defined for regular *sorters*. -**Rule 3.2:** for any *comparison sorter* called with a specific comparison function, [`std::is_sorted`](http://en.cppreference.com/w/cpp/algorithm/is_sorted) called with the same comparison function on the resulting collection shall return `true`. +**Rule 3.2:** for any *comparison sorter* called with a specific comparison function, [`std::is_sorted`](https://en.cppreference.com/w/cpp/algorithm/is_sorted) called with the same comparison function on the resulting collection shall return `true`. **Rule 3.3:** calling a *comparison sorter* with `std::less<>` or without a comparison function shall be strictly equivalent: calling `std::is_sorted` without a comparison function on the resulting collection shall return `true`. @@ -172,7 +172,7 @@ The rules for *comparison sorters* are but an extension to the rules defined for ## Handling projections -The [Ranges TS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4560.pdf) introduces the notion of callable *projections*, borrowed from the [Adobe Source Libraries](http://stlab.adobe.com/). A projection is a callable object that can be passed to an algorithm so that it "views" the values to be compared differently. For example, [`std::negate<>`](http://en.cppreference.com/w/cpp/utility/functional/negate_void) could be used to sort a collection of integers in descending order. Let's assume that our `selection_sort` algorithm from a while has been given a fourth parameter to handle projections; here is the corresponding *sorter implementation*: +The [Ranges TS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4560.pdf) introduces the notion of callable *projections*, borrowed from the [Adobe Source Libraries](https://stlab.adobe.com/). A projection is a callable object that can be passed to an algorithm so that it "views" the values to be compared differently. For example, [`std::negate<>`](https://en.cppreference.com/w/cpp/utility/functional/negate_void) could be used to sort a collection of integers in descending order. Let's assume that our `selection_sort` algorithm from a while has been given a fourth parameter to handle projections; here is the corresponding *sorter implementation*: ```cpp struct selection_sorter_impl @@ -207,7 +207,7 @@ Note that most of the algorithms (actually, every *projection sorter* provided b ```cpp struct wrapper { int value; } std::vector vec = { {5}, {9}, {6}, {1}, {2}, {8}, {3}, {0}, {7}, {4} }; -cppsort::sort(vec, selection_sorter{}, &wrapper::value); +cppsort::selection_sort(vec, &wrapper::value); ``` Thanks to that small trick, the `selection_sorter` will sort `vec`, using the member data `wrapper::value` instead of a full `wrapper` instance (which cannot be compared) to perform the comparisons on. @@ -245,7 +245,7 @@ The general rules for *projection sorters* are really close to the ones for *com **Rule 4.2:** a sorter can be both a *comparison sorter* and a *projection sorter* at once. Such a sorter shall also obey all the rules defined for *sorters*, for *comparison sorters* and for *projection sorters*. -**Rule 4.3:** for any *projection sorter* called with a specific projection function, [`std::is_sorted`](http://en.cppreference.com/w/cpp/algorithm/is_sorted) called with `std::less<>` and the same projection function on the resulting collection shall return `true`. If the *projection sorter* is also a *comparison sorter*, for any such sorter called with a specific pair of comparison and projection function, `std::is_sorted` called with the same pair of functions on the resulting collection shall return `true`. +**Rule 4.3:** for any *projection sorter* called with a specific projection function, [`std::is_sorted`](https://en.cppreference.com/w/cpp/algorithm/is_sorted) called with `std::less<>` and the same projection function on the resulting collection shall return `true`. If the *projection sorter* is also a *comparison sorter*, for any such sorter called with a specific pair of comparison and projection function, `std::is_sorted` called with the same pair of functions on the resulting collection shall return `true`. **Rule 4.4:** calling a *projection sorter* with [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) or without a projection function shall be strictly equivalent: calling `std::is_sorted` without a comparison function and without a projection function on the resulting collection shall return `true`. If the *projection sorter* is also a *comparison sorter*, calling such a sorter with any valid combination of `std::less<>` and `utility::identity`, and calling it without any additional function should be strictly equivalent. @@ -307,7 +307,7 @@ struct counting_sorter_impl }; ``` -With such an implementation, this sorter satisfies the *comparison sorter* concept when given an instance of `std::greater<>` without breaking any of the rules defined in the previous sections. Now it may seem a bit unfair for `std::less<>`... but actually [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) automagically generates several `operator()` overloads taking `std::less<>` when the provided *sorter implementation* doesn't handle it natively. Note that even though this section is about non-comparison sorters, the same applies to non-projection sorters (you could provide a specific overload for [`std::negate<>`](http://en.cppreference.com/w/cpp/utility/functional/negate_void) for a descending sort too), and `sorter_facade` would provide equivalent `operator()` overloads taking [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) for *sorter implementations* that cannot handle it natively). +With such an implementation, this sorter satisfies the *comparison sorter* concept when given an instance of `std::greater<>` without breaking any of the rules defined in the previous sections. Now it may seem a bit unfair for `std::less<>`... but actually [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) automagically generates several `operator()` overloads taking `std::less<>` when the provided *sorter implementation* doesn't handle it natively. Note that even though this section is about non-comparison sorters, the same applies to non-projection sorters (you could provide a specific overload for [`std::negate<>`](https://en.cppreference.com/w/cpp/utility/functional/negate_void) for a descending sort too), and `sorter_facade` would provide equivalent `operator()` overloads taking [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) for *sorter implementations* that cannot handle it natively). The most beautiful thing in my opinion is that no new rule is needed to support that model. All the rules previously defined guarantee that these specific overloads using standard function object as tags work. The only advice I can give is to try to use the most standard function objects as tags, or at least the ones that are the most likely to be used for the specific task. Since **cpp-sort** is heavily based on modern C++ features, it is designed to only work with the `void` specializations of the standard function objects from ``. @@ -368,7 +368,7 @@ While type-specific sorters are, by their very nature, unable to generically han ## Stability -A sorting algorithm is said to be [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) if it preserves the relative order of equivalent elements. **cpp-sort** documents the stability of every sorter by giving them an `is_always_stable` type aliasing a boolean specialization of `std::integer_constant`. This information should be accessed via [`sorter_traits`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#sorter_traits) or via the more specific [`is_always_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_always_stable) type alias. The stability of a sorter is always either [`std::true_type`](http://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type`. +A sorting algorithm is said to be [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) if it preserves the relative order of equivalent elements. **cpp-sort** documents the stability of every sorter by giving them an `is_always_stable` type aliasing a boolean specialization of `std::integer_constant`. This information should be accessed via [`sorter_traits`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#sorter_traits) or via the more specific [`is_always_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_always_stable) type alias. The stability of a sorter is always either [`std::true_type`](https://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type`. ```cpp using stability = cppsort::is_always_stable; @@ -533,7 +533,7 @@ namespace cppsort If `domain` does not exist in the `fixed_sorter_traits` specialization, it means that every specialization of the fixed-size sorter is a valid sorter. -Using the specializations of a fixed-size sorter by hand is not the sweetest thing either; that is why the library provides the fixed-size sorter adapter [`small_array_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#small_array_adapter) which takes a whole *fixed-size sorter* and almost transforms it into a sorter (the resulting class doesn't handle pairs of iterators). The resulting object, when given an instance of [`std::array`](http://en.cppreference.com/w/cpp/container/array) or a fixed-size C array, will sort it in-place with the specialization corresponding to the size of the passed array. To transform that into a full sorter able to handle anything, one needs to aggregate it with another sorter into a `hybrid_adapter`: +Using the specializations of a fixed-size sorter by hand is not the sweetest thing either; that is why the library provides the fixed-size sorter adapter [`small_array_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#small_array_adapter) which takes a whole *fixed-size sorter* and almost transforms it into a sorter (the resulting class doesn't handle pairs of iterators). The resulting object, when given an instance of [`std::array`](https://en.cppreference.com/w/cpp/container/array) or a fixed-size C array, will sort it in-place with the specialization corresponding to the size of the passed array. To transform that into a full sorter able to handle anything, one needs to aggregate it with another sorter into a `hybrid_adapter`: ```cpp using sorter = cppsort::hybrid_adapter< diff --git a/include/cpp-sort/adapters/container_aware_adapter.h b/include/cpp-sort/adapters/container_aware_adapter.h index 1412cb58..a3bf4939 100644 --- a/include/cpp-sort/adapters/container_aware_adapter.h +++ b/include/cpp-sort/adapters/container_aware_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_CONTAINER_AWARE_ADAPTER_H_ @@ -11,17 +11,17 @@ #include #include #include +#include #include #include #include -#include "../detail/projection_compare.h" #include "../detail/type_traits.h" namespace cppsort { namespace detail { - // Hide the generic cppsort::sort + // Hide potential out-of-scope sort() struct nope_type {}; template auto sort(Args&&...) @@ -221,20 +221,18 @@ namespace cppsort detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare, Projection> + projection_compare, Projection> >::value, conditional_t< Stability, std::false_type, decltype(detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::less<>{}, - std::move(projection)))) + make_projection_compare(std::less<>{}, std::move(projection)))) > > { return detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::less<>{}, - std::move(projection))); + make_projection_compare(std::less<>{}, std::move(projection))); } template< @@ -250,7 +248,7 @@ namespace cppsort not detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare, Projection> + projection_compare, Projection> >::value, conditional_t< Stability, @@ -295,20 +293,18 @@ namespace cppsort detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare + projection_compare >::value, conditional_t< Stability, std::false_type, decltype(detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::move(compare), - std::move(projection)))) + make_projection_compare(std::move(compare), std::move(projection)))) > > { return detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::move(compare), - std::move(projection))); + make_projection_compare(std::move(compare), std::move(projection))); } template< @@ -323,7 +319,7 @@ namespace cppsort not detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare + projection_compare >::value, conditional_t< Stability, diff --git a/include/cpp-sort/adapters/counting_adapter.h b/include/cpp-sort/adapters/counting_adapter.h index 92c07b08..6107f637 100644 --- a/include/cpp-sort/adapters/counting_adapter.h +++ b/include/cpp-sort/adapters/counting_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_COUNTING_ADAPTER_H_ @@ -77,13 +77,12 @@ namespace cppsort is_projection_v > > - auto operator()(Iterable&& iterable, Compare compare={}, - Projection projection={}) const + auto operator()(Iterable&& iterable, Compare compare, Projection projection) const -> CountType { CountType count(0); comparison_counter cmp(std::move(compare), count); - this->get()(std::forward(iterable), std::move(cmp), projection); + this->get()(std::forward(iterable), std::move(cmp), std::move(projection)); return count; } diff --git a/include/cpp-sort/adapters/schwartz_adapter.h b/include/cpp-sort/adapters/schwartz_adapter.h index 698cc396..9f4eff59 100644 --- a/include/cpp-sort/adapters/schwartz_adapter.h +++ b/include/cpp-sort/adapters/schwartz_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_SCHWARTZ_ADAPTER_H_ @@ -18,9 +18,12 @@ #include #include #include +#include +#include #include #include "../detail/associate_iterator.h" #include "../detail/checkers.h" +#include "../detail/config.h" #include "../detail/iterator_traits.h" #include "../detail/memory.h" #include "../detail/type_traits.h" @@ -39,7 +42,18 @@ namespace cppsort return (std::forward(value).data); } }; + } + namespace utility + { + template + struct is_probably_branchless_projection: + std::true_type + {}; + } + + namespace detail + { //////////////////////////////////////////////////////////// // Algorithm proper @@ -176,6 +190,25 @@ namespace cppsort // utility::identity does nothing, bypass schartz_adapter entirely return this->get()(std::move(first), std::move(last), std::move(compare), projection); } + +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto operator()(ForwardIterable&& iterable, Compare compare, std::identity projection) const + -> decltype(this->get()(std::forward(iterable), std::move(compare), projection)) + { + // std::identity does nothing, bypass schartz_adapter entirely + return this->get()(std::forward(iterable), std::move(compare), projection); + } + + template + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare, std::identity projection) const + -> decltype(this->get()(std::move(first), std::move(last), std::move(compare), projection)) + { + // std::identity does nothing, bypass schartz_adapter entirely + return this->get()(std::move(first), std::move(last), std::move(compare), projection); + } +#endif }; } diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index d84fc827..a140b002 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -24,6 +24,7 @@ #include "../detail/checkers.h" #include "../detail/iterator_traits.h" #include "../detail/memory.h" +#include "../detail/sized_iterator.h" namespace cppsort { @@ -132,8 +133,25 @@ namespace cppsort ); } + template< + typename ForwardIterator, + typename Compare, + typename Projection, + typename Sorter + > + auto make_stable_and_sort(sized_iterator first, difference_type_t size, + Compare&& compare, Projection&& projection, Sorter&& sorter) + -> decltype(auto) + { + // Hack to get the stable bidirectional version of vergesort + // to work correctly without duplicating tons of code + return make_stable_and_sort(first.base(), size, + std::move(compare), std::move(projection), + std::move(sorter)); + } + //////////////////////////////////////////////////////////// - // Adapter + // make_stable_impl template struct make_stable_impl: @@ -175,7 +193,8 @@ namespace cppsort Compare compare={}, Projection projection={}) const -> decltype(auto) { - return make_stable_and_sort(first, std::distance(first, last), + using std::distance; // Hack for sized_iterator + return make_stable_and_sort(first, distance(first, last), std::move(compare), std::move(projection), this->get()); } @@ -187,6 +206,9 @@ namespace cppsort }; } + //////////////////////////////////////////////////////////// + // make_stable + // Expose the underlying mechanism template struct make_stable: @@ -199,7 +221,9 @@ namespace cppsort {} }; - // Actual sorter + //////////////////////////////////////////////////////////// + // stable_adapter + template struct stable_adapter: utility::adapter_storage, @@ -240,7 +264,57 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; + using type = stable_adapter; }; + + // Accidental nesting can happen, unwrap + template + struct stable_adapter>: + stable_adapter + { + using type = stable_adapter; + }; + + //////////////////////////////////////////////////////////// + // stable_t + + namespace detail + { + template + struct stable_t_impl_false + { + // The sorter is not always stable and does not have + // a type member named 'type' + using type = stable_adapter; + }; + + template + struct stable_t_impl_false::type>> + { + // The sorter is not always stable but has a type member + // called 'type', use that one + using type = typename stable_adapter::type; + }; + + template + struct stable_t_impl + { + // The sorter is always stable, alias it directly + using type = Sorter; + }; + + template + struct stable_t_impl + { + using type = typename stable_t_impl_false::type; + }; + } + + template + using stable_t = typename detail::stable_t_impl< + Sorter, + cppsort::is_always_stable_v + >::type; } #ifdef CPPSORT_ADAPTERS_HYBRID_ADAPTER_DONE_ diff --git a/include/cpp-sort/adapters/verge_adapter.h b/include/cpp-sort/adapters/verge_adapter.h index 171a9c6f..3fe6871d 100644 --- a/include/cpp-sort/adapters/verge_adapter.h +++ b/include/cpp-sort/adapters/verge_adapter.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -25,7 +26,7 @@ namespace cppsort namespace detail { - template + template struct verge_adapter_impl: utility::adapter_storage { @@ -55,27 +56,38 @@ namespace cppsort "verge_adapter requires at least random-access iterators" ); - vergesort(std::move(first), std::move(last), last - first, - std::move(compare), std::move(projection), - this->get()); + verge::sort(std::move(first), std::move(last), last - first, + std::move(compare), std::move(projection), + this->get()); } //////////////////////////////////////////////////////////// // Sorter traits using iterator_category = std::random_access_iterator_tag; - using is_always_stable = std::false_type; + using is_always_stable = std::integral_constant; }; } template struct verge_adapter: - sorter_facade> + sorter_facade> { verge_adapter() = default; constexpr explicit verge_adapter(FallbackSorter sorter): - sorter_facade>(std::move(sorter)) + sorter_facade>(std::move(sorter)) + {} + }; + + template + struct stable_adapter>: + sorter_facade> + { + stable_adapter() = default; + + constexpr explicit stable_adapter(verge_adapter sorter): + sorter_facade>(std::move(sorter).get()) {} }; } diff --git a/include/cpp-sort/comparators/projection_compare.h b/include/cpp-sort/comparators/projection_compare.h new file mode 100644 index 00000000..e2e6fdde --- /dev/null +++ b/include/cpp-sort/comparators/projection_compare.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016-2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_COMPARATORS_PROJECTION_COMPARE_H_ +#define CPPSORT_COMPARATORS_PROJECTION_COMPARE_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include "../detail/type_traits.h" + +namespace cppsort +{ + template + class projection_compare + { + private: + + using compare_t = detail::remove_cvref_t< + decltype(utility::as_function(std::declval())) + >; + using projection_t = detail::remove_cvref_t< + decltype(utility::as_function(std::declval())) + >; + std::tuple data; + + public: + + projection_compare(Compare compare, Projection projection): + data(utility::as_function(compare), utility::as_function(projection)) + {} + + template + constexpr auto operator()(T&& lhs, U&& rhs) + noexcept(noexcept(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))))) + -> decltype(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs)))) + { + return std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))); + } + + template + constexpr auto operator()(T&& lhs, U&& rhs) const + noexcept(noexcept(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))))) + -> decltype(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs)))) + { + return std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))); + } + + using is_transparent = void; + }; + + template + auto make_projection_compare(Compare compare, Projection projection) + -> projection_compare + { + return { std::move(compare), std::move(projection) }; + } + + namespace utility + { + template + struct is_probably_branchless_comparison, T>: + cppsort::detail::conjunction< + is_probably_branchless_projection, + is_probably_branchless_comparison< + Compare, + cppsort::detail::invoke_result_t + > + > + {}; + } +} + +#endif // CPPSORT_COMPARATORS_PROJECTION_COMPARE_H_ diff --git a/include/cpp-sort/detail/adaptive_quickselect.h b/include/cpp-sort/detail/adaptive_quickselect.h new file mode 100644 index 00000000..1736c337 --- /dev/null +++ b/include/cpp-sort/detail/adaptive_quickselect.h @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ + +/* Copyright Andrei Alexandrescu, 2016-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ +/* Copyright Danila Kutenin, 2020-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ +#ifndef CPPSORT_DETAIL_QUICKSELECT_ADAPTIVE_H_ +#define CPPSORT_DETAIL_QUICKSELECT_ADAPTIVE_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "type_traits.h" + +namespace cppsort +{ +namespace detail +{ + namespace median_common_detail + { + template + auto pivot_partition(RandomAccessIterator r, difference_type_t k, + difference_type_t length, + Compare compare, Projection projection) + -> RandomAccessIterator + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(k < length); + iter_swap(r, r + k); + difference_type_t lo = 1, hi = length - 1; + for (;; ++lo, --hi) { + for (;; ++lo) { + if (lo > hi) { + goto loop_done; + } + if (not comp(proj(r[lo]), proj(*r))) break; + } + // found the left bound: r[lo] >= r[0] + CPPSORT_ASSERT(lo <= hi); + for (; comp(proj(*r), proj(r[hi])) ; --hi) {} + if (lo >= hi) break; + // found the right bound: r[hi] <= r[0], swap & make progress + iter_swap(r + lo, r + hi); + } + loop_done: + --lo; + iter_swap(r + lo, r); + return r + lo; + } + + template + auto median_index(RandomAccessIterator r, difference_type_t a, + difference_type_t b, difference_type_t c, + Compare compare, Projection projection) + -> difference_type_t + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (comp(proj(r[c]), proj(r[a]))) { + std::swap(a, c); + } + if (comp(proj(r[c]), proj(r[b]))) { + return c; + } + if (comp(proj(r[b]), proj(r[a]))) { + return a; + } + return b; + } + + template + auto median_index(RandomAccessIterator r, + difference_type_t a, difference_type_t b, + difference_type_t c, difference_type_t d, + Compare compare, Projection projection) + -> difference_type_t + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (comp(proj(r[d]), proj(r[c]))) { + std::swap(c, d); + } + + if (LeanRight) { + if (comp(proj(r[c]), proj(r[a]))) { + CPPSORT_ASSERT(comp(proj(r[c]), proj(r[a])) && not comp(proj(r[d]), proj(r[c]))); // so r[c]) is out + return median_index(r, a, b, d, std::move(compare), std::move(projection)); + } + } else { + if (not comp(proj(r[d]), proj(r[a]))) { + return median_index(r, a, b, c, std::move(compare), std::move(projection)); + } + } + + // Could return median_index(r, b, c, d) but we already know r[c] <= r[d] + if (not comp(proj(r[c]), proj(r[b]))) { + return c; + } + if (comp(proj(r[d]), proj(r[b]))) { + return d; + } + return b; + } + + template + auto ninther(RandomAccessIterator r, difference_type_t _1, difference_type_t _2, + difference_type_t _3, difference_type_t _4, + difference_type_t _5, difference_type_t _6, + difference_type_t _7, difference_type_t _8, + difference_type_t _9, Compare compare, Projection projection) + -> void + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + _2 = median_index(r, _1, _2, _3, compare, projection); + _8 = median_index(r, _7, _8, _9, compare, projection); + if (comp(proj(r[_8]), proj(r[_2]))) { + std::swap(_2, _8); + } + if (comp(proj(r[_6]), proj(r[_4]))) { + std::swap(_4, _6); + } + + // Here we know that r[_2] and r[_8] are the other two medians and that + // r[_2] <= r[_8]. We also know that r[_4] <= r[_6] + if (comp(proj(r[_5]), proj(r[_4]))) { + // r[_4] is the median of r[_4], r[_5], r[_6] + } else if (comp(proj(r[_6]), proj(r[_5]))) { + // r[_6] is the median of r[_4], r[_5], r[_6] + _4 = _6; + } else { + // Here we know r[_5] is the median of r[_4], r[_5], r[_6] + if (comp(proj(r[_5]), proj(r[_2]))) { + iter_swap(r + _5, r + _2); + return; + } + if (comp(proj(r[_8]), proj(r[_5]))) { + iter_swap(r + _5, r + _8); + return; + } + // This is the only path that returns with no swap + return; + } + + // Here we know r[_4] is the median of r[_4], r[_5], r[_6] + if (comp(proj(r[_4]), proj(r[_2]))) { + _4 = _2; + } else if (comp(proj(r[_8]), proj(r[_4]))) { + _4 = _8; + } + iter_swap(r + _5, r + _4); + } + + template + auto expand_partition_left(RandomAccessIterator r, difference_type_t lo, + difference_type_t pivot, + Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(lo > 0 && lo <= pivot); + difference_type_t left = 0; + const auto oldPivot = pivot; + for (; lo < pivot ; ++left) { + if (left == lo) { + goto done; + } + if (not comp(proj(r[oldPivot]), proj(r[left]))) continue; + --pivot; + iter_swap(r + left, r + pivot); + } + + // Second loop: make left and pivot meet + for (;; ++left) { + if (left == pivot) break; + if (not comp(proj(r[oldPivot]), proj(r[left]))) continue; + for (;;) { + if (left == pivot) { + goto done; + } + --pivot; + if (comp(proj(r[pivot]), proj(r[oldPivot]))) { + iter_swap(r + left, r + pivot); + break; + } + } + } + + done: + iter_swap(r + oldPivot, r + pivot); + return pivot; + } + + template + auto expand_partition_right(RandomAccessIterator r, difference_type_t hi, + difference_type_t rite, + Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + difference_type_t pivot = 0; + CPPSORT_ASSERT(pivot <= hi); + CPPSORT_ASSERT(hi <= rite); + + // First loop: spend r[pivot .. hi] + for (; pivot < hi ; --rite) { + if (rite == hi) { + goto done; + } + if (not comp(proj(r[rite]), proj(r[0]))) continue; + ++pivot; + iter_swap(r + rite, r + pivot); + } + + // Second loop: make left and pivot meet + for (; rite > pivot ; --rite) { + if (not comp(proj(r[rite]), proj(r[0]))) continue; + while (rite > pivot) { + ++pivot; + if (comp(proj(r[0]), proj(r[pivot]))) { + iter_swap(r + rite, r + pivot); + break; + } + } + } + + done: + iter_swap(r, r + pivot); + return pivot; + } + + template + auto expand_partition(RandomAccessIterator r, difference_type_t lo, + difference_type_t pivot, difference_type_t hi, + difference_type_t length, Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(lo <= pivot && pivot < hi && hi <= length); + --hi; + --length; + + for (difference_type_t left = 0 ; ; ++left, --length) { + for (;; ++left) { + if (left == lo) { + return pivot + expand_partition_right(r + pivot, hi - pivot, length - pivot, + std::move(compare), std::move(projection)); + } + if (comp(proj(r[pivot]), proj(r[left]))) break; + } + for (;; --length) { + if (length == hi) { + return left + expand_partition_left(r + left, lo - left, pivot - left, + std::move(compare), std::move(projection)); + } + if (not comp(proj(r[pivot]), proj(r[length]))) break; + } + iter_swap(r + left, r + length); + } + } + } + + template + auto adaptive_quickselect(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> void; + + /* + Median of minima + */ + template + auto median_of_minima(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> difference_type_t + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(length >= 2); + CPPSORT_ASSERT(n <= length / 6); + CPPSORT_ASSERT(n > 0); + + auto subset = n * 2; + auto computeMinOver = (length - subset) / subset; + CPPSORT_ASSERT(computeMinOver > 0); + + for (difference_type_t i = 0, j = subset ; i < subset ; ++i) { + auto limit = j + computeMinOver; + auto minIndex = j; + while (++j < limit) { + if (comp(proj(r[j]), proj(r[minIndex]))) { + minIndex = j; + } + } + if (comp(proj(r[minIndex]), proj(r[i]))) { + using utility::iter_swap; + iter_swap(r + i, r + minIndex); + } + CPPSORT_ASSERT(j < length || i + 1 == subset); + } + + adaptive_quickselect(r, n, subset, compare, projection); + return median_common_detail::expand_partition(r, 0, n, subset, length, + std::move(compare), std::move(projection)); + } + + /* + Median of maxima + */ + template + auto median_of_maxima(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(length >= 2); + CPPSORT_ASSERT(n < length && n / 5 >= length - n); + + auto subset = (length - n) * 2; + auto subsetStart = length - subset; + auto computeMaxOver = subsetStart / subset; + CPPSORT_ASSERT(computeMaxOver > 0); + + for (auto i = subsetStart, j = i - subset * computeMaxOver ; i < length ; ++i) { + auto limit = j + computeMaxOver; + auto maxIndex = j; + while (++j < limit) { + if (comp(proj(r[maxIndex]), proj(r[j]))) { + maxIndex = j; + } + } + if (comp(proj(r[i]), proj(r[maxIndex]))) { + iter_swap(r + i, r + maxIndex); + } + CPPSORT_ASSERT(j != 0 || i + 1 == length); + } + + adaptive_quickselect(r + subsetStart, length - n, subset, compare, projection); + return median_common_detail::expand_partition(r, subsetStart, n, length, length, + std::move(compare), std::move(projection)); + } + + /* + Partitions r[0 .. length] using a pivot of its own choosing. Attempts to pick a + pivot that approximates the median. Returns the position of the pivot. + */ + template + auto median_of_ninthers(RandomAccessIterator r, difference_type_t length, + Compare compare, Projection projection) + -> difference_type_t + { + CPPSORT_ASSERT(length >= 12); + auto frac = length <= 1024 ? length / 12 + : length <= 128 * 1024 ? length / 64 + : length / 1024; + auto pivot = frac / 2; + auto lo = length / 2 - pivot; + auto hi = lo + frac; + CPPSORT_ASSERT(lo >= frac * 4); + CPPSORT_ASSERT(length - hi >= frac * 4); + CPPSORT_ASSERT(lo / 2 >= pivot); + + auto gap = (length - 9 * frac) / 4; + auto a = lo - 4 * frac - gap; + auto b = hi + gap; + for (auto i = lo ; i < hi ; ++i, a += 3, b += 3) { + median_common_detail::ninther(r, + a, i - frac, b, + a + 1, i, b + 1, + a + 2, i + frac, b + 2, + compare, projection + ); + } + + adaptive_quickselect(r + lo, pivot, frac, compare, projection); + return median_common_detail::expand_partition( + r, lo, lo + pivot, hi, length, + std::move(compare), std::move(projection) + ); + } + + /* + Quickselect driver for median_of_ninthers, median_of_minima, and + median_of_maxima. Dispathes to each depending on the relationship between n (the + sought order statistics) and length. + */ + template + auto adaptive_quickselect(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> void + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(n < length); + for (;;) { + // Decide strategy for partitioning + if (n == 0) { + // That would be the max + difference_type_t pivot = n; + for (++n ; n < length ; ++n) { + if (comp(proj(r[n]), proj(r[pivot]))) { + pivot = n; + } + } + iter_swap(r, r + pivot); + return; + } + + if (n + 1 == length) { + // That would be the min + difference_type_t pivot = 0; + for (n = 1 ; n < length ; ++n) { + if (comp(proj(r[pivot]), proj(r[n]))) { + pivot = n; + } + } + iter_swap(r + pivot, r + (length - 1)); + return; + } + + CPPSORT_ASSERT(n < length); + difference_type_t pivot; + if (length <= 16) { + pivot = median_common_detail::pivot_partition(r, n, length, compare, projection) - r; + } else if (n <= length / 6) { + pivot = median_of_minima(r, n, length, compare, projection); + } else if (n / 5 >= length - n) { + pivot = median_of_maxima(r, n, length, compare, projection); + } else { + pivot = median_of_ninthers(r, length, compare, projection); + } + + // See how the pivot fares + if (pivot == n) return; + if (pivot > n) { + length = pivot; + } else { + ++pivot; + r += pivot; + length -= pivot; + n -= pivot; + } + } + } + + template + auto median_of_ninthers_select(RandomAccessIterator begin, RandomAccessIterator mid, RandomAccessIterator end, + Compare compare, Projection projection) + -> RandomAccessIterator + { + if (mid != end) { + adaptive_quickselect(begin, mid - begin, end - begin, compare, projection); + } + return mid; + } +}} + +#endif // CPPSORT_DETAIL_QUICKSELECT_ADAPTIVE_H_ diff --git a/include/cpp-sort/detail/block_sort.h b/include/cpp-sort/detail/block_sort.h index 291e2844..74f15646 100644 --- a/include/cpp-sort/detail/block_sort.h +++ b/include/cpp-sort/detail/block_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -206,7 +206,7 @@ namespace detail } // BlockSwap - detail::swap_ranges(std::move(A_index), std::move(A_last), std::move(insert_index)); + detail::swap_ranges_overlap(std::move(A_index), std::move(A_last), std::move(insert_index)); } // merge operation without a buffer @@ -765,7 +765,7 @@ namespace detail if (cache_size > 0 && lastA.length() <= cache_size) { detail::move(lastA.start, lastA.end, cache.begin()); } else if (buffer2.length() > 0) { - detail::swap_ranges(lastA.start, lastA.end, buffer2.start); + detail::swap_ranges_overlap(lastA.start, lastA.end, buffer2.start); } if (blockA.length() > 0) { @@ -786,7 +786,7 @@ namespace detail minA = findA; } } - detail::swap_ranges(blockA.start, blockA.start + block_size, minA); + detail::swap_ranges_overlap(blockA.start, blockA.start + block_size, minA); // swap the first item of the previous A block back with its original value, which is stored in buffer1 iter_swap(blockA.start, indexA); @@ -815,8 +815,8 @@ namespace detail detail::move(blockA.start, blockA.start + block_size, cache.begin()); detail::move(B_split, B_split + B_remaining, blockA.start + (block_size - B_remaining)); } else { - detail::swap_ranges(blockA.start, blockA.start + block_size, buffer2.start); - detail::swap_ranges(B_split, B_split + B_remaining, blockA.start + (block_size - B_remaining)); + detail::swap_ranges_overlap(blockA.start, blockA.start + block_size, buffer2.start); + detail::swap_ranges_overlap(B_split, B_split + B_remaining, blockA.start + (block_size - B_remaining)); } } else { // we are unable to use the 'buffer2' trick to speed up the rotation operation since buffer2 doesn't exist, so perform a normal rotation @@ -841,7 +841,7 @@ namespace detail blockB.end = blockB.start; } else { // roll the leftmost A block to the end by swapping it with the next B block - detail::swap_ranges(blockA.start, blockA.start + block_size, blockB.start); + detail::swap_ranges_overlap(blockA.start, blockA.start + block_size, blockB.start); lastB = { blockA.start, blockA.start + block_size }; blockA.start += block_size; diff --git a/include/cpp-sort/detail/comparison_counter.h b/include/cpp-sort/detail/comparison_counter.h index 87bfea57..3601d042 100644 --- a/include/cpp-sort/detail/comparison_counter.h +++ b/include/cpp-sort/detail/comparison_counter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_COMPARISON_COUNTER_H_ @@ -8,47 +8,58 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include #include #include +#include namespace cppsort { -namespace detail -{ - template< - typename Compare = std::less<>, - typename CountType = std::size_t - > - class comparison_counter + namespace detail + { + template + class comparison_counter + { + public: + + comparison_counter(Compare compare, CountType& count): + compare(std::move(compare)), + count(count) + {} + + template + auto operator()(T&& lhs, U&& rhs) + -> decltype(auto) + { + ++count; + auto&& comp = utility::as_function(compare); + return comp(std::forward(lhs), std::forward(rhs)); + } + + // Accessible member data + Compare compare; + + private: + + // Comparison functions are generally passed by value, + // therefore we need to know where is the original counter + // in order to increment it + CountType& count; + }; + } + + namespace utility { - public: - - comparison_counter(Compare compare, CountType& count): - compare(std::move(compare)), - count(count) - {} - - template - auto operator()(T&& lhs, U&& rhs) - -> decltype(auto) - { - ++count; - auto&& comp = utility::as_function(compare); - return comp(std::forward(lhs), std::forward(rhs)); - } - - // Accessible member data - Compare compare; - - private: - - // Comparison functions are generally passed by value, - // therefore we need to know where is the original counter - // in order to increment it - CountType& count; - }; -}} + template + struct is_probably_branchless_comparison< + cppsort::detail::comparison_counter, + T + >: + cppsort::detail::conjunction< + std::is_arithmetic, // Probably a safe enough bet + is_probably_branchless_comparison + > + {}; + } +} #endif // CPPSORT_DETAIL_COMPARISON_COUNTER_H_ diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index ba49a5e1..72d01d48 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_CONFIG_H_ @@ -29,6 +29,29 @@ # define CPPSORT_CONSTEXPR_AFTER_CXX14 #endif +//////////////////////////////////////////////////////////// +// Check for C++20 features + +// There is no feature-test macro for std::identity that can +// be used reliably, so we have to fall back to checking +// compiler and standard versions + +#if defined(__GNUC__) +# if __GNUC__ > 3 && __cplusplus > 201703L +# define CPPSORT_STD_IDENTITY_AVAILABLE 1 +# else +# define CPPSORT_STD_IDENTITY_AVAILABLE 0 +# endif +#elif defined(__clang__) +# define CPPSORT_STD_IDENTITY_AVAILABLE 0 +#else +# if defined(__cpp_lib_ranges) +# CPPSORT_STD_IDENTITY_AVAILABLE 1 +# else +# CPPSORT_STD_IDENTITY_AVAILABLE 0 +# endif +#endif + //////////////////////////////////////////////////////////// // CPPSORT_ASSUME @@ -77,6 +100,22 @@ # endif #endif +//////////////////////////////////////////////////////////// +// CPPSORT_AUDIT + +// Some debug checks might be way too expensive for most +// scenarios, but still of great help when debugging tough +// problems, hence this audit feature + +#ifndef CPPSORT_AUDIT +# ifdef CPPSORT_ENABLE_AUDITS +# include +# define CPPSORT_AUDIT(...) assert((__VA_ARGS__)) +# else +# define CPPSORT_AUDIT(...) +# endif +#endif + //////////////////////////////////////////////////////////// // CPPSORT_DEPRECATED diff --git a/include/cpp-sort/detail/container_aware/merge_sort.h b/include/cpp-sort/detail/container_aware/merge_sort.h index 99d77fce..94836f8d 100644 --- a/include/cpp-sort/detail/container_aware/merge_sort.h +++ b/include/cpp-sort/detail/container_aware/merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_CONTAINER_AWARE_MERGE_SORT_H_ @@ -14,12 +14,12 @@ #include #include #include +#include #include #include #include #include #include -#include "../projection_compare.h" namespace cppsort { diff --git a/include/cpp-sort/detail/fixed_size_list.h b/include/cpp-sort/detail/fixed_size_list.h index 93899a1e..52fb9424 100644 --- a/include/cpp-sort/detail/fixed_size_list.h +++ b/include/cpp-sort/detail/fixed_size_list.h @@ -50,7 +50,7 @@ namespace detail // Inhibit construction of the node with a value union { T value; }; - // Pointers to th previous and next nodes + // Pointers to the previous and next nodes fixed_size_list_node* prev; fixed_size_list_node* next; }; diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index 89be2579..1c1bd0c6 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -131,7 +131,7 @@ namespace grail ++M; } if (M != left_it) { - detail::swap_ranges(M, M + (middle - left_it), left_it); + detail::swap_ranges_overlap(M, M + (middle - left_it), left_it); } } @@ -389,7 +389,7 @@ namespace grail int fnext = compare(proj(keys[cidx]), midkey_proj) < 0 ? 0 : 1; if(fnext == frest) { if (havebuf) { - detail::swap_ranges(prest-lblock, prest-(lblock-lrest), prest); + detail::swap_ranges_overlap(prest-lblock, prest-(lblock-lrest), prest); } prest = pidx; lrest = lblock; @@ -411,7 +411,7 @@ namespace grail if (llast) { if (frest) { if (havebuf) { - detail::swap_ranges(prest-lblock, prest-(lblock-lrest), prest); + detail::swap_ranges_overlap(prest-lblock, prest-(lblock-lrest), prest); } prest = pidx; lrest = lblock * nblock2; @@ -427,7 +427,7 @@ namespace grail } } else { if (havebuf) { - detail::swap_ranges(prest, prest+lrest, prest-lblock); + detail::swap_ranges_overlap(prest, prest+lrest, prest-lblock); } } } @@ -570,7 +570,7 @@ namespace grail } } if (p != u - 1) { - detail::swap_ranges(arr1+(u-1)*lblock, arr1+u*lblock, arr1+p*lblock); + detail::swap_ranges_overlap(arr1+(u-1)*lblock, arr1+u*lblock, arr1+p*lblock); iter_swap(keys+(u-1), keys+p); if (midkey == u - 1 || midkey == p) { midkey ^= (u - 1) ^ p; diff --git a/include/cpp-sort/detail/inplace_merge.h b/include/cpp-sort/detail/inplace_merge.h index ad6eac95..8c4d86b6 100644 --- a/include/cpp-sort/detail/inplace_merge.h +++ b/include/cpp-sort/detail/inplace_merge.h @@ -93,14 +93,14 @@ namespace detail -> void { using rvalue_reference = remove_cvref_t>; - auto&& comp = as_function(compare); - auto&& proj = as_function(projection); + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); - // Shrink the problem size on the left side + // Shrink the problem size on the left side, makes the + // size computation potentially cheaper while (first != middle && not comp(proj(*middle), proj(*first))) { ++first; } - if (first == middle) return; auto n0 = std::distance(first, middle); @@ -138,12 +138,21 @@ namespace detail -> void { using rvalue_reference = remove_cvref_t>; + using category = iterator_category_t; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + // Shrink the problem size on the left side, makes the + // size computation potentially cheaper + while (first != middle && not comp(proj(*middle), proj(*first))) { + ++first; + } + if (first == middle) return; auto len1 = std::distance(first, middle); auto len2 = std::distance(middle, last); - temporary_buffer buffer(std::min(len1, len2)); - using category = iterator_category_t; + 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/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index b0c85c05..b4336621 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_MERGE_INSERTION_SORT_H_ @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "config.h" #include "fixed_size_list.h" @@ -230,7 +231,7 @@ namespace detail auto iter_swap(group_iterator lhs, group_iterator rhs) -> void { - detail::swap_ranges(lhs.base(), lhs.base() + lhs.size(), rhs.base()); + detail::swap_ranges_inner(lhs.base(), lhs.base() + lhs.size(), rhs.base()); } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/detail/nth_element.h b/include/cpp-sort/detail/nth_element.h index d4f1af2c..4c8db6a8 100644 --- a/include/cpp-sort/detail/nth_element.h +++ b/include/cpp-sort/detail/nth_element.h @@ -1,16 +1,7 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ - -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - #ifndef CPPSORT_DETAIL_NTH_ELEMENT_H_ #define CPPSORT_DETAIL_NTH_ELEMENT_H_ @@ -19,11 +10,9 @@ //////////////////////////////////////////////////////////// #include #include -#include -#include +#include "adaptive_quickselect.h" #include "introselect.h" #include "iterator_traits.h" -#include "selection_sort.h" namespace cppsort { @@ -33,11 +22,11 @@ namespace detail // nth_element for forward iterators with introselect template - auto nth_element(ForwardIterator first, ForwardIterator last, + auto nth_element(std::forward_iterator_tag, + ForwardIterator first, ForwardIterator last, difference_type_t nth_pos, difference_type_t size, - Compare compare, Projection projection, - std::forward_iterator_tag) + Compare compare, Projection projection) -> ForwardIterator { return introselect(first, last, nth_pos, size, detail::log2(size), @@ -45,250 +34,27 @@ namespace detail } //////////////////////////////////////////////////////////// - // nth_element for random-access iterators from libc++ - - template - auto sort3(ForwardIterator x, ForwardIterator y, ForwardIterator z, - Compare compare, Projection projection) - -> unsigned - { - using utility::iter_swap; - - auto&& comp = utility::as_function(compare); - auto&& proj = utility::as_function(projection); - - unsigned r = 0; - if (not comp(proj(*y), proj(*x))) // if x <= y - { - if (not comp(proj(*z), proj(*y))) // if y <= z - return r; // x <= y && y <= z - // x <= y && y > z - iter_swap(y, z); // x <= z && y < z - r = 1; - if (comp(proj(*y), proj(*x))) // if x > y - { - iter_swap(x, y); // x < y && y <= z - r = 2; - } - return r; // x <= y && y < z - } - if (comp(proj(*z), proj(*y))) // x > y, if y > z - { - iter_swap(x, z); // x < y && y < z - r = 1; - return r; - } - iter_swap(x, y); // x > y && y <= z - r = 1; // x < y && x <= z - if (comp(proj(*z), proj(*y))) // if y > z - { - iter_swap(y, z); // x <= y && y < z - r = 2; - } - return r; - } // x <= y && y <= z + // nth_element for random-access iterators with Andrei + // Alexandrescu's adaptive quickselect template - auto nth_element(RandomAccessIterator first, RandomAccessIterator last, + auto nth_element(std::random_access_iterator_tag, + RandomAccessIterator first, RandomAccessIterator last, difference_type_t nth_pos, difference_type_t, // unused - Compare compare, Projection projection, - std::random_access_iterator_tag) + Compare compare, Projection projection) -> RandomAccessIterator { - using utility::iter_swap; - - auto&& comp = utility::as_function(compare); - auto&& proj = utility::as_function(projection); - - using difference_type = difference_type_t; - constexpr difference_type limit = 7; - - auto nth = first + nth_pos; - - while (true) - { - restart: - if (nth == last) - return nth; - difference_type len = last - first; - switch (len) - { - case 0: - case 1: - return nth; - case 2: - if (comp(proj(*--last), proj(*first))) { - iter_swap(first, last); - } - return nth; - case 3: - RandomAccessIterator m = first; - sort3(first, ++m, --last, std::move(compare), std::move(projection)); - return nth; - } - if (len <= limit) { - selection_sort(first, last, std::move(compare), std::move(projection)); - return nth; - } - // len > limit >= 3 - RandomAccessIterator m = first + len / 2; - RandomAccessIterator lm1 = last; - unsigned n_swaps = sort3(first, m, --lm1, compare, projection); - // *m is median - // partition [first, m) < *m and *m <= [m, last) - // (this inhibits tossing elements equivalent to m around unnecessarily) - RandomAccessIterator i = first; - RandomAccessIterator j = lm1; - // j points beyond range to be tested, *lm1 is known to be <= *m - // The search going up is known to be guarded but the search coming down isn't. - // Prime the downward search with a guard. - if (not comp(proj(*i), proj(*m))) // if *first == *m - { - // *first == *m, *first doesn't go in first part - // manually guard downward moving j against i - while (true) - { - if (i == --j) - { - // *first == *m, *m <= all other elements - // Parition instead into [first, i) == *first and *first < [i, last) - ++i; // first + 1 - j = last; - if (not comp(proj(*first), proj(*--j))) // we need a guard if *first == *(last-1) - { - while (true) - { - if (i == j) - return nth; // [first, last) all equivalent elements - if (comp(proj(*first), proj(*i))) - { - iter_swap(i, j); - ++n_swaps; - ++i; - break; - } - ++i; - } - } - // [first, i) == *first and *first < [j, last) and j == last - 1 - if (i == j) - return nth; - while (true) - { - while (not comp(proj(*first), proj(*i))) - ++i; - while (comp(proj(*first), proj(*--j))) - ; - if (i >= j) - break; - iter_swap(i, j); - ++n_swaps; - ++i; - } - // [first, i) == *first and *first < [i, last) - // The first part is sorted, - if (nth < i) - return nth; - // nth_element the second part - // nth_element(i, nth, last, comp); - first = i; - goto restart; - } - if (comp(proj(*j), proj(*m))) - { - iter_swap(i, j); - ++n_swaps; - break; // found guard for downward moving j, now use unguarded partition - } - } - } - ++i; - // j points beyond range to be tested, *lm1 is known to be <= *m - // if not yet partitioned... - if (i < j) - { - // known that *(i - 1) < *m - while (true) - { - // m still guards upward moving i - while (comp(proj(*i), proj(*m))) - ++i; - // It is now known that a guard exists for downward moving j - while (not comp(proj(*--j), proj(*m))) - ; - if (i >= j) - break; - iter_swap(i, j); - ++n_swaps; - // It is known that m != j - // If m just moved, follow it - if (m == i) - m = j; - ++i; - } - } - // [first, i) < *m and *m <= [i, last) - if (i != m && comp(proj(*m), proj(*i))) - { - iter_swap(i, m); - ++n_swaps; - } - // [first, i) < *i and *i <= [i+1, last) - if (nth == i) - return nth; - if (n_swaps == 0) - { - // We were given a perfectly partitioned sequence. Coincidence? - if (nth < i) - { - // Check for [first, i) already sorted - j = m = first; - while (++j != i) - { - if (comp(proj(*j), proj(*m))) - // not yet sorted, so sort - goto not_sorted; - m = j; - } - // [first, i) sorted - return nth; - } - else - { - // Check for [i, last) already sorted - j = m = i; - while (++j != last) - { - if (comp(proj(*j), proj(*m))) - // not yet sorted, so sort - goto not_sorted; - m = j; - } - // [i, last) sorted - return nth; - } - } - not_sorted: - // nth_element on range containing nth - if (nth < i) - { - // nth_element(first, nth, i, comp); - last = i; - } - else - { - // nth_element(i+1, nth, last, comp); - first = ++i; - } - } + return median_of_ninthers_select(first, first + nth_pos, last, + std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// // Generic nth_element overload, slightly modified compared // to the standard library one to avoid recomputing sizes // over and over again, which might be too expensive for - // forward and bidirectional iterators + // forward and bidirectional iterators - also returns the + // nth iterator template auto nth_element(ForwardIterator first, ForwardIterator last, @@ -298,9 +64,8 @@ namespace detail -> ForwardIterator { using category = iterator_category_t; - return detail::nth_element(first, last, nth_pos, size, - std::move(compare), std::move(projection), - category{}); + return detail::nth_element(category{}, first, last, nth_pos, size, + std::move(compare), std::move(projection)); } }} diff --git a/include/cpp-sort/detail/pdqsort.h b/include/cpp-sort/detail/pdqsort.h index 93cbb2c3..fec53531 100644 --- a/include/cpp-sort/detail/pdqsort.h +++ b/include/cpp-sort/detail/pdqsort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -557,11 +557,13 @@ namespace detail utility::is_probably_branchless_comparison_v && utility::is_probably_branchless_projection_v; - if ((end - begin) < 2) return; + auto size = end - begin; + if (size < 2) return; + pdqsort_detail::pdqsort_loop( std::move(begin), std::move(end), std::move(compare), std::move(projection), - detail::log2(end - begin)); + detail::log2(size)); } }} diff --git a/include/cpp-sort/detail/projection_compare.h b/include/cpp-sort/detail/projection_compare.h deleted file mode 100644 index d4df7062..00000000 --- a/include/cpp-sort/detail/projection_compare.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2016-2020 Morwenn - * SPDX-License-Identifier: MIT - */ -#ifndef CPPSORT_DETAIL_PROJECTION_COMPARE_H_ -#define CPPSORT_DETAIL_PROJECTION_COMPARE_H_ - -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include "type_traits.h" - -namespace cppsort -{ -namespace detail -{ - template - class projection_compare - { - private: - - using compare_t = remove_cvref_t()))>; - using projection_t = remove_cvref_t()))>; - std::tuple data; - - public: - - projection_compare(Compare compare, Projection projection): - data(utility::as_function(compare), utility::as_function(projection)) - {} - - auto compare() const - -> compare_t - { - return std::get<0>(data); - } - - auto projection() const - -> projection_t - { - return std::get<1>(data); - } - - template - auto operator()(T&& lhs, U&& rhs) - noexcept(noexcept(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), - std::get<1>(data)(std::forward(rhs))))) - -> decltype(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), - std::get<1>(data)(std::forward(rhs)))) - { - return std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), - std::get<1>(data)(std::forward(rhs))); - } - }; - - template - auto make_projection_compare(Compare compare, Projection projection) - -> projection_compare - { - return { compare, projection }; - } -}} - -#endif // CPPSORT_DETAIL_PROJECTION_COMPARE_H_ diff --git a/include/cpp-sort/detail/quick_merge_sort.h b/include/cpp-sort/detail/quick_merge_sort.h index 176c4f69..e5d1fdf9 100644 --- a/include/cpp-sort/detail/quick_merge_sort.h +++ b/include/cpp-sort/detail/quick_merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_QUICK_MERGE_SORT_H_ @@ -17,6 +17,7 @@ #include "iterator_traits.h" #include "nth_element.h" #include "quicksort.h" +#include "sized_iterator.h" #include "swap_ranges.h" namespace cppsort @@ -53,7 +54,7 @@ namespace detail for (; first1 != last1; ++result) { if (first2 == last2) { - detail::swap_ranges(first1, last1, result); + detail::swap_ranges_inner(first1, last1, result); return; } @@ -76,7 +77,7 @@ namespace detail Compare compare, Projection projection) -> void { - auto buffer_end = detail::swap_ranges(first, middle, buffer); + auto buffer_end = detail::swap_ranges_inner(first, middle, buffer); internal_half_inplace_merge(buffer, buffer_end, middle, last, first, size_left, std::move(compare), std::move(projection)); } @@ -127,9 +128,9 @@ namespace detail Compare compare, Projection projection) -> void { - // This flavour of QuickMergeSort splits the collection in [2/3, 1/3] + // This flavour of QuickMergesort splits the collection in [2/3, 1/3] // partitions where the right partition is used as an internal buffer - // to apply mergesort to the left partition, then QuickMergeSort is + // to apply mergesort to the left partition, then QuickMergesort is // recursively applied to the smaller right partition while (size > qmsort_limit) { @@ -149,6 +150,18 @@ namespace detail } small_sort(first, last, size, std::move(compare), std::move(projection)); } + + template + auto quick_merge_sort(sized_iterator first, sized_iterator last, + difference_type_t size, + Compare compare, Projection projection) + -> void + { + // Hack to get the stable bidirectional version of vergesort + // to work correctly without duplicating tons of code + quick_merge_sort(first.base(), last.base(), size, + std::move(compare), std::move(projection)); + } }} #endif // CPPSORT_DETAIL_QUICK_MERGE_SORT_H_ diff --git a/include/cpp-sort/detail/rotate.h b/include/cpp-sort/detail/rotate.h index 6cd3c5ad..6803873b 100644 --- a/include/cpp-sort/detail/rotate.h +++ b/include/cpp-sort/detail/rotate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -116,7 +116,7 @@ namespace detail const difference_type m2 = last - middle; if (m1 == m2) { - detail::swap_ranges(first, middle, middle); + detail::swap_ranges_inner(first, middle, middle); return middle; } const difference_type g = gcd(m1, m2); diff --git a/include/cpp-sort/detail/sized_iterator.h b/include/cpp-sort/detail/sized_iterator.h new file mode 100644 index 00000000..d65b1fad --- /dev/null +++ b/include/cpp-sort/detail/sized_iterator.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_SIZED_ITERATOR_H_ +#define CPPSORT_DETAIL_SIZED_ITERATOR_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// + +namespace cppsort +{ +namespace detail +{ + //////////////////////////////////////////////////////////// + // Mostly a hack to avoid some gratuitous performance loss + // by passing bidirectional iterators + size to a function + // accepting a pair of iterators. It is worse than the + // equivalent C++20 features, but should be good enough for + // the internal use we make of it. + // + // NOTE: the full iterator features are not provided, this + // is intentional to avoid unintentional uses of the + // class in the library's internals. + + template + class sized_iterator + { + public: + + //////////////////////////////////////////////////////////// + // Public types + + using iterator_category = iterator_category_t; + using iterator_type = Iterator; + using value_type = value_type_t; + using difference_type = difference_type_t; + using pointer = pointer_t; + using reference = reference_t; + + //////////////////////////////////////////////////////////// + // Constructors + + sized_iterator() = default; + + constexpr sized_iterator(Iterator it, difference_type size): + _it(std::move(it)), + _size(size) + {} + + //////////////////////////////////////////////////////////// + // Members access + + auto base() const + -> iterator_type + { + return _it; + } + + auto size() const + -> difference_type + { + return _size; + } + + //////////////////////////////////////////////////////////// + // Element access + + auto operator*() const + -> reference + { + return *_it; + } + + auto operator->() const + -> pointer + { + return &(operator*()); + } + + private: + + Iterator _it; + difference_type _size; + }; + + // Alternative to std::distance meant to be picked up by ADL in + // specific places, uses the size of the *second* iterator + template + constexpr auto distance(sized_iterator, sized_iterator last) + -> difference_type_t + { + return last.size(); + } + + template + auto make_sized_iterator(Iterator it, difference_type_t size) + -> sized_iterator + { + return { it, size }; + } + +}} + +#endif // CPPSORT_DETAIL_SIZED_ITERATOR_H_ diff --git a/include/cpp-sort/detail/ska_sort.h b/include/cpp-sort/detail/ska_sort.h index 5081dad8..1233c5be 100644 --- a/include/cpp-sort/detail/ska_sort.h +++ b/include/cpp-sort/detail/ska_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2019 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -18,13 +18,14 @@ #include #include #include +#include #include #include #include #include #include #include -#include +#include #include "attributes.h" #include "iterator_traits.h" #include "memcpy_cast.h" @@ -134,7 +135,7 @@ namespace detail #ifdef __SIZEOF_INT128__ inline auto to_unsigned_or_bool(__int128_t l) - -> unsigned long long + -> __uint128_t { return static_cast<__uint128_t>(l) + static_cast<__uint128_t>(__int128_t(1) << (CHAR_BIT * sizeof(__int128_t) - 1)); @@ -163,12 +164,21 @@ namespace detail return u ^ (sign_bit | 0x8000000000000000); } +#ifdef UINTPTR_MAX + template + auto to_unsigned_or_bool(T* ptr) + -> std::uintptr_t + { + return reinterpret_cast(ptr); + } +#else template auto to_unsigned_or_bool(T* ptr) -> std::size_t { return reinterpret_cast(ptr); } +#endif template auto unroll_loop_four_times(RandomAccessIterator begin, std::size_t iteration_count, @@ -531,10 +541,7 @@ namespace detail auto StdSortFallback(RandomAccessIterator begin, RandomAccessIterator end, Projection projection) -> void { - auto&& proj = utility::as_function(projection); - pdqsort(std::move(begin), std::move(end), [&](auto&& l, auto&& r) { - return proj(l) < proj(r); - }, utility::identity{}); + pdqsort(std::move(begin), std::move(end), std::less<>{}, std::move(projection)); } template diff --git a/include/cpp-sort/detail/spreadsort/detail/string_sort.h b/include/cpp-sort/detail/spreadsort/detail/string_sort.h index 65a10466..cf749515 100644 --- a/include/cpp-sort/detail/spreadsort/detail/string_sort.h +++ b/include/cpp-sort/detail/spreadsort/detail/string_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ @@ -87,7 +87,7 @@ namespace spreadsort } } - //This comparison functor assumes strings are identical up to char_offset + //This comparator assumes strings are identical up to char_offset template struct offset_less_than { diff --git a/include/cpp-sort/detail/spreadsort/integer_sort.h b/include/cpp-sort/detail/spreadsort/integer_sort.h index bc61cea2..0e974536 100644 --- a/include/cpp-sort/detail/spreadsort/integer_sort.h +++ b/include/cpp-sort/detail/spreadsort/integer_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ @@ -28,7 +28,6 @@ Doxygen comments by Paul A. Bristow Jan 2015 #include #include #include -#include #include "detail/constants.h" #include "detail/integer_sort.h" #include "../pdqsort.h" @@ -67,7 +66,7 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \post The elements in the range [@c first, @c last) are sorted in ascending order. \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), - the right shift, subtraction of right-shifted elements, functors, or any operations on iterators throw. + the right shift, subtraction of right-shifted elements, projections, or any operations on iterators throw. \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. \warning Invalid arguments cause undefined behaviour. @@ -80,12 +79,8 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). */ - template< - typename RandomAccessIter, - typename Projection = utility::identity - > - auto integer_sort(RandomAccessIter first, RandomAccessIter last, - Projection projection={}) + template + auto integer_sort(RandomAccessIter first, RandomAccessIter last, Projection projection) -> void { auto&& proj = utility::as_function(projection); diff --git a/include/cpp-sort/detail/spreadsort/string_sort.h b/include/cpp-sort/detail/spreadsort/string_sort.h index a04eb06b..fad28ea0 100644 --- a/include/cpp-sort/detail/spreadsort/string_sort.h +++ b/include/cpp-sort/detail/spreadsort/string_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ @@ -64,7 +64,7 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \post The elements in the range [@c first, @c last) are sorted in ascending order. \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), - the right shift, subtraction of right-shifted elements, functors, + the right shift, subtraction of right-shifted elements, callables, or any operations on iterators throw. \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. @@ -111,12 +111,12 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \tparam RandomAccessIter Random access iterator - \tparam Comp Functor type to use for comparison. + \tparam Comp callable type to use for comparison. \tparam Unsigned_char_type Unsigned character type used for string. \param[in] first Iterator pointer to first element. \param[in] last Iterator pointing to one beyond the end of data. - \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + \param[in] comp A binary callable that returns whether the first element passed to it should go before the second in order. \param[in] unused value with the same type as the result of the [] operator, defining the Unsigned_char_type. The actual value is unused. \pre [@c first, @c last) is a valid range. @@ -129,7 +129,7 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \return @c void. \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), - the right shift, subtraction of right-shifted elements, functors, + the right shift, subtraction of right-shifted elements, callables, or any operations on iterators throw. \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. diff --git a/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h b/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h index 9d878617..e938780c 100644 --- a/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h +++ b/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019 Morwenn + * Copyright (c) 2018-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_STABLE_ADAPTER_HYBRID_ADAPTER_H_ @@ -16,14 +16,14 @@ namespace cppsort { template struct stable_adapter>: - hybrid_adapter...> + hybrid_adapter...> { private: template constexpr explicit stable_adapter(std::index_sequence, hybrid_adapter&& sorters): - hybrid_adapter...>( - (stable_adapter(std::move(sorters).template get()))... + hybrid_adapter...>( + (stable_t(std::move(sorters).template get()))... ) {} @@ -42,6 +42,7 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; + using type = hybrid_adapter...>; }; } diff --git a/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h b/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h index 92e2a468..a0ca5c46 100644 --- a/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h +++ b/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_STABLE_ADAPTER_SELF_SORT_ADAPTER_H_ @@ -22,7 +22,7 @@ namespace cppsort { template struct stable_adapter>: - utility::adapter_storage>, + utility::adapter_storage>, detail::check_iterator_category, detail::sorter_facade_fptr< stable_adapter>, @@ -35,8 +35,8 @@ namespace cppsort stable_adapter() = default; constexpr explicit stable_adapter(self_sort_adapter sorter): - utility::adapter_storage>( - stable_adapter(std::move(sorter.get())) + utility::adapter_storage>( + stable_t(std::move(sorter.get())) ) {} @@ -114,6 +114,7 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; + using type = stable_adapter>; }; } diff --git a/include/cpp-sort/detail/swap_if.h b/include/cpp-sort/detail/swap_if.h index b4636c13..ecb47b87 100644 --- a/include/cpp-sort/detail/swap_if.h +++ b/include/cpp-sort/detail/swap_if.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SWAP_IF_H_ @@ -15,6 +15,7 @@ #include #include #include +#include "config.h" #include "type_traits.h" namespace cppsort @@ -81,6 +82,94 @@ namespace detail y = std::min(dx, y); } +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto swap_if(Integer& x, Integer& y, std::less<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::less<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::greater<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::greater<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } +#endif + +#ifdef __cpp_lib_ranges + template + auto swap_if(Integer& x, Integer& y, std::ranges::less comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::less comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::ranges::greater comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::greater comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::ranges::less comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::less comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::ranges::greater comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::greater comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } +#endif + //////////////////////////////////////////////////////////// // iter_swap_if diff --git a/include/cpp-sort/detail/swap_ranges.h b/include/cpp-sort/detail/swap_ranges.h index 82f0e15b..abbee439 100644 --- a/include/cpp-sort/detail/swap_ranges.h +++ b/include/cpp-sort/detail/swap_ranges.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SWAP_RANGES_H_ @@ -8,18 +8,32 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include +#include +#include #include +#include "config.h" +#include "move.h" +#include "type_traits.h" + +#include namespace cppsort { namespace detail { + //////////////////////////////////////////////////////////// + // swap_ranges_overlap + // + // Most basic swap_ranges implementation, somehow tolerates + // overlapping ranges - at least enough to get some of the + // library's algorithms to work + template - auto swap_ranges(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2) + auto swap_ranges_overlap(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2) -> ForwardIterator2 { - while (first1 != last1) - { + while (first1 != last1) { using utility::iter_swap; iter_swap(first1, first2); @@ -28,6 +42,60 @@ namespace detail } return first2; } + + //////////////////////////////////////////////////////////// + // swap_ranges_inner + // + // swap_ranges flavor to use when two ranges belong to the + // same collection but can not overlap, might also provide + // additional diagnostics when the precondition is violated + + template + auto swap_ranges_inner_impl(std::forward_iterator_tag, + ForwardIterator first1, ForwardIterator last1, + ForwardIterator first2) + -> ForwardIterator + { +#ifdef CPPSORT_ENABLE_AUDITS + bool ranges_overlap = false; + // This check assumes that first1 <= last1 + auto last2 = std::next(first2, std::distance(first1, last1)); + for (auto it = first1 ; it != last1 ; ++it) { + if (it == first2) { + ranges_overlap = true; + } + if (it == last2 && it != first1) { + ranges_overlap = true; + } + } + CPPSORT_AUDIT(not ranges_overlap); +#endif + return swap_ranges_overlap(first1, last1, first2); + } + + template + auto swap_ranges_inner_impl(std::random_access_iterator_tag, + RandomAccessIterator first1, RandomAccessIterator last1, + RandomAccessIterator first2) + -> RandomAccessIterator + { + CPPSORT_ASSERT(first1 <= last1); + + auto last2 = first2 + (last1 - first1); + (void)last2; + CPPSORT_ASSERT(not (first2 >= first1 && first2 < last1)); + CPPSORT_ASSERT(not (last2 > first1 && last2 <= last1)); + + return detail::swap_ranges_overlap(first1, last1, first2); + } + + template + auto swap_ranges_inner(ForwardIterator first1, ForwardIterator last1, ForwardIterator first2) + -> ForwardIterator + { + using category = iterator_category_t; + return swap_ranges_inner_impl(category{}, first1, last1, first2); + } }} #endif // CPPSORT_DETAIL_SWAP_RANGES_H_ diff --git a/include/cpp-sort/detail/three_way_compare.h b/include/cpp-sort/detail/three_way_compare.h index 6a77a631..86910fbc 100644 --- a/include/cpp-sort/detail/three_way_compare.h +++ b/include/cpp-sort/detail/three_way_compare.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_THREE_WAY_COMPARE_H_ @@ -141,6 +141,35 @@ namespace detail } }; +#ifdef __cpp_lib_ranges + template<> + struct three_way_compare: + three_way_compare_base> + { + constexpr three_way_compare(std::ranges::less) {} + + using three_way_compare_base>::operator(); + + template< + typename CharT, + typename Traits1, typename Alloc1, + typename Traits2, typename Alloc2 + > + auto operator()(const std::basic_string& lhs, + const std::basic_string& rhs) const + -> int + { + return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()); + } + + constexpr auto base() const noexcept + -> std::ranges::less + { + return {}; + } + }; +#endif + template<> struct three_way_compare, true>: three_way_compare_base>> @@ -168,6 +197,36 @@ namespace detail return {}; } }; + +#ifdef __cpp_lib_ranges + template<> + struct three_way_compare: + three_way_compare_base> + { + constexpr three_way_compare(std::ranges::greater) {} + + using three_way_compare_base>::operator(); + + template< + typename CharT, + typename Traits1, typename Alloc1, + typename Traits2, typename Alloc2 + > + auto operator()(const std::basic_string& lhs, + const std::basic_string& rhs) const + -> int + { + int res = lhs.compare(0, lhs.size(), rhs.data(), rhs.size()); + return (res < 0) ? 1 : -res; + } + + constexpr auto base() const noexcept + -> std::ranges::greater + { + return {}; + } + }; +#endif }} #endif // CPPSORT_DETAIL_THREE_WAY_COMPARE_H_ diff --git a/include/cpp-sort/detail/timsort.h b/include/cpp-sort/detail/timsort.h index 453fb438..a3b21c0f 100644 --- a/include/cpp-sort/detail/timsort.h +++ b/include/cpp-sort/detail/timsort.h @@ -6,7 +6,7 @@ * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2015-2020 Morwenn. + * Copyright (c) 2015-2021 Morwenn. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -250,10 +250,6 @@ namespace detail iterator base2 = pending_[i + 1].base; difference_type len2 = pending_[i + 1].len; - CPPSORT_ASSERT(len1 > 0); - CPPSORT_ASSERT(len2 > 0); - CPPSORT_ASSERT(base1 + len1 == base2); - pending_[i].len = len1 + len2; if (i == stackSize - 3) { @@ -262,6 +258,16 @@ namespace detail pending_.pop_back(); + mergeConsecutiveRuns(base1, len1, base2, len2, std::move(compare), std::move(projection)); + } + + void mergeConsecutiveRuns(iterator base1, difference_type len1, iterator base2, difference_type len2, + Compare compare, Projection projection) + { + CPPSORT_ASSERT(len1 > 0); + CPPSORT_ASSERT(len2 > 0); + CPPSORT_ASSERT(base1 + len1 == base2); + difference_type const k = gallopRight(*base2, base1, len1, 0, compare, projection); CPPSORT_ASSERT(k >= 0); @@ -287,8 +293,8 @@ namespace detail } template - auto gallopLeft(T&& key, Iter const base, difference_type const len, difference_type const hint, - Compare compare, Projection projection) + static auto gallopLeft(T&& key, Iter const base, difference_type const len, difference_type const hint, + Compare compare, Projection projection) -> difference_type { CPPSORT_ASSERT(len > 0); @@ -345,8 +351,8 @@ namespace detail } template - auto gallopRight(T&& key, Iter const base, difference_type const len, difference_type const hint, - Compare compare, Projection projection) + static auto gallopRight(T&& key, Iter const base, difference_type const len, difference_type const hint, + Compare compare, Projection projection) -> difference_type { CPPSORT_ASSERT(len > 0); diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index faaa3e0f..e18b257f 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -11,30 +11,108 @@ #include #include #include +#include #include +#include #include #include "bitops.h" +#include "config.h" #include "inplace_merge.h" -#include "is_sorted_until.h" #include "iterator_traits.h" +#include "lower_bound.h" #include "quick_merge_sort.h" #include "reverse.h" +#include "rotate.h" +#include "sized_iterator.h" +#include "upper_bound.h" namespace cppsort { namespace detail { +namespace verge +{ + //////////////////////////////////////////////////////////// + // Run helper class, represents a non-decreasing run with + // its size and end iterator + + template + struct run + { + Iterator end; + difference_type_t size; + }; + + //////////////////////////////////////////////////////////// + // Merge a list of runs with a k-way merge + template - auto vergesort(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection, - std::bidirectional_iterator_tag) + auto merge_runs(BidirectionalIterator first, std::list>& runs, + Compare compare, Projection projection) + -> void + { + if (runs.size() < 2) return; + + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + // Merge runs pairwise until there are no runs left + do { + auto begin = first; + for (auto it = runs.begin() ; it != runs.end() && it != std::prev(runs.end()) ; ++it) { + auto next_it = std::next(it); + + // Merge the runs, micro-optimize for size 1 because it can happen, + // and the generic inplace_merge algorithm cares not + if (it->size == 1) { + auto&& target = proj(*begin); + if (comp(proj(*it->end), target)) { + auto insert_it = detail::lower_bound_n(it->end, next_it->size, target, compare, projection); + detail::rotate_left(first, insert_it); + } else { + } + } else if (next_it->size == 1) { + auto&& target = proj(*std::prev(next_it->end)); + if (comp(target, proj(*std::prev(it->end)))) { + auto insert_it = detail::upper_bound_n(begin, it->size, target, compare, projection); + detail::rotate_right(insert_it, next_it->end); + } + } else { + detail::inplace_merge(begin, it->end, next_it->end, compare, projection, + it->size, next_it->size); + } + + // Compute the size of the new merged run + next_it->size += it->size; + // Remove the middle iterator to order to fuse the two + // consecutive runs, and advance to the next pair + it = runs.erase(it); + begin = it->end; + } + } while (runs.size() > 1); + } + + //////////////////////////////////////////////////////////// + // Vergesort main algorithms + + template< + bool Stable, + typename BidirectionalIterator, + typename Compare, + typename Projection, + typename Fallback + > + auto sort(std::bidirectional_iterator_tag, + BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback) -> void { - if (size < 80) { + if (size < 128) { // vergesort is inefficient for small collections - quick_merge_sort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection)); + fallback(make_sized_iterator(first, size), + make_sized_iterator(last, size), + std::move(compare), std::move(projection)); return; } @@ -42,117 +120,219 @@ namespace detail auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); - // Limit under which quick_merge_sort is used - int unstable_limit = size / log2(size); + // Limit under which fallback is used + int minrun_limit = size / log2(size); - // Beginning of an unstable partition, last if the - // previous partition is stable - auto begin_unstable = last; + // Vergesort detects big runs in ascending or descending order, + // and remembers where each run ends by storing the end iterator + // of each run in this list, then it performs a k-way merge + std::list> runs; + + // Beginning of an "unsorted" partition, last if the previous + // partition is sorted: as long as the algorithm does not find a + // big enough run, it "accumulates" contiguous unsorted elements, + // and sorts them all at once when it reaches the end of such a + // partition, to ensure that a minimal number of calls to the + // fallback algorithm are performed + auto begin_unsorted = last; - // Size of the unstable partition - difference_type size_unstable = 0; + // Size of the current unsorted partition + difference_type size_unsorted = 0; // Pair of iterators to iterate through the collection - auto next = is_sorted_until(first, last, compare, projection); - if (next == last) return; - auto current = std::prev(next); + auto current = first; + auto next = std::next(current); + + // Choose whether to look for an ascending or descending + // run depending on the value of the first two elements; + // when comparing equivalent there is a bias towards + // ascending runs because they don't have to be reversed, + // and is always the right thing to do when sorting stably + if (comp(proj(*next), proj(*current))) { + goto find_descending; + } else { + goto find_ascending; + } while (true) { - // Decreasing range - { + + // This main loop uses goto here and there, but it is written in such + // a way that gotos are not part of the hot path: there are O(log n) + // gotos performed while the loop itself is O(n). + // + // The job of this loop is to find ascending and descending runs that + // are big enough according to vergesort's heuristic. In order to do + // that we perform a linear scan with the following intuition: when we + // reach two elements whose order does not match that of the run we are + // currently trying to identify, we know that we have reached the limit + // of the current run. When it happens, there are two possible scenarios: + // - The run we found is big enough, we add it whole to the list of runs + // to merge later. We take the next two elements and compare them to + // decide whether we should start looking for an ascending or for a + // descending run next. + // - The run we found is not big enough, so we consider the last *two* + // elements to be potentially part of the next run. We then start + // looking for a run whose direction matches that of the two elements + // we just compared. + // + // The result is that the hot path it the one that constantly fails to + // find big enough runs, and it naturally performs a kind of ping-pong + // between looking for ascending and descending runs. So this loop is + // optimized for this scenario: when we start looking for a run we + // already know the order of the first two elements. However when we + // find a big enough run we only consider the last compared element to + // be part of the next run, and not the last two like we do in the hot + // path. This break the ping-pong pattern, so we use goto to jump directly + // to the run detection code corresponding to the order of the new two + // elements. + + find_ascending: { + CPPSORT_ASSERT(not comp(proj(*next), proj(*current))); + auto begin_rng = current; + ++current; + ++next; - difference_type run_size = 1; + // Find a non-descending run + difference_type run_size = 2; while (next != last) { - if (comp(proj(*current), proj(*next))) break; + if (comp(proj(*next), proj(*current))) break; ++current; ++next; ++run_size; } - // Reverse and merge - if (run_size > unstable_limit) { - if (begin_unstable != last) { - quick_merge_sort(begin_unstable, begin_rng, size_unstable, compare, projection); - detail::reverse(begin_rng, next); - detail::inplace_merge(begin_unstable, begin_rng, next, compare, projection, - size_unstable, run_size); - detail::inplace_merge(first, begin_unstable, next, compare, projection, - std::distance(first, begin_unstable), size_unstable + run_size); - begin_unstable = last; - size_unstable = 0; + if (run_size > minrun_limit) { + if (begin_unsorted != last) { + fallback(make_sized_iterator(begin_unsorted, size_unsorted), + make_sized_iterator(begin_rng, size_unsorted), + compare, projection); + runs.push_back({ begin_rng, size_unsorted} ); + runs.push_back({ next, run_size }); + begin_unsorted = last; + size_unsorted = 0; } else { - detail::reverse(begin_rng, next); - detail::inplace_merge(first, begin_rng, next, compare, projection, - std::distance(first, begin_rng), run_size); + runs.push_back({ next, run_size }); + } + if (next == last) break; + + // Find and start a new run + ++current; + ++next; + if (next == last) { + begin_unsorted = current; + size_unsorted = 0; + break; + } + if (comp(proj(*next), proj(*current))) { + goto find_descending; + } else { + goto find_ascending; } } else { - size_unstable += run_size; - if (begin_unstable == last) { - begin_unstable = begin_rng; + size_unsorted += (run_size - 1); + if (begin_unsorted == last) { + begin_unsorted = begin_rng; } } if (next == last) break; + } + + find_descending: { + CPPSORT_ASSERT(comp(proj(*next), proj(*current))); + auto begin_rng = current; ++current; ++next; - } - // Increasing range - { - auto begin_rng = current; + difference_type run_size = 2; + if (Stable) { + // Find a strictly descending run + while (next != last) { + if (not comp(proj(*next), proj(*current))) break; + ++current; + ++next; + ++run_size; + } + } else { + // Find a non-ascending run + while (next != last) { + if (comp(proj(*current), proj(*next))) break; + ++current; + ++next; + ++run_size; + } + } - difference_type run_size = 1; - while (next != last) { - if (comp(proj(*next), proj(*current))) break; + if (run_size > minrun_limit) { + if (begin_unsorted != last) { + fallback(make_sized_iterator(begin_unsorted, size_unsorted), + make_sized_iterator(begin_rng, size_unsorted), + compare, projection); + runs.push_back({ begin_rng, size_unsorted }); + detail::reverse(begin_rng, next); + runs.push_back({ next, run_size }); + begin_unsorted = last; + size_unsorted = 0; + } else { + detail::reverse(begin_rng, next); + runs.push_back({ next, run_size }); + } + + // Find and start a new run + if (next == last) break; ++current; ++next; - ++run_size; - } - - // Merge - if (run_size > unstable_limit) { - if (begin_unstable != last) { - quick_merge_sort(begin_unstable, begin_rng, size_unstable, compare, projection); - detail::inplace_merge(begin_unstable, begin_rng, next, compare, projection, - size_unstable, run_size); - detail::inplace_merge(first, begin_unstable, next, compare, projection, - std::distance(first, begin_unstable), size_unstable + run_size); - begin_unstable = last; - size_unstable = 0; + if (next == last) { + begin_unsorted = current; + size_unsorted = 0; + break; + } + if (comp(proj(*next), proj(*current))) { + goto find_descending; } else { - detail::inplace_merge(first, begin_rng, next, compare, projection, - std::distance(first, begin_rng), run_size); + goto find_ascending; } } else { - size_unstable += run_size; - if (begin_unstable == last) { - begin_unstable = begin_rng; + size_unsorted += (run_size - 1); + if (begin_unsorted == last) { + begin_unsorted = begin_rng; } } if (next == last) break; - - ++current; - ++next; } } - if (begin_unstable != last) { - quick_merge_sort(begin_unstable, last, size_unstable, compare, projection); - detail::inplace_merge(first, begin_unstable, last, - std::move(compare), std::move(projection), - std::distance(first, begin_unstable), size_unstable); + if (begin_unsorted != last) { + // When run_size is added to size_unsorted, we retrieve one from + // it because it assumes that the last element is part of the + // next run, so we add one back here to compensate + ++size_unsorted; + if (size_unsorted > 1) { + fallback(make_sized_iterator(begin_unsorted, size_unsorted), + make_sized_iterator(last, size_unsorted), + compare, projection); + } + runs.push_back({ last, size_unsorted }); } + + // Last step: merge the runs + verge::merge_runs(first, runs, std::move(compare), std::move(projection)); } - template - auto vergesort(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback fallback, - std::random_access_iterator_tag) + template< + bool Stable, + typename RandomAccessIterator, + typename Compare, + typename Projection, + typename Fallback + > + auto sort(std::random_access_iterator_tag, + RandomAccessIterator first, RandomAccessIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback) -> void { if (size < 128) { @@ -162,85 +342,114 @@ namespace detail return; } - // Limit under which pdqsort is used to sort a sub-sequence - const difference_type_t unstable_limit = size / log2(size); - - // Vergesort detects big runs in ascending or descending order, - // and remember where each run ends by storing the end iterator - // of each run in this list, then it merges everything in the end - std::list runs; - - // Beginning of an unstable partition, or last if the previous - // partition is stable - RandomAccessIterator begin_unstable = last; + // See the bidirectional overload for the description of + // the following variables + const difference_type_t minrun_limit = size / log2(size); + std::list> runs; + auto begin_unsorted = last; // Pair of iterators to iterate through the collection - RandomAccessIterator current = first; - RandomAccessIterator next = std::next(first); + auto current = first; + auto next = std::next(first); auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); while (true) { + + // The random-access version of vergesort does not have to perform a linear + // scan over the whole collection like the bidirectional version does: + // instead it "jumps" n / log n elements at the same time, and start scanning + // to the left and to the right of the position to check whether it landed in + // a big enough run. In a collection that is truly shuffled, checking that + // there is no such partition in the scanning area should be really fast, and + // vergesort will perform log n jumps through the collection before falling + // back to the adapted sorting algorithm, making it a cheap preprocessing + // step. + // Beginning of the current sequence - RandomAccessIterator begin_range = current; + auto begin_range = current; - // If the last part of the collection to sort isn't - // big enough, consider that it is an unstable sequence - if (last - next <= unstable_limit) { - if (begin_unstable == last) { - begin_unstable = begin_range; + // If the last part of the collection to sort is not + // big enough, consider that it is an unsorted sequence + if (last - next <= minrun_limit) { + if (begin_unsorted == last) { + begin_unsorted = begin_range; } break; } // Set backward iterators - current += unstable_limit; - next += unstable_limit; + current += minrun_limit; + next += minrun_limit; // Set forward iterators - RandomAccessIterator current2 = current; - RandomAccessIterator next2 = next; + auto current2 = current; + auto next2 = next; if (comp(proj(*next), proj(*current))) { - // Found a decreasing sequence, move iterators - // to the limits of the sequence - do { - --current; - --next; - if (comp(proj(*current), proj(*next))) break; - } while (current != begin_range); - if (comp(proj(*current), proj(*next))) ++current; + // Found a descending run, scan to the left and to the right + // until the limits of the run are reached + if (Stable) { + // Find a strictly descending run to avoid breaking + // the stability of the algorithm with reverse() + do { + --current; + --next; + if (not comp(proj(*next), proj(*current))) break; + } while (current != begin_range); + if (not comp(proj(*next), proj(*current))) { + ++current; + } - ++current2; - ++next2; - while (next2 != last) { - if (comp(proj(*current2), proj(*next2))) break; ++current2; ++next2; + while (next2 != last) { + if (not comp(proj(*next2), proj(*current2))) break; + ++current2; + ++next2; + } + } else { + // Find a non-ascending sequence + do { + --current; + --next; + if (comp(proj(*current), proj(*next))) break; + } while (current != begin_range); + if (comp(proj(*current), proj(*next))) { + ++current; + } + + ++current2; + ++next2; + while (next2 != last) { + if (comp(proj(*current2), proj(*next2))) break; + ++current2; + ++next2; + } } // Check whether we found a big enough sorted sequence - if (next2 - current >= unstable_limit) { + if (next2 - current >= minrun_limit) { detail::reverse(current, next2); - if ((current - begin_range) && begin_unstable == last) { - begin_unstable = begin_range; + if ((current - begin_range) && begin_unsorted == last) { + begin_unsorted = begin_range; } - if (begin_unstable != last) { - fallback(begin_unstable, current, compare, projection); - runs.push_back(current); - begin_unstable = last; + if (begin_unsorted != last) { + fallback(begin_unsorted, current, compare, projection); + runs.push_back({ current, current - begin_unsorted }); + begin_unsorted = last; } - runs.push_back(next2); + runs.push_back({ next2, next2 - current }); } else { // Remember the beginning of the unsorted sequence - if (begin_unstable == last) { - begin_unstable = begin_range; + if (begin_unsorted == last) { + begin_unsorted = begin_range; } } } else { - // Found an increasing sequence, move iterators - // to the limits of the sequence + // Found an non-descending run, scan to the left and to + // the right until the limits of the run are reached do { --current; --next; @@ -257,20 +466,20 @@ namespace detail } // Check whether we found a big enough sorted sequence - if (next2 - current >= unstable_limit) { - if ((current - begin_range) && begin_unstable == last) { - begin_unstable = begin_range; + if (next2 - current >= minrun_limit) { + if ((current - begin_range) && begin_unsorted == last) { + begin_unsorted = begin_range; } - if (begin_unstable != last) { - fallback(begin_unstable, current, compare, projection); - runs.push_back(current); - begin_unstable = last; + if (begin_unsorted != last) { + fallback(begin_unsorted, current, compare, projection); + runs.push_back({ current, current - begin_unsorted }); + begin_unsorted = last; } - runs.push_back(next2); + runs.push_back({ next2, next2 - current }); } else { // Remember the beginning of the unsorted sequence - if (begin_unstable == last) { - begin_unstable = begin_range; + if (begin_unsorted == last) { + begin_unsorted = begin_range; } } } @@ -281,62 +490,90 @@ namespace detail next = std::next(next2); } - if (begin_unstable != last) { + if (begin_unsorted != last) { // If there are unsorted elements left, sort them - runs.push_back(last); - fallback(begin_unstable, last, compare, projection); + fallback(begin_unsorted, last, compare, projection); + runs.push_back({ last, last - begin_unsorted }); } - if (runs.size() < 2) return; + // Last step: merge the runs + verge::merge_runs(first, runs, std::move(compare), std::move(projection)); + } - // Merge runs pairwise until there aren't runs left - do { - auto begin = first; - for (auto it = runs.begin() ; it != runs.end() && it != std::prev(runs.end()) ; ++it) { - detail::inplace_merge(begin, *it, *std::next(it), compare, projection); - // Remove the middle iterator and advance - it = runs.erase(it); - begin = *it; - } - } while (runs.size() > 1); + //////////////////////////////////////////////////////////// + // Vergesort main interface + + template + auto get_maybe_stable(std::true_type, Sorter&& sorter) + -> cppsort::stable_adapter + { + return cppsort::stable_adapter(std::move(sorter)); } - template - auto vergesort(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - Compare compare, Projection projection, - std::random_access_iterator_tag category) - -> void + template + auto get_maybe_stable(std::false_type, Sorter&& sorter) + -> Sorter { - using sorter = cppsort::pdq_sorter; - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - sorter{}, category); + return std::move(sorter); } - template - auto vergesort(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback fallback) + template< + bool Stable, + typename BidirectionalIterator, + typename Compare, + typename Projection, + typename Fallback + > + auto sort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback) -> void { - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - std::move(fallback), std::random_access_iterator_tag{}); + // Adapt the fallback sorter depending on whether a stable + // or an unstable sort is wanted + verge::sort(iterator_category_t{}, + std::move(first), std::move(last), size, + std::move(compare), std::move(projection), + get_maybe_stable(std::integral_constant{}, std::move(fallback))); } - template - auto vergesort(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection) + constexpr auto default_sorter_for_impl(std::bidirectional_iterator_tag) + -> cppsort::quick_merge_sorter + { + return {}; + } + + constexpr auto default_sorter_for_impl(std::random_access_iterator_tag) + -> cppsort::pdq_sorter + { + return {}; + } + + template + constexpr auto default_sorter_for(Iterator) + -> decltype(auto) + { + iterator_category_t category; + return default_sorter_for_impl(category); + } + + template< + bool Stable, + typename BidirectionalIterator, + typename Compare, + typename Projection + > + auto sort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection) -> void { - using category = iterator_category_t; - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - category{}); + // Pick a default sorter based on the iterator category when + // none is provided + verge::sort(first, last, size, + std::move(compare), std::move(projection), + default_sorter_for(first)); } -}} +}}} #endif // CPPSORT_DETAIL_VERGESORT_H_ diff --git a/include/cpp-sort/sorter_facade.h b/include/cpp-sort/sorter_facade.h index bf71505b..8da56705 100644 --- a/include/cpp-sort/sorter_facade.h +++ b/include/cpp-sort/sorter_facade.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTER_FACADE_H_ @@ -11,11 +11,11 @@ #include #include #include +#include #include #include #include #include "detail/config.h" -#include "detail/projection_compare.h" #include "detail/type_traits.h" namespace cppsort @@ -460,6 +460,32 @@ namespace cppsort return operator()(std::forward(iterable)); } +#ifdef __cpp_lib_ranges + template + auto operator()(Iterator first, Iterator last, std::ranges::less) const + -> std::enable_if_t< + not detail::has_comparison_sort_iterator::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less) const + -> std::enable_if_t< + not detail::has_comparison_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } +#endif + //////////////////////////////////////////////////////////// // utility::identity overloads @@ -499,6 +525,44 @@ namespace cppsort return operator()(std::forward(iterable)); } +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto operator()(Iterator first, Iterator last, std::identity) const + -> std::enable_if_t< + not detail::has_projection_sort_iterator::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::less<>, + std::identity + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::identity) const + -> std::enable_if_t< + not detail::has_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::identity + >::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::less<>, + std::identity + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } +#endif + //////////////////////////////////////////////////////////// // Fused comparison-projection overloads @@ -599,8 +663,40 @@ namespace cppsort return operator()(std::forward(iterable)); } +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto operator()(Iterator first, Iterator last, std::less<>, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::less<>, + std::identity + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::less<>, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::less<>, + std::identity + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } +#endif + template - auto operator()(Iterator first, Iterator last, std::less<>, Projection projection) const + auto operator()(Iterator first, Iterator last, std::less<> compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, @@ -608,11 +704,11 @@ namespace cppsort std::less<>, refined_t >::value, - decltype(Sorter::operator()(std::move(first), std::move(last), std::less<>{}, + decltype(Sorter::operator()(std::move(first), std::move(last), compare, refined(std::move(projection)))) > { - return Sorter::operator()(std::move(first), std::move(last), std::less<>{}, + return Sorter::operator()(std::move(first), std::move(last), compare, refined(std::move(projection))); } @@ -639,7 +735,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<>, Projection projection) const + auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort< Sorter, @@ -647,16 +743,16 @@ namespace cppsort std::less<>, refined_t >::value, - decltype(Sorter::operator()(std::forward(iterable), std::less<>{}, + decltype(Sorter::operator()(std::forward(iterable), compare, refined(std::move(projection)))) > { - return Sorter::operator()(std::forward(iterable), std::less<>{}, + return Sorter::operator()(std::forward(iterable), compare, refined(std::move(projection))); } template - auto operator()(Iterable&& iterable, std::less<>, Projection projection) const + auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -670,11 +766,11 @@ namespace cppsort std::less<>, refined_t >::value, - decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), std::less<>{}, + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), compare, refined(std::move(projection)))) > { - return Sorter::operator()(std::begin(iterable), std::end(iterable), std::less<>{}, + return Sorter::operator()(std::begin(iterable), std::end(iterable), compare, refined(std::move(projection))); } @@ -739,6 +835,178 @@ namespace cppsort refined(std::move(projection))); } +#ifdef __cpp_lib_ranges + template + auto operator()(Iterator first, Iterator last, std::ranges::less, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::ranges::less, + std::identity + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + std::identity + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } + + template + auto operator()(Iterator first, Iterator last, std::ranges::less compare, Projection projection) const + -> std::enable_if_t< + detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::ranges::less, + refined_t + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last), compare, + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::move(first), std::move(last), compare, + refined(std::move(projection))); + } + + template + auto operator()(Iterator first, Iterator last, std::ranges::less, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::ranges::less, + refined_t + >::value && + detail::has_projection_sort_iterator< + Sorter, + Iterator, + refined_t + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last), + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::move(first), std::move(last), + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const + -> std::enable_if_t< + detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value, + decltype(Sorter::operator()(std::forward(iterable), compare, + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::forward(iterable), compare, + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value && + detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + refined_t + >::value, + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), compare, + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::begin(iterable), std::end(iterable), compare, + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + refined_t + >::value && + detail::has_projection_sort< + Sorter, + Iterable, + refined_t + >::value, + decltype(Sorter::operator()(std::forward(iterable), + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::forward(iterable), + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + refined_t + >::value && + not detail::has_projection_sort< + Sorter, + Iterable, + refined_t + >::value && + detail::has_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + refined_t + >::value, + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::begin(iterable), std::end(iterable), + refined(std::move(projection))); + } +#endif + //////////////////////////////////////////////////////////// // Embed projection in comparison @@ -755,16 +1023,16 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, Iterator, - detail::projection_compare, refined_t> + projection_compare, refined_t> >::value, decltype(Sorter::operator()(first, last, - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection))))) + make_projection_compare(std::less<>{}, + refined(std::move(projection))))) > { return Sorter::operator()(first, last, - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection)))); + make_projection_compare(std::less<>{}, + refined(std::move(projection)))); } template @@ -780,17 +1048,17 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, Iterator, - detail::projection_compare< + projection_compare< refined_t, refined_t > >::value, - decltype(Sorter::operator()(first, last, detail::make_projection_compare( + decltype(Sorter::operator()(first, last, make_projection_compare( refined(std::move(compare)), refined(std::move(projection))))) > { - return Sorter::operator()(first, last, detail::make_projection_compare( + return Sorter::operator()(first, last, make_projection_compare( refined(std::move(compare)), refined(std::move(projection)))); } @@ -812,16 +1080,16 @@ namespace cppsort detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< std::less<>, refined_t > >::value, - decltype(Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + decltype(Sorter::operator()(std::forward(iterable), make_projection_compare( std::less<>{}, refined(std::move(projection))))) > { - return Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + return Sorter::operator()(std::forward(iterable), make_projection_compare( std::less<>{}, refined(std::move(projection)))); } @@ -842,7 +1110,7 @@ namespace cppsort not detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< std::less<>, refined_t > @@ -850,19 +1118,19 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, decltype(std::begin(iterable)), - detail::projection_compare< + projection_compare< std::less<>, refined_t > >::value, decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection))))) + make_projection_compare(std::less<>{}, + refined(std::move(projection))))) > { return Sorter::operator()(std::begin(iterable), std::end(iterable), - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection)))); + make_projection_compare(std::less<>{}, + refined(std::move(projection)))); } template @@ -877,17 +1145,17 @@ namespace cppsort detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< refined_t, refined_t > >::value, - decltype(Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + decltype(Sorter::operator()(std::forward(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection))))) > { - return Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + return Sorter::operator()(std::forward(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection)))); } @@ -904,7 +1172,7 @@ namespace cppsort not detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< refined_t, refined_t > @@ -912,17 +1180,17 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, decltype(std::begin(iterable)), - detail::projection_compare< + projection_compare< refined_t, refined_t > >::value, - decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), detail::make_projection_compare( + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection))))) > { - return Sorter::operator()(std::begin(iterable), std::end(iterable), detail::make_projection_compare( + return Sorter::operator()(std::begin(iterable), std::end(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection)))); } diff --git a/include/cpp-sort/sorters/counting_sorter.h b/include/cpp-sort/sorters/counting_sorter.h index 693ff7ea..ec00aa09 100644 --- a/include/cpp-sort/sorters/counting_sorter.h +++ b/include/cpp-sort/sorters/counting_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_COUNTING_SORTER_H_ @@ -61,11 +61,30 @@ namespace cppsort reverse_counting_sort(std::move(first), std::move(last)); } +#ifdef __cpp_lib_ranges + template + auto operator()(ForwardIterator first, ForwardIterator last, std::ranges::greater) const + -> std::enable_if_t< + detail::is_integral>::value + > + { + static_assert( + std::is_base_of< + std::forward_iterator_tag, + iterator_category_t + >::value, + "counting_sorter requires at least forward iterators" + ); + + reverse_counting_sort(std::move(first), std::move(last)); + } + //////////////////////////////////////////////////////////// // Sorter traits using iterator_category = std::forward_iterator_tag; using is_always_stable = std::false_type; +#endif }; } diff --git a/include/cpp-sort/sorters/default_sorter.h b/include/cpp-sort/sorters/default_sorter.h index 02463901..3f675928 100644 --- a/include/cpp-sort/sorters/default_sorter.h +++ b/include/cpp-sort/sorters/default_sorter.h @@ -30,7 +30,9 @@ namespace cppsort template<> struct stable_adapter: merge_sorter - {}; + { + using type = merge_sorter; + }; //////////////////////////////////////////////////////////// // Unstable sorter diff --git a/include/cpp-sort/sorters/quick_merge_sorter.h b/include/cpp-sort/sorters/quick_merge_sorter.h index 4544a494..49aed9dd 100644 --- a/include/cpp-sort/sorters/quick_merge_sorter.h +++ b/include/cpp-sort/sorters/quick_merge_sorter.h @@ -74,8 +74,9 @@ namespace cppsort "quick_merge_sorter requires at least forward iterators" ); - auto dist = std::distance(first, last); - quick_merge_sort(std::move(first), std::move(last), dist, + using std::distance; // Hack for sized_iterator + quick_merge_sort(std::move(first), std::move(last), + distance(first, last), std::move(compare), std::move(projection)); } diff --git a/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h b/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h index ab8f2ada..8873fb74 100644 --- a/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPREAD_SORTER_FLOAT_SPREAD_SORTER_H_ @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "../../detail/iterator_traits.h" #include "../../detail/spreadsort/float_sort.h" diff --git a/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h b/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h index 680237d0..aac66f71 100644 --- a/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPREAD_SORTER_STRING_SPREAD_SORTER_H_ @@ -148,6 +148,59 @@ namespace cppsort unused); } +#ifdef __cpp_lib_ranges + template< + typename RandomAccessIterator, + typename Projection = utility::identity + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + std::ranges::greater compare, Projection projection={}) const + -> std::enable_if_t< + std::is_same_v, std::string> + || std::is_same_v, std::string_view> + > + { + static_assert( + std::is_base_of_v< + std::random_access_iterator_tag, + iterator_category_t + >, + "string_spread_sorter requires at least random-access iterators" + ); + + unsigned char unused = '\0'; + spreadsort::reverse_string_sort(std::move(first), std::move(last), + std::move(compare), std::move(projection), + unused); + } + + template< + typename RandomAccessIterator, + typename Projection = utility::identity + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + std::ranges::greater compare, Projection projection={}) const + -> std::enable_if_t<( + std::is_same_v, std::wstring> + || std::is_same_v, std::wstring_view> + ) && (sizeof(wchar_t) == 2) + > + { + static_assert( + std::is_base_of_v< + std::random_access_iterator_tag, + iterator_category_t + >, + "string_spread_sorter requires at least random-access iterators" + ); + + std::uint16_t unused = 0; + spreadsort::reverse_string_sort(std::move(first), std::move(last), + std::move(compare), std::move(projection), + unused); + } +#endif + //////////////////////////////////////////////////////////// // Sorter traits diff --git a/include/cpp-sort/sorters/std_sorter.h b/include/cpp-sort/sorters/std_sorter.h index 8a2df537..91c0cbe1 100644 --- a/include/cpp-sort/sorters/std_sorter.h +++ b/include/cpp-sort/sorters/std_sorter.h @@ -111,6 +111,8 @@ namespace cppsort constexpr explicit stable_adapter(std_sorter) noexcept: sorter_facade() {} + + using type = stable_adapter; }; //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/sorters/verge_sorter.h b/include/cpp-sort/sorters/verge_sorter.h index 44d86e6e..7869c1ba 100644 --- a/include/cpp-sort/sorters/verge_sorter.h +++ b/include/cpp-sort/sorters/verge_sorter.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,10 +25,11 @@ namespace cppsort { //////////////////////////////////////////////////////////// - // Sorter + // Sorter implementation namespace detail { + template struct verge_sorter_impl { template< @@ -49,9 +52,9 @@ namespace cppsort "verge_sorter requires at least bidirectional iterators" ); - vergesort(std::begin(iterable), std::end(iterable), - utility::size(iterable), - std::move(compare), std::move(projection)); + verge::sort(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection)); } template< @@ -75,22 +78,36 @@ namespace cppsort ); auto size = std::distance(first, last); - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection)); + verge::sort(std::move(first), std::move(last), size, + std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// // Sorter traits using iterator_category = std::bidirectional_iterator_tag; - using is_always_stable = std::false_type; + using is_always_stable = std::integral_constant; }; } + //////////////////////////////////////////////////////////// + // Sorters + struct verge_sorter: - sorter_facade + sorter_facade> {}; + template<> + struct stable_adapter: + sorter_facade> + { + stable_adapter() = default; + + constexpr explicit stable_adapter(verge_sorter): + sorter_facade>() + {} + }; + //////////////////////////////////////////////////////////// // Sort function diff --git a/include/cpp-sort/stable_sort.h b/include/cpp-sort/stable_sort.h index 0f445804..b3df6508 100644 --- a/include/cpp-sort/stable_sort.h +++ b/include/cpp-sort/stable_sort.h @@ -26,7 +26,7 @@ namespace cppsort auto stable_sort(Iterable&& iterable) -> void { - stable_adapter{}(std::forward(iterable)); + stable_t{}(std::forward(iterable)); } template< @@ -38,7 +38,7 @@ namespace cppsort auto stable_sort(Iterable&& iterable, Compare compare) -> void { - stable_adapter{}(std::forward(iterable), std::move(compare)); + stable_t{}(std::forward(iterable), std::move(compare)); } template< @@ -54,8 +54,8 @@ namespace cppsort auto stable_sort(Iterable&& iterable, Compare compare, Projection projection) -> void { - stable_adapter{}(std::forward(iterable), - std::move(compare), std::move(projection)); + stable_t{}(std::forward(iterable), + std::move(compare), std::move(projection)); } template @@ -63,7 +63,7 @@ namespace cppsort auto stable_sort(Iterator first, Iterator last) -> void { - stable_adapter{}(std::move(first), std::move(last)); + stable_t{}(std::move(first), std::move(last)); } template< @@ -75,8 +75,7 @@ namespace cppsort auto stable_sort(Iterator first, Iterator last, Compare compare) -> void { - stable_adapter{}(std::move(first), std::move(last), - std::move(compare)); + stable_t{}(std::move(first), std::move(last), std::move(compare)); } template< @@ -92,8 +91,8 @@ namespace cppsort auto stable_sort(Iterator first, Iterator last, Compare compare, Projection projection) -> void { - return stable_adapter{}(std::move(first), std::move(last), - std::move(compare), std::move(projection)); + return stable_t{}(std::move(first), std::move(last), + std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// @@ -110,7 +109,7 @@ namespace cppsort auto stable_sort(const Sorter&, Iterable&& iterable) -> decltype(auto) { - return stable_adapter{}(std::forward(iterable)); + return stable_t{}(std::forward(iterable)); } template< @@ -126,7 +125,7 @@ namespace cppsort auto stable_sort(const Sorter&, Iterable&& iterable, Func func) -> decltype(auto) { - return stable_adapter{}(std::forward(iterable), std::move(func)); + return stable_t{}(std::forward(iterable), std::move(func)); } template< @@ -140,22 +139,22 @@ namespace cppsort Compare compare, Projection projection) -> decltype(auto) { - return stable_adapter{}(std::forward(iterable), - std::move(compare), std::move(projection)); + return stable_t{}(std::forward(iterable), + std::move(compare), std::move(projection)); } template< typename Sorter, typename Iterator, typename = std::enable_if_t, Iterator + stable_t, Iterator >> > CPPSORT_DEPRECATED("cppsort::stable_sort() is deprecated and will be removed in version 2.0.0") auto stable_sort(const Sorter&, Iterator first, Iterator last) -> decltype(auto) { - return stable_adapter{}(std::move(first), std::move(last)); + return stable_t{}(std::move(first), std::move(last)); } template< @@ -163,15 +162,15 @@ namespace cppsort typename Iterator, typename Func, typename = std::enable_if_t< - is_comparison_sorter_iterator_v, Iterator, Func> || - is_projection_sorter_iterator_v, Iterator, Func> + is_comparison_sorter_iterator_v, Iterator, Func> || + is_projection_sorter_iterator_v, Iterator, Func> > > CPPSORT_DEPRECATED("cppsort::stable_sort() is deprecated and will be removed in version 2.0.0") auto stable_sort(const Sorter&, Iterator first, Iterator last, Func func) -> decltype(auto) { - return stable_adapter{}(std::move(first), std::move(last), std::move(func)); + return stable_t{}(std::move(first), std::move(last), std::move(func)); } template< @@ -185,8 +184,8 @@ namespace cppsort Compare compare, Projection projection) -> decltype(auto) { - return stable_adapter{}(std::move(first), std::move(last), - std::move(compare), std::move(projection)); + return stable_t{}(std::move(first), std::move(last), + std::move(compare), std::move(projection)); } } diff --git a/include/cpp-sort/utility/branchless_traits.h b/include/cpp-sort/utility/branchless_traits.h index 397f6f28..2f1cc9c7 100644 --- a/include/cpp-sort/utility/branchless_traits.h +++ b/include/cpp-sort/utility/branchless_traits.h @@ -10,6 +10,7 @@ //////////////////////////////////////////////////////////// #include #include +#include "../detail/config.h" #include "../detail/type_traits.h" namespace cppsort @@ -32,6 +33,13 @@ namespace utility std::is_arithmetic {}; +#ifdef __cpp_lib_ranges + template + struct is_probably_branchless_comparison_impl: + std::is_arithmetic + {}; +#endif + template struct is_probably_branchless_comparison_impl, T>: std::is_arithmetic @@ -42,6 +50,13 @@ namespace utility std::is_arithmetic {}; +#ifdef __cpp_lib_ranges + template + struct is_probably_branchless_comparison_impl: + std::is_arithmetic + {}; +#endif + template struct is_probably_branchless_comparison_impl, T>: std::is_arithmetic @@ -83,6 +98,25 @@ namespace utility struct is_probably_branchless_projection_impl: std::is_member_object_pointer {}; + +#if defined(__GLIBCXX__) +template +struct is_probably_branchless_projection_impl, U>: + std::is_member_object_pointer +{}; +#elif defined(_LIBCPP_VERSION) + template + struct is_probably_branchless_projection_impl, U>: + std::is_member_object_pointer + {}; +#endif + +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + struct is_probably_branchless_projection_impl: + std::true_type + {}; +#endif } // Strip types from cv and reference qualifications if needed diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index 5536b524..b71358d1 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_VERSION_H_ @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 1 -#define CPPSORT_VERSION_MINOR 8 -#define CPPSORT_VERSION_PATCH 1 +#define CPPSORT_VERSION_MINOR 9 +#define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_ diff --git a/mtime_cache_globs.txt b/mtime_cache_globs.txt deleted file mode 100644 index 7b3da46f..00000000 --- a/mtime_cache_globs.txt +++ /dev/null @@ -1,3 +0,0 @@ -testsuite/**/*.{%{cpp}} -include/cpp-sort/**/*.{%{cpp}} -build/Catch2-src/single_include/catch2/catch.hpp diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index eda5d3e5..69ec9913 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -1,13 +1,21 @@ -# Copyright (c) 2015-2020 Morwenn +# Copyright (c) 2015-2021 Morwenn # SPDX-License-Identifier: MIT include(cpp-sort-utils) include(DownloadProject) # Test suite options -option(USE_VALGRIND "Whether to run the tests with Valgrind" OFF) -option(ENABLE_COVERAGE "Whether to produce code coverage" OFF) -set(SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") +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)") +option(CPPSORT_USE_VALGRIND "Whether to run the tests with Valgrind" ${USE_VALGRIND}) +option(CPPSORT_ENABLE_COVERAGE "Whether to produce code coverage" ${ENABLE_COVERAGE}) +set(CPPSORT_SANITIZE ${SANITIZE} CACHE STRING "Comma-separated list of options to pass to -fsanitize") + +# Apparently ENABLE_COVERAGE is needed either way +if (CPPSORT_ENABLE_COVERAGE) + set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) +endif() # Find & configure Catch2 for the tests message(STATUS "Looking for Catch2 2.6.0+") @@ -19,7 +27,7 @@ else() message(STATUS "Catch2 not found") download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v2.13.2 + GIT_TAG v2.13.4 UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) @@ -28,7 +36,7 @@ endif() include(Catch) macro(configure_tests target) - # Make testing tools easiyl available to tests + # Make testing tools easily available to tests # regardless of the directory of the test target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -64,18 +72,18 @@ macro(configure_tests target) endif() # Optionally enable sanitizers - if (UNIX AND SANITIZE) + if (UNIX AND CPPSORT_SANITIZE) target_compile_options(${target} PRIVATE - -fsanitize=${SANITIZE} + -fsanitize=${CPPSORT_SANITIZE} -fno-sanitize-recover=all ) set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS - " -fsanitize=${SANITIZE}" + " -fsanitize=${CPPSORT_SANITIZE}" ) endif() - if (ENABLE_COVERAGE) + if (CPPSORT_ENABLE_COVERAGE) find_package(codecov) add_coverage(${target}) @@ -99,9 +107,11 @@ add_executable(main-tests every_sorter_span.cpp is_stable.cpp rebind_iterator_category.cpp + sort_array.cpp sorter_facade.cpp sorter_facade_defaults.cpp sorter_facade_iterable.cpp + stable_sort_array.cpp # Adapters tests adapters/container_aware_adapter.cpp @@ -145,6 +155,7 @@ add_executable(main-tests distributions/ascending_sawtooth.cpp distributions/descending.cpp distributions/descending_sawtooth.cpp + distributions/median_of_3_killer.cpp distributions/pipe_organ.cpp distributions/push_front.cpp distributions/push_middle.cpp @@ -188,13 +199,13 @@ add_executable(main-tests utility/as_projection.cpp utility/as_projection_iterable.cpp utility/branchless_traits.cpp - utility/chainable_projections.cpp utility/buffer.cpp + utility/chainable_projections.cpp utility/iter_swap.cpp ) configure_tests(main-tests) -if (NOT "${SANITIZE}" MATCHES "address|memory") +if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") add_executable(heap-memory-exhaustion-tests # These tests are in a separate executable because we replace # the global [de]allocation functions in order to test the @@ -209,15 +220,14 @@ if (NOT "${SANITIZE}" MATCHES "address|memory") endif() # Configure coverage -if (ENABLE_COVERAGE) - set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) +if (CPPSORT_ENABLE_COVERAGE) list(APPEND LCOV_REMOVE_PATTERNS "'/usr/*'") coverage_evaluate() endif() # Configure Valgrind -if (USE_VALGRIND) - find_program(MEMORYCHECK_COMMAND 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") if (APPLE) set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/valgrind-osx.supp) @@ -228,6 +238,6 @@ include(CTest) string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) catch_discover_tests(main-tests EXTRA_ARGS --rng-seed ${RNG_SEED}) -if (NOT "${SANITIZE}" MATCHES "address|memory") +if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") catch_discover_tests(heap-memory-exhaustion-tests EXTRA_ARGS --rng-seed ${RNG_SEED}) endif() diff --git a/testsuite/adapters/counting_adapter.cpp b/testsuite/adapters/counting_adapter.cpp index 4d883046..46a2753e 100644 --- a/testsuite/adapters/counting_adapter.cpp +++ b/testsuite/adapters/counting_adapter.cpp @@ -4,10 +4,9 @@ */ #include #include +#include #include #include -#include -#include #include #include #include @@ -16,6 +15,9 @@ #include #include #include +#include + +using wrapper = generic_wrapper; TEST_CASE( "basic counting_adapter tests", "[counting_adapter][selection_sorter]" ) @@ -32,31 +34,25 @@ TEST_CASE( "basic counting_adapter tests", // Fill the collection std::list collection; auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 65, 0); + distribution(std::back_inserter(collection), 65); // Sort and check it's sorted std::size_t res = sorter(collection); CHECK( res == 2080 ); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } SECTION( "with projections" ) { - struct wrapper { int value; }; - - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Fill the collection - std::vector tmp(80); - helpers::iota(std::begin(tmp), std::end(tmp), 0, &wrapper::value); - std::shuffle(std::begin(tmp), std::end(tmp), engine); - std::list collection(std::begin(tmp), std::end(tmp)); + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80); // Sort and check it's sorted std::size_t res = sorter(collection, &wrapper::value); CHECK( res == 3160 ); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); } } @@ -77,7 +73,7 @@ TEST_CASE( "counting_adapter tests with std_sorter", // Sort and check it's sorted sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } } @@ -98,26 +94,20 @@ TEST_CASE( "counting_adapter with span", // Sort and check it's sorted std::size_t res = sorter(make_span(collection)); CHECK( res == 2080 ); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } SECTION( "with projections" ) { - struct wrapper { int value; }; - - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Fill the collection - std::vector tmp(80); - helpers::iota(std::begin(tmp), std::end(tmp), 0, &wrapper::value); - std::shuffle(std::begin(tmp), std::end(tmp), engine); - std::list collection(std::begin(tmp), std::end(tmp)); + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80, 0); // Sort and check it's sorted std::size_t res = sorter(make_span(collection), &wrapper::value); CHECK( res == 3160 ); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); } } diff --git a/testsuite/adapters/every_adapter_stateful_sorter.cpp b/testsuite/adapters/every_adapter_stateful_sorter.cpp index 26dca270..8ff2dd8d 100644 --- a/testsuite/adapters/every_adapter_stateful_sorter.cpp +++ b/testsuite/adapters/every_adapter_stateful_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -42,11 +42,11 @@ namespace cppsort::quick_merge_sort(std::move(first), std::move(last), std::move(compare)); if (std::is_same::value) { return 1; - } else if (std::is_same::value) { + } + if (std::is_same::value) { return 2; - } else { - return 3; } + return 3; } using iterator_category = IteratorCategory; diff --git a/testsuite/adapters/hybrid_adapter_partial_compare.cpp b/testsuite/adapters/hybrid_adapter_partial_compare.cpp index 4e38d02c..e483043a 100644 --- a/testsuite/adapters/hybrid_adapter_partial_compare.cpp +++ b/testsuite/adapters/hybrid_adapter_partial_compare.cpp @@ -101,7 +101,7 @@ TEST_CASE( "hybrid_adapter over partial comparison sorter", CHECK( res2 == sorter_type::descending ); } - SECTION( "with another functor" ) + SECTION( "with another function object" ) { sorter_type res1 = sorter(vec, std::less_equal<>{}); CHECK( res1 == sorter_type::generic ); diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index 929020e4..c84e097f 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -6,24 +6,21 @@ #include #include #include -#include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include #include #include +#include #include #include #include +#include #include #include +#include namespace { @@ -94,12 +91,11 @@ TEST_CASE( "hybrid_adapter over adapters that change iterator category", TEST_CASE( "indirect sort with Schwartzian transform", "[indirect_adapter][schwartz_adapter]" ) { - struct wrapper { short value; }; + using wrapper = generic_wrapper; - std::vector collection(334); - helpers::iota(std::begin(collection), std::end(collection), -93, &wrapper::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 334, -93); SECTION( "schwartz_adapter over indirect_adapter" ) { @@ -208,3 +204,22 @@ TEST_CASE( "stability of counting_adapter over self_sort_adapter", CHECK( cppsort::is_stable::iterator, std::vector::iterator, std::negate<>)>::value ); } } + +TEST_CASE( "stable_adapter over verge_adapter", + "[stable_adapter][verge_adapter]" ) +{ + using wrapper = generic_stable_wrapper; + std::vector collection; + auto distribution = dist::descending_plateau{}; + distribution(std::back_inserter(collection), 400); + helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); + + cppsort::stable_adapter< + cppsort::verge_adapter< + cppsort::grail_sorter<> + > + > sorter; + + sorter(collection, &wrapper::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end()) ); +} diff --git a/testsuite/adapters/return_forwarding.cpp b/testsuite/adapters/return_forwarding.cpp index ccc85d79..f2064c97 100644 --- a/testsuite/adapters/return_forwarding.cpp +++ b/testsuite/adapters/return_forwarding.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2020 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -10,6 +10,7 @@ #include #include #include +#include namespace { diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index 1d587767..838e2fe7 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -14,16 +14,14 @@ #include #include #include +#include +#include -namespace -{ - // double segfaults with MinGW-w64 64 bits in Release mode - template - struct wrapper - { - T value; - }; -} +// NOTE: this test used to use wrapper, but it was later +// switched to wrapper because the former segfaults +// with MinGW-w64 64 bits in Release mode +template +using wrapper = generic_wrapper; TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapter", "[schwartz_adapter]", cppsort::block_sorter>, @@ -45,10 +43,9 @@ TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapt cppsort::tim_sorter, cppsort::verge_sorter ) { - std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::vector> collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; sorter(collection, &wrapper<>::value); @@ -65,15 +62,14 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with Schwartzian transform adapt cppsort::selection_sorter, cppsort::verge_sorter ) { - std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::list> collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 412, -125); - std::list> li(collection.begin(), collection.end()); cppsort::schwartz_adapter sorter; - sorter(li, &wrapper<>::value); - CHECK( helpers::is_sorted(li.begin(), li.end(), std::less<>{}, &wrapper<>::value) ); + sorter(collection, &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper<>::value) ); } TEMPLATE_TEST_CASE( "every forward sorter with Schwartzian transform adapter", "[schwartz_adapter]", @@ -82,31 +78,31 @@ TEMPLATE_TEST_CASE( "every forward sorter with Schwartzian transform adapter", " cppsort::quick_sorter, cppsort::selection_sorter ) { - std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::forward_list> collection; + auto distribution = dist::shuffled{}; + distribution(std::front_inserter(collection), 412, -125); - std::forward_list> fli(collection.begin(), collection.end()); cppsort::schwartz_adapter sorter; - sorter(fli, &wrapper<>::value); - CHECK( helpers::is_sorted(fli.begin(), fli.end(), std::less<>{}, &wrapper<>::value) ); + sorter(collection, &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper<>::value) ); } TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwartz_adapter]" ) { + auto distribution = dist::shuffled{}; + std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); + distribution(std::back_inserter(collection), 412, -125); + std::vector> collection2(412); - helpers::iota(collection2.begin(), collection2.end(), -125, &wrapper::value); + distribution(std::back_inserter(collection2), 412, -125); + std::vector> collection3; for (int i = -125 ; i < 287 ; ++i) { - collection3.push_back({std::to_string(i)}); + collection3.emplace_back(std::to_string(i)); } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); - std::shuffle(collection2.begin(), collection2.end(), engine); std::shuffle(collection3.begin(), collection3.end(), engine); SECTION( "ska_sorter" ) diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index 11946254..63bf43ad 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -1,27 +1,26 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include -#include +#include #include #include #include +#include #include #include #include #include #include +#include +#include -namespace -{ - // double segfaults with MinGW-w64 64 bits in Release mode - template - struct wrapper - { - T value; - }; -} +// NOTE: this test used to use wrapper, but it was later +// switched to wrapper because the former segfaults +// with MinGW-w64 64 bits in Release mode +template +using wrapper = generic_wrapper; TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse iterators", "[schwartz_adapter][reverse_iterator]", @@ -44,46 +43,48 @@ TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse cppsort::tim_sorter, cppsort::verge_sorter ) { - std::vector> collection(412); - helpers::iota(std::begin(collection), std::end(collection), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector> collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; - sorter(std::rbegin(collection), std::rend(collection), &wrapper<>::value); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + sorter(collection.rbegin(), collection.rend(), &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::greater<>{}, &wrapper<>::value) ); } TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse iterators", "[schwartz_adapter][reverse_iterator]" ) { - std::vector> collection(412); - helpers::iota(std::begin(collection), std::end(collection), -125, &wrapper<>::value); - std::vector> collection2(412); - helpers::iota(std::begin(collection2), std::end(collection2), -125, &wrapper::value); - std::vector> collection3; - for (int i = -125 ; i < 287 ; ++i) { collection3.push_back({std::to_string(i)}); } + auto distribution = dist::shuffled{}; + std::vector> collection; + distribution(std::back_inserter(collection), 412, -125); + + std::vector> collection2; + distribution(std::back_inserter(collection2), 412, -125); + + std::vector> collection3; + for (int i = -125 ; i < 287 ; ++i) { + collection3.emplace_back(std::to_string(i)); + } std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); - std::shuffle(std::begin(collection2), std::end(collection2), engine); - std::shuffle(std::begin(collection3), std::end(collection3), engine); + std::shuffle(collection3.begin(), collection3.end(), engine); SECTION( "ska_sorter" ) { cppsort::schwartz_adapter sorter; - sorter(std::rbegin(collection), std::rend(collection), &wrapper<>::value); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + sorter(collection.rbegin(), collection.rend(), &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::greater<>{}, &wrapper<>::value) ); - sorter(std::rbegin(collection2), std::rend(collection2), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection2), std::end(collection2), + sorter(collection2.rbegin(), collection2.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection2.begin(), collection2.end(), std::greater<>{}, &wrapper::value) ); - sorter(std::rbegin(collection3), std::rend(collection3), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection3), std::end(collection3), + sorter(collection3.rbegin(), collection3.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); } @@ -91,22 +92,22 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse { cppsort::schwartz_adapter sorter; - sorter(std::rbegin(collection), std::rend(collection), &wrapper<>::value); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + sorter(collection.rbegin(), collection.rend(), &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::greater<>{}, &wrapper<>::value) ); - sorter(std::rbegin(collection2), std::rend(collection2), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection2), std::end(collection2), + sorter(collection2.rbegin(), collection2.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection2.begin(), collection2.end(), std::greater<>{}, &wrapper::value) ); - sorter(std::rbegin(collection3), std::rend(collection3), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection3), std::end(collection3), + sorter(collection3.rbegin(), collection3.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection3), std::end(collection3), engine); - sorter(std::rbegin(collection3), std::rend(collection3), + std::shuffle(collection3.begin(), collection3.end(), engine); + sorter(collection3.rbegin(), collection3.rend(), std::greater<>{}, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection3), std::end(collection3), + CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::less<>{}, &wrapper::value) ); } } diff --git a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp index b14f2712..acef6084 100644 --- a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp +++ b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp @@ -11,12 +11,13 @@ #include #include #include +#include + +using wrapper = generic_wrapper; TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", "[schwartz_adapter]" ) { - struct wrapper { double value; }; - auto&& low_comparisons_sort = cppsort::schwartz_adapter< cppsort::small_array_adapter< cppsort::low_comparisons_sorter @@ -162,8 +163,6 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", TEST_CASE( "stability of Schwartzian transform adapter with fixed-size sorters", "[schwartz_adapter][is_stable]" ) { - struct wrapper { double value; }; - using namespace cppsort; using sorter = schwartz_adapter< small_array_adapter< diff --git a/testsuite/adapters/small_array_adapter.cpp b/testsuite/adapters/small_array_adapter.cpp index 1ff78616..fa748721 100644 --- a/testsuite/adapters/small_array_adapter.cpp +++ b/testsuite/adapters/small_array_adapter.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace { @@ -174,7 +175,7 @@ TEST_CASE( "small array adapter", SECTION( "with domain and projections" ) { - struct wrapper { int value; }; + using wrapper = generic_wrapper; std::array collection; cppsort::small_array_adapter< diff --git a/testsuite/adapters/stable_adapter_every_sorter.cpp b/testsuite/adapters/stable_adapter_every_sorter.cpp index d663d8ed..a1757675 100644 --- a/testsuite/adapters/stable_adapter_every_sorter.cpp +++ b/testsuite/adapters/stable_adapter_every_sorter.cpp @@ -3,37 +3,28 @@ * SPDX-License-Identifier: MIT */ #include -#include #include #include -#include #include #include #include #include #include #include +#include +#include -namespace -{ - struct wrapper - { - int value; - int order; - }; +using wrapper = generic_stable_wrapper; - auto operator<(const wrapper& lhs, const wrapper& rhs) - -> bool - { - if (lhs.value < rhs.value) { - return true; - } - if (rhs.value < lhs.value) { - return false; - } - return lhs.order < rhs.order; - } -} +// Those tests do not prove that the algorithms sort stably, +// but are meant to sometimes identify cases where they don't +// +// In order to achieve that, each element is associated to +// its original position in the collection to sort, then the +// collection is sorted accorded to its "value" and we check +// that the collection is indeed sorted, but also that +// the original position of elements with equivalent values +// are also in ascending order TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_adapter]", cppsort::block_sorter>, @@ -56,18 +47,25 @@ TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_a cppsort::tim_sorter, cppsort::verge_sorter ) { + cppsort::stable_t sorter; std::vector collection(412); - std::size_t count = 0; - for (wrapper& wrap: collection) { - wrap.value = count++ % 17; - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - cppsort::stable_adapter sorter; - sorter(collection, &wrapper::value); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); + SECTION( "shuffled_16_values" ) + { + auto distribution = dist::shuffled_16_values{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "descending_plateau" ) + { + auto distribution = dist::descending_plateau{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_adapter]", @@ -79,19 +77,25 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_a cppsort::selection_sorter, cppsort::verge_sorter ) { - std::vector collection(412); - std::size_t count = 0; - for (wrapper& wrap: collection) { - wrap.value = count++ % 17; - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + cppsort::stable_t sorter; + std::list collection(412); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - std::list li(collection.begin(), collection.end()); - cppsort::stable_adapter sorter; - sorter(li, &wrapper::value); - CHECK( std::is_sorted(li.begin(), li.end()) ); + SECTION( "shuffled_16_values" ) + { + auto distribution = dist::shuffled_16_values{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "descending_plateau" ) + { + auto distribution = dist::descending_plateau{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_adapter]", @@ -100,17 +104,24 @@ TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_ad cppsort::quick_sorter, cppsort::selection_sorter ) { - std::vector collection(412); - std::size_t count = 0; - for (wrapper& wrap: collection) { - wrap.value = count++ % 17; - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + cppsort::stable_t sorter; + const int size = 412; + std::forward_list collection(size); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - std::forward_list fli(collection.begin(), collection.end()); - cppsort::stable_adapter sorter; - sorter(fli, &wrapper::value); - CHECK( std::is_sorted(fli.begin(), fli.end()) ); + SECTION( "shuffled_16_values" ) + { + auto distribution = dist::shuffled_16_values{}; + distribution(collection.begin(), size); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "descending_plateau" ) + { + auto distribution = dist::descending_plateau{}; + distribution(collection.begin(), size); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } diff --git a/testsuite/comparators/case_insensitive_less.cpp b/testsuite/comparators/case_insensitive_less.cpp index e480d7c9..b015683a 100644 --- a/testsuite/comparators/case_insensitive_less.cpp +++ b/testsuite/comparators/case_insensitive_less.cpp @@ -26,7 +26,7 @@ namespace sub return res_t::without_locale; } - inline auto case_insensitive_less(const foo&, const foo&, const std::locale) + inline auto case_insensitive_less(const foo&, const foo&, const std::locale&) -> res_t { return res_t::with_locale; diff --git a/testsuite/distributions/median_of_3_killer.cpp b/testsuite/distributions/median_of_3_killer.cpp new file mode 100644 index 00000000..2380ad9d --- /dev/null +++ b/testsuite/distributions/median_of_3_killer.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "test random-access sorters with median_of_3_killer distribution", "[distributions]", + cppsort::block_sorter<>, + cppsort::block_sorter< + cppsort::utility::dynamic_buffer + >, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::grail_sorter< + cppsort::utility::dynamic_buffer + >, + cppsort::heap_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::ska_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::spread_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::verge_sorter ) +{ + std::vector collection; + collection.reserve(1000); + auto distribution = dist::median_of_3_killer{}; + distribution(std::back_inserter(collection), 1000); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} + +TEMPLATE_TEST_CASE( "test bidirectional sorters with median_of_3_killer distribution", "[distributions]", + cppsort::drop_merge_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::verge_sorter ) +{ + std::list collection; + auto distribution = dist::median_of_3_killer{}; + distribution(std::back_inserter(collection), 1000); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} diff --git a/testsuite/every_sorter_no_post_iterator.cpp b/testsuite/every_sorter_no_post_iterator.cpp index 227f16f5..baf602e5 100644 --- a/testsuite/every_sorter_no_post_iterator.cpp +++ b/testsuite/every_sorter_no_post_iterator.cpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include #include +#include #include TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", @@ -35,19 +37,16 @@ TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", cppsort::verge_sorter ) { std::vector collection; - for (int i = 56 ; i < 366 ; ++i) { - collection.emplace_back(i); - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 310, -56); // Iterators with no post-increment and no post-decrement - auto first = make_no_post_iterator(std::begin(collection)); - auto last = make_no_post_iterator(std::end(collection)); + auto first = make_no_post_iterator(collection.begin()); + auto last = make_no_post_iterator(collection.end()); TestType sorter; sorter(first, last); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", @@ -56,44 +55,39 @@ TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", cppsort::spread_sorter ) { TestType sorter; + auto distribution = dist::shuffled{}; std::vector collection_float; - for (float i = 56.0f ; i < 366.0f ; ++i) { - collection_float.emplace_back(i); - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection_float), std::end(collection_float), engine); + distribution(std::back_inserter(collection_float), 310, -56); // Iterators with no post-increment and no post-decrement - auto first_float = make_no_post_iterator(std::begin(collection_float)); - auto last_float = make_no_post_iterator(std::end(collection_float)); + auto first_float = make_no_post_iterator(collection_float.begin()); + auto last_float = make_no_post_iterator(collection_float.end()); sorter(first_float, last_float); - CHECK( std::is_sorted(std::begin(collection_float), std::end(collection_float)) ); + CHECK( std::is_sorted(collection_float.begin(), collection_float.end()) ); std::vector collection_double; - for (double i = 56.0 ; i < 366.0 ; ++i) { - collection_double.emplace_back(i); - } - std::shuffle(std::begin(collection_double), std::end(collection_double), engine); + distribution(std::back_inserter(collection_double), 310, -56); // Iterators with no post-increment and no post-decrement - auto first_double = make_no_post_iterator(std::begin(collection_double)); - auto last_double = make_no_post_iterator(std::end(collection_double)); + auto first_double = make_no_post_iterator(collection_double.begin()); + auto last_double = make_no_post_iterator(collection_double.end()); sorter(first_double, last_double); - CHECK( std::is_sorted(std::begin(collection_double), std::end(collection_double)) ); + CHECK( std::is_sorted(collection_double.begin(), collection_double.end()) ); std::vector collection_str; for (long i = 56 ; i < 366 ; ++i) { collection_str.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(collection_str), std::end(collection_str), engine); + std::mt19937 engine(Catch::rngSeed()); + std::shuffle(collection_str.begin(), collection_str.end(), engine); // Iterators with no post-increment and no post-decrement - auto first_str = make_no_post_iterator(std::begin(collection_str)); - auto last_str = make_no_post_iterator(std::end(collection_str)); + auto first_str = make_no_post_iterator(collection_str.begin()); + auto last_str = make_no_post_iterator(collection_str.end()); sorter(first_str, last_str); - CHECK( std::is_sorted(std::begin(collection_str), std::end(collection_str)) ); + CHECK( std::is_sorted(collection_str.begin(), collection_str.end()) ); } diff --git a/testsuite/sorter_facade.cpp b/testsuite/sorter_facade.cpp index 97f557a3..c75649a2 100644 --- a/testsuite/sorter_facade.cpp +++ b/testsuite/sorter_facade.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace { @@ -69,7 +70,7 @@ TEST_CASE( "sorter_facade miscellaneous checks", // Some checks to make sure that sorter_facade always // forwards the value correctly in the most common cases - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to "sort" std::vector vec; diff --git a/testsuite/sorter_facade_iterable.cpp b/testsuite/sorter_facade_iterable.cpp index c56fad0c..02aa23e1 100644 --- a/testsuite/sorter_facade_iterable.cpp +++ b/testsuite/sorter_facade_iterable.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace { @@ -105,7 +106,7 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables", // of a pair of iterators. We need to make sure that these // optimizations work too. - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to "sort" std::vector vec; diff --git a/testsuite/sorters/default_sorter_projection.cpp b/testsuite/sorters/default_sorter_projection.cpp index 723e6260..1c61846a 100644 --- a/testsuite/sorters/default_sorter_projection.cpp +++ b/testsuite/sorters/default_sorter_projection.cpp @@ -1,32 +1,29 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include #include -#include #include #include #include #include #include +#include +#include TEST_CASE( "default sorter tests with projections", "[default_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with random-access iterable" ) { diff --git a/testsuite/sorters/merge_insertion_sorter_projection.cpp b/testsuite/sorters/merge_insertion_sorter_projection.cpp index 587c55cd..1d983ccf 100644 --- a/testsuite/sorters/merge_insertion_sorter_projection.cpp +++ b/testsuite/sorters/merge_insertion_sorter_projection.cpp @@ -2,28 +2,25 @@ * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include -#include #include #include #include #include +#include +#include TEST_CASE( "merge_insertion_sorter tests with projections", "[merge_insertion_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with random-access iterable" ) { diff --git a/testsuite/sorters/merge_sorter_projection.cpp b/testsuite/sorters/merge_sorter_projection.cpp index 89260c4e..9b914871 100644 --- a/testsuite/sorters/merge_sorter_projection.cpp +++ b/testsuite/sorters/merge_sorter_projection.cpp @@ -2,30 +2,27 @@ * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include #include -#include #include #include #include #include +#include +#include TEST_CASE( "merge_sorter tests with projections", "[merge_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with random-access iterable" ) { diff --git a/testsuite/sorters/ska_sorter.cpp b/testsuite/sorters/ska_sorter.cpp index 4f5a8ab4..8bd02722 100644 --- a/testsuite/sorters/ska_sorter.cpp +++ b/testsuite/sorters/ska_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -15,57 +14,60 @@ #include #include #include +#include TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); + auto distribution = dist::shuffled{}; SECTION( "sort with int iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } + + SECTION( "sort with unsigned int iterators" ) + { + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::ska_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } #ifdef __SIZEOF_INT128__ - SECTION( "sort with unsigned int128 iterable" ) + SECTION( "sort with int128 iterable" ) { - std::vector<__uint128_t> vec(100'000); - std::iota(std::begin(vec), std::end(vec), __uint128_t(0)); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector<__int128_t> vec; + distribution(std::back_inserter(vec), 100'000, -10'000); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } -#endif - SECTION( "sort with unsigned int iterators" ) + SECTION( "sort with unsigned int128 iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0u); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::ska_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector<__uint128_t> vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::ska_sort(vec); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } +#endif SECTION( "sort with float iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0f); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with double iterators" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::ska_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::ska_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with std::string" ) @@ -75,13 +77,16 @@ TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + // Pseudo-random number engine + std::mt19937_64 engine(Catch::rngSeed()); + + std::shuffle(vec.begin(), vec.end(), engine); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::ska_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::shuffle(vec.begin(), vec.end(), engine); + cppsort::ska_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } } diff --git a/testsuite/sorters/ska_sorter_projection.cpp b/testsuite/sorters/ska_sorter_projection.cpp index 978a6846..00f14265 100644 --- a/testsuite/sorters/ska_sorter_projection.cpp +++ b/testsuite/sorters/ska_sorter_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -11,6 +11,7 @@ #include #include #include +#include TEST_CASE( "ska_sorter tests with projections", "[ska_sorter][projection]" ) @@ -68,11 +69,11 @@ TEST_CASE( "ska_sorter tests with projections", SECTION( "sort with std::string iterators" ) { - struct wrapper { std::string value; }; + using wrapper = generic_wrapper; std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.push_back({std::to_string(i)}); + vec.emplace_back(std::to_string(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::ska_sort(vec, &wrapper::value); diff --git a/testsuite/sorters/spread_sorter.cpp b/testsuite/sorters/spread_sorter.cpp index f5348a6d..130ca5a7 100644 --- a/testsuite/sorters/spread_sorter.cpp +++ b/testsuite/sorters/spread_sorter.cpp @@ -4,52 +4,50 @@ */ #include #include -#include #include #include #include #include #include +#include TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) { // Pseudo-random number engine std::mt19937_64 engine(Catch::rngSeed()); + auto distribution = dist::shuffled{}; + SECTION( "sort with int iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::spread_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with unsigned int iterators" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0u); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::spread_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with float iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0f); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::spread_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with double iterators" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::spread_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with std::string" ) @@ -59,13 +57,13 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(vec.begin(), vec.end(), engine); cppsort::spread_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::shuffle(vec.begin(), vec.end(), engine); + cppsort::spread_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "reverse sort with std::string" ) @@ -75,12 +73,12 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(vec.begin(), vec.end(), engine); cppsort::spread_sort(vec, std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec), std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + std::shuffle(vec.begin(), vec.end(), engine); + cppsort::spread_sort(vec.begin(), vec.end(), std::greater<>{}); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } } diff --git a/testsuite/sorters/spread_sorter_projection.cpp b/testsuite/sorters/spread_sorter_projection.cpp index 4f3f9e26..5b181cdb 100644 --- a/testsuite/sorters/spread_sorter_projection.cpp +++ b/testsuite/sorters/spread_sorter_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -11,6 +11,7 @@ #include #include #include +#include TEST_CASE( "spread_sorter tests with projections", "[spread_sorter][projection]" ) @@ -68,11 +69,11 @@ TEST_CASE( "spread_sorter tests with projections", SECTION( "sort with std::string iterators" ) { - struct wrapper { std::string value; }; + using wrapper = generic_wrapper; std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.push_back({std::to_string(i)}); + vec.emplace_back(std::to_string(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, &wrapper::value); @@ -82,11 +83,11 @@ TEST_CASE( "spread_sorter tests with projections", SECTION( "reverse sort with std::string iterators" ) { - struct wrapper { std::string value; }; + using wrapper = generic_wrapper; std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.push_back({std::to_string(i)}); + vec.emplace_back(std::to_string(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, std::greater<>{}, &wrapper::value); diff --git a/testsuite/sorters/std_sorter.cpp b/testsuite/sorters/std_sorter.cpp index 0c348cf0..5062a519 100644 --- a/testsuite/sorters/std_sorter.cpp +++ b/testsuite/sorters/std_sorter.cpp @@ -5,97 +5,85 @@ #include #include #include -#include -#include #include #include #include #include +#include +#include TEST_CASE( "std_sorter tests", "[std_sorter]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - - // Collection to sort - std::vector vec(80); - std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with iterable" ) { cppsort::std_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with iterable and compare" ) { cppsort::std_sort(vec, std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } SECTION( "sort with iterators" ) { - cppsort::std_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::std_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with iterators and compare" ) { - cppsort::std_sort(std::begin(vec), std::end(vec), std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::std_sort(vec.begin(), vec.end(), std::greater<>{}); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } } TEST_CASE( "std_sorter tests with projections", "[std_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; - // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with iterable" ) { cppsort::std_sort(vec, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } SECTION( "sort with iterable and compare" ) { cppsort::std_sort(vec, std::greater<>{}, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } SECTION( "sort with iterators" ) { - cppsort::std_sort(std::begin(vec), std::end(vec), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + cppsort::std_sort(vec.begin(), vec.end(), &wrapper::value); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } SECTION( "sort with iterators and compare" ) { - cppsort::std_sort(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + cppsort::std_sort(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } } TEST_CASE( "stable_adapter tests", "[std_sorter][stable_adapter]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - - // Collection to sort - std::vector vec(80); - std::iota(vec.begin(), vec.end(), 0); - std::shuffle(vec.begin(), vec.end(), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); auto sort = cppsort::stable_adapter(cppsort::std_sort); sort(vec); diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 715cf83e..c2ecca36 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_TESTSUITE_DISTRIBUTIONS_H_ @@ -218,6 +218,54 @@ namespace dist } } }; + + struct descending_plateau: + distribution + { + template + auto operator()(OutputIterator out, std::size_t size) const + -> void + { + std::size_t i = size; + while (i > 2 * size / 3) { + *out++ = i; + --i; + } + while (i > size / 3) { + *out++ = size / 2; + --i; + } + while (i > 0) { + *out++ = i; + --i; + } + } + }; + + struct median_of_3_killer: + distribution + { + // This distribution comes from *A Killer Adversary for Quicksort* + // by M. D. McIlroy, and is supposed to trick several quicksort + // implementations with common pivot selection methods go quadratic + + template + auto operator()(OutputIterator out, std::size_t size) const + -> void + { + std::size_t j = size / 2; + for (std::size_t 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) { + *out++ = 2 * i; + } + } + }; } #endif // CPPSORT_TESTSUITE_DISTRIBUTIONS_H_ diff --git a/testsuite/testing-tools/wrapper.h b/testsuite/testing-tools/wrapper.h new file mode 100644 index 00000000..8ed89a18 --- /dev/null +++ b/testsuite/testing-tools/wrapper.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_TESTSUITE_WRAPPER_H_ +#define CPPSORT_TESTSUITE_WRAPPER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +//////////////////////////////////////////////////////////// +// Wrapper around a simple type +// +// This wrapper with a public value is mostly used to test +// pointer to data members used a projections throughout +// the test suite + +template +struct generic_wrapper +{ + T value; + + generic_wrapper() = default; + generic_wrapper(const generic_wrapper&) = default; + generic_wrapper(generic_wrapper&&) = default; + + constexpr generic_wrapper(const T& other_value): + value(other_value) + {} + + constexpr generic_wrapper(T&& other_value): + value(std::move(other_value)) + {} + + generic_wrapper& operator=(const generic_wrapper&) = default; + generic_wrapper& operator=(generic_wrapper&&) = default; + + constexpr auto operator=(const T& other_value) + -> generic_wrapper& + { + value = other_value; + return *this; + } + + constexpr auto operator=(T&& other_value) + -> generic_wrapper& + { + value = std::move(other_value); + return *this; + } +}; + +//////////////////////////////////////////////////////////// +// Wrapper with an "order" +// +// This wrapper has an additional integral field meant to +// attach more information to it than the value it wraps, +// information which can be used to try to detect issues in +// stable sorting algorithms + +template +struct generic_stable_wrapper +{ + T value; + int order; + + generic_stable_wrapper() = default; + generic_stable_wrapper(const generic_stable_wrapper&) = default; + generic_stable_wrapper(generic_stable_wrapper&&) = default; + + constexpr generic_stable_wrapper(const T& other_value): + value(other_value) + {} + + constexpr generic_stable_wrapper(T&& other_value): + value(std::move(other_value)) + {} + + generic_stable_wrapper& operator=(const generic_stable_wrapper&) = default; + generic_stable_wrapper& operator=(generic_stable_wrapper&&) = default; + + constexpr auto operator=(const T& other_value) + -> generic_stable_wrapper& + { + value = other_value; + return *this; + } + + constexpr auto operator=(T&& other_value) + -> generic_stable_wrapper& + { + value = std::move(other_value); + return *this; + } + + friend constexpr auto operator==(const generic_stable_wrapper& lhs, const generic_stable_wrapper& rhs) + -> bool + { + return lhs.value == rhs.value + && lhs.order == rhs.order; + } + + friend constexpr auto operator<(const generic_stable_wrapper& lhs, const generic_stable_wrapper& rhs) + -> bool + { + if (lhs.value < rhs.value) { + return true; + } + if (rhs.value < lhs.value) { + return false; + } + return lhs.order < rhs.order; + } +}; + + +#endif // CPPSORT_TESTSUITE_WRAPPER_H_ diff --git a/testsuite/utility/as_projection.cpp b/testsuite/utility/as_projection.cpp index 0eaa9667..1e7413c0 100644 --- a/testsuite/utility/as_projection.cpp +++ b/testsuite/utility/as_projection.cpp @@ -1,19 +1,18 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #include #include #include -#include -#include #include #include #include -#include +#include +#include #include -#include #include +#include namespace { @@ -40,119 +39,120 @@ namespace TEST_CASE( "try mixed comparison/projection function object", "[utility][as_projection]" ) { - std::vector collection(100); - std::iota(std::begin(collection), std::end(collection), 0); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 100); tricky_function func; + cppsort::default_sorter sorter; + cppsort::stable_adapter stable_sorter; SECTION( "without an untransformed function" ) { auto vec = collection; - cppsort::sort(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + sorter(vec, func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::sort(std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + sorter(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; cppsort::pdq_sort(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::pdq_sort(vec, func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::pdq_sort(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + stable_sorter(vec, func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + stable_sorter(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::stable_adapter{}(vec, func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::stable_adapter{}(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } SECTION( "with a function wrapped in as_projection" ) { auto vec = collection; - cppsort::sort(vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + sorter(vec, cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + sorter(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::pdq_sort(vec, cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::pdq_sort(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::stable_sort(vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + stable_sorter(vec, cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::stable_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + stable_sorter(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::stable_adapter{}(vec, cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::stable_adapter{}(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "with a function wrapped in as_comparison" ) { auto vec = collection; - cppsort::sort(vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + sorter(vec, cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + sorter(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::pdq_sort(vec, cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::pdq_sort(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + stable_sorter(vec, cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + stable_sorter(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::stable_adapter{}(vec, cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::stable_adapter{}(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } } diff --git a/testsuite/utility/as_projection_iterable.cpp b/testsuite/utility/as_projection_iterable.cpp index 562ecba7..dccf6bae 100644 --- a/testsuite/utility/as_projection_iterable.cpp +++ b/testsuite/utility/as_projection_iterable.cpp @@ -5,16 +5,15 @@ #include #include #include -#include -#include #include #include #include #include -#include #include #include +#include #include +#include namespace { @@ -55,7 +54,7 @@ namespace auto operator()(Iterator first, Iterator last, Compare compare={}) const -> call { - cppsort::sort(first, last, compare); + cppsort::selection_sort(first, last, compare); return call::iterator; } @@ -69,7 +68,7 @@ namespace auto operator()(Iterable& iterable, Compare compare={}) const -> call { - cppsort::sort(iterable, compare); + cppsort::selection_sort(iterable, compare); return call::iterable; } }; @@ -87,7 +86,7 @@ namespace -> call { // Use as_projection to make an actual projection-only sorter - cppsort::sort(first, last, cppsort::utility::as_projection(projection)); + cppsort::selection_sort(first, last, cppsort::utility::as_projection(projection)); return call::iterator; } @@ -102,7 +101,7 @@ namespace -> call { // Use as_projection to make an actual projection-only sorter - cppsort::sort(iterable, cppsort::utility::as_projection(projection)); + cppsort::selection_sort(iterable, cppsort::utility::as_projection(projection)); return call::iterable; } }; @@ -123,11 +122,9 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables and mixed compar // as_projection, as_comparison and some additional sorter_facade // overloads - // Collection to sort - std::vector collection(100); - std::iota(std::begin(collection), std::end(collection), 0); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 100); auto vec = collection; tricky_function func; @@ -136,83 +133,79 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables and mixed compar SECTION( "comparison_sorter" ) { - auto res1 = cppsort::sort(comp_sort, vec, func); + auto res1 = comp_sort(vec, func); CHECK( res1 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res2 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), func); + auto res2 = comp_sort(vec.begin(), vec.end(), func); CHECK( res2 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res3 = cppsort::sort(comp_sort, vec, cppsort::utility::as_comparison(func)); + auto res3 = comp_sort(vec, cppsort::utility::as_comparison(func)); CHECK( res3 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res4 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_comparison(func)); + auto res4 = comp_sort(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); CHECK( res4 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res5 = cppsort::sort(comp_sort, vec, cppsort::utility::as_projection(func)); + auto res5 = comp_sort(vec, cppsort::utility::as_projection(func)); CHECK( res5 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res6 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_projection(func)); + auto res6 = comp_sort(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); CHECK( res6 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res7 = cppsort::sort(comp_sort, vec, func, - cppsort::utility::as_projection(func)); + auto res7 = comp_sort(vec, func, cppsort::utility::as_projection(func)); CHECK( res7 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res8 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), func, - cppsort::utility::as_projection(func)); + auto res8 = comp_sort(vec.begin(), vec.end(), func, + cppsort::utility::as_projection(func)); CHECK( res8 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res9 = cppsort::sort(comp_sort, vec, cppsort::utility::as_comparison(func), - cppsort::utility::as_projection(func)); + auto res9 = comp_sort(vec, cppsort::utility::as_comparison(func), + cppsort::utility::as_projection(func)); CHECK( res9 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res10 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_comparison(func), - cppsort::utility::as_projection(func)); + auto res10 = comp_sort(vec.begin(), vec.end(), + cppsort::utility::as_comparison(func), + cppsort::utility::as_projection(func)); CHECK( res10 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } SECTION( "projection_sorter" ) { - auto res1 = cppsort::sort(proj_sort, vec, cppsort::utility::as_projection(func)); + auto res1 = proj_sort(vec, cppsort::utility::as_projection(func)); CHECK( res1 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res2 = cppsort::sort(proj_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_projection(func)); + auto res2 = proj_sort(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); CHECK( res2 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res3 = cppsort::sort(proj_sort, vec, func); + auto res3 = proj_sort(vec, func); CHECK( res3 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res4 = cppsort::sort(proj_sort, std::begin(vec), std::end(vec), func); + auto res4 = proj_sort(vec.begin(), vec.end(), func); CHECK( res4 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } } diff --git a/testsuite/utility/branchless_traits.cpp b/testsuite/utility/branchless_traits.cpp index 68c96ed4..00c72075 100644 --- a/testsuite/utility/branchless_traits.cpp +++ b/testsuite/utility/branchless_traits.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -18,16 +18,33 @@ TEST_CASE( "test that some specific comparisons are branchless", SECTION( "standard library function objects" ) { - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, long double>::value )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, long double> )); +#ifdef __cpp_lib_ranges + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); +#endif - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, long double>::value )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, long double> )); +#ifdef __cpp_lib_ranges + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); +#endif - CHECK_FALSE(( is_probably_branchless_comparison, std::string>::value )); - CHECK_FALSE(( is_probably_branchless_comparison, std::string>::value )); + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); +#ifdef __cpp_lib_ranges + CHECK_FALSE(( is_probably_branchless_comparison_v )); +#endif + + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); +#ifdef __cpp_lib_ranges + CHECK_FALSE(( is_probably_branchless_comparison_v )); +#endif } SECTION( "partial/weak/less function objects" ) @@ -36,24 +53,27 @@ TEST_CASE( "test that some specific comparisons are branchless", using weak_t = decltype(cppsort::weak_less); using total_t = decltype(cppsort::total_less); - CHECK(( is_probably_branchless_comparison::value )); - CHECK(( is_probably_branchless_comparison::value )); - CHECK(( is_probably_branchless_comparison::value )); + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); - CHECK(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); + CHECK(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); } SECTION( "cv-qualified and reference-qualified types" ) { - CHECK(( is_probably_branchless_comparison, const int>::value )); - CHECK(( is_probably_branchless_comparison&, int>::value )); - CHECK(( is_probably_branchless_comparison, int&&>::value )); + CHECK(( is_probably_branchless_comparison_v, const int> )); + CHECK(( is_probably_branchless_comparison_v&, int> )); + CHECK(( is_probably_branchless_comparison_v, int&&> )); +#ifdef __cpp_lib_ranges + CHECK(( is_probably_branchless_comparison_v )); +#endif } } @@ -68,8 +88,16 @@ TEST_CASE( "test that some specific projections are branchless", int bar() { return 0; } }; - CHECK(( is_probably_branchless_projection::value )); + CHECK(( is_probably_branchless_projection_v )); +#if CPPSORT_STD_IDENTITY_AVAILABLE + CHECK(( is_probably_branchless_projection_v )); +#endif + + CHECK(( is_probably_branchless_projection_v )); + CHECK_FALSE(( is_probably_branchless_projection_v )); - CHECK(( is_probably_branchless_projection::value )); - CHECK_FALSE(( is_probably_branchless_projection::value )); +#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) + CHECK(( is_probably_branchless_projection_v )); +#endif + CHECK_FALSE(( is_probably_branchless_projection_v )); } diff --git a/testsuite/utility/chainable_projections.cpp b/testsuite/utility/chainable_projections.cpp index 27aa81c5..70b64b66 100644 --- a/testsuite/utility/chainable_projections.cpp +++ b/testsuite/utility/chainable_projections.cpp @@ -11,11 +11,12 @@ #include #include #include +#include + +using wrapper = generic_wrapper; namespace { - struct wrapper { int value; }; - struct proj1: cppsort::utility::projection_base { @@ -38,10 +39,9 @@ namespace TEST_CASE( "Pipe a projection_base and function pointer", "[utility][projection_base]" ) { - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::mt19937_64 engine(Catch::rngSeed()); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); proj1 projection1; proj2 projection2; @@ -49,25 +49,25 @@ TEST_CASE( "Pipe a projection_base and function pointer", SECTION( "const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection1); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } SECTION( "chained const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection1 | std::negate<>{}); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } SECTION( "non-const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection2); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } SECTION( "chained non-const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection2 | std::negate<>{}); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } } @@ -84,7 +84,7 @@ TEST_CASE( "Pipe a projection_base several times", proj2 projection2; cppsort::spin_sort(vec, projection1 | projection2); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); cppsort::spin_sort(vec2, projection2 | projection1 | projection2); CHECK( std::is_sorted(std::begin(vec2), std::end(vec2), std::greater<>{}) ); @@ -93,16 +93,15 @@ TEST_CASE( "Pipe a projection_base several times", TEST_CASE( "Pipe a projection with as_projection", "[utility][projection_base]" ) { - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::mt19937_64 engine(Catch::rngSeed()); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); auto projection1 = cppsort::utility::as_projection(&wrapper::value); auto projection2 = proj1(); // Basic check cppsort::spin_sort(vec, projection1 | projection2); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } diff --git a/testsuite/valgrind-osx.supp b/testsuite/valgrind-osx.supp deleted file mode 100644 index 569fd280..00000000 --- a/testsuite/valgrind-osx.supp +++ /dev/null @@ -1,29 +0,0 @@ -{ - Mac-OS-X-System-Leaks - Memcheck:Leak - match-leak-kinds: possible - fun:calloc - fun:map_images_nolock - fun:map_images - fun:_ZN4dyldL18notifyBatchPartialE17dyld_image_statesbPFPKcS0_jPK15dyld_image_infoEbb - fun:_ZN4dyld21registerObjCNotifiersEPFvjPKPKcPKPK11mach_headerEPFvS1_S6_ESC_ - fun:_dyld_objc_notify_register - fun:_objc_init - fun:_os_object_init - fun:libdispatch_init - fun:libSystem_initializer - fun:_ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE - fun:_ZN16ImageLoaderMachO16doInitializationERKN11ImageLoader11LinkContextE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader19processInitializersERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE - fun:_ZN4dyld24initializeMainExecutableEv - fun:_ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm - fun:_ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm - fun:_dyld_start - obj:* - obj:* - obj:* - obj:* -} \ No newline at end of file diff --git a/tools/release-checklist.md b/tools/release-checklist.md index f05eaafd..57ab4a79 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -19,6 +19,9 @@ development phase: - [ ] conanfile.py - [ ] README.md - [ ] version.h + - [ ] Home.md in the documentation + - [ ] Tooling.md/Conan in the documentation +- [ ] Make sure that the Conan recipe works. - [ ] Find a name for the new version. - [ ] Open a merge request, let the CI do its job. - [ ] Merge `develop` into `master`. diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 9a47d70a..42418799 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -9,14 +9,21 @@ #include #include #include + +#define CPPSORT_ENABLE_ASSERTIONS +//#define CPPSORT_ENABLE_AUDITS + +#include #include #include #include #include -#include "../testsuite/distributions.h" +#include "../benchmarks/benchmarking-tools/distributions.h" +#include "../testsuite/testing-tools/algorithm.h" +#include "../testsuite/testing-tools/wrapper.h" struct shuffled_string: - dist::distribution + dist::base_distribution { template auto operator()(OutputIterator out, long long int size, T start=T(0)) const @@ -31,7 +38,7 @@ struct shuffled_string: T end = start + size; for (auto i = start ; i < end ; ++i) { auto s = std::to_string(i); - vec.push_back(std::string(100 - s.size(), '0') + std::move(s)); + vec.push_back(std::string(50 - s.size(), '0') + std::move(s)); } std::shuffle(std::begin(vec), std::end(vec), engine); std::move(std::begin(vec), std::end(vec), out); @@ -41,40 +48,51 @@ struct shuffled_string: template void test(const char* name) { - const int size = 491; + const int size = 412; std::vector collection; - collection.reserve(size); auto distribution = shuffled_string{}; - distribution(std::back_inserter(collection), size, -125); + distribution(std::back_inserter(collection), size); auto copy = collection; - std::sort(std::begin(copy), std::end(copy)); + cppsort::quick_sort(std::begin(copy), std::end(copy)); - std::cout << std::boolalpha << name << '\n'; auto sorter = Sorter{}; sorter(collection); + + // Collect basic data + auto first_unsorted_it = std::is_sorted_until(std::begin(collection), std::end(collection)); + bool is_collection_sorted = first_unsorted_it == std::end(collection); + + // Display information + std::cout << std::boolalpha << name << std::endl; std::cout << "is the collection sorted? "; - std::cout << std::is_sorted(std::begin(collection), std::end(collection)) << '\n'; + std::cout << is_collection_sorted << std::endl; + if (not is_collection_sorted) { + std::cout << "position of the first unsorted element: " + << std::distance(std::begin(collection), first_unsorted_it) + << std::endl; + } std::cout << "is it the same as the one sorted with std::sort? "; - std::cout << (collection == copy) << '\n'; + std::cout << (collection == copy) << std::endl; std::cout << "were some elements altered? "; auto copy2 = collection; - std::sort(std::begin(collection), std::end(collection)); - std::cout << (collection != copy) << '\n'; + cppsort::quick_sort(std::begin(collection), std::end(collection)); + std::cout << (collection != copy) << std::endl; + // Measures of presortedness std::cout << '\n' - << "dis: " << cppsort::probe::dis(copy2) << '\n' - << "enc: " << cppsort::probe::enc(copy2) << '\n' - << "exc: " << cppsort::probe::exc(copy2) << '\n' - << "ham: " << cppsort::probe::ham(copy2) << '\n' - << "inv: " << cppsort::probe::inv(copy2) << '\n' - << "max: " << cppsort::probe::max(copy2) << '\n' - << "mono: " << cppsort::probe::mono(copy2) << '\n' - << "osc: " << cppsort::probe::osc(copy2) << '\n' - << "par: " << cppsort::probe::par(copy2) << '\n' - << "rem: " << cppsort::probe::rem(copy2) << '\n' - << "runs: " << cppsort::probe::runs(copy2) << '\n' + << "dis: " << cppsort::probe::dis(copy2) << std::endl + << "enc: " << cppsort::probe::enc(copy2) << std::endl + << "exc: " << cppsort::probe::exc(copy2) << std::endl + << "ham: " << cppsort::probe::ham(copy2) << std::endl + << "inv: " << cppsort::probe::inv(copy2) << std::endl + << "max: " << cppsort::probe::max(copy2) << std::endl + << "mono: " << cppsort::probe::mono(copy2) << std::endl + << "osc: " << cppsort::probe::osc(copy2) << std::endl + << "par: " << cppsort::probe::par(copy2) << std::endl + << "rem: " << cppsort::probe::rem(copy2) << std::endl + << "runs: " << cppsort::probe::runs(copy2) << std::endl << '\n'; if (size < 40) { @@ -82,11 +100,11 @@ void test(const char* name) for (const auto& elem: copy2) { std::cout << elem << ' '; } - std::cout << "\n\n"; + std::cout << "\n\n" << std::flush; } } int main() { - test("poplar_sort"); + test("poplar_sort"); }