├── .clang-format ├── CONTRIBUTORS ├── AUTHORS ├── benches ├── plots │ ├── result_10000000_1000.png │ ├── result_10000000_5000000.png │ ├── result_accesses_10000000_1000.png │ ├── result_accesses_10000000_5000000.png │ ├── result_comparisons_10000000_1000.png │ └── result_comparisons_10000000_5000000.png ├── benchmark_sort.cpp ├── benchmark_select.cpp └── bench_common.h ├── fuzz ├── ossfuzz.sh ├── main.cpp ├── build_like_oss_fuzz.sh ├── CMakeLists.txt ├── fuzz_select.cpp ├── fuzz_sort.cpp ├── fuzz_string_select.cpp ├── fuzz_standard_compliance.cpp └── fuzz_string_sort.cpp ├── examples └── example.cpp ├── LICENSE_1_0.txt ├── .gitignore ├── CMakeLists.txt ├── include └── miniselect │ ├── median_of_3_random.h │ ├── median_of_medians.h │ ├── heap_select.h │ ├── floyd_rivest_select.h │ ├── median_of_ninthers.h │ ├── private │ └── median_common.h │ └── pdqselect.h ├── .travis.yml ├── testing ├── test_sort.cpp ├── test_common.h └── test_select.cpp └── README.md /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # contributors (in no particular order) 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # List of authors for copyright purposes, in no particular order 2 | Danila Kutenin 3 | -------------------------------------------------------------------------------- /benches/plots/result_10000000_1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlark1/miniselect/HEAD/benches/plots/result_10000000_1000.png -------------------------------------------------------------------------------- /benches/plots/result_10000000_5000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlark1/miniselect/HEAD/benches/plots/result_10000000_5000000.png -------------------------------------------------------------------------------- /benches/plots/result_accesses_10000000_1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlark1/miniselect/HEAD/benches/plots/result_accesses_10000000_1000.png -------------------------------------------------------------------------------- /benches/plots/result_accesses_10000000_5000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlark1/miniselect/HEAD/benches/plots/result_accesses_10000000_5000000.png -------------------------------------------------------------------------------- /benches/plots/result_comparisons_10000000_1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlark1/miniselect/HEAD/benches/plots/result_comparisons_10000000_1000.png -------------------------------------------------------------------------------- /benches/plots/result_comparisons_10000000_5000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlark1/miniselect/HEAD/benches/plots/result_comparisons_10000000_5000000.png -------------------------------------------------------------------------------- /fuzz/ossfuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # entry point for oss-fuzz, so that fuzzers 4 | # and build invocation can be changed without having 5 | # to modify the oss-fuzz repo. 6 | # 7 | # invoke it from the git root. 8 | 9 | # make sure to exit on problems 10 | set -eux 11 | 12 | mkdir -p build 13 | cd build 14 | 15 | cmake .. \ 16 | -GNinja \ 17 | -DCMAKE_BUILD_TYPE=Debug \ 18 | -DENABLE_FUZZING=On \ 19 | -DMINISELECT_FUZZ_LINKMAIN=off \ 20 | -DMINISELECT_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE 21 | 22 | cmake --build . --target all_fuzzers 23 | 24 | -------------------------------------------------------------------------------- /examples/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "miniselect/median_of_ninthers.h" 5 | 6 | int main() { 7 | std::vector v = {1, 8, 4, 3, 2, 9, 0, 7, 6, 5}; 8 | miniselect::median_of_ninthers_select(v.begin(), v.begin() + 5, v.end()); 9 | for (const int i : v) { 10 | std::cout << i << ' '; 11 | } 12 | return 0; 13 | } 14 | 15 | // Compile it `clang++/g++ -I$DIRECTORY/miniselect/include/ example.cpp -std=c++11 -O3 -o example 16 | 17 | // Possible output: 0 1 4 3 2 5 8 7 6 9 18 | // ^ on the right place 19 | -------------------------------------------------------------------------------- /fuzz/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size); 7 | 8 | int main(int argc, char* argv[]) { 9 | for (int i = 1; i < argc; ++i) { 10 | std::ifstream in(argv[i]); 11 | assert(in); 12 | in.seekg(0, std::ios_base::end); 13 | const auto pos = in.tellg(); 14 | assert(pos >= 0); 15 | in.seekg(0, std::ios_base::beg); 16 | std::vector buf(static_cast(pos)); 17 | in.read(buf.data(), static_cast(buf.size())); 18 | assert(in.gcount() == pos); 19 | LLVMFuzzerTestOneInput(reinterpret_cast(buf.data()), 20 | buf.size()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /fuzz/build_like_oss_fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script emulates how oss fuzz invokes the build 4 | # process, handy for trouble shooting cmake issues and possibly 5 | # recreating testcases. For proper debugging of the oss fuzz 6 | # build, follow the procedure at https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally 7 | 8 | set -eu 9 | 10 | ossfuzz=$(readlink -f $(dirname $0))/ossfuzz.sh 11 | 12 | mkdir -p ossfuzz-out 13 | export OUT=$(pwd)/ossfuzz-out 14 | export CC=clang 15 | export CXX="clang++" 16 | export CFLAGS="-fsanitize=fuzzer-no-link" 17 | export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -O1" 18 | export LIB_FUZZING_ENGINE="-fsanitize=fuzzer" 19 | 20 | $ossfuzz 21 | 22 | echo "look at the results in $OUT" 23 | -------------------------------------------------------------------------------- /fuzz/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project(fuzz) 4 | 5 | option(ENABLE_FUZZING "enable building the fuzzers" ON) 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | if(ENABLE_FUZZING) 9 | set(MINISELECT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets") 10 | 11 | add_library(miniselect-fuzzer INTERFACE) 12 | target_link_libraries(miniselect-fuzzer INTERFACE gtest) 13 | target_link_libraries(miniselect-fuzzer INTERFACE ${MINISELECT_FUZZ_LDFLAGS}) 14 | 15 | if(MINISELECT_FUZZ_LINKMAIN) 16 | target_sources(simdjson-fuzzer INTERFACE $/main.cpp) 17 | endif() 18 | 19 | # Define the fuzzers 20 | add_custom_target(all_fuzzers) 21 | 22 | set(fuzzernames) 23 | function(implement_fuzzer name) 24 | add_executable(${name} ${name}.cpp) 25 | target_link_libraries(${name} PRIVATE miniselect-fuzzer) 26 | add_dependencies(all_fuzzers ${name}) 27 | set(fuzzernames ${fuzzernames} ${name} PARENT_SCOPE) 28 | endfunction() 29 | 30 | implement_fuzzer(fuzz_select) 31 | implement_fuzzer(fuzz_string_select) 32 | implement_fuzzer(fuzz_sort) 33 | implement_fuzzer(fuzz_string_sort) 34 | implement_fuzzer(fuzz_standard_compliance) 35 | 36 | # to be able to get a list of all fuzzers from within a script 37 | add_custom_target(print_all_fuzzernames 38 | COMMAND ${CMAKE_COMMAND} -E echo ${fuzzernames}) 39 | endif() 40 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /benches/benchmark_sort.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "bench_common.h" 16 | #include "test_common.h" 17 | 18 | namespace miniselect { 19 | namespace { 20 | 21 | static constexpr size_t kSize = 65536; 22 | 23 | template 24 | static void BM_sort(benchmark::State& state) { 25 | auto vec = DataGen::Gen(kSize); 26 | const size_t arg = state.range(0); 27 | size_t cnt = 0; 28 | size_t access_count = 0; 29 | size_t comparison_count = 0; 30 | miniselect::datagens::CountingIterator begin_v(&vec, 0, &access_count); 31 | miniselect::datagens::CountingIterator end_v(&vec, vec.size(), &access_count); 32 | for (auto _ : state) { 33 | Impl::Sort(begin_v, begin_v + arg, end_v, 34 | [&comparison_count](const auto& left, const auto& right) { 35 | comparison_count++; 36 | return left < right; 37 | }); 38 | ++cnt; 39 | benchmark::DoNotOptimize(vec[arg]); 40 | } 41 | state.counters["Array Accesses"] = 1.0 * access_count / cnt; 42 | state.counters["Comparisons"] = 1.0 * comparison_count / cnt; 43 | } 44 | 45 | BENCH(BM_sort); 46 | 47 | } // namespace 48 | } // namespace miniselect 49 | 50 | BENCHMARK_MAIN(); 51 | -------------------------------------------------------------------------------- /benches/benchmark_select.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "bench_common.h" 16 | #include "test_common.h" 17 | 18 | namespace miniselect { 19 | namespace { 20 | 21 | static constexpr size_t kSize = 65536; 22 | 23 | template 24 | static void BM_sel(benchmark::State& state) { 25 | auto vec = DataGen::Gen(kSize); 26 | const size_t arg = state.range(0); 27 | size_t cnt = 0; 28 | size_t access_count = 0; 29 | size_t comparison_count = 0; 30 | miniselect::datagens::CountingIterator begin_v(&vec, 0, &access_count); 31 | miniselect::datagens::CountingIterator end_v(&vec, vec.size(), &access_count); 32 | for (auto _ : state) { 33 | Impl::Select(begin_v, begin_v + arg, end_v, 34 | [&comparison_count](const auto& left, const auto& right) { 35 | comparison_count++; 36 | return left < right; 37 | }); 38 | ++cnt; 39 | benchmark::DoNotOptimize(vec[arg]); 40 | } 41 | state.counters["Array Accesses"] = 1.0 * access_count / cnt; 42 | state.counters["Comparisons"] = 1.0 * comparison_count / cnt; 43 | } 44 | 45 | BENCH(BM_sel); 46 | 47 | } // namespace 48 | } // namespace miniselect 49 | 50 | BENCHMARK_MAIN(); 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse project files 2 | .cproject 3 | .project 4 | .settings 5 | 6 | # emacs temp files 7 | *~ 8 | 9 | # vim temp files 10 | .*.swp 11 | 12 | # XCode 13 | ^build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | *.DS_Store 30 | 31 | # IDE specific folder for JetBrains IDEs 32 | .idea/ 33 | cmake-build-debug/ 34 | cmake-build-release/ 35 | 36 | # Visual Studio Code artifacts 37 | .vscode/* 38 | .history/ 39 | 40 | # Visual Studio artifacts 41 | /VS/ 42 | 43 | # C/C++ build outputs 44 | .build/ 45 | bins 46 | gens 47 | libs 48 | objs 49 | 50 | # C++ ignore from https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore 51 | 52 | # Prerequisites 53 | *.d 54 | 55 | # Compiled Object files 56 | *.slo 57 | *.lo 58 | *.o 59 | *.obj 60 | 61 | # Precompiled Headers 62 | *.gch 63 | *.pch 64 | 65 | # Compiled Dynamic libraries 66 | *.so 67 | *.dylib 68 | *.dll 69 | 70 | # Fortran module files 71 | *.mod 72 | *.smod 73 | 74 | # Compiled Static libraries 75 | *.lai 76 | *.la 77 | *.a 78 | *.lib 79 | 80 | # Executables 81 | *.exe 82 | *.out 83 | *.app 84 | 85 | 86 | # CMake files that may be specific to our installation 87 | 88 | # Build outputs 89 | /build*/ 90 | /visual_studio/ 91 | /benchmark/ 92 | /googletest/ 93 | 94 | # Fuzzer outputs generated by instructions in fuzz/Fuzzing.md 95 | /corpus.zip 96 | /ossfuzz-out/ 97 | /out/ 98 | 99 | # Generated docs 100 | /doc/api 101 | *.orig 102 | -------------------------------------------------------------------------------- /fuzz/fuzz_select.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_common.h" 7 | 8 | template 9 | void ChooseImplementation(uint8_t byte, std::vector& working, 10 | Iter partition_iter, const ::testing::Types&) { 11 | static_assert(sizeof...(T) < 256); 12 | int i = 0; 13 | constexpr size_t size = sizeof...(T); 14 | ( 15 | [&]() { 16 | if (byte % size == i++) { 17 | T::Select(working.begin(), partition_iter, working.end()); 18 | } 19 | }(), 20 | ...); 21 | } 22 | 23 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, 24 | std::size_t size) { 25 | if (size <= 3) return 0; 26 | uint8_t impl = data[0]; 27 | uint16_t partition_point = 0; 28 | memcpy(&partition_point, data + 1, 2); 29 | partition_point %= (size - 3); 30 | std::vector working(data + 3, data + size); 31 | auto canonical = working; 32 | const auto partition_iter = working.begin() + partition_point; 33 | ChooseImplementation(impl, working, partition_iter, 34 | miniselect::algorithms::All{}); 35 | 36 | if (partition_iter != working.end()) { 37 | const auto& nth = *partition_iter; 38 | bool is_error = false; 39 | if (!std::all_of(working.begin(), partition_iter, 40 | [&](const auto& v) { return v <= nth; })) { 41 | is_error = true; 42 | } 43 | if (!std::all_of(partition_iter, working.end(), 44 | [&](const auto& v) { return v >= nth; })) { 45 | is_error = true; 46 | } 47 | if (is_error) { 48 | std::cerr << "FAILED!\nCanonical: "; 49 | for (const auto& s : canonical) { 50 | std::cerr << static_cast(s) << ' '; 51 | } 52 | std::cerr << std::endl; 53 | std::cerr << "Got: "; 54 | for (const auto& s : working) { 55 | std::cerr << static_cast(s) << ' '; 56 | } 57 | std::cerr << std::endl; 58 | std::cerr << "partition_iter = " << partition_iter - working.begin() 59 | << std::endl; 60 | std::abort(); 61 | } 62 | } 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /fuzz/fuzz_sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_common.h" 7 | 8 | template 9 | void ChooseImplementation(uint8_t byte, std::vector& working, 10 | Iter partition_iter, const ::testing::Types&) { 11 | static_assert(sizeof...(T) < 256); 12 | int i = 0; 13 | constexpr size_t size = sizeof...(T); 14 | ( 15 | [&]() { 16 | if (byte % size == i++) { 17 | T::Sort(working.begin(), partition_iter, working.end()); 18 | } 19 | }(), 20 | ...); 21 | } 22 | 23 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, 24 | std::size_t size) { 25 | if (size <= 3) return 0; 26 | uint8_t impl = data[0]; 27 | uint16_t partition_point = 0; 28 | memcpy(&partition_point, data + 1, 2); 29 | partition_point %= (size - 3); 30 | std::vector working(data + 3, data + size); 31 | auto canonical = working; 32 | const auto partition_iter = working.begin() + partition_point; 33 | ChooseImplementation(impl, working, partition_iter, 34 | miniselect::algorithms::All{}); 35 | 36 | bool is_error = false; 37 | if (partition_iter != working.end()) { 38 | const auto& nth = *std::min_element(partition_iter, working.end()); 39 | if (!std::all_of(working.begin(), partition_iter, 40 | [&](const auto& v) { return v <= nth; })) { 41 | is_error = true; 42 | } 43 | if (!std::all_of(partition_iter, working.end(), 44 | [&](const auto& v) { return v >= nth; })) { 45 | is_error = true; 46 | } 47 | } 48 | if (!std::is_sorted(working.begin(), partition_iter)) { 49 | is_error = true; 50 | } 51 | if (is_error) { 52 | std::cerr << "FAILED!\nCanonical: "; 53 | for (const auto& s : canonical) { 54 | std::cerr << static_cast(s) << ' '; 55 | } 56 | std::cerr << std::endl; 57 | std::cerr << "Got: "; 58 | for (const auto& s : working) { 59 | std::cerr << static_cast(s) << ' '; 60 | } 61 | std::cerr << std::endl; 62 | std::cerr << "partition_iter = " << partition_iter - working.begin() 63 | << std::endl; 64 | std::abort(); 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /fuzz/fuzz_string_select.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_common.h" 7 | 8 | template 9 | void ChooseImplementation(uint8_t byte, std::vector& working, 10 | Iter partition_iter, const ::testing::Types&) { 11 | static_assert(sizeof...(T) < 256); 12 | int i = 0; 13 | constexpr size_t size = sizeof...(T); 14 | ( 15 | [&]() { 16 | if (byte % size == i++) { 17 | T::Select(working.begin(), partition_iter, working.end()); 18 | } 19 | }(), 20 | ...); 21 | } 22 | 23 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, 24 | std::size_t size) { 25 | if (size <= 3) return 0; 26 | uint8_t impl = data[0]; 27 | uint16_t partition_point = 0; 28 | memcpy(&partition_point, data + 1, 2); 29 | partition_point %= (size - 3); 30 | std::vector working; 31 | for (auto i = data + 3; i < data + size; ++i) { 32 | std::string s(1, *i); 33 | working.push_back(s); 34 | } 35 | auto canonical = working; 36 | const auto partition_iter = working.begin() + partition_point; 37 | ChooseImplementation(impl, working, partition_iter, 38 | miniselect::algorithms::All{}); 39 | // nth may be the end iterator, in this case nth_element has no effect. 40 | if (partition_iter != working.end()) { 41 | const auto& nth = *partition_iter; 42 | bool is_error = false; 43 | if (!std::all_of(working.begin(), partition_iter, 44 | [&](const auto& v) { return v <= nth; })) { 45 | is_error = true; 46 | } 47 | if (!std::all_of(partition_iter, working.end(), 48 | [&](const auto& v) { return v >= nth; })) { 49 | is_error = true; 50 | } 51 | if (is_error) { 52 | std::cerr << "FAILED!\nCanonical: "; 53 | for (const auto& s : canonical) { 54 | std::cerr << s << ' '; 55 | } 56 | std::cerr << std::endl; 57 | std::cerr << "Got: "; 58 | for (const auto& s : working) { 59 | std::cerr << s << ' '; 60 | } 61 | std::cerr << std::endl; 62 | std::cerr << "partition_iter = " << partition_iter - working.begin() 63 | << std::endl; 64 | std::abort(); 65 | } 66 | } 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /fuzz/fuzz_standard_compliance.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_common.h" 7 | 8 | template 9 | void ChooseImplementation(uint8_t byte, Iter beg, Iter end, Iter partition_iter, 10 | const ::testing::Types&) { 11 | static_assert(sizeof...(T) < 256); 12 | int i = 0; 13 | constexpr size_t size = sizeof...(T); 14 | ( 15 | [&]() { 16 | if (byte % size == i++) { 17 | T::Select(beg, partition_iter, end); 18 | } 19 | }(), 20 | ...); 21 | } 22 | 23 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, 24 | std::size_t size) { 25 | if (size <= 3) return 0; 26 | uint8_t impl = data[0]; 27 | uint8_t partition_point = data[1]; 28 | size = std::min(std::size_t{129}, size) - 2; 29 | partition_point %= size; 30 | std::vector working(data + 2, data + size + 2); 31 | miniselect::algorithms::IntegralCharIterator beg(working.data()); 32 | miniselect::algorithms::IntegralCharIterator ending(working.data() + 33 | working.size()); 34 | auto canonical = working; 35 | const auto partition_iter = beg + partition_point; 36 | ChooseImplementation(impl, beg, ending, partition_iter, 37 | miniselect::algorithms::All{}); 38 | if (partition_iter != ending) { 39 | const auto& nth = *partition_iter; 40 | bool is_error = false; 41 | if (!std::all_of(beg, partition_iter, 42 | [&](const auto& v) { return v <= nth; })) { 43 | is_error = true; 44 | } 45 | if (!std::all_of(partition_iter, ending, 46 | [&](const auto& v) { return v >= nth; })) { 47 | is_error = true; 48 | } 49 | if (is_error) { 50 | fail: 51 | std::cerr << "FAILED!\nCanonical: "; 52 | for (const auto& s : canonical) { 53 | std::cerr << static_cast(s) << ' '; 54 | } 55 | std::cerr << std::endl; 56 | std::cerr << "Got: "; 57 | for (const auto& s : working) { 58 | std::cerr << static_cast(s) << ' '; 59 | } 60 | std::cerr << std::endl; 61 | std::cerr << "partition_iter = " << static_cast(partition_iter - beg) 62 | << std::endl; 63 | std::abort(); 64 | } 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /fuzz/fuzz_string_sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_common.h" 7 | 8 | template 9 | void ChooseImplementation(uint8_t byte, std::vector& working, 10 | Iter partition_iter, const ::testing::Types&) { 11 | static_assert(sizeof...(T) < 256); 12 | int i = 0; 13 | constexpr size_t size = sizeof...(T); 14 | ( 15 | [&]() { 16 | if (byte % size == i++) { 17 | T::Sort(working.begin(), partition_iter, working.end()); 18 | } 19 | }(), 20 | ...); 21 | } 22 | 23 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, 24 | std::size_t size) { 25 | if (size <= 3) return 0; 26 | uint8_t impl = data[0]; 27 | uint16_t partition_point = 0; 28 | memcpy(&partition_point, data + 1, 2); 29 | partition_point %= (size - 3); 30 | std::vector working; 31 | for (auto i = data + 3; i < data + size; ++i) { 32 | std::string s(1, *i); 33 | working.push_back(s); 34 | } 35 | auto canonical = working; 36 | const auto partition_iter = working.begin() + partition_point; 37 | ChooseImplementation(impl, working, partition_iter, 38 | miniselect::algorithms::All{}); 39 | // nth may be the end iterator, in this case nth_element has no effect. 40 | bool is_error = false; 41 | if (partition_iter != working.end()) { 42 | const auto& nth = *std::min_element(partition_iter, working.end()); 43 | if (!std::all_of(working.begin(), partition_iter, 44 | [&](const auto& v) { return v <= nth; })) { 45 | is_error = true; 46 | } 47 | if (!std::all_of(partition_iter, working.end(), 48 | [&](const auto& v) { return v >= nth; })) { 49 | is_error = true; 50 | } 51 | } 52 | if (!std::is_sorted(working.begin(), partition_iter)) { 53 | is_error = true; 54 | } 55 | if (is_error) { 56 | std::cerr << "FAILED!\nCanonical: "; 57 | for (const auto& s : canonical) { 58 | std::cerr << s << ' '; 59 | } 60 | std::cerr << std::endl; 61 | std::cerr << "Got: "; 62 | for (const auto& s : working) { 63 | std::cerr << s << ' '; 64 | } 65 | std::cerr << std::endl; 66 | std::cerr << "partition_iter = " << partition_iter - working.begin() 67 | << std::endl; 68 | std::abort(); 69 | } 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | project(miniselect) 3 | 4 | option(MINISELECT_TESTING "Building the tests." OFF) 5 | option(MINISELECT_SANITIZE "Building the library with sanitizers." OFF) 6 | option(MINISELECT_BUILD_LIBCXX "Building the library with libcxx." OFF) 7 | option(MINISELECT_ENABLE_FUZZING "Building the library with fuzzing." OFF) 8 | 9 | include_directories(include) 10 | 11 | if (MINISELECT_TESTING) 12 | enable_testing() 13 | set(CMAKE_CXX_STANDARD 17) 14 | if (NOT CMAKE_BUILD_TYPE) 15 | message(STATUS "No build type selected, default to Release") 16 | set(CMAKE_BUILD_TYPE "Release") 17 | endif() 18 | if (MINISELECT_SANITIZE) 19 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all") 20 | endif() 21 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wall -Wextra -Wpedantic -Werror -Wno-gnu-zero-variadic-macro-arguments") 22 | 23 | if (MINISELECT_BUILD_LIBCXX AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 24 | message(STATUS "Using libcxx as a default standard C++ library") 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 26 | endif() 27 | 28 | set(BENCHMARK_ENABLE_GTEST_TESTS OFF) 29 | set(BENCHMARK_ENABLE_TESTING OFF) 30 | message(STATUS "gtest tests are off") 31 | 32 | add_subdirectory(benchmark) 33 | add_subdirectory(googletest) 34 | include_directories(testing) 35 | include_directories(benches) 36 | 37 | add_executable(benchmark_sort benches/benchmark_sort.cpp) 38 | target_link_libraries(benchmark_sort benchmark::benchmark gtest) 39 | add_executable(benchmark_select benches/benchmark_select.cpp) 40 | target_link_libraries(benchmark_select benchmark::benchmark gtest) 41 | 42 | set(TEST_SOURCES testing/test_select.cpp) 43 | add_executable(test_select ${TEST_SOURCES}) 44 | target_link_libraries(test_select gtest gmock gtest_main) 45 | add_test(NAME test_select COMMAND test_select) 46 | 47 | set(TEST_SOURCES testing/test_sort.cpp) 48 | add_executable(test_sort ${TEST_SOURCES}) 49 | target_link_libraries(test_sort gtest gmock gtest_main) 50 | add_test(NAME test_sort COMMAND test_sort) 51 | endif() 52 | 53 | if(MINISELECT_ENABLE_FUZZING) 54 | set(BENCHMARK_ENABLE_GTEST_TESTS OFF) 55 | set(BENCHMARK_ENABLE_TESTING OFF) 56 | message(STATUS "gtest tests are off") 57 | add_subdirectory(benchmark) 58 | add_subdirectory(googletest) 59 | include_directories(testing) 60 | add_subdirectory(fuzz) 61 | endif() 62 | -------------------------------------------------------------------------------- /include/miniselect/median_of_3_random.h: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "private/median_common.h" 16 | 17 | namespace miniselect { 18 | namespace median_of_3_random_detail { 19 | 20 | template 21 | inline Iter partition(Iter r, Iter end, Compare&& comp) { 22 | using DiffType = typename std::iterator_traits::difference_type; 23 | const DiffType len = end - r; 24 | assert(len >= 3); 25 | static std::mt19937_64 gen(1); 26 | std::uniform_int_distribution dis(0, len - 1); 27 | DiffType x = dis(gen); 28 | DiffType y = dis(gen); 29 | DiffType z = dis(gen); 30 | return median_common_detail::pivot_partition( 31 | r, median_common_detail::median_index(r, x, y, z, comp), len, comp); 32 | } 33 | 34 | } // namespace median_of_3_random_detail 35 | 36 | template 37 | inline void median_of_3_random_select(Iter begin, Iter mid, Iter end, 38 | Compare comp) { 39 | if (mid == end) return; 40 | using CompType = typename floyd_rivest_detail::CompareRefType::type; 41 | 42 | median_common_detail::quickselect< 43 | Iter, CompType, &median_of_3_random_detail::partition>( 44 | begin, mid, end, comp); 45 | } 46 | 47 | template 48 | inline void median_of_3_random_select(Iter begin, Iter mid, Iter end) { 49 | typedef typename std::iterator_traits::value_type T; 50 | median_of_3_random_select(begin, mid, end, std::less()); 51 | } 52 | 53 | template 54 | inline void median_of_3_random_partial_sort(Iter begin, Iter mid, Iter end, 55 | Compare comp) { 56 | if (begin == mid) return; 57 | using CompType = typename floyd_rivest_detail::CompareRefType::type; 58 | median_common_detail::quickselect< 59 | Iter, CompType, &median_of_3_random_detail::partition>( 60 | begin, mid - 1, end, comp); 61 | std::sort(begin, mid, comp); 62 | } 63 | 64 | template 65 | inline void median_of_3_random_partial_sort(Iter begin, Iter mid, Iter end) { 66 | typedef typename std::iterator_traits::value_type T; 67 | median_of_3_random_partial_sort(begin, mid, end, std::less()); 68 | } 69 | 70 | } // namespace miniselect 71 | -------------------------------------------------------------------------------- /include/miniselect/median_of_medians.h: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "private/median_common.h" 15 | 16 | namespace miniselect { 17 | namespace median_of_medians_detail { 18 | 19 | template 20 | inline Iter partition(Iter r, Iter end, Compare&& comp) { 21 | using CompType = typename median_common_detail::CompareRefType::type; 22 | using DiffType = typename std::iterator_traits::difference_type; 23 | const DiffType len = end - r; 24 | if (len < 5) { 25 | return median_common_detail::pivot_partition( 26 | r, static_cast(len / 2), len, comp); 27 | } 28 | DiffType j = 0; 29 | DiffType end_range = len - 5; 30 | for (DiffType i = 0; i <= end_range; i += 5, ++j) { 31 | median_common_detail::partition5( 32 | r, static_cast(i), static_cast(i + 1), 33 | static_cast(i + 2), static_cast(i + 3), 34 | static_cast(i + 4), comp); 35 | std::swap(r[i], r[j]); 36 | } 37 | median_common_detail::quickselect( 38 | r, r + static_cast(j / 2), r + j, comp); 39 | return median_common_detail::pivot_partition(r, static_cast(j / 2), 40 | len, comp); 41 | } 42 | 43 | } // namespace median_of_medians_detail 44 | 45 | template 46 | inline void median_of_medians_select(Iter begin, Iter mid, Iter end, 47 | Compare comp) { 48 | if (mid == end) return; 49 | using CompType = typename median_common_detail::CompareRefType::type; 50 | 51 | median_common_detail::quickselect< 52 | Iter, CompType, &median_of_medians_detail::partition>( 53 | begin, mid, end, comp); 54 | } 55 | 56 | template 57 | inline void median_of_medians_select(Iter begin, Iter mid, Iter end) { 58 | using T = typename std::iterator_traits::value_type; 59 | median_of_medians_select(begin, mid, end, std::less()); 60 | } 61 | 62 | template 63 | inline void median_of_medians_partial_sort(Iter begin, Iter mid, Iter end, 64 | Compare comp) { 65 | if (begin == mid) return; 66 | using CompType = typename median_common_detail::CompareRefType::type; 67 | median_common_detail::quickselect< 68 | Iter, CompType, &median_of_medians_detail::partition>( 69 | begin, mid - 1, end, comp); 70 | std::sort(begin, mid, comp); 71 | } 72 | 73 | template 74 | inline void median_of_medians_partial_sort(Iter begin, Iter mid, Iter end) { 75 | using T = typename std::iterator_traits::value_type; 76 | median_of_medians_partial_sort(begin, mid, end, std::less()); 77 | } 78 | 79 | } // namespace miniselect 80 | -------------------------------------------------------------------------------- /include/miniselect/heap_select.h: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace miniselect { 15 | namespace heap_select_detail { 16 | 17 | template 18 | struct CompareRefType { 19 | // Pass the comparator by lvalue reference. Or in debug mode, using a 20 | // debugging wrapper that stores a reference. 21 | using type = typename std::add_lvalue_reference::type; 22 | }; 23 | 24 | template 25 | inline void sift_down(Iter first, Compare comp, 26 | typename std::iterator_traits::difference_type len, 27 | Iter start) { 28 | using difference_type = typename std::iterator_traits::difference_type; 29 | using value_type = typename std::iterator_traits::value_type; 30 | difference_type child = start - first; 31 | 32 | if (len < 2 || (len - 2) / 2 < child) return; 33 | 34 | child = 2 * child + 1; 35 | Iter child_i = first + child; 36 | 37 | if ((child + 1) < len && comp(*child_i, *(child_i + 1))) { 38 | ++child_i; 39 | ++child; 40 | } 41 | 42 | if (comp(*child_i, *start)) { 43 | return; 44 | } 45 | 46 | value_type top(std::move(*start)); 47 | do { 48 | *start = std::move(*child_i); 49 | start = child_i; 50 | 51 | if ((len - 2) / 2 < child) { 52 | break; 53 | } 54 | 55 | child = 2 * child + 1; 56 | child_i = first + child; 57 | 58 | if ((child + 1) < len && comp(*child_i, *(child_i + 1))) { 59 | ++child_i; 60 | ++child; 61 | } 62 | } while (!comp(*child_i, top)); 63 | *start = std::move(top); 64 | } 65 | 66 | template 67 | inline void heap_select_loop(Iter first, Iter middle, Iter last, Compare comp) { 68 | std::make_heap(first, middle, comp); 69 | typename std::iterator_traits::difference_type len = middle - first; 70 | for (Iter i = middle; i != last; ++i) { 71 | if (comp(*i, *first)) { 72 | std::swap(*i, *first); 73 | sift_down(first, comp, len, first); 74 | } 75 | } 76 | } 77 | 78 | } // namespace heap_select_detail 79 | 80 | template 81 | inline void heap_select(Iter first, Iter middle, Iter end, Compare comp) { 82 | if (middle == end) return; 83 | heap_select_detail::heap_select_loop(first, middle + 1, end, comp); 84 | std::swap(*first, *middle); 85 | } 86 | 87 | template 88 | inline void heap_select(Iter first, Iter middle, Iter end) { 89 | heap_select(first, middle, end, 90 | std::less::value_type>()); 91 | } 92 | 93 | template 94 | inline void heap_partial_sort(Iter first, Iter middle, Iter end, Compare comp) { 95 | heap_select_detail::heap_select_loop(first, middle, end, comp); 96 | std::sort_heap(first, middle, comp); 97 | } 98 | 99 | template 100 | inline void heap_partial_sort(Iter first, Iter middle, Iter end) { 101 | heap_partial_sort( 102 | first, middle, end, 103 | std::less::value_type>()); 104 | } 105 | 106 | } // namespace miniselect 107 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | dist: bionic 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-8 14 | env: 15 | - COMPILER="CC=gcc-8 && CXX=g++-8" 16 | compiler: gcc-8 17 | 18 | - os: linux 19 | addons: 20 | apt: 21 | sources: 22 | - ubuntu-toolchain-r-test 23 | packages: 24 | - g++-9 25 | env: 26 | - COMPILER="CC=gcc-9 && CXX=g++-9" 27 | compiler: gcc-9 28 | 29 | - os: linux 30 | addons: 31 | apt: 32 | sources: 33 | - ubuntu-toolchain-r-test 34 | packages: 35 | - g++-10 36 | env: 37 | - COMPILER="CC=gcc-10 && CXX=g++-10" 38 | compiler: gcc-10 39 | 40 | - os: linux 41 | addons: 42 | apt: 43 | sources: 44 | - ubuntu-toolchain-r-test 45 | packages: 46 | - g++-10 47 | env: 48 | - COMPILER="CC=gcc-10 && CXX=g++-10" 49 | - SANITIZE="on" 50 | compiler: gcc-10-sanitize 51 | 52 | - os: linux 53 | addons: 54 | apt: 55 | sources: 56 | - llvm-toolchain-bionic-6.0 57 | packages: 58 | - clang-6.0 59 | env: 60 | - COMPILER="CC=clang-6.0 && CXX=clang++-6.0" 61 | compiler: clang-6 62 | 63 | - os: linux 64 | addons: 65 | apt: 66 | sources: 67 | - llvm-toolchain-bionic-7 68 | packages: 69 | - clang-7 70 | env: 71 | - COMPILER="CC=clang-7 && CXX=clang++-7" 72 | compiler: clang-7 73 | 74 | - os: linux 75 | addons: 76 | apt: 77 | sources: 78 | - llvm-toolchain-bionic-8 79 | packages: 80 | - clang-8 81 | env: 82 | - COMPILER="CC=clang-8 && CXX=clang++-8" 83 | compiler: clang-8 84 | 85 | - os: linux 86 | addons: 87 | apt: 88 | sources: 89 | - llvm-toolchain-bionic-9 90 | packages: 91 | - clang-9 92 | env: 93 | - COMPILER="CC=clang-9 && CXX=clang++-9" 94 | compiler: clang-9 95 | 96 | - os: linux 97 | addons: 98 | apt: 99 | packages: 100 | - clang-10 101 | sources: 102 | - ubuntu-toolchain-r-test 103 | - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main' 104 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 105 | env: 106 | - COMPILER="CC=clang-10 && CXX=clang++-10" 107 | compiler: clang-10 108 | 109 | - os: linux 110 | addons: 111 | apt: 112 | packages: 113 | - clang-10 114 | sources: 115 | - ubuntu-toolchain-r-test 116 | - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main' 117 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 118 | env: 119 | - COMPILER="CC=clang-10 && CXX=clang++-10" 120 | - SANITIZE="on" 121 | compiler: clang-10-sanitize 122 | 123 | before_install: 124 | - eval "${COMPILER}" 125 | - git clone https://github.com/google/benchmark.git 126 | - git clone https://github.com/google/googletest.git 127 | 128 | install: 129 | - export CMAKE_FLAGS="-DMINISELECT_TESTING=on -DBENCHMARK_ENABLE_GTEST_TESTS=off -DBENCHMARK_ENABLE_TESTING=off -DCMAKE_BUILD_TYPE=RelWithDebInfo"; 130 | - if [[ "${SANITIZE}" == "on" ]]; then 131 | export CMAKE_FLAGS="${CMAKE_FLAGS} -DMINISELECT_SANITIZE=on"; 132 | fi 133 | - export CTEST_FLAGS="-j4 --output-on-failure -E checkperf" 134 | 135 | script: 136 | - mkdir build 137 | - cd build 138 | - cmake $CMAKE_FLAGS .. 139 | - cmake --build . -- -j2 140 | - ctest $CTEST_FLAGS 141 | -------------------------------------------------------------------------------- /include/miniselect/floyd_rivest_select.h: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace miniselect { 17 | namespace floyd_rivest_detail { 18 | 19 | enum floyd_rivest_constants { 20 | kQCap = 600, 21 | }; 22 | 23 | template 24 | struct CompareRefType { 25 | // Pass the comparator by lvalue reference. Or in debug mode, using a 26 | // debugging wrapper that stores a reference. 27 | using type = typename std::add_lvalue_reference::type; 28 | }; 29 | 30 | template ::difference_type> 32 | inline void floyd_rivest_select_loop(Iter begin, DiffType left, DiffType right, 33 | DiffType k, Compare comp) { 34 | while (right > left) { 35 | DiffType size = right - left; 36 | if (size > floyd_rivest_constants::kQCap) { 37 | DiffType n = right - left + 1; 38 | DiffType i = k - left + 1; 39 | 40 | double z = log(n); 41 | double s = 0.5 * exp(2 * z / 3); 42 | double sd = 0.5 * sqrt(z * s * (n - s) / n); 43 | if (i < n / 2) { 44 | sd *= -1.0; 45 | } 46 | DiffType new_left = 47 | std::max(left, static_cast(k - i * s / n + sd)); 48 | DiffType new_right = 49 | std::min(right, static_cast(k + (n - i) * s / n + sd)); 50 | floyd_rivest_select_loop(begin, new_left, 51 | new_right, k, comp); 52 | } 53 | DiffType i = left; 54 | DiffType j = right; 55 | 56 | std::swap(begin[left], begin[k]); 57 | const bool to_swap = comp(begin[left], begin[right]); 58 | if (to_swap) { 59 | std::swap(begin[left], begin[right]); 60 | } 61 | // Make sure that non copyable types compile. 62 | const auto& t = to_swap ? begin[left] : begin[right]; 63 | while (i < j) { 64 | std::swap(begin[i], begin[j]); 65 | i++; 66 | j--; 67 | while (comp(begin[i], t)) { 68 | i++; 69 | } 70 | while (comp(t, begin[j])) { 71 | j--; 72 | } 73 | } 74 | 75 | if (to_swap) { 76 | std::swap(begin[left], begin[j]); 77 | } else { 78 | j++; 79 | std::swap(begin[right], begin[j]); 80 | } 81 | 82 | if (j <= k) { 83 | left = j + 1; 84 | } 85 | if (k <= j) { 86 | right = j - 1; 87 | } 88 | } 89 | } 90 | 91 | } // namespace floyd_rivest_detail 92 | 93 | template 94 | inline void floyd_rivest_partial_sort(Iter begin, Iter mid, Iter end, 95 | Compare comp) { 96 | if (begin == end) return; 97 | if (begin == mid) return; 98 | using CompType = typename floyd_rivest_detail::CompareRefType::type; 99 | using DiffType = typename std::iterator_traits::difference_type; 100 | floyd_rivest_detail::floyd_rivest_select_loop( 101 | begin, DiffType{0}, static_cast(end - begin - 1), 102 | static_cast(mid - begin - 1), comp); 103 | // std::sort proved to be better than other sorts because of pivoting. 104 | std::sort(begin, mid, comp); 105 | } 106 | 107 | template 108 | inline void floyd_rivest_partial_sort(Iter begin, Iter mid, Iter end) { 109 | typedef typename std::iterator_traits::value_type T; 110 | floyd_rivest_partial_sort(begin, mid, end, std::less()); 111 | } 112 | 113 | template 114 | inline void floyd_rivest_select(Iter begin, Iter mid, Iter end, Compare comp) { 115 | if (mid == end) return; 116 | using CompType = typename floyd_rivest_detail::CompareRefType::type; 117 | using DiffType = typename std::iterator_traits::difference_type; 118 | floyd_rivest_detail::floyd_rivest_select_loop( 119 | begin, DiffType{0}, static_cast(end - begin - 1), 120 | static_cast(mid - begin), comp); 121 | } 122 | 123 | template 124 | inline void floyd_rivest_select(Iter begin, Iter mid, Iter end) { 125 | typedef typename std::iterator_traits::value_type T; 126 | floyd_rivest_select(begin, mid, end, std::less()); 127 | } 128 | 129 | } // namespace miniselect 130 | -------------------------------------------------------------------------------- /testing/test_sort.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "test_common.h" 17 | 18 | using ::testing::Eq; 19 | 20 | namespace miniselect { 21 | namespace { 22 | 23 | struct IndirectLess { 24 | // Non const comparator with deleted copy. 25 | template 26 | bool operator()(const P &x, const P &y) const { 27 | return *x < *y; 28 | } 29 | IndirectLess(const IndirectLess &) = default; 30 | IndirectLess &operator=(const IndirectLess &) = default; 31 | IndirectLess(IndirectLess &&) = default; 32 | IndirectLess &operator=(IndirectLess &&) = default; 33 | }; 34 | 35 | struct CustomInt { 36 | size_t x = 0; 37 | bool operator<(const CustomInt& other) const { 38 | return x < other.x; 39 | } 40 | }; 41 | 42 | template 43 | class PartialSortTest : public ::testing::Test { 44 | public: 45 | static void TestSorts(size_t N, size_t M) { 46 | ASSERT_NE(N, 0); 47 | ASSERT_GE(N, M); 48 | SCOPED_TRACE(N); 49 | SCOPED_TRACE(M); 50 | std::vector array(N); 51 | for (size_t i = 0; i < N; ++i) { 52 | array[i] = i; 53 | } 54 | auto array_smaller = array; 55 | std::mt19937_64 mersenne_engine; 56 | std::shuffle(array.begin(), array.end(), mersenne_engine); 57 | Sorter::Sort(array.begin(), array.begin() + M, array.end(), 58 | std::greater()); 59 | for (size_t i = 0; i < M; ++i) { 60 | EXPECT_EQ(array[i], N - i - 1); 61 | } 62 | std::shuffle(array_smaller.begin(), array_smaller.end(), mersenne_engine); 63 | Sorter::Sort(array_smaller.begin(), array_smaller.begin() + M, 64 | array_smaller.end()); 65 | for (size_t i = 0; i < M; ++i) { 66 | EXPECT_EQ(array_smaller[i], i); 67 | } 68 | } 69 | 70 | static void TestRandomAccessIterator(size_t N, size_t M) { 71 | ASSERT_NE(N, 0); 72 | ASSERT_GT(N, M); 73 | SCOPED_TRACE(N); 74 | SCOPED_TRACE(M); 75 | std::vector array(N); 76 | for (size_t i = 0; i < N; ++i) { 77 | array[i] = i; 78 | } 79 | std::mt19937_64 mersenne_engine; 80 | std::shuffle(array.begin(), array.end(), mersenne_engine); 81 | algorithms::IntegralCharIterator beg(array.data()); 82 | algorithms::IntegralCharIterator mid(array.data() + M); 83 | algorithms::IntegralCharIterator end(array.data() + array.size()); 84 | Sorter::Sort(beg, mid, end, std::greater()); 85 | for (size_t i = 0; i < M; ++i) { 86 | EXPECT_EQ(array[i], N - i - 1); 87 | } 88 | } 89 | 90 | static void TestManyRandomAccessIterators(size_t N) { 91 | TestRandomAccessIterator(N, 0); 92 | TestRandomAccessIterator(N, 1); 93 | TestRandomAccessIterator(N, 2); 94 | TestRandomAccessIterator(N, N / 2 - 1); 95 | TestRandomAccessIterator(N, N / 2); 96 | TestRandomAccessIterator(N, N / 2 + 1); 97 | TestRandomAccessIterator(N, N - 2); 98 | TestRandomAccessIterator(N, N - 1); 99 | } 100 | 101 | static void TestRandomAccessIterators() { 102 | TestManyRandomAccessIterators(127); 103 | } 104 | 105 | static void TestSorts(size_t N) { 106 | TestSorts(N, 0); 107 | TestSorts(N, 1); 108 | TestSorts(N, 2); 109 | TestSorts(N, 3); 110 | TestSorts(N, N / 2 - 1); 111 | TestSorts(N, N / 2); 112 | TestSorts(N, N / 2 + 1); 113 | TestSorts(N, N - 2); 114 | TestSorts(N, N - 1); 115 | TestSorts(N, N); 116 | } 117 | 118 | static void TestManySorts() { 119 | TestSorts(10); 120 | TestSorts(256); 121 | TestSorts(257); 122 | TestSorts(499); 123 | TestSorts(500); 124 | TestSorts(997); 125 | TestSorts(1000); 126 | TestSorts(1000 * 100); 127 | TestSorts(1009); 128 | TestSorts(1009 * 109); 129 | } 130 | 131 | static void TestCustomComparators() { 132 | std::vector> v(1000); 133 | for (size_t i = 0; i < v.size(); ++i) { 134 | v[i] = std::make_unique(i); 135 | } 136 | Sorter::Sort(v.begin(), v.begin() + v.size() / 2, v.end(), IndirectLess{}); 137 | for (size_t i = 0; i < v.size() / 2; ++i) { 138 | ASSERT_NE(v[i], nullptr); 139 | EXPECT_EQ(*v[i], i); 140 | } 141 | } 142 | 143 | static void TestOnlyOperatorLess() { 144 | std::vector v(1000); 145 | for (size_t i = 0; i < v.size(); ++i) { 146 | v[i].x = v.size() - i - 1; 147 | } 148 | Sorter::Sort(v.begin(), v.begin() + v.size() / 2, v.end()); 149 | for (size_t i = 0; i < v.size() / 2; ++i) { 150 | EXPECT_EQ(v[i].x, i); 151 | } 152 | } 153 | }; 154 | 155 | TYPED_TEST_SUITE(PartialSortTest, algorithms::All); 156 | 157 | TYPED_TEST(PartialSortTest, TestSmall) { 158 | std::vector v = {"ab", "aaa", "ab"}; 159 | TypeParam::Sort(v.begin(), v.begin() + 1, v.end()); 160 | EXPECT_THAT(v, Eq(std::vector{"aaa", "ab", "ab"})); 161 | v = {"aba"}; 162 | TypeParam::Sort(v.begin(), v.begin(), v.end()); 163 | EXPECT_THAT(v, Eq(std::vector{"aba"})); 164 | v.clear(); 165 | TypeParam::Sort(v.begin(), v.end(), v.end()); 166 | EXPECT_TRUE(v.empty()); 167 | } 168 | 169 | TYPED_TEST(PartialSortTest, TestAnotherSmall) { 170 | std::vector v = {"ab", "ab", "aaa"}; 171 | TypeParam::Sort(v.begin(), v.begin() + 1, v.end()); 172 | EXPECT_THAT(v, Eq(std::vector{"aaa", "ab", "ab"})); 173 | } 174 | 175 | TYPED_TEST(PartialSortTest, TestEmptySmall) { 176 | std::vector v = {"", ""}; 177 | TypeParam::Sort(v.begin(), v.begin() + 1, v.end()); 178 | EXPECT_THAT(v, Eq(std::vector{"", ""})); 179 | } 180 | 181 | TYPED_TEST(PartialSortTest, TestBasic) { TestFixture::TestManySorts(); } 182 | 183 | TYPED_TEST(PartialSortTest, TestComparators) { 184 | TestFixture::TestCustomComparators(); 185 | } 186 | 187 | TYPED_TEST(PartialSortTest, TestRandomAccessIterators) { 188 | TestFixture::TestRandomAccessIterators(); 189 | } 190 | 191 | TYPED_TEST(PartialSortTest, TestOnlyOperatorLess) { 192 | TestFixture::TestOnlyOperatorLess(); 193 | } 194 | 195 | // The standard says that the order of other elements is unspecified even if 196 | // nothing should be sorted so it fails for libcxx and PDQ which is Ok. Saving 197 | // this test for a reference. 198 | TYPED_TEST(PartialSortTest, DISABLED_TestEmpty) { 199 | std::vector array(100); 200 | for (size_t i = 0; i < 100; ++i) { 201 | array[i] = i; 202 | } 203 | std::mt19937_64 mersenne_engine; 204 | std::shuffle(array.begin(), array.end(), mersenne_engine); 205 | size_t cmp = 0; 206 | auto copy_array = array; 207 | // Should be no effect. 208 | TypeParam::Sort(array.begin(), array.begin(), array.end(), 209 | [&cmp](const auto &lhs, const auto &rhs) { 210 | ++cmp; 211 | return lhs < rhs; 212 | }); 213 | EXPECT_EQ(cmp, 0); 214 | EXPECT_EQ(copy_array, array); 215 | } 216 | 217 | } // namespace 218 | } // namespace miniselect 219 | 220 | int main(int argc, char **argv) { 221 | ::testing::InitGoogleTest(&argc, argv); 222 | return RUN_ALL_TESTS(); 223 | } 224 | -------------------------------------------------------------------------------- /include/miniselect/median_of_ninthers.h: -------------------------------------------------------------------------------- 1 | /* Copyright Andrei Alexandrescu, 2016-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | /* Copyright Danila Kutenin, 2020-. 7 | * Distributed under the Boost Software License, Version 1.0. 8 | * (See accompanying file LICENSE_1_0.txt or copy at 9 | * https://boost.org/LICENSE_1_0.txt) 10 | */ 11 | // Adjusted from Alexandrescu paper to support arbitrary comparators. 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "private/median_common.h" 22 | 23 | namespace miniselect { 24 | namespace median_of_ninthers_detail { 25 | 26 | template ::difference_type> 28 | void adaptive_quickselect(Iter r, DiffType n, DiffType length, Compare&& comp); 29 | 30 | /** 31 | Median of minima 32 | */ 33 | template ::difference_type> 35 | inline DiffType median_of_minima(Iter const r, const DiffType n, 36 | const DiffType length, Compare&& comp) { 37 | assert(length >= 2); 38 | assert(n <= length / 6); 39 | assert(n > 0); 40 | const DiffType subset = n * 2, computeMinOver = (length - subset) / subset; 41 | assert(computeMinOver > 0); 42 | for (DiffType i = 0, j = subset; i < subset; ++i) { 43 | const DiffType limit = j + computeMinOver; 44 | DiffType minIndex = j; 45 | while (++j < limit) 46 | if (comp(r[j], r[minIndex])) minIndex = j; 47 | if (comp(r[minIndex], r[i])) std::swap(r[i], r[minIndex]); 48 | assert(j < length || i + 1 == subset); 49 | } 50 | adaptive_quickselect(r, n, subset, comp); 51 | return median_common_detail::expand_partition(r, DiffType{0}, n, subset, 52 | length, comp); 53 | } 54 | 55 | /** 56 | Median of maxima 57 | */ 58 | template ::difference_type> 60 | inline DiffType median_of_maxima(Iter const r, const DiffType n, 61 | const DiffType length, Compare&& comp) { 62 | assert(length >= 2); 63 | assert(n < length && n / 5 >= length - n); 64 | const DiffType subset = (length - n) * 2, subsetStart = length - subset, 65 | computeMaxOver = subsetStart / subset; 66 | assert(computeMaxOver > 0); 67 | for (DiffType i = subsetStart, j = i - subset * computeMaxOver; i < length; 68 | ++i) { 69 | const DiffType limit = j + computeMaxOver; 70 | DiffType maxIndex = j; 71 | while (++j < limit) 72 | if (comp(r[maxIndex], r[j])) maxIndex = j; 73 | if (comp(r[i], r[maxIndex])) std::swap(r[i], r[maxIndex]); 74 | assert(j != 0 || i + 1 == length); 75 | } 76 | adaptive_quickselect(r + subsetStart, static_cast(length - n), 77 | subset, comp); 78 | return median_common_detail::expand_partition(r, subsetStart, n, length, 79 | length, comp); 80 | } 81 | 82 | /** 83 | Partitions r[0 .. length] using a pivot of its own choosing. Attempts to pick a 84 | pivot that approximates the median. Returns the position of the pivot. 85 | */ 86 | template ::difference_type> 88 | inline DiffType median_of_ninthers(Iter const r, const DiffType length, 89 | Compare&& comp) { 90 | assert(length >= 12); 91 | const DiffType frac = length <= 1024 ? length / 12 92 | : length <= 128 * 1024 ? length / 64 93 | : length / 1024; 94 | DiffType pivot = frac / 2; 95 | const DiffType lo = length / 2 - pivot, hi = lo + frac; 96 | assert(lo >= frac * 4); 97 | assert(length - hi >= frac * 4); 98 | assert(lo / 2 >= pivot); 99 | const DiffType gap = (length - 9 * frac) / 4; 100 | DiffType a = lo - 4 * frac - gap, b = hi + gap; 101 | for (DiffType i = lo; i < hi; ++i, a += 3, b += 3) { 102 | median_common_detail::ninther( 103 | r, a, static_cast(i - frac), b, static_cast(a + 1), 104 | i, static_cast(b + 1), static_cast(a + 2), 105 | static_cast(i + frac), static_cast(b + 2), comp); 106 | } 107 | 108 | adaptive_quickselect(r + lo, pivot, frac, comp); 109 | return median_common_detail::expand_partition( 110 | r, lo, static_cast(lo + pivot), hi, length, comp); 111 | } 112 | 113 | /** 114 | Quickselect driver for median_of_ninthers, median_of_minima, and 115 | median_of_maxima. Dispathes to each depending on the relationship between n (the 116 | sought order statistics) and length. 117 | */ 118 | template 119 | inline void adaptive_quickselect(Iter r, DiffType n, DiffType length, 120 | Compare&& comp) { 121 | assert(n < length); 122 | for (;;) { 123 | // Decide strategy for partitioning 124 | if (n == 0) { 125 | // That would be the max 126 | DiffType pivot = n; 127 | for (++n; n < length; ++n) 128 | if (comp(r[n], r[pivot])) pivot = n; 129 | std::swap(r[0], r[pivot]); 130 | return; 131 | } 132 | if (n + 1 == length) { 133 | // That would be the min 134 | DiffType pivot = 0; 135 | for (n = 1; n < length; ++n) 136 | if (comp(r[pivot], r[n])) pivot = n; 137 | std::swap(r[pivot], r[length - 1]); 138 | return; 139 | } 140 | assert(n < length); 141 | DiffType pivot; 142 | if (length <= 16) 143 | pivot = median_common_detail::pivot_partition(r, n, length, comp) - r; 144 | else if (n <= length / 6) 145 | pivot = median_of_minima(r, n, length, comp); 146 | else if (n / 5 >= length - n) 147 | pivot = median_of_maxima(r, n, length, comp); 148 | else 149 | pivot = median_of_ninthers(r, length, comp); 150 | 151 | // See how the pivot fares 152 | if (pivot == n) { 153 | return; 154 | } 155 | if (pivot > n) { 156 | length = pivot; 157 | } else { 158 | ++pivot; 159 | r += pivot; 160 | length -= pivot; 161 | n -= pivot; 162 | } 163 | } 164 | } 165 | 166 | } // namespace median_of_ninthers_detail 167 | 168 | template 169 | inline void median_of_ninthers_select(Iter begin, Iter mid, Iter end, 170 | Compare comp) { 171 | if (mid == end) return; 172 | using CompType = typename median_common_detail::CompareRefType::type; 173 | 174 | median_of_ninthers_detail::adaptive_quickselect( 175 | begin, mid - begin, end - begin, comp); 176 | } 177 | 178 | template 179 | inline void median_of_ninthers_select(Iter begin, Iter mid, Iter end) { 180 | using T = typename std::iterator_traits::value_type; 181 | median_of_ninthers_select(begin, mid, end, std::less()); 182 | } 183 | 184 | template 185 | inline void median_of_ninthers_partial_sort(Iter begin, Iter mid, Iter end, 186 | Compare comp) { 187 | if (begin == mid) return; 188 | using CompType = typename median_common_detail::CompareRefType::type; 189 | using DiffType = typename std::iterator_traits::difference_type; 190 | 191 | median_of_ninthers_detail::adaptive_quickselect( 192 | begin, static_cast(mid - begin - 1), end - begin, comp); 193 | std::sort(begin, mid, comp); 194 | } 195 | 196 | template 197 | inline void median_of_ninthers_partial_sort(Iter begin, Iter mid, Iter end) { 198 | typedef typename std::iterator_traits::value_type T; 199 | median_of_ninthers_partial_sort(begin, mid, end, std::less()); 200 | } 201 | 202 | } // namespace miniselect 203 | -------------------------------------------------------------------------------- /benches/bench_common.h: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace miniselect { 13 | namespace datagens { 14 | 15 | struct Random { 16 | static std::vector Gen(size_t size) { 17 | std::mt19937_64 mersenne_engine{1}; 18 | std::vector v; 19 | v.reserve(size); 20 | for (size_t i = 0; i < size; ++i) { 21 | v.push_back(i); 22 | } 23 | std::shuffle(v.begin(), v.end(), mersenne_engine); 24 | return v; 25 | } 26 | }; 27 | 28 | struct Shuffled16 { 29 | static std::vector Gen(size_t size) { 30 | std::mt19937_64 mersenne_engine{1}; 31 | std::vector v; 32 | v.reserve(size); 33 | for (size_t i = 0; i < size; ++i) { 34 | v.push_back(i % 16); 35 | } 36 | std::shuffle(v.begin(), v.end(), mersenne_engine); 37 | return v; 38 | } 39 | }; 40 | 41 | struct Random01 { 42 | static std::vector Gen(size_t size) { 43 | std::mt19937_64 mersenne_engine{1}; 44 | std::vector v; 45 | v.reserve(size); 46 | for (size_t i = 0; i < size; ++i) { 47 | v.push_back(i % 2); 48 | } 49 | std::shuffle(v.begin(), v.end(), mersenne_engine); 50 | return v; 51 | } 52 | }; 53 | 54 | struct Ascending { 55 | static std::vector Gen(size_t size) { 56 | std::vector v; 57 | v.reserve(size); 58 | for (size_t i = 0; i < size; ++i) { 59 | v.push_back(i); 60 | } 61 | return v; 62 | } 63 | }; 64 | 65 | struct Descending { 66 | static std::vector Gen(size_t size) { 67 | std::vector v; 68 | v.reserve(size); 69 | for (int i = size - 1; i >= 0; --i) { 70 | v.push_back(i); 71 | } 72 | return v; 73 | } 74 | }; 75 | 76 | struct PipeOrgan { 77 | static std::vector Gen(size_t size) { 78 | std::vector v; 79 | v.reserve(size); 80 | for (size_t i = 0; i < size / 2; ++i) { 81 | v.push_back(i); 82 | } 83 | for (size_t i = size / 2; i < size; ++i) { 84 | v.push_back(size - i); 85 | } 86 | return v; 87 | } 88 | }; 89 | 90 | struct PushFront { 91 | static std::vector Gen(size_t size) { 92 | std::vector v; 93 | v.reserve(size); 94 | for (size_t i = 1; i < size; ++i) { 95 | v.push_back(i); 96 | } 97 | v.push_back(0); 98 | return v; 99 | } 100 | }; 101 | 102 | struct PushMiddle { 103 | static std::vector Gen(size_t size) { 104 | std::vector v; 105 | v.reserve(size); 106 | for (size_t i = 0; i < size; ++i) { 107 | if (i != size / 2) { 108 | v.push_back(i); 109 | } 110 | } 111 | v.push_back(size / 2); 112 | return v; 113 | } 114 | }; 115 | 116 | struct Median3Killer { 117 | static std::vector Gen(size_t size) { 118 | size_t k = size / 2; 119 | std::vector v; 120 | v.reserve(size); 121 | for (size_t i = 1; i < k + 1; ++i) { 122 | if (i & 1) { 123 | v.push_back(i); 124 | } else { 125 | v.push_back(k + i - 1); 126 | } 127 | } 128 | for (size_t i = 1; i < k + 1; ++i) { 129 | v.push_back(2 * i); 130 | } 131 | return v; 132 | } 133 | }; 134 | 135 | template 136 | class CountingIterator { 137 | public: 138 | using base_type = typename U::iterator; 139 | 140 | using iterator_category = std::random_access_iterator_tag; 141 | using value_type = typename std::iterator_traits::value_type; 142 | using difference_type = 143 | typename std::iterator_traits::difference_type; 144 | using reference = typename std::iterator_traits::reference; 145 | using pointer = typename std::iterator_traits::pointer; 146 | 147 | CountingIterator(U* array, size_t pos, size_t* access_count) 148 | : array_(array), pos_(pos), access_count_(access_count) {} 149 | 150 | CountingIterator(const CountingIterator& r) 151 | : array_(r.array_), pos_(r.pos_), access_count_(r.access_count_) {} 152 | 153 | CountingIterator& operator=(const CountingIterator& r) { 154 | array_ = r.array_, pos_ = r.pos_; 155 | access_count_ = r.access_count_; 156 | return *this; 157 | } 158 | 159 | CountingIterator& operator++() { 160 | ++pos_; 161 | return *this; 162 | } 163 | 164 | CountingIterator& operator--() { 165 | --pos_; 166 | return *this; 167 | } 168 | 169 | CountingIterator operator++(int) { 170 | return CountingIterator(array_, pos_++, access_count_); 171 | } 172 | 173 | CountingIterator operator--(int) { 174 | return CountingIterator(array_, pos_--, access_count_); 175 | } 176 | 177 | CountingIterator operator+(difference_type n) const { 178 | return CountingIterator(array_, pos_ + n, access_count_); 179 | } 180 | 181 | CountingIterator& operator+=(difference_type n) { 182 | pos_ += n; 183 | return *this; 184 | } 185 | 186 | CountingIterator operator-(difference_type n) const { 187 | return CountingIterator(array_, pos_ - n, access_count_); 188 | } 189 | 190 | CountingIterator& operator-=(difference_type n) { 191 | pos_ -= n; 192 | return *this; 193 | } 194 | 195 | reference operator*() const { 196 | ++(*access_count_); 197 | return (*array_)[pos_]; 198 | } 199 | 200 | pointer operator->() const { 201 | ++(*access_count_); 202 | return &(*array_)[pos_]; 203 | } 204 | 205 | reference operator[](difference_type n) const { 206 | ++(*access_count_); 207 | return (*array_)[pos_ + n]; 208 | } 209 | 210 | bool operator==(const CountingIterator& r) { 211 | return (array_ == r.array_) && (pos_ == r.pos_); 212 | } 213 | 214 | bool operator!=(const CountingIterator& r) { 215 | return (array_ != r.array_) || (pos_ != r.pos_); 216 | } 217 | 218 | bool operator<(const CountingIterator& r) { 219 | return (array_ == r.array_ ? (pos_ < r.pos_) : (array_ < r.array_)); 220 | } 221 | 222 | bool operator>(const CountingIterator& r) { 223 | return (array_ == r.array_ ? (pos_ > r.pos_) : (array_ > r.array_)); 224 | } 225 | 226 | bool operator<=(const CountingIterator& r) { 227 | return (array_ == r.array_ ? (pos_ <= r.pos_) : (array_ <= r.array_)); 228 | } 229 | 230 | bool operator>=(const CountingIterator& r) { 231 | return (array_ == r.array_ ? (pos_ >= r.pos_) : (array_ >= r.array_)); 232 | } 233 | 234 | difference_type operator+(const CountingIterator& r2) const { 235 | assert(array_ == r2.array_); 236 | return (pos_ + r2.pos_); 237 | } 238 | 239 | difference_type operator-(const CountingIterator& r2) const { 240 | assert(array_ == r2.array_); 241 | return (pos_ - r2.pos_); 242 | } 243 | 244 | private: 245 | U* array_; 246 | size_t pos_; 247 | size_t* access_count_; 248 | }; 249 | 250 | #define BENCH_IMPL(BENCH, GEN, IMPL) \ 251 | BENCHMARK_TEMPLATE(BENCH, GEN, IMPL) \ 252 | ->Unit(benchmark::kMicrosecond) \ 253 | ->Arg(kSize - 10) \ 254 | ->Arg(kSize / 2) \ 255 | ->Arg(10000) \ 256 | ->Arg(1000) \ 257 | ->Arg(100) \ 258 | ->Arg(10) \ 259 | ->Arg(1) 260 | 261 | #define BENCH_GENS(BENCH, IMPL) \ 262 | BENCH_IMPL(BENCH, datagens::Random, IMPL); \ 263 | BENCH_IMPL(BENCH, datagens::Shuffled16, IMPL); \ 264 | BENCH_IMPL(BENCH, datagens::Random01, IMPL); \ 265 | BENCH_IMPL(BENCH, datagens::Ascending, IMPL); \ 266 | BENCH_IMPL(BENCH, datagens::Descending, IMPL); \ 267 | BENCH_IMPL(BENCH, datagens::PipeOrgan, IMPL); \ 268 | BENCH_IMPL(BENCH, datagens::PushMiddle, IMPL); \ 269 | BENCH_IMPL(BENCH, datagens::PushFront, IMPL); \ 270 | BENCH_IMPL(BENCH, datagens::Median3Killer, IMPL) 271 | 272 | #define BENCH(NAME) \ 273 | BENCH_GENS(NAME, algorithms::FloydRivest); \ 274 | BENCH_GENS(NAME, algorithms::MedianOfNinthers); \ 275 | BENCH_GENS(NAME, algorithms::MedianOfMedians); \ 276 | BENCH_GENS(NAME, algorithms::MedianOf3Random); \ 277 | BENCH_GENS(NAME, algorithms::PDQ); \ 278 | BENCH_GENS(NAME, algorithms::PDQBranchless); \ 279 | BENCH_GENS(NAME, algorithms::Heap); \ 280 | BENCH_GENS(NAME, algorithms::STD) 281 | 282 | } // namespace datagens 283 | } // namespace miniselect 284 | -------------------------------------------------------------------------------- /testing/test_common.h: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #pragma once 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include "miniselect/floyd_rivest_select.h" 13 | #include "miniselect/heap_select.h" 14 | #include "miniselect/median_of_3_random.h" 15 | #include "miniselect/median_of_medians.h" 16 | #include "miniselect/median_of_ninthers.h" 17 | #include "miniselect/pdqselect.h" 18 | 19 | namespace miniselect { 20 | namespace algorithms { 21 | 22 | struct STD { 23 | template 24 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 25 | std::partial_sort(begin, mid, end, std::move(comp)); 26 | } 27 | 28 | template 29 | static void Sort(Iter begin, Iter mid, Iter end) { 30 | std::partial_sort(begin, mid, end); 31 | } 32 | 33 | template 34 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 35 | std::nth_element(begin, mid, end, std::move(comp)); 36 | } 37 | 38 | template 39 | static void Select(Iter begin, Iter mid, Iter end) { 40 | std::nth_element(begin, mid, end); 41 | } 42 | }; 43 | 44 | struct PDQ { 45 | template 46 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 47 | pdqpartial_sort(begin, mid, end, std::move(comp)); 48 | } 49 | 50 | template 51 | static void Sort(Iter begin, Iter mid, Iter end) { 52 | pdqpartial_sort(begin, mid, end); 53 | } 54 | 55 | template 56 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 57 | pdqselect(begin, mid, end, std::move(comp)); 58 | } 59 | 60 | template 61 | static void Select(Iter begin, Iter mid, Iter end) { 62 | pdqselect(begin, mid, end); 63 | } 64 | }; 65 | 66 | struct PDQBranchless { 67 | template 68 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 69 | pdqpartial_sort_branchless(begin, mid, end, std::move(comp)); 70 | } 71 | 72 | template 73 | static void Sort(Iter begin, Iter mid, Iter end) { 74 | pdqpartial_sort_branchless(begin, mid, end); 75 | } 76 | 77 | template 78 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 79 | pdqselect_branchless(begin, mid, end, std::move(comp)); 80 | } 81 | 82 | template 83 | static void Select(Iter begin, Iter mid, Iter end) { 84 | pdqselect_branchless(begin, mid, end); 85 | } 86 | }; 87 | 88 | struct FloydRivest { 89 | template 90 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 91 | floyd_rivest_partial_sort(begin, mid, end, std::move(comp)); 92 | } 93 | 94 | template 95 | static void Sort(Iter begin, Iter mid, Iter end) { 96 | floyd_rivest_partial_sort(begin, mid, end); 97 | } 98 | 99 | template 100 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 101 | floyd_rivest_select(begin, mid, end, std::move(comp)); 102 | } 103 | 104 | template 105 | static void Select(Iter begin, Iter mid, Iter end) { 106 | floyd_rivest_select(begin, mid, end); 107 | } 108 | }; 109 | 110 | struct MedianOfNinthers { 111 | template 112 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 113 | median_of_ninthers_partial_sort(begin, mid, end, std::move(comp)); 114 | } 115 | 116 | template 117 | static void Sort(Iter begin, Iter mid, Iter end) { 118 | median_of_ninthers_partial_sort(begin, mid, end); 119 | } 120 | 121 | template 122 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 123 | median_of_ninthers_select(begin, mid, end, std::move(comp)); 124 | } 125 | 126 | template 127 | static void Select(Iter begin, Iter mid, Iter end) { 128 | median_of_ninthers_select(begin, mid, end); 129 | } 130 | }; 131 | 132 | struct MedianOfMedians { 133 | template 134 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 135 | median_of_medians_partial_sort(begin, mid, end, std::move(comp)); 136 | } 137 | 138 | template 139 | static void Sort(Iter begin, Iter mid, Iter end) { 140 | median_of_medians_partial_sort(begin, mid, end); 141 | } 142 | 143 | template 144 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 145 | median_of_medians_select(begin, mid, end, std::move(comp)); 146 | } 147 | 148 | template 149 | static void Select(Iter begin, Iter mid, Iter end) { 150 | median_of_medians_select(begin, mid, end); 151 | } 152 | }; 153 | 154 | struct MedianOf3Random { 155 | template 156 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 157 | median_of_3_random_partial_sort(begin, mid, end, std::move(comp)); 158 | } 159 | 160 | template 161 | static void Sort(Iter begin, Iter mid, Iter end) { 162 | median_of_3_random_partial_sort(begin, mid, end); 163 | } 164 | 165 | template 166 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 167 | median_of_3_random_select(begin, mid, end, std::move(comp)); 168 | } 169 | 170 | template 171 | static void Select(Iter begin, Iter mid, Iter end) { 172 | median_of_3_random_select(begin, mid, end); 173 | } 174 | }; 175 | 176 | struct Heap { 177 | template 178 | static void Sort(Iter begin, Iter mid, Iter end, Compare&& comp) { 179 | heap_partial_sort(begin, mid, end, std::move(comp)); 180 | } 181 | 182 | template 183 | static void Sort(Iter begin, Iter mid, Iter end) { 184 | heap_partial_sort(begin, mid, end); 185 | } 186 | 187 | template 188 | static void Select(Iter begin, Iter mid, Iter end, Compare&& comp) { 189 | heap_select(begin, mid, end, std::move(comp)); 190 | } 191 | 192 | template 193 | static void Select(Iter begin, Iter mid, Iter end) { 194 | heap_select(begin, mid, end); 195 | } 196 | }; 197 | 198 | using All = 199 | ::testing::Types; 201 | 202 | template 203 | struct IntegralCharIterator { 204 | using difference_type = Integral; 205 | using value_type = char; 206 | using pointer = char*; 207 | using reference = char&; 208 | using iterator_category = std::random_access_iterator_tag; 209 | 210 | IntegralCharIterator() = default; 211 | IntegralCharIterator(const IntegralCharIterator& other) : pos(other.pos) {} 212 | IntegralCharIterator(IntegralCharIterator&& other) : pos(other.pos) {} 213 | IntegralCharIterator& operator=(const IntegralCharIterator& other) { 214 | pos = other.pos; 215 | return *this; 216 | } 217 | IntegralCharIterator& operator=(IntegralCharIterator&& other) { 218 | pos = other.pos; 219 | return *this; 220 | } 221 | ~IntegralCharIterator() = default; 222 | IntegralCharIterator(pointer p) : pos(p) {} 223 | 224 | IntegralCharIterator& operator+=(difference_type other) { 225 | pos += other; 226 | return *this; 227 | } 228 | 229 | IntegralCharIterator& operator-=(difference_type other) { 230 | pos -= other; 231 | return *this; 232 | } 233 | 234 | value_type& operator[](difference_type other) { 235 | return pos[static_cast(other)]; 236 | } 237 | 238 | value_type& operator[](difference_type other) const { 239 | return pos[static_cast(other)]; 240 | } 241 | 242 | IntegralCharIterator& operator++() { 243 | ++pos; 244 | return *this; 245 | } 246 | 247 | IntegralCharIterator operator++(int) { return IntegralCharIterator(pos++); } 248 | 249 | IntegralCharIterator& operator--() { 250 | --pos; 251 | return *this; 252 | } 253 | 254 | IntegralCharIterator operator--(int) { return IntegralCharIterator(pos--); } 255 | 256 | value_type& operator*() { return *pos; } 257 | 258 | value_type& operator*() const { return *pos; } 259 | 260 | difference_type operator-(const IntegralCharIterator& other) const { 261 | return pos - other.pos; 262 | } 263 | 264 | IntegralCharIterator operator-(difference_type other) const { 265 | return IntegralCharIterator(pos - other); 266 | } 267 | 268 | IntegralCharIterator operator+(difference_type other) const { 269 | return IntegralCharIterator(pos + other); 270 | } 271 | 272 | bool operator==(const IntegralCharIterator& other) const { 273 | return pos == other.pos; 274 | } 275 | 276 | bool operator!=(const IntegralCharIterator& other) const { 277 | return pos != other.pos; 278 | } 279 | 280 | bool operator<(const IntegralCharIterator& other) const { 281 | return pos < other.pos; 282 | } 283 | 284 | bool operator>(const IntegralCharIterator& other) const { 285 | return pos > other.pos; 286 | } 287 | 288 | bool operator<=(const IntegralCharIterator& other) const { 289 | return pos <= other.pos; 290 | } 291 | 292 | bool operator>=(const IntegralCharIterator& other) const { 293 | return pos >= other.pos; 294 | } 295 | 296 | char* pos = nullptr; 297 | }; 298 | 299 | template 300 | inline IntegralCharIterator operator+( 301 | typename IntegralCharIterator::difference_type diff, 302 | const IntegralCharIterator iter) { 303 | return iter + diff; 304 | } 305 | 306 | } // namespace algorithms 307 | } // namespace miniselect 308 | -------------------------------------------------------------------------------- /testing/test_select.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright Danila Kutenin, 2020-. 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "test_common.h" 16 | 17 | using ::testing::Eq; 18 | 19 | namespace miniselect { 20 | namespace { 21 | 22 | struct IndirectLess { 23 | // Non const comparator with deleted copy. 24 | template 25 | bool operator()(const P &x, const P &y) const { 26 | return *x < *y; 27 | } 28 | IndirectLess(const IndirectLess &) = default; 29 | IndirectLess &operator=(const IndirectLess &) = default; 30 | IndirectLess(IndirectLess &&) = default; 31 | IndirectLess &operator=(IndirectLess &&) = default; 32 | }; 33 | 34 | struct CustomInt { 35 | size_t x = 0; 36 | bool operator<(const CustomInt& other) const { 37 | return x < other.x; 38 | } 39 | }; 40 | 41 | template 42 | class SelectTest : public ::testing::Test { 43 | public: 44 | using Base = Selector; 45 | 46 | static void TestSelects(size_t N, size_t M) { 47 | ASSERT_NE(N, 0); 48 | ASSERT_GT(N, M); 49 | SCOPED_TRACE(N); 50 | SCOPED_TRACE(M); 51 | std::vector array(N); 52 | for (size_t i = 0; i < N; ++i) { 53 | array[i] = i; 54 | } 55 | auto array_smaller = array; 56 | std::mt19937_64 mersenne_engine; 57 | std::shuffle(array.begin(), array.end(), mersenne_engine); 58 | Selector::Select(array.begin(), array.begin() + M, array.end(), 59 | std::greater()); 60 | EXPECT_EQ(array[M], N - M - 1); 61 | for (size_t i = 0; i < M; ++i) { 62 | EXPECT_GE(array[i], array[M]); 63 | } 64 | for (size_t i = M; i < N; ++i) { 65 | EXPECT_LE(array[i], array[M]); 66 | } 67 | std::shuffle(array_smaller.begin(), array_smaller.end(), mersenne_engine); 68 | Selector::Select(array_smaller.begin(), array_smaller.begin() + M, 69 | array_smaller.end()); 70 | EXPECT_EQ(array_smaller[M], M); 71 | for (size_t i = 0; i < M; ++i) { 72 | EXPECT_LE(array_smaller[i], array_smaller[M]); 73 | } 74 | for (size_t i = M; i < N; ++i) { 75 | EXPECT_GE(array_smaller[i], array_smaller[M]); 76 | } 77 | } 78 | 79 | static void TestRandomAccessIterator(size_t N, size_t M) { 80 | ASSERT_NE(N, 0); 81 | ASSERT_GT(N, M); 82 | SCOPED_TRACE(N); 83 | SCOPED_TRACE(M); 84 | std::vector array(N); 85 | for (size_t i = 0; i < N; ++i) { 86 | array[i] = i; 87 | } 88 | std::mt19937_64 mersenne_engine; 89 | std::shuffle(array.begin(), array.end(), mersenne_engine); 90 | algorithms::IntegralCharIterator beg(array.data()); 91 | algorithms::IntegralCharIterator mid(array.data() + M); 92 | algorithms::IntegralCharIterator end(array.data() + array.size()); 93 | Selector::Select(beg, mid, end, std::greater()); 94 | EXPECT_EQ(array[M], N - M - 1); 95 | for (size_t i = 0; i < M; ++i) { 96 | EXPECT_GE(array[i], array[M]); 97 | } 98 | for (size_t i = M; i < N; ++i) { 99 | EXPECT_LE(array[i], array[M]); 100 | } 101 | } 102 | 103 | static void TestManyRandomAccessIterators(size_t N) { 104 | TestRandomAccessIterator(N, 0); 105 | TestRandomAccessIterator(N, 1); 106 | TestRandomAccessIterator(N, 2); 107 | TestRandomAccessIterator(N, N / 2 - 1); 108 | TestRandomAccessIterator(N, N / 2); 109 | TestRandomAccessIterator(N, N / 2 + 1); 110 | TestRandomAccessIterator(N, N - 2); 111 | TestRandomAccessIterator(N, N - 1); 112 | } 113 | 114 | static void TestRandomAccessIterators() { 115 | TestManyRandomAccessIterators(127); 116 | } 117 | 118 | static void TestSelects(size_t N) { 119 | TestSelects(N, 0); 120 | TestSelects(N, 1); 121 | TestSelects(N, 2); 122 | TestSelects(N, 3); 123 | TestSelects(N, N / 2 - 1); 124 | TestSelects(N, N / 2); 125 | TestSelects(N, N / 2 + 1); 126 | TestSelects(N, N - 2); 127 | TestSelects(N, N - 1); 128 | } 129 | 130 | static void TestManySelects() { 131 | TestSelects(10); 132 | TestSelects(256); 133 | TestSelects(257); 134 | TestSelects(499); 135 | TestSelects(500); 136 | TestSelects(997); 137 | TestSelects(1000); 138 | TestSelects(1000 * 100); 139 | TestSelects(1009); 140 | TestSelects(1009 * 109); 141 | } 142 | 143 | static void TestCustomComparators() { 144 | std::vector> v(1000); 145 | for (size_t i = 0; i < v.size(); ++i) { 146 | v[i] = std::make_unique(i); 147 | } 148 | Selector::Select(v.begin(), v.begin() + v.size() / 2, v.end(), 149 | IndirectLess{}); 150 | EXPECT_EQ(*v[v.size() / 2], v.size() / 2); 151 | for (size_t i = 0; i < v.size() / 2; ++i) { 152 | ASSERT_NE(v[i], nullptr); 153 | EXPECT_LE(*v[i], v.size() / 2); 154 | } 155 | for (size_t i = v.size() / 2; i < v.size(); ++i) { 156 | ASSERT_NE(v[i], nullptr); 157 | EXPECT_GE(*v[i], v.size() / 2); 158 | } 159 | } 160 | 161 | static void TestOnlyOperatorLess() { 162 | std::vector v(1000); 163 | for (size_t i = 0; i < v.size(); ++i) { 164 | v[i].x = v.size() - i - 1; 165 | } 166 | Selector::Select(v.begin(), v.begin() + v.size() / 2, v.end()); 167 | EXPECT_EQ(v[v.size() / 2].x, v.size() / 2); 168 | for (size_t i = 0; i < v.size() / 2; ++i) { 169 | EXPECT_LE(v[i].x, v.size() / 2); 170 | } 171 | for (size_t i = v.size() / 2; i < v.size(); ++i) { 172 | EXPECT_GE(v[i].x, v.size() / 2); 173 | } 174 | } 175 | 176 | static void TestRepeat(size_t N, size_t M) { 177 | ASSERT_NE(N, 0); 178 | ASSERT_GT(N, M); 179 | SCOPED_TRACE(N); 180 | SCOPED_TRACE(M); 181 | std::mt19937_64 mersenne_engine(10); 182 | std::vector array(N); 183 | for (size_t i = 0; i < M; ++i) { 184 | array[i] = false; 185 | } 186 | for (size_t i = M; i < N; ++i) { 187 | array[i] = true; 188 | } 189 | std::shuffle(array.begin(), array.end(), mersenne_engine); 190 | Selector::Select(array.begin(), array.begin() + M, array.end()); 191 | EXPECT_EQ(array[M], true); 192 | for (size_t i = 0; i < M; ++i) { 193 | EXPECT_EQ(array[i], false); 194 | } 195 | for (size_t i = M; i < N; ++i) { 196 | EXPECT_EQ(array[i], true); 197 | } 198 | std::shuffle(array.begin(), array.end(), mersenne_engine); 199 | Selector::Select(array.begin(), array.begin() + M / 2, array.end()); 200 | EXPECT_EQ(array[M / 2], false); 201 | for (size_t i = 0; i < M / 2; ++i) { 202 | EXPECT_EQ(array[i], false); 203 | } 204 | std::shuffle(array.begin(), array.end(), mersenne_engine); 205 | Selector::Select(array.begin(), array.begin() + M - 1, array.end()); 206 | EXPECT_EQ(array[M - 1], false); 207 | for (size_t i = 0; i < M - 1; ++i) { 208 | EXPECT_EQ(array[i], false); 209 | } 210 | } 211 | 212 | static void TestRepeats(size_t N) { 213 | TestRepeat(N, 1); 214 | TestRepeat(N, 2); 215 | TestRepeat(N, 3); 216 | TestRepeat(N, N / 2 - 1); 217 | TestRepeat(N, N / 2); 218 | TestRepeat(N, N / 2 + 1); 219 | TestRepeat(N, N - 2); 220 | TestRepeat(N, N - 1); 221 | } 222 | 223 | static void TestManyRepeats() { 224 | TestRepeats(10); 225 | TestRepeats(100); 226 | TestRepeats(257); 227 | TestRepeats(1000); 228 | TestRepeats(100000); 229 | } 230 | }; 231 | 232 | TYPED_TEST_SUITE(SelectTest, algorithms::All); 233 | 234 | TYPED_TEST(SelectTest, TestSmall) { 235 | std::vector v = {"ab", "aaa", "ab"}; 236 | TypeParam::Select(v.begin(), v.begin() + 1, v.end()); 237 | EXPECT_THAT(v, Eq(std::vector{"aaa", "ab", "ab"})); 238 | v = {"aba"}; 239 | TypeParam::Select(v.begin(), v.begin(), v.end()); 240 | EXPECT_THAT(v, Eq(std::vector{"aba"})); 241 | v.clear(); 242 | TypeParam::Select(v.begin(), v.end(), v.end()); 243 | EXPECT_TRUE(v.empty()); 244 | } 245 | 246 | TYPED_TEST(SelectTest, TestAnotherSmall) { 247 | std::vector v = {"ab", "ab", "aaa"}; 248 | TypeParam::Select(v.begin(), v.begin() + 1, v.end()); 249 | EXPECT_THAT(v, Eq(std::vector{"aaa", "ab", "ab"})); 250 | } 251 | 252 | TYPED_TEST(SelectTest, TestEmptySmall) { 253 | std::vector v = {"", ""}; 254 | TypeParam::Select(v.begin(), v.begin() + 1, v.end()); 255 | EXPECT_THAT(v, Eq(std::vector{"", ""})); 256 | } 257 | 258 | TYPED_TEST(SelectTest, TestBasic) { TestFixture::TestManySelects(); } 259 | 260 | TYPED_TEST(SelectTest, TestComparators) { 261 | TestFixture::TestCustomComparators(); 262 | } 263 | 264 | TYPED_TEST(SelectTest, TestOnlyOperatorLess) { 265 | TestFixture::TestOnlyOperatorLess(); 266 | } 267 | 268 | TYPED_TEST(SelectTest, TestRepeats) { TestFixture::TestManyRepeats(); } 269 | 270 | TYPED_TEST(SelectTest, TestRandomAccessIterators) { 271 | TestFixture::TestRandomAccessIterators(); 272 | } 273 | 274 | TYPED_TEST(SelectTest, TestLast) { 275 | std::vector array(100); 276 | for (size_t i = 0; i < 100; ++i) { 277 | array[i] = i; 278 | } 279 | auto array_smaller = array; 280 | std::mt19937_64 mersenne_engine; 281 | std::shuffle(array.begin(), array.end(), mersenne_engine); 282 | auto copy_array = array; 283 | // Should be no effect. 284 | size_t cmp = 0; 285 | TypeParam::Select(array.begin(), array.end(), array.end(), 286 | [&cmp](const auto &lhs, const auto &rhs) { 287 | ++cmp; 288 | return lhs < rhs; 289 | }); 290 | EXPECT_EQ(cmp, 0); 291 | EXPECT_EQ(copy_array, array); 292 | } 293 | 294 | } // namespace 295 | } // namespace miniselect 296 | 297 | int main(int argc, char **argv) { 298 | ::testing::InitGoogleTest(&argc, argv); 299 | return RUN_ALL_TESTS(); 300 | } 301 | -------------------------------------------------------------------------------- /include/miniselect/private/median_common.h: -------------------------------------------------------------------------------- 1 | /* Copyright Andrei Alexandrescu, 2016-, 2 | * Distributed under the Boost Software License, Version 1.0. 3 | * (See accompanying file LICENSE_1_0.txt or copy at 4 | * https://boost.org/LICENSE_1_0.txt) 5 | */ 6 | /* Copyright Danila Kutenin, 2020-. 7 | * Distributed under the Boost Software License, Version 1.0. 8 | * (See accompanying file LICENSE_1_0.txt or copy at 9 | * https://boost.org/LICENSE_1_0.txt) 10 | */ 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace miniselect { 19 | namespace median_common_detail { 20 | 21 | template 22 | struct CompareRefType { 23 | // Pass the comparator by lvalue reference. Or in debug mode, using a 24 | // debugging wrapper that stores a reference. 25 | using type = typename std::add_lvalue_reference::type; 26 | }; 27 | /** 28 | Swaps the median of r[a], r[b], and r[c] into r[b]. 29 | */ 30 | template ::difference_type> 32 | inline void median3(Iter r, DiffType a, DiffType b, DiffType c, 33 | Compare&& comp) { 34 | if (comp(r[b], r[a])) // b < a 35 | { 36 | if (comp(r[b], r[c])) // b < a, b < c 37 | { 38 | if (comp(r[c], r[a])) // b < c < a 39 | std::swap(r[b], r[c]); 40 | else // b < a <= c 41 | std::swap(r[b], r[a]); 42 | } 43 | } else if (comp(r[c], r[b])) // a <= b, c < b 44 | { 45 | if (comp(r[c], r[a])) // c < a <= b 46 | std::swap(r[b], r[a]); 47 | else // a <= c < b 48 | std::swap(r[b], r[c]); 49 | } 50 | } 51 | 52 | /** 53 | Sorts in place r[a], r[b], and r[c]. 54 | */ 55 | template ::difference_type> 57 | inline void sort3(Iter r, DiffType a, DiffType b, DiffType c, Compare&& comp) { 58 | typedef typename std::iterator_traits::value_type T; 59 | if (comp(r[b], r[a])) // b < a 60 | { 61 | if (comp(r[c], r[b])) // c < b < a 62 | { 63 | std::swap(r[a], r[c]); // a < b < c 64 | } else // b < a, b <= c 65 | { 66 | T t = std::move(r[a]); 67 | r[a] = std::move(r[b]); 68 | if (comp(r[c], t)) // b <= c < a 69 | { 70 | r[b] = std::move(r[c]); 71 | r[c] = std::move(t); 72 | } else // b < a <= c 73 | { 74 | r[b] = std::move(t); 75 | } 76 | } 77 | } else if (comp(r[c], r[b])) // a <= b, c < b 78 | { 79 | T t = std::move(r[c]); 80 | r[c] = std::move(r[b]); 81 | if (comp(t, r[a])) // c < a < b 82 | { 83 | r[b] = std::move(r[a]); 84 | r[a] = std::move(t); 85 | } else // a <= c < b 86 | { 87 | r[b] = std::move(t); 88 | } 89 | } 90 | 91 | assert(!comp(r[b], r[a]) && !comp(r[c], r[b])); 92 | } 93 | 94 | /** 95 | If leanRight == false, swaps the lower median of r[a]...r[d] into r[b] and 96 | the minimum into r[a]. If leanRight == true, swaps the upper median of 97 | r[a]...r[d] into r[c] and the minimum into r[d]. 98 | */ 99 | template ::difference_type> 101 | inline void partition4(Iter r, DiffType a, DiffType b, DiffType c, DiffType d, 102 | Compare&& comp) { 103 | assert(a != b && a != c && a != d && b != c && b != d && c != d); 104 | /* static */ if (leanRight) { 105 | // In the median of 5 algorithm, consider r[e] infinite 106 | if (comp(r[c], r[a])) { 107 | std::swap(r[a], r[c]); 108 | } // a <= c 109 | if (comp(r[d], r[b])) { 110 | std::swap(r[b], r[d]); 111 | } // a <= c, b <= d 112 | if (comp(r[d], r[c])) { 113 | std::swap(r[c], r[d]); // a <= d, b <= c < d 114 | std::swap(r[a], r[b]); // b <= d, a <= c < d 115 | } // a <= c <= d, b <= d 116 | if (comp(r[c], r[b])) { // a <= c <= d, c < b <= d 117 | std::swap(r[b], r[c]); // a <= b <= c <= d 118 | } // a <= b <= c <= d 119 | } else { 120 | // In the median of 5 algorithm consider r[a] infinitely small, then 121 | // change b->a. c->b, d->c, e->d 122 | if (comp(r[c], r[a])) { 123 | std::swap(r[a], r[c]); 124 | } 125 | if (comp(r[c], r[b])) { 126 | std::swap(r[b], r[c]); 127 | } 128 | if (comp(r[d], r[a])) { 129 | std::swap(r[a], r[d]); 130 | } 131 | if (comp(r[d], r[b])) { 132 | std::swap(r[b], r[d]); 133 | } else { 134 | if (comp(r[b], r[a])) { 135 | std::swap(r[a], r[b]); 136 | } 137 | } 138 | } 139 | } 140 | 141 | /** 142 | Places the median of r[a]...r[e] in r[c] and partitions the other elements 143 | around it. 144 | */ 145 | template ::difference_type> 147 | inline void partition5(Iter r, DiffType a, DiffType b, DiffType c, DiffType d, 148 | DiffType e, Compare&& comp) { 149 | assert(a != b && a != c && a != d && a != e && b != c && b != d && b != e && 150 | c != d && c != e && d != e); 151 | if (comp(r[c], r[a])) { 152 | std::swap(r[a], r[c]); 153 | } 154 | if (comp(r[d], r[b])) { 155 | std::swap(r[b], r[d]); 156 | } 157 | if (comp(r[d], r[c])) { 158 | std::swap(r[c], r[d]); 159 | std::swap(r[a], r[b]); 160 | } 161 | if (comp(r[e], r[b])) { 162 | std::swap(r[b], r[e]); 163 | } 164 | if (comp(r[e], r[c])) { 165 | std::swap(r[c], r[e]); 166 | if (comp(r[c], r[a])) { 167 | std::swap(r[a], r[c]); 168 | } 169 | } else { 170 | if (comp(r[c], r[b])) { 171 | std::swap(r[b], r[c]); 172 | } 173 | } 174 | } 175 | 176 | /** 177 | Implements Hoare partition. 178 | */ 179 | template ::difference_type> 181 | inline Iter pivot_partition(Iter r, DiffType k, DiffType length, 182 | Compare&& comp) { 183 | assert(k < length); 184 | std::swap(*r, r[k]); 185 | DiffType lo = 1, hi = length - 1; 186 | for (;; ++lo, --hi) { 187 | for (;; ++lo) { 188 | if (lo > hi) goto loop_done; 189 | if (!comp(r[lo], *r)) break; 190 | } 191 | // found the left bound: r[lo] >= r[0] 192 | assert(lo <= hi); 193 | for (; comp(*r, r[hi]); --hi) { 194 | } 195 | if (lo >= hi) break; 196 | // found the right bound: r[hi] <= r[0], swap & make progress 197 | std::swap(r[lo], r[hi]); 198 | } 199 | loop_done: 200 | --lo; 201 | std::swap(r[lo], *r); 202 | return r + lo; 203 | } 204 | 205 | /** 206 | Implements the quickselect algorithm, parameterized with a partition function. 207 | */ 208 | template 209 | inline void quickselect(Iter r, Iter mid, Iter end, Compare&& comp) { 210 | if (r == end || mid >= end) return; 211 | assert(r <= mid && mid < end); 212 | for (;;) switch (end - r) { 213 | case 1: 214 | return; 215 | case 2: 216 | if (comp(r[1], *r)) std::swap(*r, r[1]); 217 | return; 218 | case 3: 219 | sort3(r, 0, 1, 2, comp); 220 | return; 221 | case 4: 222 | switch (mid - r) { 223 | case 0: 224 | goto select_min; 225 | case 1: 226 | partition4(r, 0, 1, 2, 3, comp); 227 | break; 228 | case 2: 229 | partition4(r, 0, 1, 2, 3, comp); 230 | break; 231 | case 3: 232 | goto select_max; 233 | default: 234 | assert(false); 235 | } 236 | return; 237 | default: 238 | assert(end - r > 4); 239 | if (r == mid) { 240 | select_min: 241 | auto pivot = r; 242 | for (++mid; mid < end; ++mid) 243 | if (comp(*mid, *pivot)) pivot = mid; 244 | std::swap(*r, *pivot); 245 | return; 246 | } 247 | if (mid + 1 == end) { 248 | select_max: 249 | auto pivot = r; 250 | for (mid = r + 1; mid < end; ++mid) 251 | if (comp(*pivot, *mid)) pivot = mid; 252 | std::swap(*pivot, *(end - 1)); 253 | return; 254 | } 255 | auto pivot = partition(r, end, comp); 256 | if (pivot == mid) return; 257 | if (mid < pivot) { 258 | end = pivot; 259 | } else { 260 | r = pivot + 1; 261 | } 262 | } 263 | } 264 | 265 | /** 266 | Returns the index of the median of r[a], r[b], and r[c] without writing 267 | anything. 268 | */ 269 | template ::difference_type> 271 | inline DiffType median_index(const Iter r, DiffType a, DiffType b, DiffType c, 272 | Compare&& comp) { 273 | if (comp(r[c], r[a])) std::swap(a, c); 274 | if (comp(r[c], r[b])) return c; 275 | if (comp(r[b], r[a])) return a; 276 | return b; 277 | } 278 | 279 | /** 280 | Returns the index of the median of r[a], r[b], r[c], and r[d] without writing 281 | anything. If leanRight is true, computes the upper median. Otherwise, conputes 282 | the lower median. 283 | */ 284 | template ::difference_type> 286 | inline DiffType median_index(Iter r, DiffType a, DiffType b, DiffType c, 287 | DiffType d, Compare&& comp) { 288 | if (comp(r[d], r[c])) std::swap(c, d); 289 | if (leanRight) { 290 | if (comp(r[c], r[a])) { 291 | assert(comp(r[c], r[a]) && !comp(r[d], r[c])); // so r[c]) is out 292 | return median_index(r, a, b, d, comp); 293 | } 294 | } else { 295 | if (!comp(r[d], r[a])) { 296 | return median_index(r, a, b, c, comp); 297 | } 298 | } 299 | // Could return median_index(r, b, c, d) but we already know r[c] <= r[d] 300 | if (!comp(r[c], r[b])) return c; 301 | if (comp(r[d], r[b])) return d; 302 | return b; 303 | } 304 | 305 | /** 306 | Tukey's Ninther: compute the median of r[_1], r[_2], r[_3], then the median of 307 | r[_4], r[_5], r[_6], then the median of r[_7], r[_8], r[_9], and then swap the 308 | median of those three medians into r[_5]. 309 | */ 310 | template ::difference_type> 312 | inline void ninther(Iter r, DiffType _1, DiffType _2, DiffType _3, DiffType _4, 313 | DiffType _5, DiffType _6, DiffType _7, DiffType _8, 314 | DiffType _9, Compare&& comp) { 315 | _2 = median_index(r, _1, _2, _3, comp); 316 | _8 = median_index(r, _7, _8, _9, comp); 317 | if (comp(r[_8], r[_2])) std::swap(_2, _8); 318 | if (comp(r[_6], r[_4])) std::swap(_4, _6); 319 | // Here we know that r[_2] and r[_8] are the other two medians and that 320 | // r[_2] <= r[_8]. We also know that r[_4] <= r[_6] 321 | if (comp(r[_5], r[_4])) { 322 | // r[_4] is the median of r[_4], r[_5], r[_6] 323 | } else if (comp(r[_6], r[_5])) { 324 | // r[_6] is the median of r[_4], r[_5], r[_6] 325 | _4 = _6; 326 | } else { 327 | // Here we know r[_5] is the median of r[_4], r[_5], r[_6] 328 | if (comp(r[_5], r[_2])) return std::swap(r[_5], r[_2]); 329 | if (comp(r[_8], r[_5])) return std::swap(r[_5], r[_8]); 330 | // This is the only path that returns with no swap 331 | return; 332 | } 333 | // Here we know r[_4] is the median of r[_4], r[_5], r[_6] 334 | if (comp(r[_4], r[_2])) 335 | _4 = _2; 336 | else if (comp(r[_8], r[_4])) 337 | _4 = _8; 338 | std::swap(r[_5], r[_4]); 339 | } 340 | 341 | /** 342 | Input assumptions: 343 | (a) hi <= rite 344 | (c) the range r[0 .. hi] contains elements no smaller than r[0] 345 | Output guarantee: same as Hoare partition using r[0] as pivot. Returns the new 346 | position of the pivot. 347 | */ 348 | template ::difference_type> 350 | inline DiffType expand_partition_right(Iter r, DiffType hi, DiffType rite, 351 | Compare&& comp) { 352 | DiffType pivot = 0; 353 | assert(pivot <= hi); 354 | assert(hi <= rite); 355 | // First loop: spend r[pivot .. hi] 356 | for (; pivot < hi; --rite) { 357 | if (rite == hi) goto done; 358 | if (!comp(r[rite], r[0])) continue; 359 | ++pivot; 360 | std::swap(r[rite], r[pivot]); 361 | } 362 | // Second loop: make left and pivot meet 363 | for (; rite > pivot; --rite) { 364 | if (!comp(r[rite], r[0])) continue; 365 | while (rite > pivot) { 366 | ++pivot; 367 | if (comp(r[0], r[pivot])) { 368 | std::swap(r[rite], r[pivot]); 369 | break; 370 | } 371 | } 372 | } 373 | 374 | done: 375 | std::swap(r[0], r[pivot]); 376 | return pivot; 377 | } 378 | 379 | /** 380 | Input assumptions: 381 | (a) lo > 0, lo <= pivot 382 | (b) the range r[lo .. pivot] already contains elements no greater than r[pivot] 383 | Output guarantee: Same as Hoare partition around r[pivot]. Returns the new 384 | position of the pivot. 385 | */ 386 | template ::difference_type> 388 | inline DiffType expand_partition_left(Iter r, DiffType lo, DiffType pivot, 389 | Compare&& comp) { 390 | assert(lo > 0 && lo <= pivot); 391 | DiffType left = 0; 392 | const auto oldPivot = pivot; 393 | for (; lo < pivot; ++left) { 394 | if (left == lo) goto done; 395 | if (!comp(r[oldPivot], r[left])) continue; 396 | --pivot; 397 | std::swap(r[left], r[pivot]); 398 | } 399 | // Second loop: make left and pivot meet 400 | for (;; ++left) { 401 | if (left == pivot) break; 402 | if (!comp(r[oldPivot], r[left])) continue; 403 | for (;;) { 404 | if (left == pivot) goto done; 405 | --pivot; 406 | if (comp(r[pivot], r[oldPivot])) { 407 | std::swap(r[left], r[pivot]); 408 | break; 409 | } 410 | } 411 | } 412 | 413 | done: 414 | std::swap(r[oldPivot], r[pivot]); 415 | return pivot; 416 | } 417 | 418 | /** 419 | Input assumptions: 420 | (a) lo <= pivot, pivot < hi, hi <= length 421 | (b) the range r[lo .. pivot] already contains elements no greater than 422 | r[pivot] 423 | (c) the range r[pivot .. hi] already contains elements no smaller than 424 | r[pivot] 425 | Output guarantee: Same as Hoare partition around r[pivot], returning the new 426 | position of the pivot. 427 | */ 428 | template ::difference_type> 430 | inline DiffType expand_partition(Iter r, DiffType lo, DiffType pivot, 431 | DiffType hi, DiffType length, Compare&& comp) { 432 | assert(lo <= pivot && pivot < hi && hi <= length); 433 | --hi; 434 | --length; 435 | DiffType left = 0; 436 | for (;; ++left, --length) { 437 | for (;; ++left) { 438 | if (left == lo) 439 | return pivot + expand_partition_right(r + pivot, hi - pivot, 440 | length - pivot, comp); 441 | if (comp(r[pivot], r[left])) break; 442 | } 443 | for (;; --length) { 444 | if (length == hi) 445 | return left + 446 | expand_partition_left(r + left, lo - left, pivot - left, comp); 447 | if (!comp(r[pivot], r[length])) break; 448 | } 449 | std::swap(r[left], r[length]); 450 | } 451 | } 452 | 453 | } // namespace median_common_detail 454 | } // namespace miniselect 455 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/danlark1/miniselect.svg?branch=main)](https://travis-ci.com/danlark1/miniselect) 2 | [![License](https://img.shields.io/badge/License-Boost%201.0-lightblue.svg)](https://www.boost.org/LICENSE_1_0.txt) 3 | 4 | miniselect: Generic selection and partial ordering algorithms 5 | ============================================================== 6 | 7 | `miniselect` is a C++ header-only library that contains various generic selection 8 | and partial sorting algorithms with the ease of use, testing, advice on usage and 9 | benchmarking. 10 | 11 | Sorting is everywhere and there are many outstanding sorting algorithms that 12 | compete in speed, comparison count and cache friendliness. However, selection 13 | algorithms are always a bit outside of the competition scope, they are 14 | pretty important, for example, in databases ORDER BY LIMIT N is used extremely 15 | often which can benefit from more optimal selection and partial sorting 16 | algorithms. This library tries to solve this problem with Modern C++. 17 | 18 | * **Easy:** First-class, easy to use dependency and carefully documented APIs and algorithm properties. 19 | * **Fast:** We do care about speed of the algorithms and provide reasonable implementations. 20 | * **Standard compliant:** We provide C++11 compatible APIs that are compliant to the standard [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element) and [`std::partial_sort`](https://en.cppreference.com/w/cpp/algorithm/partial_sort) functions including custom comparators and order guarantees. Just replace the names of the functions in your project and it should work! 21 | * **Well tested:** We test all algorithms with a unified framework, under sanitizers and fuzzing. 22 | * **Benchmarked:** We gather benchmarks for all implementations to better understand good and bad spots. 23 | 24 | Table of Contents 25 | ----------------- 26 | 27 | * [Quick Start](#quick-start) 28 | * [Testing](#testing) 29 | * [Documentation](#documentation) 30 | * [Performance results](#performance-results) 31 | * [Real-world usage](#real-world-usage) 32 | * [Contributing](#contributing) 33 | * [Motivation](#motivation) 34 | * [License](#license) 35 | 36 | Quick Start 37 | ----------- 38 | 39 | You can either include this project as a cmake dependency and then use the 40 | headers that are provided in the [include](./include) folder or just pass the 41 | [include](./include) folder to your compiler. 42 | 43 | ```cpp 44 | #include 45 | #include 46 | 47 | #include "miniselect/median_of_ninthers.h" 48 | 49 | int main() { 50 | std::vector v = {1, 8, 4, 3, 2, 9, 0, 7, 6, 5}; 51 | miniselect::median_of_ninthers_select(v.begin(), v.begin() + 5, v.end()); 52 | for (const int i : v) { 53 | std::cout << i << ' '; 54 | } 55 | return 0; 56 | } 57 | // Compile it `clang++/g++ -I$DIRECTORY/miniselect/include/ example.cpp -std=c++11 -O3 -o example 58 | // Possible output: 0 1 4 3 2 5 8 7 6 9 59 | // ^ on the right place 60 | ``` 61 | 62 | Examples can be found in [examples](./examples). 63 | 64 | We support all compilers starting from GCC 7 and Clang 6. We are also planning 65 | to support Windows, for now it is best effort but no issues are known so far. 66 | 67 | More on which algorithms are available, see [documentation](#documentation). 68 | For overview of this work you can read the [article](https://danlark.org/2020/11/11/miniselect-practical-and-generic-selection-algorithms/) 69 | in the author's blog. 70 | 71 | Testing 72 | ------- 73 | 74 | To test and benchmark, we use [Google benchmark](https://github.com/google/benchmark) library. 75 | Simply do in the root directory: 76 | 77 | ```console 78 | # Check out the libraries. 79 | $ git clone https://github.com/google/benchmark.git 80 | $ git clone https://github.com/google/googletest.git 81 | $ mkdir build && cd build 82 | $ cmake -DMINISELECT_TESTING=on -DBENCHMARK_ENABLE_GTEST_TESTS=off -DBENCHMARK_ENABLE_TESTING=off .. 83 | $ make -j 84 | $ ctest -j4 --output-on-failure 85 | ``` 86 | 87 | It will create two tests and two benchmarks `test_sort`, `test_select`, 88 | `benchmark_sort`, `benchmark_select`. Use them to validate or contribute. You 89 | can also use `ctest`. 90 | 91 | Documentation 92 | ------------- 93 | 94 | There are several selection algorithms available, further $n$ is the number 95 | of elements in the array, $k$ is the selection element that is needed to be found (all algorithms are deterministic and not stable unless otherwise is specified): 96 | 97 | 98 | | Name | Average | Best Case | Worst Case | Comparisons | Memory | 99 | |------------------------- |--------------------------------------------------------------------------------------------------------- |--------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |--------------------------------------------------------------------------------------------------------------------------------- | 100 | | [pdqselect](./include/miniselect/pdqselect.h) | $O(n)$ | $O(n)$ | $`O(n\log n)`$ | At least $2n$. Random data $2.5n$ | $O(1)$ | 101 | | [Floyd-Rivest](./include/miniselect/floyd_rivest_select.h) | $O(n)$ | $O(n)$ | $O(n^2)$ | Avg: $n + \min(k, n - k) + O(\sqrt{n \log n})$ | $`O(\log\log n)`$ | 102 | | [Median Of Medians](./include/miniselect/median_of_medians.h) | $O(n)$ | $O(n)$ | $O(n)$ | Between $2n$ and $22n$. Random data $2.5n$ | $O(\log n)$ | 103 | | [Median Of Ninthers](./include/miniselect/median_of_ninthers.h) | $O(n)$ | $O(n)$ | $O(n)$ | Between $2n$ and $21n$. Random data $2n$ | $O(\log n)$ | 104 | | [Median Of 3 Random](./include/miniselect/median_of_3_random.h) | $O(n)$ | $O(n)$ | $O(n^2)$ | At least $2n$. Random data $3n$ | $O(\log n)$ | 105 | | [HeapSelect](./include/miniselect/heap_select.h) | $`O(n\log k)`$ | $O(n)$ | $`O(n\log k)`$ | $n\log k$ on average, for some data patterns might be better | $O(1)$ | 106 | | [libstdc++ (introselect)](https://github.com/gcc-mirror/gcc/blob/e0af865ab9d9d5b6b3ac7fdde26cf9bbf635b6b4/libstdc%2B%2B-v3/include/bits/stl_algo.h#L4748) | $O(n)$ | $O(n)$ | $`O(n\log n)`$ | At least $2n$. Random data $3n$ | $O(1)$ | 107 | | [libc++ (median of 3)](https://github.com/llvm/llvm-project/blob/3ed89b51da38f081fedb57727076262abb81d149/libcxx/include/algorithm#L5159) | $O(n)$ | $O(n)$ | $O(n^2)$ | At least $2n$. Random data $3n$ | $O(1)$ | 108 | 109 | For sorting the situation is similar except every line adds $O(k\log k)$ comparisons and pdqselect is using $O(\log n)$ memory. 110 | 111 | ## API 112 | 113 | All functions end either in `select`, either in `partial_sort` and 114 | their behavior is exactly the same as for 115 | [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element) 116 | and [`std::partial_sort`](https://en.cppreference.com/w/cpp/algorithm/partial_sort) 117 | respectively, i.e. they accept 3 arguments as `first`, `middle`, `end` iterators 118 | and an optional comparator. Several notes: 119 | 120 | * You should not throw exceptions from `Compare` function. Standard library 121 | also does not specify the behavior in that matter. 122 | * We don't support ParallelSTL for now. 123 | * C++20 constexpr specifiers might be added but currently we don't have them 124 | because of some floating point math in several algorithms. 125 | * All functions are in the `miniselect` namespace. See the example for that. 126 | 127 | - pdqselect 128 | - This algorithm is based on [`pdqsort`](https://github.com/orlp/pdqsort) which is acknowledged as one of the fastest generic sort algorithms. 129 | - **Location:** [`miniselect/pdqselect.h`](./include/miniselect/pdqselect.h). 130 | - **Functions:** `pdqselect`, `pdqselect_branchless`, `pdqpartial_sort`, `pdqpartial_sort_branchless`. Branchless version uses branchless partition algorithm provided by [`pdqsort`](https://github.com/orlp/pdqsort). Use it if your comparison function is branchless, it might give performance for very big ranges. 131 | - **Performance advice:** Use it when you need to sort a big chunk so that $k$ is close to $n$. 132 | 133 |

134 | 135 | - Floyd-Rivest 136 | - This algorithm is based on [Floyd-Rivest algorithm](https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm). 137 | - **Location:** [`miniselect/floyd_rivest_select.h`](./include/miniselect/floyd_rivest_select.h). 138 | - **Functions:** `floyd_rivest_select`, `floyd_rivest_partial_sort`. 139 | - **Performance advice:** Given that this algorithm performs as one of the best on average case in terms of comparisons and speed, we highly advise to 140 | at least try this in your project. Especially it is good for small $k$ or types that are expensive to compare (for example, strings). But even for median the benchmarks show it outperforms others. It is not easy for this algorithm to build a reasonable worst case but one of examples when this algorithm does not perform well is when there are lots of similar values of linear size (random01 dataset showed some moderate penalties). 141 | 142 | We present here two gifs, for median and for $k = n/10$ order statistic. 143 | 144 |

145 | 146 | 147 |

148 | 149 | - Median Of Medians 150 | - This algorithm is based on [Median of Medians](https://en.wikipedia.org/wiki/Median_of_medians) algorithm, one of the first deterministic linear time worst case median algorithm. 151 | - **Location:** [`miniselect/median_of_medians.h`](./include/miniselect/median_of_medians.h). 152 | - **Functions:** `median_of_medians_select`, `median_of_medians_partial_sort`. 153 | - **Performance advice:** This algorithm does not show advantages over others, implemented for historical reasons and for bechmarking. 154 | 155 |

156 | 157 | - Median Of Ninthers 158 | - This algorithm is based on [Fast Deterministic Selection](https://erdani.com/research/sea2017.pdf) paper by Andrei Alexandrescu, one of the latest and fastest deterministic linear time worst case median algorithms. 159 | - **Location:** [`miniselect/median_of_ninthers.h`](./include/miniselect/median_of_ninthers.h). 160 | - **Functions:** `median_of_ninthers_select`, `median_of_ninthers_partial_sort`. 161 | - **Performance advice:** Use this algorithm if you absolutely need linear time worst case scenario for selection algorithm. This algorithm shows some strengths over other deterministic [`PICK`](https://en.wikipedia.org/wiki/Median_of_medians) algorithms and has lower constanst than MedianOfMedians. 162 | 163 |

164 | 165 | - Median Of 3 Random 166 | - This algorithm is based on QuickSelect with the random median of 3 pivot choice algorithm (it chooses random 3 elements in the range and takes the middle value). It is a randomized algorithm. 167 | - **Location:** [`miniselect/median_of_3_random.h`](./include/miniselect/median_of_3_random.h). 168 | - **Functions:** `median_of_3_random_select`, `median_of_3_random_partial_sort`. 169 | - **Performance advice:** This is a randomized algorithm and also it did not show any strengths against Median Of Ninthers. 170 | 171 |

172 | 173 | - Introselect 174 | - This algorithm is based on [Introselect](https://en.wikipedia.org/wiki/Introselect) algorithm, it is used in libstdc++ in `std::nth_element`, however instead of falling back to MedianOfMedians it is using HeapSelect which adds logarithm to its worst complexity. 175 | - **Location:** ``. 176 | - **Functions:** `std::nth_element`. 177 | - **Performance advice:** This algorithm is used in standard library and is not recommended to use if you are looking for performance. 178 | 179 |

180 | 181 | - Median Of 3 182 | - This algorithm is based on QuickSelect with median of 3 pivot choice algorithm (the middle value between begin, mid and end values), it is used in libc++ in `std::nth_element`. 183 | - **Location:** ``. 184 | - **Functions:** `std::nth_element`. 185 | - **Performance advice:** This algorithm is used in standard library and is not recommended to use if you are looking for performance. 186 | 187 |

188 | 189 | - `std::partial_sort` or `HeapSelect` 190 | - This algorithm has [heap-based solutions](https://en.wikipedia.org/wiki/Partial_sorting) both in libc++ and libstdc++, from the first $k$ elements the max heap is built, then one by one the elements are trying to be pushed to that heap with HeapSort in the end. 191 | - **Location:** ``, [`miniselect/heap_select.h`](./include/miniselect/heap_select.h). 192 | - **Functions:** `std::partial_sort`, `heap_select`, `heap_partial_sort`. 193 | - **Performance advice:** This algorithm is very good for random data and small $k$ and might outperform all selection+sort algorithms. However, for descending data it starts to significantly degrade and is not recommended for use if you have such patterns in real data. 194 | 195 |

196 | 197 | ## Other algorithms to come 198 | 199 | * Kiwiel modification of FloydRivest algorithm which is described in [On Floyd and Rivest’s SELECT algorithm](https://core.ac.uk/download/pdf/82672439.pdf) with ternary and quintary pivots. 200 | * Combination of FloydRivest and pdqsort pivot strategies, currently all experiments did not show any boost. 201 | 202 | Performance results 203 | ------------------- 204 | 205 | We use 10 datasets and 8 algorithms with 10000000 elements to find median and 206 | other $k$ on `Intel(R) Core(TM) i5-4200H CPU @ 2.80GHz` for `std::vector`, 207 | for median the benchmarks are the following: 208 | 209 | ![median](benches/plots/result_10000000_5000000.png) 210 | 211 | ![median](benches/plots/result_comparisons_10000000_5000000.png) 212 | 213 | ![median](benches/plots/result_accesses_10000000_5000000.png) 214 | 215 | For smaller $k$, 216 | for example, 1000, the results are the following 217 | 218 | ![k equals 1000](benches/plots/result_10000000_1000.png) 219 | 220 | ![k equals 1000](benches/plots/result_comparisons_10000000_1000.png) 221 | 222 | ![k equals 1000](benches/plots/result_accesses_10000000_1000.png) 223 | 224 | Other benchmarks can be found [here](https://drive.google.com/drive/folders/1DHEaeXgZuX6AJ9eByeZ8iQVQv0ueP8XM). 225 | 226 | 227 | Real-world usage 228 | ---------------- 229 | 230 | - [ClickHouse Inc.](https://github.com/ClickHouse/ClickHouse): Fast Open-Source OLAP DBMS 231 | - [YDB](https://github.com/ydb-platform/ydb/commit/698b400d09b3c1c7aff6ebf986eb3dc3ced74a08): Open-Source Distributed SQL Database 232 | - [cpp-sort](https://github.com/Morwenn/cpp-sort): Largest Sorting algorithms & related tools for C++ 233 | 234 | If you are planning to use miniselect in your product, please work from one of 235 | our releases and if you wish, you can write the acknowledgment in this section 236 | for visibility. 237 | 238 | Contributing 239 | ------------ 240 | 241 | Patches are welcome with new algorithms! You should add the selection algorithm 242 | together with the partial sorting algorithm in [include](./include), add 243 | tests in [testing](./testing) and ideally run benchmarks to see how it performs. 244 | If you also have some data cases to test against, we would be more than happy 245 | to merge them. 246 | 247 | Motivation 248 | ---------- 249 | 250 | The author was surveying research on small 251 | $k$ 252 | in selection algorithms and was struggling to find working implementations to 253 | compare different approaches from standard library and quickselect algorithms. 254 | It turned out that the problem is much more interesting than it looks, and after 255 | consulting The Art of Computer Programming from Donald Knuth about 256 | minimum comparison sorting and selection algorithms, the author decided to look 257 | through unpopular algorithms and try them out. Not finding any satisfactory 258 | library for selection algorithms nor research corresponding to the open source codes, 259 | the author set out to write one generic library. 260 | 261 | For a big story of adventures see 262 | the author's [blog post](https://danlark.org/2020/11/11/miniselect-practical-and-generic-selection-algorithms/). 263 | 264 | License 265 | ------- 266 | 267 | The code is made available under the [Boost License 1.0](https://boost.org/LICENSE_1_0.txt). 268 | 269 | Third-Party Libraries Used and Adjusted 270 | --------------------------------------- 271 | 272 | | Library | License | 273 | |---------------------|--------------------------------------------------------------------------------------------------| 274 | | pdqsort | [MIT](https://github.com/orlp/pdqsort/blob/47a46767d76fc852284eaa083e4b7034ee6e2559/license.txt) | 275 | | MedianOfNinthers | [Boost License 1.0](https://github.com/andralex/MedianOfNinthers/blob/master/LICENSE_1_0.txt) | 276 | 277 | -------------------------------------------------------------------------------- /include/miniselect/pdqselect.h: -------------------------------------------------------------------------------- 1 | /* 2 | pdqsort.h - Pattern-defeating quicksort. 3 | 4 | Copyright (c) 2015 Orson Peters 5 | 6 | This software is provided 'as-is', without any express or implied warranty. 7 | In no event will the authors be held liable for any damages arising from the 8 | use of this software. 9 | 10 | Permission is granted to anyone to use this software for any purpose, 11 | including commercial applications, and to alter it and redistribute it 12 | freely, subject to the following restrictions: 13 | 14 | 1. The origin of this software must not be misrepresented; you must not 15 | claim that you wrote the original software. If you use this software in a 16 | product, an acknowledgment in the product documentation would be appreciated 17 | but is not required. 18 | 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | /* Copyright Danila Kutenin, 2020-. 25 | * Distributed under the Boost Software License, Version 1.0. 26 | * (See accompanying file LICENSE_1_0.txt or copy at 27 | * https://boost.org/LICENSE_1_0.txt) 28 | */ 29 | // Adjusted by Danila Kutenin to support pdqselect and pdqpartial_sort. 30 | 31 | #ifndef PDQSORT_H 32 | #define PDQSORT_H 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #if __cplusplus >= 201103L 41 | #include 42 | #include 43 | #define PDQSORT_PREFER_MOVE(x) std::move(x) 44 | #else 45 | #define PDQSORT_PREFER_MOVE(x) (x) 46 | #endif 47 | 48 | #include "private/median_common.h" 49 | 50 | namespace miniselect { 51 | namespace pdqsort_detail { 52 | 53 | template 54 | struct CompareRefType { 55 | // Pass the comparator by lvalue reference. Or in debug mode, using a 56 | // debugging wrapper that stores a reference. 57 | using type = typename std::add_lvalue_reference::type; 58 | }; 59 | 60 | enum { 61 | // Partitions below this size are sorted using insertion sort. 62 | insertion_sort_threshold = 24, 63 | 64 | // Partitions above this size use Tukey's ninther to select the pivot. 65 | ninther_threshold = 128, 66 | 67 | // When we detect an already sorted partition, attempt an insertion sort that 68 | // allows this 69 | // amount of element moves before giving up. 70 | partial_insertion_sort_limit = 8, 71 | 72 | // Must be multiple of 8 due to loop unrolling, and < 256 to fit in unsigned 73 | // char. 74 | block_size = 64, 75 | 76 | // Cacheline size, assumes power of two. 77 | cacheline_size = 64 78 | 79 | }; 80 | 81 | #if __cplusplus >= 201103L 82 | template 83 | struct is_default_compare : std::false_type {}; 84 | template 85 | struct is_default_compare> : std::true_type {}; 86 | template 87 | struct is_default_compare> : std::true_type {}; 88 | #endif 89 | 90 | // Returns floor(log2(n)), assumes n > 0. 91 | template 92 | inline int log2(T n) { 93 | int log = 0; 94 | while (n >>= 1) ++log; 95 | return log; 96 | } 97 | 98 | // Sorts [begin, end) using insertion sort with the given comparison function. 99 | template 100 | inline void insertion_sort(Iter begin, Iter end, Compare& comp) { 101 | typedef typename std::iterator_traits::value_type T; 102 | if (begin == end) return; 103 | 104 | for (Iter cur = begin + 1; cur != end; ++cur) { 105 | Iter sift = cur; 106 | Iter sift_1 = cur - 1; 107 | 108 | // Compare first so we can avoid 2 moves for an element already positioned 109 | // correctly. 110 | if (comp(*sift, *sift_1)) { 111 | T tmp = PDQSORT_PREFER_MOVE(*sift); 112 | 113 | do { 114 | *sift-- = PDQSORT_PREFER_MOVE(*sift_1); 115 | } while (sift != begin && comp(tmp, *--sift_1)); 116 | 117 | *sift = PDQSORT_PREFER_MOVE(tmp); 118 | } 119 | } 120 | } 121 | 122 | // Sorts [begin, end) using insertion sort with the given comparison function. 123 | // Assumes 124 | // *(begin - 1) is an element smaller than or equal to any element in [begin, 125 | // end). 126 | template 127 | inline void unguarded_insertion_sort(Iter begin, Iter end, Compare& comp) { 128 | typedef typename std::iterator_traits::value_type T; 129 | if (begin == end) return; 130 | 131 | for (Iter cur = begin + 1; cur != end; ++cur) { 132 | Iter sift = cur; 133 | Iter sift_1 = cur - 1; 134 | 135 | // Compare first so we can avoid 2 moves for an element already positioned 136 | // correctly. 137 | if (comp(*sift, *sift_1)) { 138 | T tmp = PDQSORT_PREFER_MOVE(*sift); 139 | 140 | do { 141 | *sift-- = PDQSORT_PREFER_MOVE(*sift_1); 142 | } while (comp(tmp, *--sift_1)); 143 | 144 | *sift = PDQSORT_PREFER_MOVE(tmp); 145 | } 146 | } 147 | } 148 | 149 | // Attempts to use insertion sort on [begin, end). Will return false if more 150 | // than partial_insertion_sort_limit elements were moved, and abort sorting. 151 | // Otherwise it will successfully sort and return true. 152 | template 153 | inline bool partial_insertion_sort(Iter begin, Iter end, Compare& comp) { 154 | typedef typename std::iterator_traits::value_type T; 155 | if (begin == end) return true; 156 | 157 | std::size_t limit = 0; 158 | for (Iter cur = begin + 1; cur != end; ++cur) { 159 | Iter sift = cur; 160 | Iter sift_1 = cur - 1; 161 | 162 | // Compare first so we can avoid 2 moves for an element already positioned 163 | // correctly. 164 | if (comp(*sift, *sift_1)) { 165 | T tmp = PDQSORT_PREFER_MOVE(*sift); 166 | 167 | do { 168 | *sift-- = PDQSORT_PREFER_MOVE(*sift_1); 169 | } while (sift != begin && comp(tmp, *--sift_1)); 170 | 171 | *sift = PDQSORT_PREFER_MOVE(tmp); 172 | limit += cur - sift; 173 | } 174 | 175 | if (limit > partial_insertion_sort_limit) return false; 176 | } 177 | 178 | return true; 179 | } 180 | 181 | template 182 | inline void sort2(Iter a, Iter b, Compare& comp) { 183 | if (comp(*b, *a)) std::iter_swap(a, b); 184 | } 185 | 186 | // Sorts the elements *a, *b and *c using comparison function comp. 187 | template 188 | inline void sort3(Iter a, Iter b, Iter c, Compare& comp) { 189 | sort2(a, b, comp); 190 | sort2(b, c, comp); 191 | sort2(a, b, comp); 192 | } 193 | 194 | template 195 | inline T* align_cacheline(T* p) { 196 | #if defined(UINTPTR_MAX) && __cplusplus >= 201103L 197 | std::uintptr_t ip = reinterpret_cast(p); 198 | #else 199 | std::size_t ip = reinterpret_cast(p); 200 | #endif 201 | ip = (ip + cacheline_size - 1) & -cacheline_size; 202 | return reinterpret_cast(ip); 203 | } 204 | 205 | template 206 | inline void swap_offsets(Iter first, Iter last, unsigned char* offsets_l, 207 | unsigned char* offsets_r, int num, bool use_swaps) { 208 | typedef typename std::iterator_traits::value_type T; 209 | if (use_swaps) { 210 | // This case is needed for the descending distribution, where we need 211 | // to have proper swapping for pdqsort to remain O(n). 212 | for (int i = 0; i < num; ++i) { 213 | std::iter_swap(first + offsets_l[i], last - offsets_r[i]); 214 | } 215 | } else if (num > 0) { 216 | Iter l = first + offsets_l[0]; 217 | Iter r = last - offsets_r[0]; 218 | T tmp(PDQSORT_PREFER_MOVE(*l)); 219 | *l = PDQSORT_PREFER_MOVE(*r); 220 | for (int i = 1; i < num; ++i) { 221 | l = first + offsets_l[i]; 222 | *r = PDQSORT_PREFER_MOVE(*l); 223 | r = last - offsets_r[i]; 224 | *l = PDQSORT_PREFER_MOVE(*r); 225 | } 226 | *r = PDQSORT_PREFER_MOVE(tmp); 227 | } 228 | } 229 | 230 | // Partitions [begin, end) around pivot *begin using comparison function comp. 231 | // Elements equal to the pivot are put in the right-hand partition. Returns the 232 | // position of the pivot after partitioning and whether the passed sequence 233 | // already was correctly partitioned. Assumes the pivot is a median of at least 234 | // 3 elements and that [begin, end) is at least insertion_sort_threshold long. 235 | // Uses branchless partitioning. 236 | template 237 | inline std::pair partition_right_branchless(Iter begin, Iter end, 238 | Compare& comp) { 239 | typedef typename std::iterator_traits::value_type T; 240 | 241 | // Move pivot into local for speed. 242 | T pivot(PDQSORT_PREFER_MOVE(*begin)); 243 | Iter first = begin; 244 | Iter last = end; 245 | 246 | // Find the first element greater than or equal than the pivot (the median of 247 | // 3 guarantees this exists). 248 | while (comp(*++first, pivot)) 249 | ; 250 | 251 | // Find the first element strictly smaller than the pivot. We have to guard 252 | // this search if there was no element before *first. 253 | if (first - 1 == begin) 254 | while (first < last && !comp(*--last, pivot)) 255 | ; 256 | else 257 | while (!comp(*--last, pivot)) 258 | ; 259 | 260 | // If the first pair of elements that should be swapped to partition are the 261 | // same element, the passed in sequence already was correctly partitioned. 262 | bool already_partitioned = first >= last; 263 | if (!already_partitioned) { 264 | std::iter_swap(first, last); 265 | ++first; 266 | } 267 | 268 | // The following branchless partitioning is derived from "BlockQuicksort: How 269 | // Branch Mispredictions don’t affect Quicksort" by Stefan Edelkamp and Armin 270 | // Weiss. 271 | unsigned char offsets_l_storage[block_size + cacheline_size]; 272 | unsigned char offsets_r_storage[block_size + cacheline_size]; 273 | unsigned char* offsets_l = align_cacheline(offsets_l_storage); 274 | unsigned char* offsets_r = align_cacheline(offsets_r_storage); 275 | int num_l, num_r, start_l, start_r; 276 | num_l = num_r = start_l = start_r = 0; 277 | 278 | while (last - first > 2 * block_size) { 279 | // Fill up offset blocks with elements that are on the wrong side. 280 | if (num_l == 0) { 281 | start_l = 0; 282 | Iter it = first; 283 | for (unsigned char i = 0; i < block_size;) { 284 | offsets_l[num_l] = i++; 285 | num_l += !comp(*it, pivot); 286 | ++it; 287 | offsets_l[num_l] = i++; 288 | num_l += !comp(*it, pivot); 289 | ++it; 290 | offsets_l[num_l] = i++; 291 | num_l += !comp(*it, pivot); 292 | ++it; 293 | offsets_l[num_l] = i++; 294 | num_l += !comp(*it, pivot); 295 | ++it; 296 | offsets_l[num_l] = i++; 297 | num_l += !comp(*it, pivot); 298 | ++it; 299 | offsets_l[num_l] = i++; 300 | num_l += !comp(*it, pivot); 301 | ++it; 302 | offsets_l[num_l] = i++; 303 | num_l += !comp(*it, pivot); 304 | ++it; 305 | offsets_l[num_l] = i++; 306 | num_l += !comp(*it, pivot); 307 | ++it; 308 | } 309 | } 310 | if (num_r == 0) { 311 | start_r = 0; 312 | Iter it = last; 313 | for (unsigned char i = 0; i < block_size;) { 314 | offsets_r[num_r] = ++i; 315 | num_r += comp(*--it, pivot); 316 | offsets_r[num_r] = ++i; 317 | num_r += comp(*--it, pivot); 318 | offsets_r[num_r] = ++i; 319 | num_r += comp(*--it, pivot); 320 | offsets_r[num_r] = ++i; 321 | num_r += comp(*--it, pivot); 322 | offsets_r[num_r] = ++i; 323 | num_r += comp(*--it, pivot); 324 | offsets_r[num_r] = ++i; 325 | num_r += comp(*--it, pivot); 326 | offsets_r[num_r] = ++i; 327 | num_r += comp(*--it, pivot); 328 | offsets_r[num_r] = ++i; 329 | num_r += comp(*--it, pivot); 330 | } 331 | } 332 | 333 | // Swap elements and update block sizes and first/last boundaries. 334 | int num = std::min(num_l, num_r); 335 | swap_offsets(first, last, offsets_l + start_l, offsets_r + start_r, num, 336 | num_l == num_r); 337 | num_l -= num; 338 | num_r -= num; 339 | start_l += num; 340 | start_r += num; 341 | if (num_l == 0) first += block_size; 342 | if (num_r == 0) last -= block_size; 343 | } 344 | 345 | int l_size = 0, r_size = 0; 346 | int unknown_left = (int)(last - first) - ((num_r || num_l) ? block_size : 0); 347 | if (num_r) { 348 | // Handle leftover block by assigning the unknown elements to the other 349 | // block. 350 | l_size = unknown_left; 351 | r_size = block_size; 352 | } else if (num_l) { 353 | l_size = block_size; 354 | r_size = unknown_left; 355 | } else { 356 | // No leftover block, split the unknown elements in two blocks. 357 | l_size = unknown_left / 2; 358 | r_size = unknown_left - l_size; 359 | } 360 | 361 | // Fill offset buffers if needed. 362 | if (unknown_left && !num_l) { 363 | start_l = 0; 364 | Iter it = first; 365 | for (unsigned char i = 0; i < l_size;) { 366 | offsets_l[num_l] = i++; 367 | num_l += !comp(*it, pivot); 368 | ++it; 369 | } 370 | } 371 | if (unknown_left && !num_r) { 372 | start_r = 0; 373 | Iter it = last; 374 | for (unsigned char i = 0; i < r_size;) { 375 | offsets_r[num_r] = ++i; 376 | num_r += comp(*--it, pivot); 377 | } 378 | } 379 | 380 | int num = std::min(num_l, num_r); 381 | swap_offsets(first, last, offsets_l + start_l, offsets_r + start_r, num, 382 | num_l == num_r); 383 | num_l -= num; 384 | num_r -= num; 385 | start_l += num; 386 | start_r += num; 387 | if (num_l == 0) first += l_size; 388 | if (num_r == 0) last -= r_size; 389 | 390 | // We have now fully identified [first, last)'s proper position. Swap the last 391 | // elements. 392 | if (num_l) { 393 | offsets_l += start_l; 394 | while (num_l--) std::iter_swap(first + offsets_l[num_l], --last); 395 | first = last; 396 | } 397 | if (num_r) { 398 | offsets_r += start_r; 399 | while (num_r--) std::iter_swap(last - offsets_r[num_r], first), ++first; 400 | last = first; 401 | } 402 | 403 | // Put the pivot in the right place. 404 | Iter pivot_pos = first - 1; 405 | *begin = PDQSORT_PREFER_MOVE(*pivot_pos); 406 | *pivot_pos = PDQSORT_PREFER_MOVE(pivot); 407 | 408 | return std::make_pair(pivot_pos, already_partitioned); 409 | } 410 | 411 | // Partitions [begin, end) around pivot *begin using comparison function comp. 412 | // Elements equal to the pivot are put in the right-hand partition. Returns the 413 | // position of the pivot after partitioning and whether the passed sequence 414 | // already was correctly partitioned. Assumes the pivot is a median of at least 415 | // 3 elements and that [begin, end) is at least insertion_sort_threshold long. 416 | template 417 | inline std::pair partition_right(Iter begin, Iter end, 418 | Compare& comp) { 419 | typedef typename std::iterator_traits::value_type T; 420 | 421 | // Move pivot into local for speed. 422 | T pivot(PDQSORT_PREFER_MOVE(*begin)); 423 | 424 | Iter first = begin; 425 | Iter last = end; 426 | 427 | // Find the first element greater than or equal than the pivot (the median of 428 | // 3 guarantees this exists). 429 | while (comp(*++first, pivot)) 430 | ; 431 | 432 | // Find the first element strictly smaller than the pivot. We have to guard 433 | // this search if there was no element before *first. 434 | if (first - 1 == begin) 435 | while (first < last && !comp(*--last, pivot)) 436 | ; 437 | else 438 | while (!comp(*--last, pivot)) 439 | ; 440 | 441 | // If the first pair of elements that should be swapped to partition are the 442 | // same element, the passed in sequence already was correctly partitioned. 443 | bool already_partitioned = first >= last; 444 | 445 | // Keep swapping pairs of elements that are on the wrong side of the pivot. 446 | // Previously swapped pairs guard the searches, which is why the first 447 | // iteration is special-cased above. 448 | while (first < last) { 449 | std::iter_swap(first, last); 450 | while (comp(*++first, pivot)) 451 | ; 452 | while (!comp(*--last, pivot)) 453 | ; 454 | } 455 | 456 | // Put the pivot in the right place. 457 | Iter pivot_pos = first - 1; 458 | *begin = PDQSORT_PREFER_MOVE(*pivot_pos); 459 | *pivot_pos = PDQSORT_PREFER_MOVE(pivot); 460 | 461 | return std::make_pair(pivot_pos, already_partitioned); 462 | } 463 | 464 | // Similar function to the one above, except elements equal to the pivot are put 465 | // to the left of the pivot and it doesn't check or return if the passed 466 | // sequence already was partitioned. Since this is rarely used (the many equal 467 | // case), and in that case pdqsort already has O(n) performance, no block 468 | // quicksort is applied here for simplicity. 469 | template 470 | inline Iter partition_left(Iter begin, Iter end, Compare& comp) { 471 | typedef typename std::iterator_traits::value_type T; 472 | 473 | T pivot(PDQSORT_PREFER_MOVE(*begin)); 474 | Iter first = begin; 475 | Iter last = end; 476 | 477 | while (comp(pivot, *--last)) 478 | ; 479 | 480 | if (last + 1 == end) 481 | while (first < last && !comp(pivot, *++first)) 482 | ; 483 | else 484 | while (!comp(pivot, *++first)) 485 | ; 486 | 487 | while (first < last) { 488 | std::iter_swap(first, last); 489 | while (comp(pivot, *--last)) 490 | ; 491 | while (!comp(pivot, *++first)) 492 | ; 493 | } 494 | 495 | Iter pivot_pos = last; 496 | *begin = PDQSORT_PREFER_MOVE(*pivot_pos); 497 | *pivot_pos = PDQSORT_PREFER_MOVE(pivot); 498 | 499 | return pivot_pos; 500 | } 501 | 502 | template 503 | inline void pdqsort_loop(Iter begin, Iter end, Compare& comp, int bad_allowed, 504 | bool leftmost = true) { 505 | typedef typename std::iterator_traits::difference_type diff_t; 506 | 507 | // Use a while loop for tail recursion elimination. 508 | while (true) { 509 | diff_t size = end - begin; 510 | 511 | // Insertion sort is faster for small arrays. 512 | if (size < insertion_sort_threshold) { 513 | if (leftmost) 514 | insertion_sort(begin, end, comp); 515 | else 516 | unguarded_insertion_sort(begin, end, comp); 517 | return; 518 | } 519 | 520 | // Choose pivot as median of 3 or pseudomedian of 9. 521 | diff_t s2 = size / 2; 522 | if (size > ninther_threshold) { 523 | sort3(begin, begin + s2, end - 1, comp); 524 | sort3(begin + 1, begin + (s2 - 1), end - 2, comp); 525 | sort3(begin + 2, begin + (s2 + 1), end - 3, comp); 526 | sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp); 527 | std::iter_swap(begin, begin + s2); 528 | } else 529 | sort3(begin + s2, begin, end - 1, comp); 530 | 531 | // If *(begin - 1) is the end of the right partition of a previous partition 532 | // operation there is no element in [begin, end) that is smaller than 533 | // *(begin - 1). Then if our pivot compares equal to *(begin - 1) we change 534 | // strategy, putting equal elements in the left partition, greater elements 535 | // in the right partition. We do not have to recurse on the left partition, 536 | // since it's sorted (all equal). 537 | if (!leftmost && !comp(*(begin - 1), *begin)) { 538 | begin = partition_left(begin, end, comp) + 1; 539 | continue; 540 | } 541 | 542 | // Partition and get results. 543 | std::pair part_result = 544 | Branchless ? partition_right_branchless(begin, end, comp) 545 | : partition_right(begin, end, comp); 546 | Iter pivot_pos = part_result.first; 547 | bool already_partitioned = part_result.second; 548 | 549 | // Check for a highly unbalanced partition. 550 | diff_t l_size = pivot_pos - begin; 551 | diff_t r_size = end - (pivot_pos + 1); 552 | bool highly_unbalanced = l_size < size / 8 || r_size < size / 8; 553 | 554 | // If we got a highly unbalanced partition we shuffle elements to break many 555 | // patterns. 556 | if (highly_unbalanced) { 557 | // If we had too many bad partitions, switch to heapsort to guarantee O(n 558 | // log n). 559 | if (--bad_allowed == 0) { 560 | std::make_heap(begin, end, comp); 561 | std::sort_heap(begin, end, comp); 562 | return; 563 | } 564 | 565 | if (l_size >= insertion_sort_threshold) { 566 | std::iter_swap(begin, begin + l_size / 4); 567 | std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4); 568 | 569 | if (l_size > ninther_threshold) { 570 | std::iter_swap(begin + 1, begin + (l_size / 4 + 1)); 571 | std::iter_swap(begin + 2, begin + (l_size / 4 + 2)); 572 | std::iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1)); 573 | std::iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2)); 574 | } 575 | } 576 | 577 | if (r_size >= insertion_sort_threshold) { 578 | std::iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4)); 579 | std::iter_swap(end - 1, end - r_size / 4); 580 | 581 | if (r_size > ninther_threshold) { 582 | std::iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4)); 583 | std::iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4)); 584 | std::iter_swap(end - 2, end - (1 + r_size / 4)); 585 | std::iter_swap(end - 3, end - (2 + r_size / 4)); 586 | } 587 | } 588 | } else { 589 | // If we were decently balanced and we tried to sort an already 590 | // partitioned sequence try to use insertion sort. 591 | if (already_partitioned && 592 | partial_insertion_sort(begin, pivot_pos, comp) && 593 | partial_insertion_sort(pivot_pos + 1, end, comp)) 594 | return; 595 | } 596 | 597 | // Sort the left partition first using recursion and do tail recursion 598 | // elimination for the right-hand partition. 599 | pdqsort_loop(begin, pivot_pos, comp, bad_allowed, 600 | leftmost); 601 | begin = pivot_pos + 1; 602 | leftmost = false; 603 | } 604 | } 605 | 606 | template 607 | inline void pdqpartial_sort_loop(Iter begin, Iter mid, Iter end, Compare& comp, 608 | int bad_allowed, bool leftmost = true) { 609 | typedef typename std::iterator_traits::difference_type diff_t; 610 | 611 | // Use a while loop for tail recursion elimination. 612 | while (true) { 613 | diff_t size = end - begin; 614 | 615 | // Insertion sort is faster for small arrays. 616 | if (size < insertion_sort_threshold) { 617 | if (leftmost) 618 | insertion_sort(begin, end, comp); 619 | else 620 | unguarded_insertion_sort(begin, end, comp); 621 | return; 622 | } 623 | 624 | // Choose pivot as median of 3 or pseudomedian of 9. 625 | diff_t s2 = size / 2; 626 | if (size > ninther_threshold) { 627 | sort3(begin, begin + s2, end - 1, comp); 628 | sort3(begin + 1, begin + (s2 - 1), end - 2, comp); 629 | sort3(begin + 2, begin + (s2 + 1), end - 3, comp); 630 | sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp); 631 | std::iter_swap(begin, begin + s2); 632 | } else 633 | sort3(begin + s2, begin, end - 1, comp); 634 | 635 | // If *(begin - 1) is the end of the right partition of a previous partition 636 | // operation there is no element in [begin, end) that is smaller than 637 | // *(begin - 1). Then if our pivot compares equal to *(begin - 1) we change 638 | // strategy, putting equal elements in the left partition, greater elements 639 | // in the right partition. We do not have to recurse on the left partition, 640 | // since it's sorted (all equal). 641 | if (!leftmost && !comp(*(begin - 1), *begin)) { 642 | begin = partition_left(begin, end, comp) + 1; 643 | continue; 644 | } 645 | 646 | // Partition and get results. 647 | std::pair part_result = 648 | Branchless ? partition_right_branchless(begin, end, comp) 649 | : partition_right(begin, end, comp); 650 | Iter pivot_pos = part_result.first; 651 | bool already_partitioned = part_result.second; 652 | 653 | // Check for a highly unbalanced partition. 654 | diff_t l_size = pivot_pos - begin; 655 | diff_t r_size = end - (pivot_pos + 1); 656 | bool highly_unbalanced = l_size < size / 8 || r_size < size / 8; 657 | 658 | // If we got a highly unbalanced partition we shuffle elements to break many 659 | // patterns. 660 | if (highly_unbalanced) { 661 | // If we had too many bad partitions, switch to heapsort to guarantee O(n 662 | // log n). 663 | if (--bad_allowed == 0) { 664 | std::make_heap(begin, end, comp); 665 | std::sort_heap(begin, end, comp); 666 | return; 667 | } 668 | 669 | if (l_size >= insertion_sort_threshold) { 670 | std::iter_swap(begin, begin + l_size / 4); 671 | std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4); 672 | 673 | if (l_size > ninther_threshold) { 674 | std::iter_swap(begin + 1, begin + (l_size / 4 + 1)); 675 | std::iter_swap(begin + 2, begin + (l_size / 4 + 2)); 676 | std::iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1)); 677 | std::iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2)); 678 | } 679 | } 680 | 681 | if (r_size >= insertion_sort_threshold) { 682 | std::iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4)); 683 | std::iter_swap(end - 1, end - r_size / 4); 684 | 685 | if (r_size > ninther_threshold) { 686 | std::iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4)); 687 | std::iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4)); 688 | std::iter_swap(end - 2, end - (1 + r_size / 4)); 689 | std::iter_swap(end - 3, end - (2 + r_size / 4)); 690 | } 691 | } 692 | } else { 693 | // If we were decently balanced and we tried to sort an already 694 | // partitioned sequence try to use insertion sort. 695 | if (already_partitioned && 696 | partial_insertion_sort(begin, pivot_pos, comp) && 697 | partial_insertion_sort(pivot_pos + 1, end, comp)) 698 | return; 699 | } 700 | 701 | // Sort the left partition first using recursion and do tail recursion 702 | // elimination for the right-hand partition. 703 | if (pivot_pos < mid) { 704 | pdqsort_loop(begin, pivot_pos, comp, 705 | bad_allowed, leftmost); 706 | begin = pivot_pos + 1; 707 | leftmost = false; 708 | } else { 709 | end = pivot_pos; 710 | } 711 | } 712 | } 713 | 714 | template 715 | inline void pdqselect_loop(Iter begin, Iter mid, Iter end, Compare& comp, 716 | int bad_allowed, bool leftmost = true) { 717 | typedef typename std::iterator_traits::difference_type diff_t; 718 | 719 | // Use a while loop for tail recursion elimination. 720 | while (true) { 721 | diff_t size = end - begin; 722 | 723 | // Insertion sort is faster for small arrays. 724 | if (size < insertion_sort_threshold) { 725 | if (leftmost) 726 | insertion_sort(begin, end, comp); 727 | else 728 | unguarded_insertion_sort(begin, end, comp); 729 | return; 730 | } 731 | 732 | // Choose pivot as median of 3 or pseudomedian of 9. 733 | diff_t s2 = size / 2; 734 | if (size > ninther_threshold) { 735 | sort3(begin, begin + s2, end - 1, comp); 736 | sort3(begin + 1, begin + (s2 - 1), end - 2, comp); 737 | sort3(begin + 2, begin + (s2 + 1), end - 3, comp); 738 | sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp); 739 | std::iter_swap(begin, begin + s2); 740 | } else 741 | sort3(begin + s2, begin, end - 1, comp); 742 | 743 | // If *(begin - 1) is the end of the right partition of a previous partition 744 | // operation there is no element in [begin, end) that is smaller than 745 | // *(begin - 1). Then if our pivot compares equal to *(begin - 1) we change 746 | // strategy, putting equal elements in the left partition, greater elements 747 | // in the right partition. We do not have to recurse on the left partition, 748 | // since it's sorted (all equal). 749 | if (!leftmost && !comp(*(begin - 1), *begin)) { 750 | begin = partition_left(begin, end, comp) + 1; 751 | continue; 752 | } 753 | 754 | // Partition and get results. 755 | std::pair part_result = 756 | Branchless ? partition_right_branchless(begin, end, comp) 757 | : partition_right(begin, end, comp); 758 | Iter pivot_pos = part_result.first; 759 | bool already_partitioned = part_result.second; 760 | 761 | // Check for a highly unbalanced partition. 762 | diff_t l_size = pivot_pos - begin; 763 | diff_t r_size = end - (pivot_pos + 1); 764 | bool highly_unbalanced = l_size < size / 8 || r_size < size / 8; 765 | 766 | // If we got a highly unbalanced partition we shuffle elements to break many 767 | // patterns. 768 | if (highly_unbalanced) { 769 | // If we had too many bad partitions, switch to heapsort to guarantee O(n 770 | // log n). 771 | if (--bad_allowed == 0) { 772 | std::nth_element(begin, mid, end, comp); 773 | return; 774 | } 775 | 776 | if (l_size >= insertion_sort_threshold) { 777 | std::iter_swap(begin, begin + l_size / 4); 778 | std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4); 779 | 780 | if (l_size > ninther_threshold) { 781 | std::iter_swap(begin + 1, begin + (l_size / 4 + 1)); 782 | std::iter_swap(begin + 2, begin + (l_size / 4 + 2)); 783 | std::iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1)); 784 | std::iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2)); 785 | } 786 | } 787 | 788 | if (r_size >= insertion_sort_threshold) { 789 | std::iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4)); 790 | std::iter_swap(end - 1, end - r_size / 4); 791 | 792 | if (r_size > ninther_threshold) { 793 | std::iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4)); 794 | std::iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4)); 795 | std::iter_swap(end - 2, end - (1 + r_size / 4)); 796 | std::iter_swap(end - 3, end - (2 + r_size / 4)); 797 | } 798 | } 799 | } else { 800 | // If we were decently balanced and we tried to sort an already 801 | // partitioned sequence try to use insertion sort. 802 | if (already_partitioned && 803 | partial_insertion_sort(begin, pivot_pos, comp) && 804 | partial_insertion_sort(pivot_pos + 1, end, comp)) 805 | return; 806 | } 807 | // Sort the left partition first using recursion and do tail recursion 808 | // elimination for the right-hand partition. 809 | if (pivot_pos < mid) { 810 | begin = pivot_pos + 1; 811 | leftmost = false; 812 | } else { 813 | end = pivot_pos; 814 | } 815 | } 816 | } 817 | } // namespace pdqsort_detail 818 | 819 | template 820 | inline void pdqsort(Iter begin, Iter end, Compare comp) { 821 | if (begin == end) return; 822 | 823 | #if __cplusplus >= 201103L 824 | pdqsort_detail::pdqsort_loop< 825 | Iter, Compare, 826 | pdqsort_detail::is_default_compare< 827 | typename std::decay::type>::value && 828 | std::is_arithmetic< 829 | typename std::iterator_traits::value_type>::value>( 830 | begin, end, comp, pdqsort_detail::log2(end - begin)); 831 | #else 832 | pdqsort_detail::pdqsort_loop( 833 | begin, end, comp, pdqsort_detail::log2(end - begin)); 834 | #endif 835 | } 836 | 837 | template 838 | inline void pdqsort(Iter begin, Iter end) { 839 | typedef typename std::iterator_traits::value_type T; 840 | pdqsort(begin, end, std::less()); 841 | } 842 | 843 | template 844 | inline void pdqsort_branchless(Iter begin, Iter end, Compare comp) { 845 | if (begin == end) return; 846 | pdqsort_detail::pdqsort_loop( 847 | begin, end, comp, pdqsort_detail::log2(end - begin)); 848 | } 849 | 850 | template 851 | inline void pdqsort_branchless(Iter begin, Iter end) { 852 | typedef typename std::iterator_traits::value_type T; 853 | pdqsort_branchless(begin, end, std::less()); 854 | } 855 | 856 | template 857 | inline void pdqpartial_sort(Iter begin, Iter mid, Iter end, Compare comp) { 858 | if (begin == end) return; 859 | 860 | #if __cplusplus >= 201103L 861 | pdqsort_detail::pdqpartial_sort_loop< 862 | Iter, Compare, 863 | pdqsort_detail::is_default_compare< 864 | typename std::decay::type>::value && 865 | std::is_arithmetic< 866 | typename std::iterator_traits::value_type>::value>( 867 | begin, mid, end, comp, pdqsort_detail::log2(end - begin)); 868 | #else 869 | pdqsort_detail::pdqpartial_sort_loop( 870 | begin, end, comp, pdqsort_detail::log2(end - begin)); 871 | #endif 872 | } 873 | 874 | template 875 | inline void pdqpartial_sort(Iter begin, Iter mid, Iter end) { 876 | typedef typename std::iterator_traits::value_type T; 877 | pdqpartial_sort(begin, mid, end, std::less()); 878 | } 879 | 880 | template 881 | inline void pdqpartial_sort_branchless(Iter begin, Iter mid, Iter end, 882 | Compare comp) { 883 | if (begin == end) return; 884 | pdqsort_detail::pdqpartial_sort_loop( 885 | begin, mid, end, comp, pdqsort_detail::log2(end - begin)); 886 | } 887 | 888 | template 889 | inline void pdqpartial_sort_branchless(Iter begin, Iter mid, Iter end) { 890 | typedef typename std::iterator_traits::value_type T; 891 | pdqpartial_sort_branchless(begin, mid, end, std::less()); 892 | } 893 | 894 | template 895 | inline void pdqselect(Iter begin, Iter mid, Iter end, Compare comp) { 896 | if (mid == end) return; 897 | using CompType = typename median_common_detail::CompareRefType::type; 898 | 899 | #if __cplusplus >= 201103L 900 | pdqsort_detail::pdqselect_loop< 901 | Iter, CompType, 902 | pdqsort_detail::is_default_compare< 903 | typename std::decay::type>::value && 904 | std::is_arithmetic< 905 | typename std::iterator_traits::value_type>::value>( 906 | begin, mid, end, comp, pdqsort_detail::log2(end - begin)); 907 | #else 908 | pdqsort_detail::pdqselect_loop( 909 | begin, end, comp, pdqsort_detail::log2(end - begin)); 910 | #endif 911 | } 912 | 913 | template 914 | inline void pdqselect(Iter begin, Iter mid, Iter end) { 915 | typedef typename std::iterator_traits::value_type T; 916 | pdqselect(begin, mid, end, std::less()); 917 | } 918 | 919 | template 920 | inline void pdqselect_branchless(Iter begin, Iter mid, Iter end, Compare comp) { 921 | if (mid == end) return; 922 | using CompType = typename median_common_detail::CompareRefType::type; 923 | pdqsort_detail::pdqselect_loop( 924 | begin, mid, end, comp, pdqsort_detail::log2(end - begin)); 925 | } 926 | 927 | template 928 | inline void pdqselect_branchless(Iter begin, Iter mid, Iter end) { 929 | typedef typename std::iterator_traits::value_type T; 930 | pdqselect_branchless(begin, mid, end, std::less()); 931 | } 932 | 933 | #undef PDQSORT_PREFER_MOVE 934 | 935 | #endif 936 | 937 | } // namespace miniselect 938 | --------------------------------------------------------------------------------