├── .clang-format ├── .github └── workflows │ ├── build-macos.yml │ ├── build-mingw.yml │ ├── build-msvc.yml │ └── build-ubuntu.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmarks ├── CMakeLists.txt ├── bench_merge.cpp ├── bench_sort.cpp └── benchmarker.hpp ├── cmake └── gfx-timsort-config.cmake.in ├── include └── gfx │ └── timsort.hpp └── tests ├── CMakeLists.txt ├── cxx_11_tests.cpp ├── cxx_17_tests.cpp ├── cxx_20_tests.cpp ├── cxx_23_tests.cpp ├── cxx_98_tests.cpp ├── merge_cxx_11_tests.cpp ├── test_helpers.hpp ├── verbose_abort.cpp └── windows.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 2 | BasedOnStyle: LLVM 3 | ColumnLimit: 100 4 | IndentWidth: 4 5 | IndentPPDirectives: AfterHash 6 | IndentRequiresClause: true 7 | PointerAlignment: Left 8 | QualifierAlignment: Right 9 | ReferenceAlignment: Left 10 | AllowShortFunctionsOnASingleLine: false 11 | Standard: Cpp20 12 | -------------------------------------------------------------------------------- /.github/workflows/build-macos.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 Morwenn 2 | # SPDX-License-Identifier: MIT 3 | 4 | name: MacOS Builds 5 | 6 | on: 7 | push: 8 | paths: 9 | - '.github/workflows/build-macos.yml' 10 | - 'CMakeLists.txt' 11 | - 'cmake/**' 12 | - 'include/gfx/timsort.hpp' 13 | - 'tests/*' 14 | pull_request: 15 | paths: 16 | - '.github/workflows/build-macos.yml' 17 | - 'CMakeLists.txt' 18 | - 'cmake/**' 19 | - 'include/gfx/timsort.hpp' 20 | - 'tests/*' 21 | 22 | jobs: 23 | build: 24 | runs-on: macos-11 25 | 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | cxx: 30 | - g++-10 31 | - $(brew --prefix llvm)/bin/clang++ # Clang 17 32 | config: 33 | # Release build 34 | - build_type: Release 35 | # Debug builds 36 | - build_type: Debug 37 | sanitize: address 38 | - build_type: Debug 39 | sanitize: undefined 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: seanmiddleditch/gha-setup-ninja@master 44 | 45 | - name: Configure CMake 46 | working-directory: ${{runner.workspace}} 47 | run: | 48 | export CXX=${{matrix.cxx}} 49 | cmake -H${{github.event.repository.name}} -Bbuild \ 50 | -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ 51 | -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ 52 | -DGFX_TIMSORT_SANITIZE=${{matrix.config.sanitize}} \ 53 | -GNinja \ 54 | -DBUILD_BENCHMARKS=ON 55 | 56 | - name: Build the test suite 57 | shell: bash 58 | working-directory: ${{runner.workspace}}/build 59 | run: cmake --build . --config ${{matrix.config.build_type}} -j 2 60 | 61 | - name: Run the test suite 62 | env: 63 | CTEST_OUTPUT_ON_FAILURE: 1 64 | working-directory: ${{runner.workspace}}/build 65 | run: ctest -C ${{matrix.config.build_type}} 66 | -------------------------------------------------------------------------------- /.github/workflows/build-mingw.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 Morwenn 2 | # SPDX-License-Identifier: MIT 3 | 4 | name: MinGW-w64 Builds 5 | 6 | on: 7 | push: 8 | paths: 9 | - '.github/workflows/build-mingw.yml' 10 | - 'CMakeLists.txt' 11 | - 'cmake/**' 12 | - 'include/gfx/timsort.hpp' 13 | - 'tests/*' 14 | pull_request: 15 | paths: 16 | - '.github/workflows/build-mingw.yml' 17 | - 'CMakeLists.txt' 18 | - 'cmake/**' 19 | - 'include/gfx/timsort.hpp' 20 | - 'tests/*' 21 | 22 | jobs: 23 | build: 24 | runs-on: windows-2022 25 | 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | build_type: [Debug, Release] 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Configure CMake 35 | shell: pwsh 36 | working-directory: ${{runner.workspace}} 37 | run: | 38 | cmake -H${{github.event.repository.name}} -Bbuild ` 39 | -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` 40 | -G"MinGW Makefiles" ` 41 | -DBUILD_BENCHMARKS=ON 42 | 43 | - name: Build the test suite 44 | working-directory: ${{runner.workspace}}/build 45 | run: cmake --build . --config ${{matrix.build_type}} -j 2 46 | 47 | - name: Run the test suite 48 | env: 49 | CTEST_OUTPUT_ON_FAILURE: 1 50 | working-directory: ${{runner.workspace}}/build 51 | run: ctest -C ${{matrix.build_type}} 52 | -------------------------------------------------------------------------------- /.github/workflows/build-msvc.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 Morwenn 2 | # SPDX-License-Identifier: MIT 3 | 4 | name: MSVC Builds 5 | 6 | on: 7 | push: 8 | paths: 9 | - '.github/workflows/build-msvc.yml' 10 | - 'CMakeLists.txt' 11 | - 'cmake/**' 12 | - 'include/gfx/timsort.hpp' 13 | - 'tests/*' 14 | pull_request: 15 | paths: 16 | - '.github/workflows/build-msvc.yml' 17 | - 'CMakeLists.txt' 18 | - 'cmake/**' 19 | - 'include/gfx/timsort.hpp' 20 | - 'tests/*' 21 | 22 | jobs: 23 | build: 24 | runs-on: windows-2022 25 | 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | build_type: [Debug, Release] 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Configure CMake 35 | shell: pwsh 36 | working-directory: ${{runner.workspace}} 37 | run: | 38 | cmake -H${{github.event.repository.name}} -Bbuild ` 39 | -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` 40 | -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` 41 | -G"Visual Studio 17 2022" -A x64 ` 42 | -DBUILD_BENCHMARKS=ON 43 | 44 | - name: Build the test suite 45 | working-directory: ${{runner.workspace}}/build 46 | run: cmake --build . --config ${{matrix.build_type}} -j 2 47 | 48 | - name: Run the test suite 49 | env: 50 | CTEST_OUTPUT_ON_FAILURE: 1 51 | working-directory: ${{runner.workspace}}/build 52 | run: ctest -C ${{matrix.build_type}} 53 | -------------------------------------------------------------------------------- /.github/workflows/build-ubuntu.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 Morwenn 2 | # SPDX-License-Identifier: MIT 3 | 4 | name: Ubuntu Builds 5 | 6 | on: 7 | push: 8 | paths: 9 | - '.github/workflows/build-ubuntu.yml' 10 | - 'CMakeLists.txt' 11 | - 'cmake/**' 12 | - 'include/gfx/timsort.hpp' 13 | - 'tests/*' 14 | pull_request: 15 | paths: 16 | - '.github/workflows/build-ubuntu.yml' 17 | - 'CMakeLists.txt' 18 | - 'cmake/**' 19 | - 'include/gfx/timsort.hpp' 20 | - 'tests/*' 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-20.04 25 | 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | cxx: 30 | - g++-10 31 | - clang++-11 32 | config: 33 | # Release build 34 | - build_type: Release 35 | # Debug builds 36 | - build_type: Debug 37 | valgrind: ON 38 | - build_type: Debug 39 | sanitize: address 40 | - build_type: Debug 41 | sanitize: undefined 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - name: Install Valgrind 47 | if: ${{matrix.config.valgrind == 'ON'}} 48 | run: sudo apt update && sudo apt install -y valgrind 49 | 50 | - name: Configure CMake 51 | working-directory: ${{runner.workspace}} 52 | env: 53 | CXX: ${{matrix.cxx}} 54 | run: | 55 | cmake -H${{github.event.repository.name}} -Bbuild \ 56 | -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ 57 | -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ 58 | -DGFX_TIMSORT_SANITIZE=${{matrix.config.sanitize}} \ 59 | -DGFX_TIMSORT_USE_VALGRIND=${{matrix.config.valgrind}} \ 60 | -G"Unix Makefiles" \ 61 | -DBUILD_BENCHMARKS=ON 62 | 63 | - name: Build the test suite 64 | shell: bash 65 | working-directory: ${{runner.workspace}}/build 66 | run: cmake --build . --config ${{matrix.config.build_type}} -j 2 67 | 68 | - name: Run the test suite 69 | if: ${{matrix.config.valgrind != 'ON'}} 70 | env: 71 | CTEST_OUTPUT_ON_FAILURE: 1 72 | working-directory: ${{runner.workspace}}/build 73 | run: ctest -C ${{matrix.config.build_type}} 74 | 75 | - name: Run the test suite with Memcheck 76 | if: ${{matrix.config.valgrind == 'ON'}} 77 | env: 78 | CTEST_OUTPUT_ON_FAILURE: 1 79 | working-directory: ${{runner.workspace}}/build 80 | run: | 81 | ctest -T memcheck -C ${{matrix.config.build_type}} -j 2 82 | find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | .bin 4 | *.gc* 5 | coverage.txt 6 | build 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14.0) 2 | 3 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 4 | 5 | project(timsort VERSION 3.0.1 LANGUAGES CXX) 6 | 7 | include(CMakePackageConfigHelpers) 8 | include(GNUInstallDirs) 9 | 10 | # Project options 11 | option(BUILD_TESTING "Build the tests" ON) 12 | option(BUILD_BENCHMARKS "Build the benchmarks" OFF) 13 | 14 | # Create gfx::timsort as an interface library 15 | add_library(timsort INTERFACE) 16 | 17 | target_include_directories(timsort INTERFACE 18 | $ 19 | $ 20 | ) 21 | 22 | target_compile_features(timsort INTERFACE cxx_std_20) 23 | 24 | add_library(gfx::timsort ALIAS timsort) 25 | 26 | # Install targets and files 27 | install( 28 | TARGETS timsort 29 | EXPORT gfx-timsort-targets 30 | DESTINATION ${CMAKE_INSTALL_LIBDIR} 31 | ) 32 | 33 | install( 34 | EXPORT gfx-timsort-targets 35 | NAMESPACE gfx:: 36 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/gfx 37 | ) 38 | 39 | install( 40 | FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/gfx/timsort.hpp 41 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gfx 42 | ) 43 | 44 | configure_package_config_file( 45 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/gfx-timsort-config.cmake.in 46 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-config.cmake 47 | INSTALL_DESTINATION 48 | ${CMAKE_INSTALL_LIBDIR}/cmake/gfx 49 | ) 50 | 51 | write_basic_package_version_file( 52 | ${CMAKE_BINARY_DIR}/cmake/gfx-timsort-config-version.cmake 53 | COMPATIBILITY SameMajorVersion 54 | ARCH_INDEPENDENT 55 | ) 56 | 57 | install( 58 | FILES 59 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-config.cmake 60 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-config-version.cmake 61 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/gfx 62 | ) 63 | 64 | # Export target so that it can be used in subdirectories 65 | export( 66 | EXPORT gfx-timsort-targets 67 | FILE ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-targets.cmake 68 | NAMESPACE gfx:: 69 | ) 70 | 71 | # Build tests and/or benchmarks if this is the main project 72 | if (PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 73 | if (BUILD_TESTING) 74 | enable_testing() 75 | add_subdirectory(tests) 76 | endif() 77 | 78 | if (BUILD_BENCHMARKS) 79 | add_subdirectory(benchmarks) 80 | endif() 81 | endif() 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Fuji Goro (gfx) . 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Release](https://img.shields.io/badge/release-3.0.1-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v3.0.1) 2 | [![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F3.0.1-blue.svg)](https://conan.io/center/recipes/timsort?version=3.0.1) 3 | [![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork) 4 | 5 | ## TimSort 6 | 7 | A C++ implementation of TimSort, an O(n log n) stable sorting algorithm, ported from Python's and OpenJDK's. 8 | 9 | See also the following links for a detailed description of TimSort: 10 | * http://svn.python.org/projects/python/trunk/Objects/listsort.txt 11 | * http://en.wikipedia.org/wiki/Timsort 12 | 13 | This version of the library requires at least C++20. Older versions of the library, available in different branches, 14 | offer support for older standards though implement fewer features: 15 | * Branch `2.x.y` is compatible with C++11, and is slightly more permissive in some reagard due to the lack of 16 | concepts to constrain its interface (it notaby supports iterators without postfix operator++/--). 17 | * Branch `1.x.y` is compatible with C++03. 18 | Older versions are not actively maintained anymore. If you need extended support for those, please open specific 19 | issues for the problems you want solved. 20 | 21 | According to the benchmarks, `gfx::timsort` is slower than [`std::ranges::sort`][std-sort] on randomized sequences, 22 | but faster on partially-sorted ones. It can be used as a drop-in replacement for [`std::ranges::stable_sort`][std-stable-sort], 23 | with the difference that it can't fall back to a O(n log² n) algorithm when there isn't enough extra heap memory 24 | available. 25 | 26 | Merging sorted ranges efficiently is an important part of the TimSort algorithm. This library exposes `gfx::timmerge` 27 | in the public API, a drop-in replacement for [`std::ranges::inplace_merge`][std-inplace-merge] with the difference 28 | that it can't fall back to a O(n log n) algorithm when there isn't enough extra heap memory available. According to 29 | the benchmarks, `gfx::timmerge` is slower than `std::ranges::inplace_merge` on heavily/randomly overlapping subranges 30 | of simple elements, but it is faster for complex elements such as `std::string`, and on sparsely overlapping subranges. 31 | 32 | The ibrary exposes the following functions in namespace `gfx`: 33 | 34 | ```cpp 35 | // timsort 36 | 37 | template < 38 | std::random_access_iterator Iterator, 39 | std::sentinel_for Sentinel, 40 | typename Compare = std::ranges::less, 41 | typename Projection = std::identity 42 | > 43 | requires std::sortable 44 | auto timsort(Iterator first, Sentinel last, 45 | Compare compare={}, Projection projection={}) 46 | -> Iterator; 47 | 48 | template < 49 | std::ranges::random_access_range Range, 50 | typename Compare = std::ranges::less, 51 | typename Projection = std::identity 52 | > 53 | requires std::sortable, Compare, Projection> 54 | auto timsort(Range &range, Compare compare={}, Projection projection={}) 55 | -> std::ranges::borrowed_iterator_t; 56 | 57 | // timmerge 58 | 59 | template < 60 | std::random_access_iterator Iterator, 61 | std::sentinel_for Sentinel, 62 | typename Compare = std::ranges::less, 63 | typename Projection = std::identity 64 | > 65 | requires std::sortable 66 | auto timmerge(Iterator first, Iterator middle, Sentinel last, 67 | Compare compare={}, Projection projection={}) 68 | -> Iterator; 69 | 70 | template < 71 | std::ranges::random_access_range Range, 72 | typename Compare = std::ranges::less, 73 | typename Projection = std::identity 74 | > 75 | requires std::sortable, Compare, Projection> 76 | auto timmerge(Range &&range, std::ranges::iterator_t middle, 77 | Compare compare={}, Projection projection={}) 78 | -> std::ranges::borrowed_iterator_t; 79 | ``` 80 | 81 | ## EXAMPLE 82 | 83 | Example of using timsort with a defaulted comparison function and a projection function to sort a vector of strings 84 | by length: 85 | 86 | ```cpp 87 | #include 88 | #include 89 | #include 90 | 91 | size_t len(const std::string& str) { 92 | return str.size(); 93 | } 94 | 95 | // Sort a vector of strings by length 96 | std::vector collection = { /* ... */ }; 97 | gfx::timsort(collection, {}, &len); 98 | ``` 99 | 100 | ## INSTALLATION & COMPATIBILITY 101 | 102 | [![Ubuntu Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-ubuntu.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-ubuntu.yml) 103 | [![MSVC Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-msvc.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-msvc.yml) 104 | [![MinGW-w64 Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-mingw.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-mingw.yml) 105 | [![MacOS Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-macos.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-macos.yml) 106 | 107 | The library is tested with the following compilers: 108 | * Ubuntu: GCC 10, Clang 11 109 | * Windows: MSVC 19.37.32826.1, MinGW-w64 GCC 12 110 | * MacOS: GCC 10, Clang 17 111 | 112 | The library can be installed on the system via [CMake][cmake] (at least 3.14) with the following commands: 113 | 114 | ```sh 115 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 116 | cmake --install build 117 | ``` 118 | 119 | Alternatively the library is also available [Conan Center][conan-center] and can be directly installed in your local 120 | [Conan][conan] cache with the following command: 121 | 122 | ```sh 123 | conan install --requires=timsort/3.0.1 124 | ``` 125 | 126 | ## DIAGNOSTICS & INFORMATION 127 | 128 | The following configuration macros allow `gfx::timsort` and `gfx::timmerge` to emit diagnostics, which can be helpful 129 | to diagnose issues: 130 | * Defining `GFX_TIMSORT_ENABLE_ASSERT` light inserts assertions in key locations in the algorithm to avoid logic errors. 131 | * Defining `GFX_TIMSORT_ENABLE_AUDIT` inserts assertions that verify pre- and postconditions. These verifications can 132 | slow the algorithm down significantly. Enable the audits only while testing or debugging. Enabling audits automatically 133 | enables lighter assertions too. 134 | * Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the 135 | algorithm. 136 | 137 | **cpp-TimSort** follows semantic versioning and provides the following macros to retrieve the current major, minor 138 | and patch versions: 139 | 140 | ```cpp 141 | GFX_TIMSORT_VERSION_MAJOR 142 | GFX_TIMSORT_VERSION_MINOR 143 | GFX_TIMSORT_VERSION_PATCH 144 | ``` 145 | 146 | ## TESTS 147 | 148 | The tests are written with Catch2 and can be compiled with CMake and run through CTest. 149 | 150 | When using the project's main `CMakeLists.txt`, the CMake option `BUILD_TESTING` is `ON` by default unless the 151 | project is included as a subdirectory. The following CMake options are available to change the way the tests are 152 | built with CMake: 153 | * `GFX_TIMSORT_USE_VALGRIND`: if `ON`, the tests will be run through Valgrind (`OFF` by default) 154 | * `GFX_TIMSORT_SANITIZE`: this variable takes a comma-separated list of sanitizers options to run the tests (empty by default) 155 | 156 | ## BENCHMARKS 157 | 158 | Benchmarks are available in the `benchmarks` subdirectory, and can be constructed directly by passing the option 159 | `-DBUILD_BENCHMARKS=ON` to CMake during the configuration step. 160 | 161 | Example bench_sort output (timing scale: sec.): 162 | 163 | c++ -v 164 | Apple LLVM version 7.0.0 (clang-700.0.72) 165 | Target: x86_64-apple-darwin14.5.0 166 | Thread model: posix 167 | c++ -I. -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 example/bench.cpp -o .bin/bench 168 | ./.bin/bench 169 | RANDOMIZED SEQUENCE 170 | [int] 171 | size 100000 172 | std::sort 0.695253 173 | std::stable_sort 0.868916 174 | timsort 1.255825 175 | [std::string] 176 | size 100000 177 | std::sort 3.438217 178 | std::stable_sort 4.122629 179 | timsort 5.791845 180 | REVERSED SEQUENCE 181 | [int] 182 | size 100000 183 | std::sort 0.045461 184 | std::stable_sort 0.575431 185 | timsort 0.019139 186 | [std::string] 187 | size 100000 188 | std::sort 0.586707 189 | std::stable_sort 2.715778 190 | timsort 0.345099 191 | SORTED SEQUENCE 192 | [int] 193 | size 100000 194 | std::sort 0.021876 195 | std::stable_sort 0.087993 196 | timsort 0.008042 197 | [std::string] 198 | size 100000 199 | std::sort 0.402458 200 | std::stable_sort 2.436326 201 | timsort 0.298639 202 | 203 | Example bench_merge output (timing scale: milliseconds; omitted detailed results for different 204 | middle iterator positions, reformatted to improve readability): 205 | 206 | c++ -v 207 | Using built-in specs. 208 | ... 209 | Target: x86_64-pc-linux-gnu 210 | ... 211 | gcc version 10.2.0 (GCC) 212 | c++ -I ../include -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 bench_merge.cpp -o bench_merge 213 | ./bench_merge 214 | size 100000 215 | element type\algorithm: std::inplace_merge timmerge 216 | RANDOMIZED SEQUENCE 217 | [int] approx. average 33.404430 37.047990 218 | [std::string] approx. average 324.964249 210.297207 219 | REVERSED SEQUENCE 220 | [int] approx. average 11.441404 4.017482 221 | [std::string] approx. average 305.649503 114.773898 222 | SORTED SEQUENCE 223 | [int] approx. average 4.291098 0.105571 224 | [std::string] approx. average 158.238114 0.273858 225 | 226 | Detailed bench_merge results for different middle iterator positions can be found at 227 | https://github.com/timsort/cpp-TimSort/wiki/Benchmark-results 228 | 229 | 230 | [cmake]: https://cmake.org/ 231 | [conan]: https://conan.io/ 232 | [conan-center]: https://conan.io/center 233 | [std-inplace-merge]: https://en.cppreference.com/w/cpp/algorithm/ranges/inplace_merge 234 | [std-sort]: https://en.cppreference.com/w/cpp/algorithm/ranges/sort 235 | [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/ranges/stable_sort 236 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | foreach(filename bench_merge.cpp bench_sort.cpp) 3 | get_filename_component(name ${filename} NAME_WE) 4 | add_executable(${name} ${filename}) 5 | target_link_libraries(${name} PRIVATE gfx::timsort) 6 | endforeach() 7 | -------------------------------------------------------------------------------- /benchmarks/bench_merge.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Igor Kushnir . 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "benchmarker.hpp" 16 | 17 | namespace 18 | { 19 | std::vector generate_middle_positions(int size) { 20 | std::vector result = { 21 | 0, 1, 2, 5, 100, size/100, size/20, size/5, size/3, size/2, 3*size/4, 22 | 6*size/7, 24*size/25, 90*size/91, size-85, size-8, size-2, size-1, size 23 | }; 24 | 25 | // The code below can remove or reorder elements if size is small. 26 | 27 | auto logical_end = std::remove_if(result.begin(), result.end(), [size](int middle) { 28 | return middle < 0 || middle > size; 29 | }); 30 | result.erase(logical_end, result.end()); 31 | 32 | std::sort(result.begin(), result.end()); 33 | logical_end = std::unique(result.begin(), result.end()); 34 | result.erase(logical_end, result.end()); 35 | 36 | return result; 37 | } 38 | 39 | using Result = std::valarray; 40 | Result zeroResult() { return Result(2); } 41 | } 42 | 43 | template 44 | struct Bench { 45 | void operator()(const std::vector &source) const { 46 | const int size = static_cast(source.size()); 47 | const auto middle_positions = generate_middle_positions(size); 48 | 49 | int prev_middle = 0; 50 | auto prev_result = zeroResult(); 51 | auto result_sum = zeroResult(); 52 | 53 | std::cerr << "middle\\algorithm:\tstd::inplace_merge\ttimmerge" << std::endl; 54 | constexpr int width = 10; 55 | constexpr const char* padding = " \t"; 56 | 57 | std::vector a(source.size()); 58 | for (auto middle : middle_positions) { 59 | std::copy(source.begin(), source.end(), a.begin()); 60 | std::sort(a.begin(), a.begin() + middle); 61 | std::sort(a.begin() + middle, a.end()); 62 | const auto result = run(a, middle); 63 | 64 | if (middle != prev_middle) { 65 | // Trapezoidal rule for approximating the definite integral. 66 | result_sum += 0.5 * (result + prev_result) 67 | * static_cast(middle - prev_middle); 68 | prev_middle = middle; 69 | } 70 | prev_result = result; 71 | 72 | std::cerr << std::setw(width) << middle 73 | << " \t" << std::setw(width) << result[0] 74 | << padding << std::setw(width) << result[1] 75 | << std::endl; 76 | } 77 | 78 | if (size != 0) { 79 | result_sum /= static_cast(size); 80 | std::cerr << "approx. average" 81 | << " \t" << std::setw(width) << result_sum[0] 82 | << padding << std::setw(width) << result_sum[1] 83 | << std::endl; 84 | } 85 | } 86 | 87 | private: 88 | static Result run(const std::vector &a, const int middle) { 89 | std::vector b(a.size()); 90 | const auto assert_is_sorted = [&b] { 91 | if (!std::is_sorted(b.cbegin(), b.cend())) { 92 | std::cerr << "Not sorted!" << std::endl; 93 | std::abort(); 94 | } 95 | }; 96 | 97 | auto result = zeroResult(); 98 | for (auto *total_time_ms : { &result[0], &result[1] }) { 99 | using Clock = std::chrono::steady_clock; 100 | decltype(Clock::now() - Clock::now()) total_time{0}; 101 | 102 | for (int i = 0; i < 100; ++i) { 103 | std::copy(a.begin(), a.end(), b.begin()); 104 | const auto time_begin = Clock::now(); 105 | 106 | if (total_time_ms == &result[0]) { 107 | std::inplace_merge(b.begin(), b.begin() + middle, b.end()); 108 | } else { 109 | gfx::timmerge(b.begin(), b.begin() + middle, b.end()); 110 | } 111 | 112 | const auto time_end = Clock::now(); 113 | total_time += time_end - time_begin; 114 | 115 | // Verifying that b is sorted should prevent the compiler from optimizing anything out. 116 | assert_is_sorted(); 117 | } 118 | 119 | *total_time_ms = std::chrono::duration_cast< 120 | std::chrono::microseconds>(total_time).count() / 1000.0; 121 | } 122 | return result; 123 | } 124 | }; 125 | 126 | int main(int argc, const char *argv[]) { 127 | const int size = argc > 1 ? std::stoi(argv[1]) : 100 * 1000; 128 | Benchmarker benchmarker(size); 129 | benchmarker.run(); 130 | } 131 | -------------------------------------------------------------------------------- /benchmarks/bench_sort.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019 Morwenn. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "benchmarker.hpp" 14 | 15 | template 16 | struct Bench { 17 | void operator()(const std::vector &a) const { 18 | { 19 | std::vector b(a); 20 | 21 | std::clock_t start = std::clock(); 22 | for (int i = 0; i < 100; ++i) { 23 | std::copy(a.begin(), a.end(), b.begin()); 24 | std::sort(b.begin(), b.end()); 25 | } 26 | std::clock_t stop = std::clock(); 27 | 28 | std::cerr << "std::sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; 29 | } 30 | 31 | { 32 | std::vector b(a); 33 | 34 | std::clock_t start = std::clock(); 35 | for (int i = 0; i < 100; ++i) { 36 | std::copy(a.begin(), a.end(), b.begin()); 37 | std::stable_sort(b.begin(), b.end()); 38 | } 39 | std::clock_t stop = clock(); 40 | 41 | std::cerr << "std::stable_sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; 42 | } 43 | 44 | { 45 | std::vector b(a); 46 | 47 | std::clock_t start = std::clock(); 48 | for (int i = 0; i < 100; ++i) { 49 | std::copy(a.begin(), a.end(), b.begin()); 50 | gfx::timsort(b.begin(), b.end()); 51 | } 52 | std::clock_t stop = std::clock(); 53 | 54 | std::cerr << "timsort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; 55 | } 56 | } 57 | }; 58 | 59 | int main(int argc, const char *argv[]) { 60 | const int size = argc > 1 ? std::atoi(argv[1]) : 100 * 1000; 61 | Benchmarker benchmarker(size); 62 | benchmarker.run(); 63 | } 64 | -------------------------------------------------------------------------------- /benchmarks/benchmarker.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019 Morwenn. 4 | * Copyright (c) 2021 Igor Kushnir . 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #ifndef GFX_TIMSORT_BENCHMARKER_HPP 10 | #define GFX_TIMSORT_BENCHMARKER_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace BenchmarkerHelpers { 23 | template 24 | struct convert_to 25 | { 26 | static T from(int value) { 27 | return T(value); 28 | } 29 | }; 30 | 31 | template <> 32 | struct convert_to 33 | { 34 | static std::string from(int value) { 35 | std::ostringstream ss; 36 | ss << value; 37 | return ss.str(); 38 | } 39 | }; 40 | } 41 | 42 | template class Bench> 43 | class Benchmarker { 44 | private: 45 | enum state_t { sorted, randomized, reversed }; 46 | 47 | template 48 | void bench(state_t const state) { 49 | thread_local std::mt19937 random_engine(2581470); // fixed seed is enough 50 | 51 | std::vector a; 52 | for (int i = 0; i < size_; ++i) { 53 | a.push_back(BenchmarkerHelpers::convert_to::from((i + 1) * 10)); 54 | } 55 | 56 | switch (state) { 57 | case randomized: 58 | std::shuffle(a.begin(), a.end(), random_engine); 59 | break; 60 | case reversed: 61 | std::stable_sort(a.begin(), a.end()); 62 | std::reverse(a.begin(), a.end()); 63 | break; 64 | case sorted: 65 | std::stable_sort(a.begin(), a.end()); 66 | break; 67 | default: 68 | std::abort(); // unreachable 69 | } 70 | 71 | Bench()(a); 72 | } 73 | 74 | void doit(state_t const state) { 75 | std::cerr << "[int]" << std::endl; 76 | bench(state); 77 | 78 | std::cerr << "[std::string]" << std::endl; 79 | bench(state); 80 | } 81 | 82 | const int size_; 83 | 84 | public: 85 | explicit Benchmarker(int size) : size_(size) { 86 | } 87 | 88 | void run() { 89 | std::cerr << "size\t" << size_ << std::endl; 90 | 91 | std::cerr << std::setprecision(6) << std::setiosflags(std::ios::fixed); 92 | 93 | std::srand(0); 94 | 95 | std::cerr << "RANDOMIZED SEQUENCE" << std::endl; 96 | doit(randomized); 97 | 98 | std::cerr << "REVERSED SEQUENCE" << std::endl; 99 | doit(reversed); 100 | 101 | std::cerr << "SORTED SEQUENCE" << std::endl; 102 | doit(sorted); 103 | } 104 | }; 105 | 106 | #endif // GFX_TIMSORT_BENCHMARKER_HPP 107 | -------------------------------------------------------------------------------- /cmake/gfx-timsort-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | if (NOT TARGET gfx::timsort) 4 | include(${CMAKE_CURRENT_LIST_DIR}/gfx-timsort-targets.cmake) 5 | endif() 6 | -------------------------------------------------------------------------------- /include/gfx/timsort.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * C++ implementation of timsort 3 | * 4 | * ported from Python's and OpenJDK's: 5 | * - http://svn.python.org/projects/python/trunk/Objects/listobject.c 6 | * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java 7 | * 8 | * Copyright (c) 2011 Fuji, Goro (gfx) . 9 | * Copyright (c) 2019-2024 Morwenn. 10 | * Copyright (c) 2021 Igor Kushnir . 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to 14 | * deal in the Software without restriction, including without limitation the 15 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 16 | * sell copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 28 | * IN THE SOFTWARE. 29 | */ 30 | 31 | #ifndef GFX_TIMSORT_HPP 32 | #define GFX_TIMSORT_HPP 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | // Semantic versioning macros 42 | 43 | #define GFX_TIMSORT_VERSION_MAJOR 3 44 | #define GFX_TIMSORT_VERSION_MINOR 0 45 | #define GFX_TIMSORT_VERSION_PATCH 0 46 | 47 | // Diagnostic selection macros 48 | 49 | #if defined(GFX_TIMSORT_ENABLE_ASSERT) || defined(GFX_TIMSORT_ENABLE_AUDIT) 50 | # include 51 | # define GFX_TIMSORT_ASSERT(expr) assert(expr) 52 | #else 53 | # define GFX_TIMSORT_ASSERT(expr) ((void)0) 54 | #endif 55 | 56 | #ifdef GFX_TIMSORT_ENABLE_AUDIT 57 | # define GFX_TIMSORT_AUDIT(expr) assert(expr) 58 | #else 59 | # define GFX_TIMSORT_AUDIT(expr) ((void)0) 60 | #endif 61 | 62 | #ifdef GFX_TIMSORT_ENABLE_LOG 63 | # include 64 | # define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << expr << std::endl) 65 | #else 66 | # define GFX_TIMSORT_LOG(expr) ((void)0) 67 | #endif 68 | 69 | 70 | namespace gfx { 71 | 72 | // --------------------------------------- 73 | // Implementation details 74 | // --------------------------------------- 75 | 76 | namespace detail { 77 | 78 | template 79 | struct run { 80 | using diff_t = typename std::iterator_traits::difference_type; 81 | 82 | Iterator base; 83 | diff_t len; 84 | 85 | run(Iterator b, diff_t l) : base(b), len(l) { 86 | } 87 | }; 88 | 89 | template 90 | class TimSort { 91 | using iter_t = RandomAccessIterator; 92 | using diff_t = std::iter_difference_t; 93 | 94 | static constexpr int MIN_MERGE = 32; 95 | static constexpr int MIN_GALLOP = 7; 96 | 97 | int minGallop_ = MIN_GALLOP; 98 | std::vector> tmp_; // temp storage for merges 99 | std::vector> pending_; 100 | 101 | template 102 | static void binarySort(iter_t const lo, iter_t const hi, iter_t start, 103 | Compare comp, Projection proj) { 104 | GFX_TIMSORT_ASSERT(lo <= start); 105 | GFX_TIMSORT_ASSERT(start <= hi); 106 | if (start == lo) { 107 | ++start; 108 | } 109 | for (; start < hi; ++start) { 110 | GFX_TIMSORT_ASSERT(lo <= start); 111 | auto pos = std::ranges::upper_bound(lo, start, std::invoke(proj, *start), comp, proj); 112 | rotateRight(pos, std::ranges::next(start)); 113 | } 114 | } 115 | 116 | template 117 | static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, 118 | Compare comp, Projection proj) { 119 | GFX_TIMSORT_ASSERT(lo < hi); 120 | 121 | auto runHi = std::ranges::next(lo); 122 | if (runHi == hi) { 123 | return 1; 124 | } 125 | 126 | if (std::invoke(comp, std::invoke(proj, *runHi), std::invoke(proj, *lo))) { // decreasing 127 | do { 128 | ++runHi; 129 | } while (runHi < hi && std::invoke(comp, 130 | std::invoke(proj, *runHi), 131 | std::invoke(proj, *std::ranges::prev(runHi)))); 132 | std::ranges::reverse(lo, runHi); 133 | } else { // non-decreasing 134 | do { 135 | ++runHi; 136 | } while (runHi < hi && !std::invoke(comp, 137 | std::invoke(proj, *runHi), 138 | std::invoke(proj, *std::ranges::prev(runHi)))); 139 | } 140 | 141 | return runHi - lo; 142 | } 143 | 144 | static diff_t minRunLength(diff_t n) { 145 | GFX_TIMSORT_ASSERT(n >= 0); 146 | 147 | diff_t r = 0; 148 | while (n >= 2 * MIN_MERGE) { 149 | r |= (n & 1); 150 | n >>= 1; 151 | } 152 | return n + r; 153 | } 154 | 155 | void pushRun(iter_t const runBase, diff_t const runLen) { 156 | pending_.emplace_back(runBase, runLen); 157 | } 158 | 159 | template 160 | void mergeCollapse(Compare comp, Projection proj) { 161 | while (pending_.size() > 1) { 162 | diff_t n = pending_.size() - 2; 163 | 164 | if ((n > 0 && pending_[n - 1].len <= pending_[n].len + pending_[n + 1].len) || 165 | (n > 1 && pending_[n - 2].len <= pending_[n - 1].len + pending_[n].len)) { 166 | if (pending_[n - 1].len < pending_[n + 1].len) { 167 | --n; 168 | } 169 | mergeAt(n, comp, proj); 170 | } else if (pending_[n].len <= pending_[n + 1].len) { 171 | mergeAt(n, comp, proj); 172 | } else { 173 | break; 174 | } 175 | } 176 | } 177 | 178 | template 179 | void mergeForceCollapse(Compare comp, Projection proj) { 180 | while (pending_.size() > 1) { 181 | diff_t n = pending_.size() - 2; 182 | 183 | if (n > 0 && pending_[n - 1].len < pending_[n + 1].len) { 184 | --n; 185 | } 186 | mergeAt(n, comp, proj); 187 | } 188 | } 189 | 190 | template 191 | void mergeAt(diff_t const i, Compare comp, Projection proj) { 192 | diff_t const stackSize = pending_.size(); 193 | GFX_TIMSORT_ASSERT(stackSize >= 2); 194 | GFX_TIMSORT_ASSERT(i >= 0); 195 | GFX_TIMSORT_ASSERT(i == stackSize - 2 || i == stackSize - 3); 196 | 197 | auto base1 = pending_[i].base; 198 | auto len1 = pending_[i].len; 199 | auto base2 = pending_[i + 1].base; 200 | auto len2 = pending_[i + 1].len; 201 | 202 | pending_[i].len = len1 + len2; 203 | 204 | if (i == stackSize - 3) { 205 | pending_[i + 1] = pending_[i + 2]; 206 | } 207 | 208 | pending_.pop_back(); 209 | 210 | mergeConsecutiveRuns(base1, len1, base2, len2, std::move(comp), std::move(proj)); 211 | } 212 | 213 | template 214 | void mergeConsecutiveRuns(iter_t base1, diff_t len1, iter_t base2, diff_t len2, 215 | Compare comp, Projection proj) { 216 | GFX_TIMSORT_ASSERT(len1 > 0); 217 | GFX_TIMSORT_ASSERT(len2 > 0); 218 | GFX_TIMSORT_ASSERT(base1 + len1 == base2); 219 | 220 | auto k = gallopRight(std::invoke(proj, *base2), base1, len1, 0, comp, proj); 221 | GFX_TIMSORT_ASSERT(k >= 0); 222 | 223 | base1 += k; 224 | len1 -= k; 225 | 226 | if (len1 == 0) { 227 | return; 228 | } 229 | 230 | len2 = gallopLeft(std::invoke(proj, base1[len1 - 1]), base2, len2, len2 - 1, comp, proj); 231 | GFX_TIMSORT_ASSERT(len2 >= 0); 232 | if (len2 == 0) { 233 | return; 234 | } 235 | 236 | if (len1 <= len2) { 237 | mergeLo(base1, len1, base2, len2, comp, proj); 238 | } else { 239 | mergeHi(base1, len1, base2, len2, comp, proj); 240 | } 241 | } 242 | 243 | template 244 | static diff_t gallopLeft(T const& key, Iter const base, diff_t const len, diff_t const hint, 245 | Compare comp, Projection proj) { 246 | GFX_TIMSORT_ASSERT(len > 0); 247 | GFX_TIMSORT_ASSERT(hint >= 0); 248 | GFX_TIMSORT_ASSERT(hint < len); 249 | 250 | diff_t lastOfs = 0; 251 | diff_t ofs = 1; 252 | 253 | if (std::invoke(comp, std::invoke(proj, base[hint]), key)) { 254 | auto maxOfs = len - hint; 255 | while (ofs < maxOfs && std::invoke(comp, std::invoke(proj, base[hint + ofs]), key)) { 256 | lastOfs = ofs; 257 | ofs = (ofs << 1) + 1; 258 | 259 | if (ofs <= 0) { // int overflow 260 | ofs = maxOfs; 261 | } 262 | } 263 | if (ofs > maxOfs) { 264 | ofs = maxOfs; 265 | } 266 | 267 | lastOfs += hint; 268 | ofs += hint; 269 | } else { 270 | diff_t const maxOfs = hint + 1; 271 | while (ofs < maxOfs && !std::invoke(comp, std::invoke(proj, base[hint - ofs]), key)) { 272 | lastOfs = ofs; 273 | ofs = (ofs << 1) + 1; 274 | 275 | if (ofs <= 0) { 276 | ofs = maxOfs; 277 | } 278 | } 279 | if (ofs > maxOfs) { 280 | ofs = maxOfs; 281 | } 282 | 283 | diff_t const tmp = lastOfs; 284 | lastOfs = hint - ofs; 285 | ofs = hint - tmp; 286 | } 287 | GFX_TIMSORT_ASSERT(-1 <= lastOfs); 288 | GFX_TIMSORT_ASSERT(lastOfs < ofs); 289 | GFX_TIMSORT_ASSERT(ofs <= len); 290 | 291 | return std::ranges::lower_bound(base + (lastOfs + 1), base + ofs, key, comp, proj) - base; 292 | } 293 | 294 | template 295 | static diff_t gallopRight(T const& key, Iter const base, diff_t const len, diff_t const hint, 296 | Compare comp, Projection proj) { 297 | GFX_TIMSORT_ASSERT(len > 0); 298 | GFX_TIMSORT_ASSERT(hint >= 0); 299 | GFX_TIMSORT_ASSERT(hint < len); 300 | 301 | diff_t ofs = 1; 302 | diff_t lastOfs = 0; 303 | 304 | if (std::invoke(comp, key, std::invoke(proj, base[hint]))) { 305 | diff_t const maxOfs = hint + 1; 306 | while (ofs < maxOfs && std::invoke(comp, key, std::invoke(proj, base[hint - ofs]))) { 307 | lastOfs = ofs; 308 | ofs = (ofs << 1) + 1; 309 | 310 | if (ofs <= 0) { 311 | ofs = maxOfs; 312 | } 313 | } 314 | if (ofs > maxOfs) { 315 | ofs = maxOfs; 316 | } 317 | 318 | diff_t const tmp = lastOfs; 319 | lastOfs = hint - ofs; 320 | ofs = hint - tmp; 321 | } else { 322 | diff_t const maxOfs = len - hint; 323 | while (ofs < maxOfs && !std::invoke(comp, key, std::invoke(proj, base[hint + ofs]))) { 324 | lastOfs = ofs; 325 | ofs = (ofs << 1) + 1; 326 | 327 | if (ofs <= 0) { // int overflow 328 | ofs = maxOfs; 329 | } 330 | } 331 | if (ofs > maxOfs) { 332 | ofs = maxOfs; 333 | } 334 | 335 | lastOfs += hint; 336 | ofs += hint; 337 | } 338 | GFX_TIMSORT_ASSERT(-1 <= lastOfs); 339 | GFX_TIMSORT_ASSERT(lastOfs < ofs); 340 | GFX_TIMSORT_ASSERT(ofs <= len); 341 | 342 | return std::ranges::upper_bound(base + (lastOfs + 1), base + ofs, key, comp, proj) - base; 343 | } 344 | 345 | static void rotateLeft(iter_t first, iter_t last) { 346 | std::iter_value_t tmp = std::ranges::iter_move(first); 347 | auto [_, last_1] = std::ranges::move(std::ranges::next(first), last, first); 348 | *last_1 = std::move(tmp); 349 | } 350 | 351 | static void rotateRight(iter_t first, iter_t last) { 352 | auto last_1 = std::ranges::prev(last); 353 | std::iter_value_t tmp = std::ranges::iter_move(last_1); 354 | std::ranges::move_backward(first, last_1, last); 355 | *first = std::move(tmp); 356 | } 357 | 358 | template 359 | void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, 360 | Compare comp, Projection proj) { 361 | GFX_TIMSORT_ASSERT(len1 > 0); 362 | GFX_TIMSORT_ASSERT(len2 > 0); 363 | GFX_TIMSORT_ASSERT(base1 + len1 == base2); 364 | 365 | if (len1 == 1) { 366 | return rotateLeft(base1, base2 + len2); 367 | } 368 | if (len2 == 1) { 369 | return rotateRight(base1, base2 + len2); 370 | } 371 | 372 | move_to_tmp(base1, len1); 373 | 374 | auto cursor1 = tmp_.begin(); 375 | auto cursor2 = base2; 376 | auto dest = base1; 377 | 378 | *dest = std::ranges::iter_move(cursor2); 379 | ++cursor2; 380 | ++dest; 381 | --len2; 382 | 383 | int minGallop(minGallop_); 384 | 385 | // outer: 386 | while (true) { 387 | diff_t count1 = 0; 388 | diff_t count2 = 0; 389 | 390 | do { 391 | GFX_TIMSORT_ASSERT(len1 > 1); 392 | GFX_TIMSORT_ASSERT(len2 > 0); 393 | 394 | if (std::invoke(comp, std::invoke(proj, *cursor2), std::invoke(proj, *cursor1))) { 395 | *dest = std::ranges::iter_move(cursor2); 396 | ++cursor2; 397 | ++dest; 398 | ++count2; 399 | count1 = 0; 400 | if (--len2 == 0) { 401 | goto epilogue; 402 | } 403 | } else { 404 | *dest = std::ranges::iter_move(cursor1); 405 | ++cursor1; 406 | ++dest; 407 | ++count1; 408 | count2 = 0; 409 | if (--len1 == 1) { 410 | goto epilogue; 411 | } 412 | } 413 | } while ((count1 | count2) < minGallop); 414 | 415 | do { 416 | GFX_TIMSORT_ASSERT(len1 > 1); 417 | GFX_TIMSORT_ASSERT(len2 > 0); 418 | 419 | count1 = gallopRight(std::invoke(proj, *cursor2), cursor1, len1, 0, comp, proj); 420 | if (count1 != 0) { 421 | std::ranges::move_backward(cursor1, cursor1 + count1, dest + count1); 422 | dest += count1; 423 | cursor1 += count1; 424 | len1 -= count1; 425 | 426 | if (len1 <= 1) { 427 | goto epilogue; 428 | } 429 | } 430 | *dest = std::ranges::iter_move(cursor2); 431 | ++cursor2; 432 | ++dest; 433 | if (--len2 == 0) { 434 | goto epilogue; 435 | } 436 | 437 | count2 = gallopLeft(std::invoke(proj, *cursor1), cursor2, len2, 0, comp, proj); 438 | if (count2 != 0) { 439 | std::ranges::move(cursor2, cursor2 + count2, dest); 440 | dest += count2; 441 | cursor2 += count2; 442 | len2 -= count2; 443 | if (len2 == 0) { 444 | goto epilogue; 445 | } 446 | } 447 | *dest = std::ranges::iter_move(cursor1); 448 | ++cursor1; 449 | ++dest; 450 | if (--len1 == 1) { 451 | goto epilogue; 452 | } 453 | 454 | --minGallop; 455 | } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); 456 | 457 | if (minGallop < 0) { 458 | minGallop = 0; 459 | } 460 | minGallop += 2; 461 | } // end of "outer" loop 462 | 463 | epilogue: // merge what is left from either cursor1 or cursor2 464 | 465 | minGallop_ = (std::min)(minGallop, 1); 466 | 467 | if (len1 == 1) { 468 | GFX_TIMSORT_ASSERT(len2 > 0); 469 | std::ranges::move(cursor2, cursor2 + len2, dest); 470 | *(dest + len2) = std::ranges::iter_move(cursor1); 471 | } else { 472 | GFX_TIMSORT_ASSERT(len1 != 0 && "Comparison function violates its general contract"); 473 | GFX_TIMSORT_ASSERT(len2 == 0); 474 | GFX_TIMSORT_ASSERT(len1 > 1); 475 | std::ranges::move(cursor1, cursor1 + len1, dest); 476 | } 477 | } 478 | 479 | template 480 | void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, 481 | Compare comp, Projection proj) { 482 | GFX_TIMSORT_ASSERT(len1 > 0); 483 | GFX_TIMSORT_ASSERT(len2 > 0); 484 | GFX_TIMSORT_ASSERT(base1 + len1 == base2); 485 | 486 | if (len1 == 1) { 487 | return rotateLeft(base1, base2 + len2); 488 | } 489 | if (len2 == 1) { 490 | return rotateRight(base1, base2 + len2); 491 | } 492 | 493 | move_to_tmp(base2, len2); 494 | 495 | auto cursor1 = base1 + len1; 496 | auto cursor2 = tmp_.begin() + (len2 - 1); 497 | auto dest = base2 + (len2 - 1); 498 | 499 | *dest = std::ranges::iter_move(--cursor1); 500 | --dest; 501 | --len1; 502 | 503 | int minGallop(minGallop_); 504 | 505 | // outer: 506 | while (true) { 507 | diff_t count1 = 0; 508 | diff_t count2 = 0; 509 | 510 | // The next loop is a hot path of the algorithm, so we decrement 511 | // eagerly the cursor so that it always points directly to the value 512 | // to compare, but we have to implement some trickier logic to make 513 | // sure that it points to the next value again by the end of said loop 514 | --cursor1; 515 | 516 | do { 517 | GFX_TIMSORT_ASSERT(len1 > 0); 518 | GFX_TIMSORT_ASSERT(len2 > 1); 519 | 520 | if (std::invoke(comp, std::invoke(proj, *cursor2), std::invoke(proj, *cursor1))) { 521 | *dest = std::ranges::iter_move(cursor1); 522 | --dest; 523 | ++count1; 524 | count2 = 0; 525 | if (--len1 == 0) { 526 | goto epilogue; 527 | } 528 | --cursor1; 529 | } else { 530 | *dest = std::ranges::iter_move(cursor2); 531 | --cursor2; 532 | --dest; 533 | ++count2; 534 | count1 = 0; 535 | if (--len2 == 1) { 536 | ++cursor1; // See comment before the loop 537 | goto epilogue; 538 | } 539 | } 540 | } while ((count1 | count2) < minGallop); 541 | ++cursor1; // See comment before the loop 542 | 543 | do { 544 | GFX_TIMSORT_ASSERT(len1 > 0); 545 | GFX_TIMSORT_ASSERT(len2 > 1); 546 | 547 | count1 = len1 - gallopRight(std::invoke(proj, *cursor2), 548 | base1, len1, len1 - 1, comp, proj); 549 | if (count1 != 0) { 550 | dest -= count1; 551 | cursor1 -= count1; 552 | len1 -= count1; 553 | std::ranges::move_backward(cursor1, cursor1 + count1, dest + (1 + count1)); 554 | 555 | if (len1 == 0) { 556 | goto epilogue; 557 | } 558 | } 559 | *dest = std::ranges::iter_move(cursor2); 560 | --cursor2; 561 | --dest; 562 | if (--len2 == 1) { 563 | goto epilogue; 564 | } 565 | 566 | count2 = len2 - gallopLeft(std::invoke(proj, *std::ranges::prev(cursor1)), 567 | tmp_.begin(), len2, len2 - 1, comp, proj); 568 | if (count2 != 0) { 569 | dest -= count2; 570 | cursor2 -= count2; 571 | len2 -= count2; 572 | std::ranges::move(std::ranges::next(cursor2), 573 | cursor2 + (1 + count2), 574 | std::ranges::next(dest)); 575 | if (len2 <= 1) { 576 | goto epilogue; 577 | } 578 | } 579 | *dest = std::ranges::iter_move(--cursor1); 580 | --dest; 581 | if (--len1 == 0) { 582 | goto epilogue; 583 | } 584 | 585 | --minGallop; 586 | } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); 587 | 588 | if (minGallop < 0) { 589 | minGallop = 0; 590 | } 591 | minGallop += 2; 592 | } // end of "outer" loop 593 | 594 | epilogue: // merge what is left from either cursor1 or cursor2 595 | 596 | minGallop_ = (std::min)(minGallop, 1); 597 | 598 | if (len2 == 1) { 599 | GFX_TIMSORT_ASSERT(len1 > 0); 600 | dest -= len1; 601 | std::ranges::move_backward(cursor1 - len1, cursor1, dest + (1 + len1)); 602 | *dest = std::ranges::iter_move(cursor2); 603 | } else { 604 | GFX_TIMSORT_ASSERT(len2 != 0 && "Comparison function violates its general contract"); 605 | GFX_TIMSORT_ASSERT(len1 == 0); 606 | GFX_TIMSORT_ASSERT(len2 > 1); 607 | std::ranges::move(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); 608 | } 609 | } 610 | 611 | void move_to_tmp(iter_t const begin, diff_t len) { 612 | tmp_.assign(std::make_move_iterator(begin), 613 | std::make_move_iterator(begin + len)); 614 | } 615 | 616 | public: 617 | 618 | template 619 | static void merge(iter_t const lo, iter_t const mid, iter_t const hi, 620 | Compare comp, Projection proj) { 621 | GFX_TIMSORT_ASSERT(lo <= mid); 622 | GFX_TIMSORT_ASSERT(mid <= hi); 623 | 624 | if (lo == mid || mid == hi) { 625 | return; // nothing to do 626 | } 627 | 628 | TimSort ts; 629 | ts.mergeConsecutiveRuns(lo, mid - lo, mid, hi - mid, std::move(comp), std::move(proj)); 630 | 631 | GFX_TIMSORT_LOG("1st size: " << (mid - lo) << "; 2nd size: " << (hi - mid) 632 | << "; tmp_.size(): " << ts.tmp_.size()); 633 | } 634 | 635 | template 636 | static void sort(iter_t const lo, iter_t const hi, Compare comp, Projection proj) { 637 | GFX_TIMSORT_ASSERT(lo <= hi); 638 | 639 | auto nRemaining = hi - lo; 640 | if (nRemaining < 2) { 641 | return; // nothing to do 642 | } 643 | 644 | if (nRemaining < MIN_MERGE) { 645 | auto initRunLen = countRunAndMakeAscending(lo, hi, comp, proj); 646 | GFX_TIMSORT_LOG("initRunLen: " << initRunLen); 647 | binarySort(lo, hi, lo + initRunLen, comp, proj); 648 | return; 649 | } 650 | 651 | TimSort ts; 652 | auto minRun = minRunLength(nRemaining); 653 | auto cur = lo; 654 | do { 655 | auto runLen = countRunAndMakeAscending(cur, hi, comp, proj); 656 | 657 | if (runLen < minRun) { 658 | auto force = (std::min)(nRemaining, minRun); 659 | binarySort(cur, cur + force, cur + runLen, comp, proj); 660 | runLen = force; 661 | } 662 | 663 | ts.pushRun(cur, runLen); 664 | ts.mergeCollapse(comp, proj); 665 | 666 | cur += runLen; 667 | nRemaining -= runLen; 668 | } while (nRemaining != 0); 669 | 670 | GFX_TIMSORT_ASSERT(cur == hi); 671 | ts.mergeForceCollapse(comp, proj); 672 | GFX_TIMSORT_ASSERT(ts.pending_.size() == 1); 673 | 674 | GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() 675 | << " pending_.size(): " << ts.pending_.size()); 676 | } 677 | }; 678 | 679 | } // namespace detail 680 | 681 | 682 | // --------------------------------------- 683 | // Public interface implementation 684 | // --------------------------------------- 685 | 686 | /** 687 | * Stably merges two consecutive sorted ranges [first, middle) and [middle, last) into one 688 | * sorted range [first, last) with a comparison function and a projection function. 689 | */ 690 | template < 691 | std::random_access_iterator Iterator, 692 | std::sentinel_for Sentinel, 693 | typename Compare = std::ranges::less, 694 | typename Projection = std::identity 695 | > 696 | requires std::sortable 697 | auto timmerge(Iterator first, Iterator middle, Sentinel last, 698 | Compare comp={}, Projection proj={}) 699 | -> Iterator 700 | { 701 | auto last_it = std::ranges::next(first, last); 702 | GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, middle, comp, proj) && "Precondition"); 703 | GFX_TIMSORT_AUDIT(std::ranges::is_sorted(middle, last_it, comp, proj) && "Precondition"); 704 | detail::TimSort::merge(first, middle, last_it, comp, proj); 705 | GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, last_it, comp, proj) && "Postcondition"); 706 | return last_it; 707 | } 708 | 709 | /** 710 | * Stably merges two sorted halves [first, middle) and [middle, last) of a range into one 711 | * sorted range [first, last) with a comparison function and a projection function. 712 | */ 713 | template < 714 | std::ranges::random_access_range Range, 715 | typename Compare = std::ranges::less, 716 | typename Projection = std::identity 717 | > 718 | requires std::sortable, Compare, Projection> 719 | auto timmerge(Range &&range, std::ranges::iterator_t middle, 720 | Compare comp={}, Projection proj={}) 721 | -> std::ranges::borrowed_iterator_t 722 | { 723 | return gfx::timmerge(std::begin(range), middle, std::end(range), comp, proj); 724 | } 725 | 726 | /** 727 | * Stably sorts a range with a comparison function and a projection function. 728 | */ 729 | template < 730 | std::random_access_iterator Iterator, 731 | std::sentinel_for Sentinel, 732 | typename Compare = std::ranges::less, 733 | typename Projection = std::identity 734 | > 735 | requires std::sortable 736 | auto timsort(Iterator first, Sentinel last, 737 | Compare comp={}, Projection proj={}) 738 | -> Iterator 739 | { 740 | auto last_it = std::ranges::next(first, last); 741 | detail::TimSort::sort(first, last_it, comp, proj); 742 | GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, last_it, comp, proj) && "Postcondition"); 743 | return last_it; 744 | } 745 | 746 | /** 747 | * Stably sorts a range with a comparison function and a projection function. 748 | */ 749 | template < 750 | std::ranges::random_access_range Range, 751 | typename Compare = std::ranges::less, 752 | typename Projection = std::identity 753 | > 754 | requires std::sortable, Compare, Projection> 755 | auto timsort(Range &&range, Compare comp={}, Projection proj={}) 756 | -> std::ranges::borrowed_iterator_t 757 | { 758 | return gfx::timsort(std::begin(range), std::end(range), comp, proj); 759 | } 760 | 761 | } // namespace gfx 762 | 763 | #undef GFX_TIMSORT_ENABLE_ASSERT 764 | #undef GFX_TIMSORT_ASSERT 765 | #undef GFX_TIMSORT_ENABLE_AUDIT 766 | #undef GFX_TIMSORT_AUDIT 767 | #undef GFX_TIMSORT_ENABLE_LOG 768 | #undef GFX_TIMSORT_LOG 769 | 770 | #endif // GFX_TIMSORT_HPP 771 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2024 Morwenn 2 | # SPDX-License-Identifier: MIT 3 | 4 | cmake_minimum_required(VERSION 3.24.0) 5 | 6 | include(FetchContent) 7 | 8 | # Test suite options 9 | option(GFX_TIMSORT_USE_VALGRIND "Whether to run the tests with Valgrind" OFF) 10 | set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") 11 | 12 | # Find/download Catch2 13 | FetchContent_Declare( 14 | Catch2 15 | GIT_REPOSITORY https://github.com/catchorg/Catch2 16 | GIT_TAG fa43b77429ba76c462b1898d6cd2f2d7a9416b14 # v3.7.1 17 | SYSTEM 18 | FIND_PACKAGE_ARGS 3.1.0 19 | ) 20 | FetchContent_MakeAvailable(Catch2) 21 | list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) 22 | include(Catch) 23 | 24 | # Configure Valgrind 25 | if (${GFX_TIMSORT_USE_VALGRIND}) 26 | find_program(MEMORYCHECK_COMMAND valgrind) 27 | set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") 28 | endif() 29 | 30 | macro(configure_tests target) 31 | # Add required dependencies to tests 32 | target_link_libraries(${target} PRIVATE 33 | Catch2::Catch2WithMain 34 | gfx::timsort 35 | ) 36 | 37 | target_compile_definitions(${target} PRIVATE 38 | # Somewhat speed up Catch2 compile times 39 | CATCH_CONFIG_FAST_COMPILE 40 | # Fortify test suite for more thorough checks 41 | _FORTIFY_SOURCE=3 42 | _GLIBCXX_ASSERTIONS 43 | _LIBCPP_ENABLE_ASSERTIONS=1 44 | GFX_TIMSORT_ENABLE_ASSERT 45 | ) 46 | 47 | # Add warnings 48 | if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") 49 | target_compile_options(${target} PRIVATE 50 | -Wall -Wextra -Wcast-align -Wmissing-declarations -Wmissing-include-dirs 51 | -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code 52 | $<$:-Wlogical-op -Wuseless-cast> 53 | ) 54 | elseif (MSVC) 55 | target_compile_options(${target} PRIVATE /W4) 56 | endif() 57 | 58 | # Configure optimization options 59 | if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") 60 | target_compile_options(${target} PRIVATE 61 | $<$,$>:-O0> 62 | $<$,$>:-Og> 63 | ) 64 | endif() 65 | 66 | # Use lld or the gold linker if possible 67 | if (UNIX AND NOT APPLE) 68 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 69 | set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -fuse-ld=lld") 70 | else() 71 | set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -fuse-ld=gold") 72 | endif() 73 | endif() 74 | 75 | # Optionally enable sanitizers 76 | if (UNIX AND GFX_TIMSORT_SANITIZE) 77 | target_compile_options(${target} PRIVATE 78 | -fsanitize=${GFX_TIMSORT_SANITIZE} 79 | -fno-sanitize-recover=all 80 | ) 81 | set_property(TARGET ${target} 82 | APPEND_STRING PROPERTY LINK_FLAGS 83 | " -fsanitize=${GFX_TIMSORT_SANITIZE}" 84 | ) 85 | endif() 86 | endmacro() 87 | 88 | # Tests that can run with C++98 89 | add_executable(cxx_98_tests 90 | cxx_98_tests.cpp 91 | verbose_abort.cpp 92 | ) 93 | configure_tests(cxx_98_tests) 94 | target_compile_features(cxx_98_tests PRIVATE cxx_std_98) 95 | 96 | # Tests requiring C++11 support 97 | add_executable(cxx_11_tests 98 | merge_cxx_11_tests.cpp 99 | cxx_11_tests.cpp 100 | verbose_abort.cpp 101 | ) 102 | configure_tests(cxx_11_tests) 103 | target_compile_features(cxx_11_tests PRIVATE cxx_std_11) 104 | 105 | # Tests requiring C++17 support 106 | add_executable(cxx_17_tests 107 | cxx_17_tests.cpp 108 | verbose_abort.cpp 109 | ) 110 | configure_tests(cxx_17_tests) 111 | target_compile_features(cxx_17_tests PRIVATE cxx_std_17) 112 | 113 | # Tests requiring C++20 support 114 | add_executable(cxx_20_tests 115 | cxx_20_tests.cpp 116 | verbose_abort.cpp 117 | ) 118 | configure_tests(cxx_20_tests) 119 | target_compile_features(cxx_20_tests PRIVATE cxx_std_20) 120 | 121 | # Tests requiring C++23 support 122 | if ("cxx_std_23" IN_LIST CMAKE_CXX_COMPILE_FEATURES) 123 | add_executable(cxx_23_tests 124 | cxx_23_tests.cpp 125 | verbose_abort.cpp 126 | ) 127 | configure_tests(cxx_23_tests) 128 | target_compile_features(cxx_23_tests PRIVATE cxx_std_23) 129 | endif() 130 | 131 | # Windows-specific tests 132 | if (WIN32) 133 | add_executable(windows_tests 134 | windows.cpp 135 | ) 136 | configure_tests(windows_tests) 137 | target_compile_features(windows_tests PRIVATE cxx_std_98) 138 | endif() 139 | 140 | include(CTest) 141 | include(Catch) 142 | 143 | string(RANDOM LENGTH 5 ALPHABET 123456789 RNG_SEED) 144 | catch_discover_tests(cxx_98_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) 145 | catch_discover_tests(cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) 146 | catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) 147 | catch_discover_tests(cxx_20_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) 148 | if ("cxx_std_23" IN_LIST CMAKE_CXX_COMPILE_FEATURES) 149 | catch_discover_tests(cxx_23_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) 150 | endif() 151 | if (WIN32) 152 | catch_discover_tests(windows_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) 153 | endif() 154 | -------------------------------------------------------------------------------- /tests/cxx_11_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019-2024 Morwenn. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "test_helpers.hpp" 16 | 17 | //////////////////////////////////////////////////////////// 18 | // Move-only type for benchmarks 19 | // 20 | // std::sort and std::stable_sort are supposed to be able to 21 | // sort collections of types that are move-only and that are 22 | // not default-constructible. The class template move_only 23 | // wraps such a type and can be fed to algorithms to check 24 | // whether they still compile. 25 | // 26 | // Additionally, move_only detects attempts to read the value 27 | // after a move has been performed and throws an exceptions 28 | // when it happens. 29 | // 30 | // It also checks that no self-move is performed, since the 31 | // standard algorithms can't rely on that to work either. 32 | // 33 | 34 | namespace 35 | { 36 | template struct move_only { 37 | // Not default-constructible 38 | move_only() = delete; 39 | 40 | // Move-only 41 | move_only(const move_only &) = delete; 42 | move_only& operator=(const move_only &) = delete; 43 | 44 | // Can be constructed from a T for convenience 45 | move_only(const T &value) : can_read(true), value(value) { 46 | } 47 | 48 | // Move operators 49 | 50 | move_only(move_only &&other) : can_read(true), value(std::move(other.value)) { 51 | if (!exchange(other.can_read, false)) { 52 | throw std::logic_error("illegal read from a moved-from value"); 53 | } 54 | } 55 | 56 | auto operator=(move_only &&other) -> move_only & { 57 | // Self-move should be ok if the object is already in a moved-from 58 | // state because it incurs no data loss, but should otherwise be 59 | // frowned upon 60 | if (&other == this && can_read) { 61 | throw std::logic_error("illegal self-move was performed"); 62 | } 63 | 64 | // Assign before overwriting other.can_read 65 | can_read = other.can_read; 66 | value = std::move(other.value); 67 | 68 | // If the two objects are not the same and we try to read from an 69 | // object in a moved-from state, then it's a hard error because 70 | // data might be lost 71 | if (!exchange(other.can_read, false) && &other != this) { 72 | throw std::logic_error("illegal read from a moved-from value"); 73 | } 74 | 75 | return *this; 76 | } 77 | 78 | // A C++11 backport of std::exchange() 79 | template auto exchange(U &obj, U &&new_val) -> U { 80 | U old_val = std::move(obj); 81 | obj = std::forward(new_val); 82 | return old_val; 83 | } 84 | 85 | // Whether the value can be read 86 | bool can_read = false; 87 | // Actual value 88 | T value; 89 | }; 90 | } 91 | 92 | template 93 | bool operator<(const move_only &lhs, const move_only &rhs) 94 | { 95 | return lhs.value < rhs.value; 96 | } 97 | 98 | template 99 | void swap(move_only &lhs, move_only &rhs) 100 | { 101 | // This function matters because we want to prevent self-moves 102 | // but we don't want to prevent self-swaps because it is the 103 | // responsibility of class authors to make sure that self-swap 104 | // does the right thing, and not the responsibility of algorithm 105 | // authors to prevent them from happening 106 | 107 | // Both operands need to be readable 108 | if (!(lhs.can_read || rhs.can_read)) { 109 | throw std::logic_error("illegal read from a moved-from value"); 110 | } 111 | 112 | // Swapping the values is enough to preserve the preconditions 113 | using std::swap; 114 | swap(lhs.value, rhs.value); 115 | } 116 | 117 | TEST_CASE( "shuffle10k_for_move_only_types" ) { 118 | const int size = 1024 * 10; // should be even number of elements 119 | 120 | std::vector > a; 121 | for (int i = 0; i < size; ++i) { 122 | a.push_back((i + 1) * 10); 123 | } 124 | 125 | for (int n = 0; n < 100; ++n) { 126 | test_helpers::shuffle(a.begin(), a.end()); 127 | 128 | gfx::timsort(a.begin(), a.end(), [](const move_only &x, const move_only &y) { return x.value < y.value; }); 129 | 130 | for (int i = 0; i < size; ++i) { 131 | CHECK(a[i].value == (i + 1) * 10); 132 | } 133 | } 134 | } 135 | 136 | TEST_CASE( "merge_shuffle10k_for_move_only_types" ) { 137 | const int size = 1024 * 10; // should be even number of elements 138 | 139 | std::vector > a; 140 | for (int i = 0; i < size; ++i) { 141 | a.push_back((i + 1) * 10); 142 | } 143 | 144 | for (int n = 0; n < 100; ++n) { 145 | test_helpers::shuffle(a.begin(), a.end()); 146 | 147 | const auto compare = [](const move_only &x, const move_only &y) { return x.value < y.value; }; 148 | const auto middle = a.begin() + rand() % size; 149 | gfx::timsort(a.begin(), middle, compare); 150 | gfx::timsort(middle, a.end(), compare); 151 | gfx::timmerge(a.begin(), middle, a.end(), compare); 152 | 153 | for (int i = 0; i < size; ++i) { 154 | CHECK(a[i].value == (i + 1) * 10); 155 | } 156 | } 157 | } 158 | 159 | TEST_CASE( "issue14" ) { 160 | const int a[] = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, 161 | 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; 162 | std::deque c(std::begin(a), std::end(a)); 163 | 164 | SECTION( "timsort" ) { 165 | gfx::timsort(std::begin(c), std::end(c)); 166 | CHECK(std::is_sorted(std::begin(c), std::end(c))); 167 | } 168 | 169 | SECTION( "timmerge" ) { 170 | for (auto middle = c.begin(); ; ++middle) { 171 | std::copy(std::begin(a), std::end(a), c.begin()); 172 | 173 | gfx::timsort(c.begin(), middle); 174 | gfx::timsort(middle, c.end()); 175 | gfx::timmerge(c.begin(), middle, c.end()); 176 | CHECK(std::is_sorted(c.cbegin(), c.cend())); 177 | 178 | if (middle == c.end()) { 179 | break; 180 | } 181 | } 182 | } 183 | } 184 | 185 | TEST_CASE( "range signatures" ) { 186 | std::vector vec(50, 0); 187 | std::iota(vec.begin(), vec.end(), -25); 188 | test_helpers::shuffle(vec.begin(), vec.end()); 189 | 190 | SECTION( "range only" ) { 191 | gfx::timsort(vec); 192 | CHECK(std::is_sorted(vec.begin(), vec.end())); 193 | } 194 | 195 | SECTION( "range with a comparison function" ) { 196 | using value_type = std::vector::value_type; 197 | gfx::timsort(vec, std::greater{}); 198 | CHECK(std::is_sorted(vec.begin(), vec.end(), std::greater{})); 199 | } 200 | 201 | SECTION( "range with comparison and projection functions" ) { 202 | using value_type = std::vector::value_type; 203 | gfx::timsort(vec, std::greater{}, std::negate{}); 204 | CHECK(std::is_sorted(vec.begin(), vec.end())); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /tests/cxx_17_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019-2024 Morwenn. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace 16 | { 17 | struct wrapper { 18 | wrapper() = default; 19 | wrapper(wrapper const&) = default; 20 | wrapper(wrapper&&) = default; 21 | 22 | wrapper(int val) : value(val) { 23 | } 24 | 25 | wrapper& operator=(wrapper const&) = default; 26 | wrapper& operator=(wrapper&&) = default; 27 | 28 | wrapper& operator=(int val) { 29 | value = val; 30 | return *this; 31 | } 32 | 33 | bool compare_to(wrapper const& other) const { 34 | return value < other.value; 35 | } 36 | 37 | int value = 0; 38 | }; 39 | } 40 | 41 | TEST_CASE( "generalized callables" ) { 42 | std::vector vec(50); 43 | std::iota(vec.begin(), vec.end(), -25); 44 | std::mt19937 gen(123456); // fixed seed is enough 45 | std::shuffle(vec.begin(), vec.end(), gen); 46 | 47 | const auto is_vec_sorted = [&vec] { 48 | return std::is_sorted(vec.begin(), vec.end(), [](wrapper const& lhs, wrapper const& rhs) { 49 | return lhs.value < rhs.value; 50 | }); 51 | }; 52 | 53 | SECTION( "timsort for comparisons" ) { 54 | gfx::timsort(vec, &wrapper::compare_to); 55 | CHECK(is_vec_sorted()); 56 | } 57 | 58 | SECTION( "timsort for projections" ) { 59 | gfx::timsort(vec, std::less<>{}, &wrapper::value); 60 | CHECK(is_vec_sorted()); 61 | } 62 | 63 | std::uniform_int_distribution random_middle(0, static_cast(vec.size())); 64 | 65 | SECTION( "timmerge for comparisons" ) { 66 | const auto middle = vec.begin() + random_middle(gen); 67 | gfx::timsort(vec.begin(), middle, &wrapper::compare_to); 68 | gfx::timsort(middle, vec.end(), &wrapper::compare_to); 69 | gfx::timmerge(vec.begin(), middle, vec.end(), &wrapper::compare_to); 70 | CHECK(is_vec_sorted()); 71 | } 72 | 73 | SECTION( "timmerge for projections" ) { 74 | const auto middle = vec.begin() + random_middle(gen); 75 | gfx::timsort(vec.begin(), middle, std::less<>{}, &wrapper::value); 76 | gfx::timsort(middle, vec.end(), std::less<>{}, &wrapper::value); 77 | gfx::timmerge(vec.begin(), middle, vec.end(), std::less<>{}, &wrapper::value); 78 | CHECK(is_vec_sorted()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/cxx_20_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Morwenn. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "test_helpers.hpp" 15 | 16 | TEST_CASE( "support for temporary types" ) { 17 | SECTION( "timsort over std::span" ) { 18 | std::vector vec(50); 19 | std::iota(vec.begin(), vec.end(), -25); 20 | test_helpers::shuffle(vec); 21 | 22 | auto last_it = gfx::timsort(std::span(vec)); 23 | CHECK(std::ranges::is_sorted(vec)); 24 | CHECK(last_it == std::span(vec).end()); 25 | } 26 | 27 | SECTION( "timmerge over std::span" ) { 28 | std::vector vec(100); 29 | std::iota(vec.begin(), vec.end(), -25); 30 | test_helpers::shuffle(vec); 31 | 32 | auto middle = vec.begin() + 38; 33 | gfx::timsort(vec.begin(), middle); 34 | gfx::timsort(middle, vec.end()); 35 | 36 | auto view = std::span(vec); 37 | auto last_it = gfx::timmerge(std::span(vec), view.begin() + 38); 38 | CHECK(std::ranges::is_sorted(vec)); 39 | CHECK(last_it == view.end()); 40 | } 41 | } 42 | 43 | TEST_CASE( "dangling return type" ) { 44 | SECTION( "timsort over temporary std::vector" ) { 45 | std::vector vec(50, 8); 46 | auto last_it = gfx::timsort(std::move(vec)); 47 | STATIC_CHECK(std::is_same_v); 48 | } 49 | 50 | SECTION( "timmerge over temporary std::vector" ) { 51 | std::vector vec(30, 5); 52 | auto last_it = gfx::timmerge(std::move(vec), vec.begin() + 14); 53 | STATIC_CHECK(std::is_same_v); 54 | } 55 | } 56 | 57 | TEST_CASE( "support for sentinels" ) { 58 | SECTION( "timsort with sentinel" ) { 59 | std::vector vec(100); 60 | std::iota(vec.begin(), vec.end(), -25); 61 | test_helpers::shuffle(vec); 62 | 63 | auto last_it = gfx::timsort(std::counted_iterator(vec.begin(), 85), 64 | std::default_sentinel); 65 | CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); 66 | CHECK(last_it == std::counted_iterator(vec.begin() + 85, 0)); 67 | CHECK(last_it == std::default_sentinel); 68 | } 69 | 70 | SECTION( "timmerge with sentinel" ) { 71 | std::vector vec(100); 72 | std::iota(vec.begin(), vec.end(), -25); 73 | test_helpers::shuffle(vec); 74 | 75 | auto middle = vec.begin() + 38; 76 | gfx::timsort(vec.begin(), middle); 77 | gfx::timsort(middle, vec.end()); 78 | 79 | auto last_it = gfx::timmerge(std::counted_iterator(vec.begin(), 85), 80 | std::counted_iterator(middle, 85 - 38), 81 | std::default_sentinel); 82 | CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); 83 | CHECK(last_it == std::counted_iterator(vec.begin() + 85, 0)); 84 | CHECK(last_it == std::default_sentinel); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/cxx_23_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Morwenn. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "test_helpers.hpp" 14 | 15 | 16 | #if defined(__cpp_lib_ranges_zip) 17 | 18 | TEST_CASE( "support for std::ranges::views::zip" ) 19 | { 20 | SECTION( "zip two small collections" ) { 21 | // issue #40 22 | std::vector vec = {4, 2, 3, 1}; 23 | std::array arr = {'A', 'C', 'B', 'D'}; 24 | auto zipped = std::views::zip(vec, arr); 25 | 26 | gfx::timsort( 27 | zipped, {}, 28 | [](std::tuple const& pair) { 29 | return std::get<0>(pair); 30 | } 31 | ); 32 | CHECK( std::ranges::is_sorted(vec) ); 33 | CHECK( std::ranges::is_sorted(arr, std::ranges::greater{}) ); 34 | } 35 | 36 | SECTION( "zip two big collections" ) { 37 | std::vector vec(3000); 38 | std::deque deq(3000); 39 | std::iota(vec.begin(), vec.end(), -500); 40 | std::ranges::reverse(vec); 41 | std::iota(deq.begin(), deq.end(), -500); 42 | 43 | auto zipped = std::views::zip(vec, deq); 44 | test_helpers::shuffle(zipped); 45 | 46 | gfx::timsort(zipped); 47 | CHECK( std::ranges::is_sorted(vec) ); 48 | CHECK( std::ranges::is_sorted(deq, std::ranges::greater{}) ); 49 | } 50 | } 51 | 52 | #endif // defined(__cpp_lib_ranges_zip) 53 | -------------------------------------------------------------------------------- /tests/cxx_98_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019-2024 Morwenn. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "test_helpers.hpp" 15 | 16 | using namespace test_helpers; 17 | 18 | TEST_CASE( "simple0" ) { 19 | std::vector a; 20 | 21 | gfx::timsort(a.begin(), a.end(), std::less()); 22 | 23 | CHECK(a.size() == std::size_t(0)); 24 | } 25 | 26 | TEST_CASE( "simple1" ) { 27 | std::vector a; 28 | 29 | a.push_back(42); 30 | 31 | gfx::timsort(a.begin(), a.end(), std::less()); 32 | 33 | CHECK(a.size() == std::size_t(1)); 34 | CHECK(a[0] == 42); 35 | } 36 | 37 | TEST_CASE( "simple2" ) { 38 | std::vector a; 39 | 40 | a.push_back(10); 41 | a.push_back(20); 42 | 43 | gfx::timsort(a.begin(), a.end(), std::less()); 44 | 45 | CHECK(a.size() == std::size_t(2)); 46 | CHECK(a[0] == 10); 47 | CHECK(a[1] == 20); 48 | 49 | a.clear(); 50 | a.push_back(20); 51 | a.push_back(10); 52 | 53 | gfx::timsort(a.begin(), a.end(), std::less()); 54 | 55 | CHECK(a.size() == std::size_t(2)); 56 | CHECK(a[0] == 10); 57 | CHECK(a[1] == 20); 58 | 59 | a.clear(); 60 | a.push_back(10); 61 | a.push_back(10); 62 | 63 | gfx::timsort(a.begin(), a.end(), std::less()); 64 | 65 | CHECK(a.size() == std::size_t(2)); 66 | CHECK(a[0] == 10); 67 | CHECK(a[1] == 10); 68 | } 69 | 70 | TEST_CASE( "simple10" ) { 71 | std::vector a; 72 | a.push_back(60); 73 | a.push_back(50); 74 | a.push_back(10); 75 | a.push_back(40); 76 | a.push_back(80); 77 | a.push_back(20); 78 | a.push_back(30); 79 | a.push_back(70); 80 | a.push_back(10); 81 | a.push_back(90); 82 | 83 | gfx::timsort(a.begin(), a.end(), std::less()); 84 | 85 | CHECK(a[0] == 10); 86 | CHECK(a[1] == 10); 87 | CHECK(a[2] == 20); 88 | CHECK(a[3] == 30); 89 | CHECK(a[4] == 40); 90 | CHECK(a[5] == 50); 91 | CHECK(a[6] == 60); 92 | CHECK(a[7] == 70); 93 | CHECK(a[8] == 80); 94 | CHECK(a[9] == 90); 95 | 96 | std::reverse(a.begin(), a.end()); 97 | 98 | gfx::timsort(a.begin(), a.end(), std::less()); 99 | 100 | CHECK(a[0] == 10); 101 | CHECK(a[1] == 10); 102 | CHECK(a[2] == 20); 103 | CHECK(a[3] == 30); 104 | CHECK(a[4] == 40); 105 | CHECK(a[5] == 50); 106 | CHECK(a[6] == 60); 107 | CHECK(a[7] == 70); 108 | CHECK(a[8] == 80); 109 | CHECK(a[9] == 90); 110 | } 111 | 112 | TEST_CASE( "shuffle30" ) { 113 | const int size = 30; 114 | 115 | std::vector a; 116 | for (int i = 0; i < size; ++i) { 117 | a.push_back((i + 1) * 10); 118 | } 119 | test_helpers::shuffle(a.begin(), a.end()); 120 | 121 | gfx::timsort(a.begin(), a.end(), std::less()); 122 | 123 | CHECK(a.size() == std::size_t(size)); 124 | for (int i = 0; i < size; ++i) { 125 | CHECK(a[i] == (i + 1) * 10); 126 | } 127 | } 128 | 129 | TEST_CASE( "shuffle31" ) { 130 | const int size = 31; 131 | 132 | std::vector a; 133 | for (int i = 0; i < size; ++i) { 134 | a.push_back((i + 1) * 10); 135 | } 136 | test_helpers::shuffle(a.begin(), a.end()); 137 | 138 | gfx::timsort(a.begin(), a.end(), std::less()); 139 | 140 | CHECK(a.size() == std::size_t(size)); 141 | for (int i = 0; i < size; ++i) { 142 | CHECK(a[i] == (i + 1) * 10); 143 | } 144 | } 145 | 146 | TEST_CASE( "shuffle32" ) { 147 | const int size = 32; 148 | 149 | std::vector a; 150 | for (int i = 0; i < size; ++i) { 151 | a.push_back((i + 1) * 10); 152 | } 153 | test_helpers::shuffle(a.begin(), a.end()); 154 | 155 | gfx::timsort(a.begin(), a.end(), std::less()); 156 | 157 | CHECK(a.size() == std::size_t(size)); 158 | for (int i = 0; i < size; ++i) { 159 | CHECK(a[i] == (i + 1) * 10); 160 | } 161 | } 162 | 163 | TEST_CASE( "shuffle128" ) { 164 | const int size = 128; 165 | 166 | std::vector a; 167 | for (int i = 0; i < size; ++i) { 168 | a.push_back((i + 1) * 10); 169 | } 170 | test_helpers::shuffle(a.begin(), a.end()); 171 | 172 | gfx::timsort(a.begin(), a.end(), std::less()); 173 | 174 | CHECK(a.size() == std::size_t(size)); 175 | for (int i = 0; i < size; ++i) { 176 | CHECK(a[i] == (i + 1) * 10); 177 | } 178 | } 179 | 180 | TEST_CASE( "shuffle1023" ) { 181 | const int size = 1023; // odd number of elements 182 | 183 | std::vector a; 184 | for (int i = 0; i < size; ++i) { 185 | a.push_back((i + 1) * 10); 186 | } 187 | 188 | for (int n = 0; n < 100; ++n) { 189 | test_helpers::shuffle(a.begin(), a.end()); 190 | 191 | gfx::timsort(a.begin(), a.end(), std::less()); 192 | 193 | for (int i = 0; i < size; ++i) { 194 | CHECK(a[i] == (i + 1) * 10); 195 | } 196 | } 197 | } 198 | 199 | TEST_CASE( "shuffle1024" ) { 200 | const int size = 1024; // should be even number of elements 201 | 202 | std::vector a; 203 | for (int i = 0; i < size; ++i) { 204 | a.push_back((i + 1) * 10); 205 | } 206 | 207 | for (int n = 0; n < 100; ++n) { 208 | test_helpers::shuffle(a.begin(), a.end()); 209 | 210 | gfx::timsort(a.begin(), a.end(), std::less()); 211 | 212 | for (int i = 0; i < size; ++i) { 213 | CHECK(a[i] == (i + 1) * 10); 214 | } 215 | } 216 | } 217 | 218 | TEST_CASE( "partial_shuffle1023" ) { 219 | const int size = 1023; 220 | 221 | std::vector a; 222 | for (int i = 0; i < size; ++i) { 223 | a.push_back((i + 1) * 10); 224 | } 225 | 226 | // sorted-shuffled-sorted pattern 227 | for (int n = 0; n < 100; ++n) { 228 | test_helpers::shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); 229 | 230 | gfx::timsort(a.begin(), a.end(), std::less()); 231 | 232 | for (int i = 0; i < size; ++i) { 233 | CHECK(a[i] == (i + 1) * 10); 234 | } 235 | } 236 | 237 | // shuffled-sorted-shuffled pattern 238 | for (int n = 0; n < 100; ++n) { 239 | test_helpers::shuffle(a.begin(), a.begin() + (size / 3 * 1)); 240 | test_helpers::shuffle(a.begin() + (size / 3 * 2), a.end()); 241 | 242 | gfx::timsort(a.begin(), a.end(), std::less()); 243 | 244 | for (int i = 0; i < size; ++i) { 245 | CHECK(a[i] == (i + 1) * 10); 246 | } 247 | } 248 | } 249 | 250 | TEST_CASE( "partial_shuffle1024" ) { 251 | const int size = 1024; 252 | 253 | std::vector a; 254 | for (int i = 0; i < size; ++i) { 255 | a.push_back((i + 1) * 10); 256 | } 257 | 258 | // sorted-shuffled-sorted pattern 259 | for (int n = 0; n < 100; ++n) { 260 | test_helpers::shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); 261 | 262 | gfx::timsort(a.begin(), a.end(), std::less()); 263 | 264 | for (int i = 0; i < size; ++i) { 265 | CHECK(a[i] == (i + 1) * 10); 266 | } 267 | } 268 | 269 | // shuffled-sorted-shuffled pattern 270 | for (int n = 0; n < 100; ++n) { 271 | test_helpers::shuffle(a.begin(), a.begin() + (size / 3 * 1)); 272 | test_helpers::shuffle(a.begin() + (size / 3 * 2), a.end()); 273 | 274 | gfx::timsort(a.begin(), a.end(), std::less()); 275 | 276 | for (int i = 0; i < size; ++i) { 277 | CHECK(a[i] == (i + 1) * 10); 278 | } 279 | } 280 | } 281 | 282 | TEST_CASE( "shuffle1024r" ) { 283 | const int size = 1024; 284 | 285 | std::vector a; 286 | for (int i = 0; i < size; ++i) { 287 | a.push_back((i + 1) * 10); 288 | } 289 | 290 | for (int n = 0; n < 100; ++n) { 291 | test_helpers::shuffle(a.begin(), a.end()); 292 | 293 | gfx::timsort(a.begin(), a.end(), std::greater()); 294 | 295 | int j = size; 296 | for (int i = 0; i < size; ++i) { 297 | CHECK(a[i] == (--j + 1) * 10); 298 | } 299 | } 300 | } 301 | 302 | TEST_CASE( "partial_reversed1023" ) { 303 | const int size = 1023; 304 | 305 | std::vector a; 306 | for (int i = 0; i < size; ++i) { 307 | a.push_back((i + 1) * 10); 308 | } 309 | 310 | for (int n = 0; n < 100; ++n) { 311 | std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed 312 | 313 | gfx::timsort(a.begin(), a.end(), std::less()); 314 | 315 | for (int i = 0; i < size; ++i) { 316 | CHECK(a[i] == (i + 1) * 10); 317 | } 318 | } 319 | } 320 | 321 | TEST_CASE( "partial_reversed1024" ) { 322 | const int size = 1024; 323 | 324 | std::vector a; 325 | for (int i = 0; i < size; ++i) { 326 | a.push_back((i + 1) * 10); 327 | } 328 | 329 | for (int n = 0; n < 100; ++n) { 330 | std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed 331 | 332 | gfx::timsort(a.begin(), a.end(), std::less()); 333 | 334 | for (int i = 0; i < size; ++i) { 335 | CHECK(a[i] == (i + 1) * 10); 336 | } 337 | } 338 | } 339 | 340 | TEST_CASE( "c_array" ) { 341 | int a[] = {7, 1, 5, 3, 9}; 342 | 343 | gfx::timsort(a, a + sizeof(a) / sizeof(int), std::less()); 344 | 345 | CHECK(a[0] == 1); 346 | CHECK(a[1] == 3); 347 | CHECK(a[2] == 5); 348 | CHECK(a[3] == 7); 349 | CHECK(a[4] == 9); 350 | } 351 | 352 | TEST_CASE( "string_array" ) { 353 | std::string a[] = {"7", "1", "5", "3", "9"}; 354 | 355 | gfx::timsort(a, a + sizeof(a) / sizeof(std::string), std::less()); 356 | 357 | CHECK(a[0] == "1"); 358 | CHECK(a[1] == "3"); 359 | CHECK(a[2] == "5"); 360 | CHECK(a[3] == "7"); 361 | CHECK(a[4] == "9"); 362 | } 363 | 364 | TEST_CASE( "non_default_constructible" ) { 365 | NonDefaultConstructible a[] = {7, 1, 5, 3, 9}; 366 | 367 | gfx::timsort(a, a + sizeof(a) / sizeof(a[0]), std::less()); 368 | 369 | CHECK(a[0].i == 1); 370 | CHECK(a[1].i == 3); 371 | CHECK(a[2].i == 5); 372 | CHECK(a[3].i == 7); 373 | CHECK(a[4].i == 9); 374 | } 375 | 376 | TEST_CASE( "default_compare_function" ) { 377 | const int size = 128; 378 | 379 | std::vector a; 380 | for (int i = 0; i < size; ++i) { 381 | a.push_back((i + 1) * 10); 382 | } 383 | test_helpers::shuffle(a.begin(), a.end()); 384 | 385 | gfx::timsort(a.begin(), a.end()); 386 | 387 | CHECK(a.size() == std::size_t(size)); 388 | for (int i = 0; i < size; ++i) { 389 | CHECK(a[i] == (i + 1) * 10); 390 | } 391 | } 392 | 393 | TEST_CASE( "stability" ) { 394 | std::vector a; 395 | 396 | for (int i = 100; i >= 0; --i) { 397 | a.push_back(std::make_pair(i, foo)); 398 | a.push_back(std::make_pair(i, bar)); 399 | a.push_back(std::make_pair(i, baz)); 400 | } 401 | 402 | gfx::timsort(a.begin(), a.end(), &less_in_first); 403 | 404 | CHECK(a[0].first == 0); 405 | CHECK(a[0].second == foo); 406 | CHECK(a[1].first == 0); 407 | CHECK(a[1].second == bar); 408 | CHECK(a[2].first == 0); 409 | CHECK(a[2].second == baz); 410 | 411 | CHECK(a[3].first == 1); 412 | CHECK(a[3].second == foo); 413 | CHECK(a[4].first == 1); 414 | CHECK(a[4].second == bar); 415 | CHECK(a[5].first == 1); 416 | CHECK(a[5].second == baz); 417 | 418 | CHECK(a[6].first == 2); 419 | CHECK(a[6].second == foo); 420 | CHECK(a[7].first == 2); 421 | CHECK(a[7].second == bar); 422 | CHECK(a[8].first == 2); 423 | CHECK(a[8].second == baz); 424 | 425 | CHECK(a[9].first == 3); 426 | CHECK(a[9].second == foo); 427 | CHECK(a[10].first == 3); 428 | CHECK(a[10].second == bar); 429 | CHECK(a[11].first == 3); 430 | CHECK(a[11].second == baz); 431 | } 432 | 433 | TEST_CASE( "issue2_duplication" ) { 434 | std::vector > a; 435 | 436 | for (int i = 0; i < 10000; ++i) { 437 | int first = static_cast(rand()); 438 | int second = static_cast(rand()); 439 | 440 | a.push_back(std::make_pair(first, second)); 441 | } 442 | 443 | std::vector > expected(a); 444 | 445 | std::sort(expected.begin(), expected.end()); 446 | gfx::timsort(a.begin(), a.end()); 447 | 448 | #if 0 449 | for (std::size_t i = 0; i < a.size(); ++i) { 450 | std::clog << i << " "; 451 | std::clog << "(" << a[i].first << ", " << a[i].second << ")"; 452 | std::clog << " "; 453 | std::clog << "(" << expected[i].first << ", " << expected[i].second << ") "; 454 | std::clog << "\n"; 455 | } 456 | return; 457 | #endif 458 | 459 | CHECK(a.size() == expected.size()); 460 | 461 | // test some points 462 | 463 | CHECK(a[0].first == expected[0].first); 464 | CHECK(a[0].second == expected[0].second); 465 | 466 | CHECK(a[1].first == expected[1].first); 467 | CHECK(a[1].second == expected[1].second); 468 | 469 | CHECK(a[10].first == expected[10].first); 470 | CHECK(a[10].second == expected[10].second); 471 | 472 | CHECK(a[11].first == expected[11].first); 473 | CHECK(a[11].second == expected[11].second); 474 | 475 | CHECK(a[100].first == expected[100].first); 476 | CHECK(a[100].second == expected[100].second); 477 | 478 | CHECK(a[101].first == expected[101].first); 479 | CHECK(a[101].second == expected[101].second); 480 | 481 | CHECK(a[111].first == expected[111].first); 482 | CHECK(a[111].second == expected[111].second); 483 | 484 | CHECK(a[112].first == expected[112].first); 485 | CHECK(a[112].second == expected[112].second); 486 | 487 | CHECK(a[999].first == expected[999].first); 488 | CHECK(a[999].second == expected[999].second); 489 | 490 | CHECK(a[1000].first == expected[1000].first); 491 | CHECK(a[1000].second == expected[1000].second); 492 | 493 | CHECK(a[1001].first == expected[1001].first); 494 | CHECK(a[1001].second == expected[1001].second); 495 | } 496 | 497 | TEST_CASE( "projection" ) { 498 | const int size = 128; 499 | 500 | std::vector vec; 501 | for (int i = 0; i < size; ++i) { 502 | vec.push_back(i - 40); 503 | } 504 | test_helpers::shuffle(vec.begin(), vec.end()); 505 | 506 | gfx::timsort(vec.begin(), vec.end(), std::greater(), std::negate()); 507 | for (int i = 0; i < size; ++i) { 508 | CHECK(vec[i] == i - 40); 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /tests/merge_cxx_11_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019-2024 Morwenn. 4 | * Copyright (c) 2021 Igor Kushnir . 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "test_helpers.hpp" 19 | 20 | using namespace test_helpers; 21 | 22 | namespace 23 | { 24 | template 25 | void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle, Compare compare) { 26 | const auto first = std::begin(range); 27 | const auto last = std::end(range); 28 | gfx::timsort(first, middle, compare); 29 | gfx::timsort(middle, last, compare); 30 | gfx::timmerge(first, middle, last, compare); 31 | } 32 | 33 | template 34 | void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle) { 35 | using value_type = typename std::iterator_traits::value_type; 36 | sort_and_merge(range, middle, std::less()); 37 | } 38 | 39 | inline std::mt19937& random_engine() 40 | { 41 | thread_local std::mt19937 res(Catch::rngSeed()); 42 | return res; 43 | } 44 | } 45 | 46 | TEST_CASE( "merge_simple0" ) { 47 | std::vector a; 48 | 49 | gfx::timmerge(a.begin(), a.end(), a.end()); 50 | 51 | CHECK(a.size() == std::size_t(0)); 52 | } 53 | 54 | TEST_CASE( "merge_simple1" ) { 55 | std::vector a; 56 | 57 | a.push_back(-54); 58 | 59 | gfx::timmerge(a.begin(), a.end(), a.end(), std::greater()); 60 | 61 | CHECK(a.size() == std::size_t(1)); 62 | CHECK(a[0] == -54); 63 | } 64 | 65 | TEST_CASE( "merge_simple2" ) { 66 | std::vector a = { 10, 20 }; 67 | 68 | gfx::timmerge(a.begin(), a.begin() + 1, a.end()); 69 | 70 | CHECK(a.size() == std::size_t(2)); 71 | CHECK(a[0] == 10); 72 | CHECK(a[1] == 20); 73 | 74 | a = { 20, 10 }; 75 | 76 | gfx::timmerge(a.begin(), a.begin() + 1, a.end(), std::less()); 77 | 78 | CHECK(a.size() == std::size_t(2)); 79 | CHECK(a[0] == 10); 80 | CHECK(a[1] == 20); 81 | 82 | a = { 10, 10 }; 83 | 84 | gfx::timmerge(a.begin(), a.begin() + 1, a.end(), std::less()); 85 | 86 | CHECK(a.size() == std::size_t(2)); 87 | CHECK(a[0] == 10); 88 | CHECK(a[1] == 10); 89 | } 90 | 91 | TEST_CASE( "merge_simple10" ) { 92 | std::vector a = { 60, 50, 10, 40, 80, 20, 30, 70, 10, 90 }; 93 | 94 | sort_and_merge(a, a.begin() + 5, std::less()); 95 | 96 | CHECK(a[0] == 10); 97 | CHECK(a[1] == 10); 98 | CHECK(a[2] == 20); 99 | CHECK(a[3] == 30); 100 | CHECK(a[4] == 40); 101 | CHECK(a[5] == 50); 102 | CHECK(a[6] == 60); 103 | CHECK(a[7] == 70); 104 | CHECK(a[8] == 80); 105 | CHECK(a[9] == 90); 106 | 107 | std::reverse(a.begin(), a.end()); 108 | 109 | sort_and_merge(a, a.begin() + 2, std::less()); 110 | 111 | CHECK(a[0] == 10); 112 | CHECK(a[1] == 10); 113 | CHECK(a[2] == 20); 114 | CHECK(a[3] == 30); 115 | CHECK(a[4] == 40); 116 | CHECK(a[5] == 50); 117 | CHECK(a[6] == 60); 118 | CHECK(a[7] == 70); 119 | CHECK(a[8] == 80); 120 | CHECK(a[9] == 90); 121 | } 122 | 123 | TEST_CASE( "merge_shuffle30" ) { 124 | const int size = 30; 125 | 126 | std::vector a; 127 | for (int i = 0; i < size; ++i) { 128 | a.push_back((i + 1) * 10); 129 | } 130 | test_helpers::shuffle(a); 131 | 132 | sort_and_merge(a, a.begin() + 24); 133 | 134 | CHECK(a.size() == std::size_t(size)); 135 | for (int i = 0; i < size; ++i) { 136 | CHECK(a[i] == (i + 1) * 10); 137 | } 138 | } 139 | 140 | TEST_CASE( "merge_shuffle128" ) { 141 | const int size = 128; 142 | 143 | std::vector a; 144 | for (int i = 0; i < size; ++i) { 145 | a.push_back((i + 1) * 10); 146 | } 147 | test_helpers::shuffle(a); 148 | 149 | sort_and_merge(a, a.begin() + 51); 150 | 151 | CHECK(a.size() == std::size_t(size)); 152 | for (int i = 0; i < size; ++i) { 153 | CHECK(a[i] == (i + 1) * 10); 154 | } 155 | } 156 | 157 | TEST_CASE( "merge_shuffle204x" ) { 158 | for (int size : {2047, 2048}) { 159 | std::vector a; 160 | for (int i = 0; i < size; ++i) { 161 | a.push_back((i + 1) * 10); 162 | } 163 | 164 | std::uniform_int_distribution random_middle(0, size); 165 | for (int n = 0; n < 30; ++n) { 166 | test_helpers::shuffle(a); 167 | 168 | sort_and_merge(a, a.begin() + random_middle(random_engine())); 169 | 170 | for (int i = 0; i < size; ++i) { 171 | CHECK(a[i] == (i + 1) * 10); 172 | } 173 | } 174 | } 175 | } 176 | 177 | TEST_CASE( "merge_partial_shuffle102x" ) { 178 | for (int size : {1023, 1024}) { 179 | std::vector a; 180 | for (int i = 0; i < size; ++i) { 181 | a.push_back((i + 1) * 10); 182 | } 183 | 184 | std::uniform_int_distribution random_middle(0, size); 185 | 186 | // sorted-shuffled-sorted pattern 187 | for (int n = 0; n < 100; ++n) { 188 | test_helpers::shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); 189 | 190 | sort_and_merge(a, a.begin() + random_middle(random_engine()), std::less()); 191 | 192 | for (int i = 0; i < size; ++i) { 193 | CHECK(a[i] == (i + 1) * 10); 194 | } 195 | } 196 | 197 | // shuffled-sorted-shuffled pattern 198 | for (int n = 0; n < 100; ++n) { 199 | test_helpers::shuffle(a.begin(), a.begin() + (size / 3 * 1)); 200 | test_helpers::shuffle(a.begin() + (size / 3 * 2), a.end()); 201 | 202 | sort_and_merge(a, a.begin() + random_middle(random_engine())); 203 | 204 | for (int i = 0; i < size; ++i) { 205 | CHECK(a[i] == (i + 1) * 10); 206 | } 207 | } 208 | } 209 | } 210 | 211 | TEST_CASE( "merge_shuffle1025r" ) { 212 | const int size = 1025; 213 | 214 | std::vector a; 215 | for (int i = 0; i < size; ++i) { 216 | a.push_back((i + 1) * 10); 217 | } 218 | 219 | std::uniform_int_distribution random_middle(0, size); 220 | for (int n = 0; n < 100; ++n) { 221 | test_helpers::shuffle(a); 222 | 223 | sort_and_merge(a, a.begin() + random_middle(random_engine()), std::greater()); 224 | 225 | int j = size; 226 | for (int i = 0; i < size; ++i) { 227 | CHECK(a[i] == (--j + 1) * 10); 228 | } 229 | } 230 | } 231 | 232 | TEST_CASE( "merge_partial_reversed307x" ) { 233 | for (int size : {3071, 3072}) { 234 | std::vector a; 235 | for (int i = 0; i < size; ++i) { 236 | a.push_back((i + 1) * 10); 237 | } 238 | 239 | std::uniform_int_distribution random_middle(0, size); 240 | for (int n = 0; n < 20; ++n) { 241 | std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed 242 | 243 | sort_and_merge(a, a.begin() + random_middle(random_engine())); 244 | 245 | for (int i = 0; i < size; ++i) { 246 | CHECK(a[i] == (i + 1) * 10); 247 | } 248 | } 249 | } 250 | } 251 | 252 | TEST_CASE( "merge_c_array" ) { 253 | int a[] = {7, 1, 5, 3, 9}; 254 | 255 | sort_and_merge(a, a + 2); 256 | 257 | CHECK(a[0] == 1); 258 | CHECK(a[1] == 3); 259 | CHECK(a[2] == 5); 260 | CHECK(a[3] == 7); 261 | CHECK(a[4] == 9); 262 | } 263 | 264 | TEST_CASE( "merge_string_array" ) { 265 | std::string a[] = {"7", "1", "5", "3", "9"}; 266 | 267 | sort_and_merge(a, a + 3); 268 | 269 | CHECK(a[0] == "1"); 270 | CHECK(a[1] == "3"); 271 | CHECK(a[2] == "5"); 272 | CHECK(a[3] == "7"); 273 | CHECK(a[4] == "9"); 274 | } 275 | 276 | TEST_CASE( "merge_non_default_constructible" ) { 277 | NonDefaultConstructible a[] = {7, 1, 5, 3, 9}; 278 | 279 | sort_and_merge(a, a + 1); 280 | 281 | CHECK(a[0].i == 1); 282 | CHECK(a[1].i == 3); 283 | CHECK(a[2].i == 5); 284 | CHECK(a[3].i == 7); 285 | CHECK(a[4].i == 9); 286 | } 287 | 288 | TEST_CASE( "merge_default_compare_function" ) { 289 | const int size = 128; 290 | 291 | std::vector a; 292 | for (int i = 0; i < size; ++i) { 293 | a.push_back((i + 1) * 10); 294 | } 295 | test_helpers::shuffle(a); 296 | 297 | const auto middle = a.begin() + a.size() / 3; 298 | gfx::timsort(a.begin(), middle); 299 | gfx::timsort(middle, a.end()); 300 | gfx::timmerge(a.begin(), middle, a.end()); 301 | 302 | CHECK(a.size() == std::size_t(size)); 303 | for (int i = 0; i < size; ++i) { 304 | CHECK(a[i] == (i + 1) * 10); 305 | } 306 | } 307 | 308 | TEST_CASE( "merge_stability" ) { 309 | std::vector a; 310 | 311 | for (int i = 100; i >= 0; --i) { 312 | a.push_back(std::make_pair(i, foo)); 313 | a.push_back(std::make_pair(i, bar)); 314 | a.push_back(std::make_pair(i, baz)); 315 | } 316 | 317 | sort_and_merge(a, a.begin() + 117, &less_in_first); 318 | 319 | CHECK(a[0].first == 0); 320 | CHECK(a[0].second == foo); 321 | CHECK(a[1].first == 0); 322 | CHECK(a[1].second == bar); 323 | CHECK(a[2].first == 0); 324 | CHECK(a[2].second == baz); 325 | 326 | CHECK(a[3].first == 1); 327 | CHECK(a[3].second == foo); 328 | CHECK(a[4].first == 1); 329 | CHECK(a[4].second == bar); 330 | CHECK(a[5].first == 1); 331 | CHECK(a[5].second == baz); 332 | 333 | CHECK(a[6].first == 2); 334 | CHECK(a[6].second == foo); 335 | CHECK(a[7].first == 2); 336 | CHECK(a[7].second == bar); 337 | CHECK(a[8].first == 2); 338 | CHECK(a[8].second == baz); 339 | 340 | CHECK(a[9].first == 3); 341 | CHECK(a[9].second == foo); 342 | CHECK(a[10].first == 3); 343 | CHECK(a[10].second == bar); 344 | CHECK(a[11].first == 3); 345 | CHECK(a[11].second == baz); 346 | } 347 | 348 | TEST_CASE( "merge_issue2_duplication" ) { 349 | std::vector > a; 350 | 351 | for (int i = 0; i < 10000; ++i) { 352 | int first = static_cast(rand()); 353 | int second = static_cast(rand()); 354 | 355 | a.push_back(std::make_pair(first, second)); 356 | } 357 | 358 | std::vector > expected(a); 359 | 360 | std::sort(expected.begin(), expected.end()); 361 | sort_and_merge(a, a.begin() + 824); 362 | 363 | CHECK(a == expected); 364 | } 365 | 366 | TEST_CASE( "merge_projection" ) { 367 | const int size = 128; 368 | 369 | std::vector a; 370 | for (int i = 0; i < size; ++i) { 371 | a.push_back(i - 40); 372 | } 373 | test_helpers::shuffle(a); 374 | 375 | const auto middle = a.begin() + 43; 376 | gfx::timsort(a.begin(), middle, std::greater(), std::negate()); 377 | gfx::timsort(middle, a.end(), std::greater(), std::negate()); 378 | gfx::timmerge(a.begin(), middle, a.end(), std::greater(), std::negate()); 379 | 380 | for (int i = 0; i < size; ++i) { 381 | CHECK(a[i] == i - 40); 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /tests/test_helpers.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019-2024 Morwenn. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | 8 | #ifndef GFX_TIMSORT_TEST_HELPERS_HPP 9 | #define GFX_TIMSORT_TEST_HELPERS_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace Catch 17 | { 18 | // This functions is only available in an internal header that 19 | // drags a lot of dependencies, it's cheaper to just declare 20 | // it ourselves in this wrapper 21 | unsigned int rngSeed(); 22 | } 23 | 24 | namespace test_helpers { 25 | // Helper types for the tests 26 | 27 | //////////////////////////////////////////////////////////// 28 | // Timsort should work with types that are not 29 | // default-constructible 30 | 31 | struct NonDefaultConstructible { 32 | int i; 33 | 34 | NonDefaultConstructible(int i_) : i(i_) { 35 | } 36 | 37 | friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { 38 | return x.i < y.i; 39 | } 40 | }; 41 | 42 | //////////////////////////////////////////////////////////// 43 | // Tools to test the stability of the sort 44 | 45 | enum id { foo, bar, baz }; 46 | 47 | typedef std::pair pair_t; 48 | 49 | inline bool less_in_first(pair_t x, pair_t y) { 50 | return x.first < y.first; 51 | } 52 | 53 | //////////////////////////////////////////////////////////// 54 | // Timsort should work with iterators that don't have a 55 | // post-increment or post-decrement operation 56 | 57 | template 58 | class NoPostIterator 59 | { 60 | public: 61 | 62 | //////////////////////////////////////////////////////////// 63 | // Public types 64 | 65 | typedef typename std::iterator_traits::iterator_category iterator_category; 66 | typedef Iterator iterator_type; 67 | typedef typename std::iterator_traits::value_type value_type; 68 | typedef typename std::iterator_traits::difference_type difference_type; 69 | typedef typename std::iterator_traits::pointer pointer; 70 | typedef typename std::iterator_traits::reference reference; 71 | 72 | //////////////////////////////////////////////////////////// 73 | // Constructors 74 | 75 | NoPostIterator() : _it() { 76 | } 77 | 78 | explicit NoPostIterator(Iterator it) : _it(it) { 79 | } 80 | 81 | //////////////////////////////////////////////////////////// 82 | // Members access 83 | 84 | iterator_type base() const { 85 | return _it; 86 | } 87 | 88 | //////////////////////////////////////////////////////////// 89 | // Element access 90 | 91 | reference operator*() const { 92 | return *base(); 93 | } 94 | 95 | pointer operator->() const { 96 | return &(operator*()); 97 | } 98 | 99 | //////////////////////////////////////////////////////////// 100 | // Increment/decrement operators 101 | 102 | NoPostIterator& operator++() { 103 | ++_it; 104 | return *this; 105 | } 106 | 107 | NoPostIterator& operator--() { 108 | --_it; 109 | return *this; 110 | } 111 | 112 | NoPostIterator& operator+=(difference_type increment) { 113 | _it += increment; 114 | return *this; 115 | } 116 | 117 | NoPostIterator& operator-=(difference_type increment) { 118 | _it -= increment; 119 | return *this; 120 | } 121 | 122 | //////////////////////////////////////////////////////////// 123 | // Comparison operators 124 | 125 | friend bool operator==(NoPostIterator const& lhs, NoPostIterator const& rhs) { 126 | return lhs.base() == rhs.base(); 127 | } 128 | 129 | friend bool operator!=(NoPostIterator const& lhs, NoPostIterator const& rhs) { 130 | return lhs.base() != rhs.base(); 131 | } 132 | 133 | //////////////////////////////////////////////////////////// 134 | // Relational operators 135 | 136 | friend bool operator<(NoPostIterator const& lhs, NoPostIterator const& rhs) { 137 | return lhs.base() < rhs.base(); 138 | } 139 | 140 | friend bool operator<=(NoPostIterator const& lhs, NoPostIterator const& rhs) { 141 | return lhs.base() <= rhs.base(); 142 | } 143 | 144 | friend bool operator>(NoPostIterator const& lhs, NoPostIterator const& rhs) { 145 | return lhs.base() > rhs.base(); 146 | } 147 | 148 | friend bool operator>=(NoPostIterator const& lhs, NoPostIterator const& rhs) { 149 | return lhs.base() >= rhs.base(); 150 | } 151 | 152 | //////////////////////////////////////////////////////////// 153 | // Arithmetic operators 154 | 155 | friend NoPostIterator operator+(NoPostIterator it, difference_type size) { 156 | return it += size; 157 | } 158 | 159 | friend NoPostIterator operator+(difference_type size, NoPostIterator it) { 160 | return it += size; 161 | } 162 | 163 | friend NoPostIterator operator-(NoPostIterator it, difference_type size) { 164 | return it -= size; 165 | } 166 | 167 | friend difference_type operator-(NoPostIterator const& lhs, NoPostIterator const& rhs) { 168 | return lhs.base() - rhs.base(); 169 | } 170 | 171 | private: 172 | 173 | Iterator _it; 174 | }; 175 | 176 | template 177 | NoPostIterator make_no_post_iterator(Iterator it) { 178 | return NoPostIterator(it); 179 | } 180 | 181 | //////////////////////////////////////////////////////////// 182 | // Shuffle function 183 | 184 | template 185 | void shuffle(RandomAccessIterator first, RandomAccessIterator last) 186 | { 187 | thread_local std::mt19937 random_engine(Catch::rngSeed()); 188 | 189 | std::shuffle(first, last, random_engine); 190 | } 191 | 192 | template 193 | void shuffle(RandomAccessRange &range) 194 | { 195 | test_helpers::shuffle(std::begin(range), std::end(range)); 196 | } 197 | } 198 | 199 | #endif // GFX_TIMSORT_TEST_HELPERS_HPP 200 | -------------------------------------------------------------------------------- /tests/verbose_abort.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Morwenn. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | #include 7 | 8 | #ifdef _LIBCPP_VERSION 9 | #if defined(_LIBCPP_ENABLE_ASSERTIONS) && _LIBCPP_ENABLE_ASSERTIONS 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace std::inline __1 16 | { 17 | /* 18 | * Required to avoid linking issues with AppleClang when 19 | * compiling with _LIBCPP_ENABLE_ASSERTIONS. 20 | * See https://releases.llvm.org/16.0.0/projects/libcxx/docs/UsingLibcxx.html#enabling-the-safe-libc-mode 21 | */ 22 | [[noreturn]] 23 | void __libcpp_verbose_abort(char const* format, ...) { 24 | std::va_list list; 25 | va_start(list, format); 26 | std::vfprintf(stderr, format, list); 27 | va_end(list); 28 | 29 | std::abort(); 30 | } 31 | } 32 | 33 | #endif 34 | #endif 35 | -------------------------------------------------------------------------------- /tests/windows.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Fuji, Goro (gfx) . 3 | * Copyright (c) 2019-2024 Morwenn. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "test_helpers.hpp" 16 | 17 | TEST_CASE( "check inclusion of windows.h" ) { 18 | const int size = 100; 19 | 20 | std::vector vec; 21 | for (int i = 0; i < size; ++i) { 22 | vec.push_back(i); 23 | } 24 | 25 | test_helpers::shuffle(vec); 26 | gfx::timsort(vec.begin(), vec.end()); 27 | 28 | for (int i = 0; i < size; ++i) { 29 | CHECK(vec[i] == i); 30 | } 31 | } 32 | --------------------------------------------------------------------------------