From ac202299abda259361736fa3d1606247aee371d4 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 27 Sep 2020 22:40:22 +0200 Subject: [PATCH 01/15] Update metadata to 1.8.0 I was in a bit of a rush because I wanted to finally release that version 1.8.0, to the point that I forgot to update the metadata... --- CMakeLists.txt | 2 +- README.md | 16 ++++++++-------- conanfile.py | 2 +- include/cpp-sort/version.h | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 681d1f40..779d75d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 1.7.0 LANGUAGES CXX) +project(cpp-sort VERSION 1.8.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index e873c782..e047e83a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--sort%2F1.7.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases) -[![Conan Package](https://img.shields.io/badge/conan-1.7.0-blue.svg)](https://bintray.com/conan/conan-center/cpp-sort%3A_) +[![Latest Release](https://img.shields.io/badge/release-cpp--sort%2F1.8.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases) +[![Conan Package](https://img.shields.io/badge/conan-1.8.0-blue.svg)](https://bintray.com/conan/conan-center/cpp-sort%3A_) [![Build Status](https://travis-ci.org/Morwenn/cpp-sort.svg?branch=master)](https://travis-ci.org/Morwenn/cpp-sort) [![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/master/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) @@ -34,8 +34,8 @@ int main() # The main features & the extra features -**cpp-sort** actually provides a full set of sorting-related features. Here are the main -building blocks of the library: +**cpp-sort** provides a full set of sorting-related features. Here are the main building blocks +of the library: * Every sorting algorithm exists as a function object called a [sorter](https://github.com/Morwenn/cpp-sort/wiki/Sorters) * Sorters can be wrapped in [sorter adapters](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters) to augment their behaviour * The library provides a [sorter facade](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) to easily build sorters @@ -99,13 +99,13 @@ and extending **cpp-sort** in [the wiki](https://github.com/Morwenn/cpp-sort/wik The following graph has been generated with a script found in the benchmarks directory. It shows the time needed for a sorting algorithm to sort one million -shuffled `std::array` of sizes 0 to 15. It compares the sorters generally +shuffled `std::array` of sizes 0 to 32. It compares the sorters generally used to sort small arrays: -![small shuffled int arrays](https://i.imgur.com/mpV0Qur.png) +![Benchmark speed of small sorts with increasing size for std::array](https://i.imgur.com/dOa3vyl.png) -These results were generated with MinGW g++ 6.1.0 with the compiler options -`-std=c++1z -O2 -march=native`. That benchmark is just an example to make this +These results were generated with MinGW-w64 g++ 10.1 with the compiler options +`-std=c++2a -O3 -march=native`. That benchmark is merely an example to make this introduction look good. You can find more commented benchmarks in the [dedicated wiki page](https://github.com/Morwenn/cpp-sort/wiki/Benchmarks). diff --git a/conanfile.py b/conanfile.py index b1ccd0d5..8bab626f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -8,7 +8,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.7.0" + version = "1.8.0" description = "Additional sorting algorithms & related tools" topics = "conan", "cpp-sort", "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index 9652fe28..4cf835b8 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 1 -#define CPPSORT_VERSION_MINOR 7 +#define CPPSORT_VERSION_MINOR 8 #define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_ From 7755039d570865b45d8d5697832c1039c4e1c3de Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 28 Sep 2020 21:00:39 +0200 Subject: [PATCH 02/15] Add a checklist of actions to perform for every new release [ci skip] --- tools/release-checklist.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tools/release-checklist.md diff --git a/tools/release-checklist.md b/tools/release-checklist.md new file mode 100644 index 00000000..62f6aefb --- /dev/null +++ b/tools/release-checklist.md @@ -0,0 +1,32 @@ +List of actions to perform when releasing a new cpp-sort version. + +### During the development + +The following things need be done prior to the release if they didn't happen during the main +development phase: +- [ ] Update the documentation. +- [ ] Update the releases notes. + +### Before the release + +- [ ] Check that all issues linked to the milestone are closed. +- [ ] Check `NOTICE.txt` and `README.md` conformance for stolen code. +- [ ] Make sure that tests pass and examples build. +- [ ] Regenerate the benchmarks. +- [ ] Replace occurences of the version number: + - [ ] CMakeLists.txt + - [ ] conanfile.py + - [ ] README.md + - [ ] version.h +- [ ] Find a name for the new version. +- [ ] Open a merge request, let the CI do its job. +- [ ] Merge `develop` into `master`. +- [ ] Publish the release, don't forget to target `master`. + +### After the release + +- [ ] Add the Zenodo badge to the release notes. +- [ ] Close the new version's milestone. +- [ ] Push the new version to Bincrafters. +- [ ] Add the new version to Conan Center Index. +- [ ] Brag about it where relevant. From c2fa21df17cc2b85d5b1037b423d6167420c9fde Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 29 Sep 2020 12:02:55 +0200 Subject: [PATCH 03/15] Add actions related to the 2.0.0-develop branch to release-checklist.md [ci skip] --- tools/release-checklist.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/release-checklist.md b/tools/release-checklist.md index 62f6aefb..6dde3c19 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -6,6 +6,7 @@ The following things need be done prior to the release if they didn't happen dur development phase: - [ ] Update the documentation. - [ ] Update the releases notes. +- [ ] Keep track of the things that will be changed in 2.0.0. ### Before the release @@ -30,3 +31,7 @@ development phase: - [ ] Push the new version to Bincrafters. - [ ] Add the new version to Conan Center Index. - [ ] Brag about it where relevant. + +- [ ] Merge master into 2.0.0-develop branch. +- [ ] Fix merge issues. +- [ ] Improve as needed with C++17 and C++20 features. From ab7326535844e558a0936e4e01eaafba6fb8578d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 30 Sep 2020 22:10:36 +0200 Subject: [PATCH 04/15] Fix a few #include --- include/cpp-sort/adapters/indirect_adapter.h | 1 + include/cpp-sort/detail/recmerge_bidirectional.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/adapters/indirect_adapter.h b/include/cpp-sort/adapters/indirect_adapter.h index 0cd9f680..cf55866d 100644 --- a/include/cpp-sort/adapters/indirect_adapter.h +++ b/include/cpp-sort/adapters/indirect_adapter.h @@ -26,6 +26,7 @@ #include "../detail/iterator_traits.h" #include "../detail/memory.h" #include "../detail/scope_exit.h" +#include "../detail/type_traits.h" namespace cppsort { diff --git a/include/cpp-sort/detail/recmerge_bidirectional.h b/include/cpp-sort/detail/recmerge_bidirectional.h index 0e4c7354..5f9a6c76 100644 --- a/include/cpp-sort/detail/recmerge_bidirectional.h +++ b/include/cpp-sort/detail/recmerge_bidirectional.h @@ -26,7 +26,6 @@ #include "iterator_traits.h" #include "lower_bound.h" #include "rotate.h" -#include "type_traits.h" #include "upper_bound.h" namespace cppsort From e46fcd8e7356eade48781263f63688e13aae36c0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 4 Oct 2020 15:04:56 +0200 Subject: [PATCH 05/15] Fix the iterator category of quick_merge_sorter As mentioned in the documentation, quick_merge_sorter can handle forward iterators, not only random-access iterators. --- include/cpp-sort/sorters/quick_merge_sorter.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/cpp-sort/sorters/quick_merge_sorter.h b/include/cpp-sort/sorters/quick_merge_sorter.h index aba8b444..4544a494 100644 --- a/include/cpp-sort/sorters/quick_merge_sorter.h +++ b/include/cpp-sort/sorters/quick_merge_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_QUICK_MERGE_SORTER_H_ @@ -82,7 +82,7 @@ namespace cppsort //////////////////////////////////////////////////////////// // Sorter traits - using iterator_category = std::random_access_iterator_tag; + using iterator_category = std::forward_iterator_tag; using is_always_stable = std::false_type; }; } From 32b874fb025a6e8f86f1db568cd38682cb9f62d9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 4 Oct 2020 16:18:29 +0200 Subject: [PATCH 06/15] Fix big in median-of-9 pivot selection Median-of-9 pivot selection takes 9 samples in a collection, takes the medians of the 3 sets of 3 samples, then takes the median of those three medians. Our implementation incorrectly picked two correct medians and a value that wasn't a median for the last triplet of samples. This is now fixed with this commit. How it affects the speed of the quicksort family of algorithms is unclear. It might make a difference for specific patterns. --- include/cpp-sort/detail/introselect.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/introselect.h b/include/cpp-sort/detail/introselect.h index cb6da92d..c28ee5d4 100644 --- a/include/cpp-sort/detail/introselect.h +++ b/include/cpp-sort/detail/introselect.h @@ -250,7 +250,7 @@ namespace detail iter_sort3(first, it1, it2, compare, projection); iter_sort3(it3, middle, it4, compare, projection); iter_sort3(it5, it6, last_1, compare, projection); - auto median_it = iter_sort3(it1, middle, it4, std::move(compare), std::move(projection)); + auto median_it = iter_sort3(it1, middle, it6, std::move(compare), std::move(projection)); return std::make_pair(median_it, last_1); } else { auto last_1 = last_it(first, last, size); From 6ac592cb838cd911237b20e3a72909e2f64d172d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 12 Oct 2020 18:19:51 +0200 Subject: [PATCH 07/15] Apply upstream fix to timsort minRunLength() (fixes #172) --- include/cpp-sort/detail/timsort.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/timsort.h b/include/cpp-sort/detail/timsort.h index 28449671..453fb438 100644 --- a/include/cpp-sort/detail/timsort.h +++ b/include/cpp-sort/detail/timsort.h @@ -189,7 +189,7 @@ namespace detail CPPSORT_ASSERT(n >= 0); difference_type r = 0; - while (n >= min_merge) { + while (n >= 2 * min_merge) { r |= (n & 1); n >>= 1; } From d3952d401a87febc3eb19d389f154c40a7330063 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 17 Oct 2020 20:21:03 +0200 Subject: [PATCH 08/15] Bidirectional vergesort: early return for already sorted data --- include/cpp-sort/detail/vergesort.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index 6dc63c8a..faaa3e0f 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -54,6 +54,7 @@ namespace detail // Pair of iterators to iterate through the collection auto next = is_sorted_until(first, last, compare, projection); + if (next == last) return; auto current = std::prev(next); while (true) { From 81f684f720c1298c3510413ec5df1ae409efe37f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 30 Sep 2020 22:04:04 +0200 Subject: [PATCH 09/15] Add minimal standard version check to conanfile.py [ci skip] --- conanfile.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index 8bab626f..4686d4c7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -26,6 +26,10 @@ class CppSortConan(ConanFile): no_copy_source = True settings = "os", "compiler", "build_type", "arch" + def configure(self): + if self.settings.get_safe("compiler.cppstd"): + tools.check_min_cppstd(self, 14) + def package(self): # Install with CMake cmake = CMake(self) @@ -35,8 +39,8 @@ def package(self): cmake.patch_config_paths() # Copy license files - self.copy("LICENSE.txt", dst="licenses") - self.copy("NOTICE.txt", dst="licenses") + for file in ["LICENSE.txt", "NOTICE.txt"]: + self.copy(file, dst="licenses") def package_id(self): self.info.header_only() From 4f7ef14cd11881003d198ab56afdeb92e7896f8a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 22 Oct 2020 23:06:40 +0200 Subject: [PATCH 10/15] Fix downloaded Catch2 version to v2.13.2 --- testsuite/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 3c2d52d6..eda5d3e5 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -19,7 +19,7 @@ else() message(STATUS "Catch2 not found") download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG master + GIT_TAG v2.13.2 UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) From c36a87cd317b1795e74f2c246da7e3a1924ced99 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 22 Oct 2020 23:41:34 +0200 Subject: [PATCH 11/15] Fix formatting in the release checklist [ci skip] --- tools/release-checklist.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/release-checklist.md b/tools/release-checklist.md index 6dde3c19..f05eaafd 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -31,7 +31,6 @@ development phase: - [ ] Push the new version to Bincrafters. - [ ] Add the new version to Conan Center Index. - [ ] Brag about it where relevant. - - [ ] Merge master into 2.0.0-develop branch. - [ ] Fix merge issues. - [ ] Improve as needed with C++17 and C++20 features. From 21a2b92529f6030afd3c3108d2b09f91e6dab8a3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 24 Oct 2020 13:27:36 +0200 Subject: [PATCH 12/15] Fixed cmplexity of quick_sort and quick_merge_sort in documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I had not realize that std::partition performed O(n log n) swaps for forward iterators, which brings the complexity of quick_sort and quick_merge_sort down to O(n log² n) for those iterators. [ci skip] --- docs/Sorters.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/Sorters.md b/docs/Sorters.md index 893a38c2..8b4302b9 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -228,11 +228,12 @@ Implements a flavour of [QuickMergeSort](https://arxiv.org/abs/1307.3033). | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | | n | n log n | n log n | log n | No | Random-access | -| n | n log n | n log n | log² n | No | Forward | +| n | n log n | n log n | log² n | No | Bidirectional | +| n | n log² n | n log² n | log² n | No | Forward | QuickMergeSort is an algorithm that performs a quicksort-like partition and tries to use mergesort on the bigger partition, using the smaller one as a swap buffer used for the merge operation when possible. The flavour of QuickMergeSort used by `quick_merge_sorter` actually uses an equivalent of [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element) to partition the collection in 1/3-2/3 parts in order to maximize the size of the partition (2 thirds of the space) that can be merge-sorted using the other partition as a swap buffer. -The log n memory is due to top-down mergesort stack recursion in the random-access version, while the memory of the forward version use is dominated by the mutually recursive [introselect](https://en.wikipedia.org/wiki/Introselect) algorithm which is used to implement an `nth_element` equivalent for forward iterators. +The change in time complexity for forward iterators is due to the partitioning algorithm being O(n log n) instead of O(n). The log n memory is due to top-down mergesort stack recursion in the random-access version, while the memory of the forward version use is dominated by the mutually recursive [introselect](https://en.wikipedia.org/wiki/Introselect) algorithm which is used to implement an `nth_element` equivalent for forward iterators. This sorter can't throw `std::bad_alloc`. @@ -248,13 +249,14 @@ Implements a [quicksort](https://en.wikipedia.org/wiki/Quicksort). | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | -| n | n log n | n log n | log² n | No | Forward | +| n | n log n | n log n | log² n | No | Bidirectional | +| n | n log² n | n log² n | log² n | No | Forward | Despite the name, this sorter actually implements some flavour of introsort: if quicksort performs more than 2*log(n) steps, it falls back to a [median-of-medians](https://en.wikipedia.org/wiki/Median_of_medians) pivot selection instead of the usual median-of-9 one. The median-of-medians selection being mutually recursive with an introselect algorithm explains the use of log²n stack memory. This sorter can't throw `std::bad_alloc`. -*Changed in version 1.2.0:* `quick_sorter` used to run in O(n²), but a fallback to median-of-medians pivot selection was introduced to make it run in O(n log n), the tradeoff being the log²n space used by stack recursion (as opposed to the previous log n one). +*Changed in version 1.2.0:* `quick_sorter` used to run in O(n²), but a fallback to median-of-medians pivot selection was introduced to make it run in O(n log n) or O(n log² n) depending of the iterator category, the tradeoff being the log² n space used by stack recursion (as opposed to the previous log n one). ### `selection_sorter` From b9fc62d034ffc60eb1020bf7247bf15c63bea82d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 24 Oct 2020 13:31:08 +0200 Subject: [PATCH 13/15] Update the Original Research page of the wiki - Add a small section about quick_merge_sort - Fix the section about the best-known comparison sort complexities - Include vergesort actual complexity and improved complexity for bidirectional iterators - Mention that vergesort can be made stable if needed [ci skip] --- docs/Original-research.md | 64 ++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/docs/Original-research.md b/docs/Original-research.md index 5ca381af..ea367f8a 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -1,31 +1,31 @@ While most of **cpp-sort**'s algorithms are actually taken from other open-source projects, and/or naive implementations of well-known sorting algorithms, it also contains a bit of original research. Most of the things described here are far from being ground-breaking discoveries, but even slight improvements to known algorithms deserve to at least be documented so that they can be reused, so... here we go. -You can find some experiments and interesting pieces of code [in my Gist](https://gist.github.com/Morwenn) too - generally related to this library -, even though it doesn't always come with proper explanation or build instructions. +You can find some experiments and interesting pieces of code [in my Gist][morwenn-gist] too - generally related to this library -, even though it doesn't always come with proper explanation or build instructions. -### Of iterator categories & algorithms complexity +### Of iterator categories & algorithm complexities -One of the main observations which naturally occured as long as I was putting together this library was about the best complexity tradeoffs between running and memory depending on the iterator categories of the different sorting algorithms (only taking comparison sorts into account): -* Algorithms that work on random-access iterators can run in O(n log n) time with O(1) extra memory, and can even be stable with such guarantees (block sort being the best example) -* Unstable algorithms that work on bidirectional iterators can run in O(n log n) time with O(1) extra memory: QuickMergeSort can be implemented with a bottom-up mergesort and a raw median-of-medians algorithms (instead of the introselect mutual recursion) -* Stable algorithms that work on bidirectional iterators can run in O(n log n) time with O(n) extra memory (mergesort), or in O(n log²n) with O(1) extra memory (mergesort with in-place merge) -* Algorithms that work on forward iterators have the same known bounds than the ones that work with bidirectional iterators +One of the main observations which naturally occured as long as I was putting together this library was about the best complexity tradeoffs between time and memory depending on the iterator categories of the different sorting algorithms (only taking comparison sorts into account): +* Algorithms that work on random-access iterators can run in O(n log n) time with O(1) extra memory, and can even be stable with such guarantees (block sort being the best example). +* Unstable algorithms that work on bidirectional iterators can run in O(n log n) time with O(1) extra memory: QuickMergeSort [can be implemented][https://github.com/Morwenn/quick_merge_sort] with a bottom-up mergesort and a raw median-of-medians algorithms (instead of the introselect mutual recursion). +* Stable algorithms that work on bidirectional iterators can run in O(n log n) time with O(n) extra memory (mergesort), or in O(n log² n) time with O(1) extra memory (mergesort with in-place merge). +* Stable algorithms that work on forward iterators can get down to the same time and memory complexities than the the ones working on bidirectional iterators: mergesort works just as well. +* Unstable algorithms that work on forward iterators can run in O(n log² n) time and O(1) space, QuickMergeSort being once again the prime example of such an algorithm. * Taking advantage of the list data structure allows for sorting algorithms running in O(n log n) time with O(1) extra memory, be it for stable sorting (mergesort) or unstable sorting (melsort), but those techniques can't be generically retrofitted to generically work with bidirectional iterators Now, those observations/claims are there to be challenged: if you know of any stable comparison sorting algorithm that runs on bidirectional iterators in O(n log n) with O(1) extra memory, don't hesitate to be the ones challenging those claims :) ### Vergesort -The vergesort is a new sorting algorithm which combines merge operations on almost sorted data, and falls back to a [pattern-defeating quicksort][1] when the data is not sorted enough. Just like TimSort, it achieves linear time on some patterns, generally for almost sorted data, and should never be worse than O(n log n) in the worst case. This last statement has not been proven, but the many benchmarks show that it is only slightly slower than a pattern-defeating quicksort for shuffled data, which is its worst case, so... +The vergesort is a new sorting algorithm which combines merge operations on almost sorted data, and falls back to another sorting algorithm when the data is not sorted enough. Somehow it can be considered as a cheap preprocessing step to take advantage of almost-sorted data when possible. Just like TimSort, it achieves linear time on some patterns, generally for almost sorted data, and should never be worse than O(n log n) in the worst case. This last statement has not been proven, but the many benchmarks show that it is only slightly slower than a pattern-defeating quicksort for shuffled data, which is its worst case, so... Best Average Worst Memory Stable n n log n n log n n No -While vergesort has been designed to work with random-access iterators, there also exists a version that works with bidirectional iterators. The algorithm is slightly different and a bit slower. Basically, it falls back on a regular quicksort instead of a pattern-defeating quicksort since the latter has to fall back to a heapsort, which only works with random-access iterators. The complexity for the bidirectional iterators version is as follows: +While vergesort has been designed to work with random-access iterators, there also exists a version that works with bidirectional iterators. The algorithm is slightly different and a bit slower because it can't as easily jump through the collection. The complexity of that bidirectional iterators algorithm can get down to the same time as the random-access one when `quick_merge_sort` is used as the fallback algorithm. - Best Average Worst Memory Stable - n n log n n² n No +The actual cmplexity of the algorithm is O(n log n log log n), but interestingly it is only reached in one of its paths that is among the fastest in practice. -This sorting algorithm has been split as a separate project. You can read more about in the [dedicated repository][2]. +This sorting algorithm has been split as a separate project. You can read more about in the [dedicated repository][vergesort]. ### Double insertion sort @@ -49,7 +49,7 @@ Anyway, the nice property of this half-cleaning is that it can be performed on n ### Sorting networks for 23 and 24 inputs -While trying to reimplement size-optimal sorting networks as described by [*Finding Better Sorting Networks*][3], I ended up implementing a sorting network for 24 inputs whose size was equivalent to that of the one described in the paper (123 compare-exchange units). However, it seems that this sorting network does not use an odd-even merge network but another merge network, obtained by the method described in the previous section. From this network, it was also trivial to generate the corresponding network for 23 inputs, whose size also corresponds to the best-known one for that many inputs (118). The depth of both networks is 18, which is probably one more than the depth of the sorting networks using the odd-even merge (if I'm not mistaken, the depth of the odd-even merge is one less). Here are the two networks and the corresponding 0-based sequences of indices: +While trying to reimplement size-optimal sorting networks as described by [*Finding Better Sorting Networks*][better-sorting-networks], I ended up implementing a sorting network for 24 inputs whose size was equivalent to that of the one described in the paper (123 compare-exchange units). However, it seems that this sorting network does not use an odd-even merge network but another merge network, obtained by the method described in the previous section. From this network, it was also trivial to generate the corresponding network for 23 inputs, whose size also corresponds to the best-known one for that many inputs (118). The depth of both networks is 18, which is probably one more than the depth of the sorting networks using the odd-even merge (if I'm not mistaken, the depth of the odd-even merge is one less). Here are the two networks and the corresponding 0-based sequences of indices: ![Sorting network 23](https://github.com/Morwenn/cpp-sort/wiki/images/sorting-network-23.png) @@ -97,7 +97,7 @@ While trying to reimplement size-optimal sorting networks as described by [*Find The following sorting network for 29 inputs has 165 compare-exchange-units, which is one less that the most size-optimal 29-input sorting networks that I could find in the litterature. Here is how I generated it: first it sorts the first 16 inputs and the last 13 inputs independently. Then it merges the two sorted subarrays using a size 32 Batcher odd-even merge network (the version that does not need the inputs to be interleaved), where all compare-exchange units working on indexes greater than 28 have been dropped. Dropping comparators in such a way is ok: consider that the values at the indexes [29, 32) are greater than every other value in the array to sort, and it will become intuitive that dropping them generates a correct merging network of a smaller size. -That said, even though I have been unable to find a 29-input sorting network with as few compare-exchange units as 165 in the litterature, I can't claim that I found the technique used to generate it: the unclassified 1971 paper [*A Generalization of the Divide-Sort-Merge Strategy for Sorting Networks*][8] by David C. Van Voorhis already describes the as follows: +That said, even though I have been unable to find a 29-input sorting network with as few compare-exchange units as 165 in the litterature, I can't claim that I found the technique used to generate it: the unclassified 1971 paper [*A Generalization of the Divide-Sort-Merge Strategy for Sorting Networks*][divide-sort-merge-strategy] by David C. Van Voorhis already describes the as follows: > The improved 26-,27-,28-, and 34-sorters all use two initial sort units, one of them the particularly efficient 16-sorter designed by M. W. Green, followed by Batcher's [2,2] merge network. @@ -167,27 +167,35 @@ The paper does not mention a better result than 166 CEUs for the 29-input sortin ### Mountain sort -The mountain sort is a new indirect sorting algorithm designed to perform a minimal number of move operations on the elements of the collection to sort. It derives from [cycle sort][4] and [Exact-Sort][5] but is still slightly different: the goal of cycle sort is to perform a minimal number of writes to the original array, and while Exact-Sort indeed performs the same number of moves and writes to the original array than cycle sort, the description says that it's the best algorithm to sort fridges by price, so its goal would actually be closer to that of mountain sort. However, both have a rather similar implementation: find cycles of values to rotate, swap the first value into a temporary variable, find where it goes, swap the contents of the temporary variable with the value in the location, find where the new value goes, etc... Exact-Sort is a bit more optimized but the lookup still makes it a O(n²) algorithm. Mountain sort chooses to consume more memory and to store the iterators and to sort them beforehand so that the move part can be done with only one write to a temporary variable per cycle. I don't think that any sorting algorithm can perform less moves than mountain sort. Its basic implementation relies on `std::sort` to sort the iterators, more or less leading to the following complexity: +The mountain sort is a new indirect sorting algorithm designed to perform a minimal number of move operations on the elements of the collection to sort. It derives from [cycle sort][cycle-sort] and [Exact-Sort][exact-sort] but is still slightly different: the goal of cycle sort is to perform a minimal number of writes to the original array, and while Exact-Sort indeed performs the same number of moves and writes to the original array than cycle sort, the description says that it's the best algorithm to sort fridges by price, so its goal would actually be closer to that of mountain sort. However, both have a rather similar implementation: find cycles of values to rotate, swap the first value into a temporary variable, find where it goes, swap the contents of the temporary variable with the value in the location, find where the new value goes, etc... Exact-Sort is a bit more optimized but the lookup still makes it a O(n²) algorithm. Mountain sort chooses to consume more memory and to store the iterators and to sort them beforehand so that the move part can be done with only one write to a temporary variable per cycle. I don't think that any sorting algorithm can perform less moves than mountain sort. Its basic implementation relies on `std::sort` to sort the iterators, more or less leading to the following complexity: Best Average Worst Memory Stable n log n n log n n log n n No -However, **cpp-sort** implements it [as a sorter adapter][6] so you can actually choose the sorting algorithm that will be used to sort the iterators, making it possible to use a stable sorting algorithm instead. Note that the memory footprint of the algorithm is negligible when the size of the elements to sort is big since only iterators and booleans are stored. It makes mountain sort the ideal sorting algorithm when the objects to sort are huge and the comparisons are cheap (remember: you may want to sort fridges by price, or mountains by height). You can find a standalone implementation of mountain sort in [the dedicated repository][7]. +However, **cpp-sort** implements it [as a sorter adapter][indirect_adapter] so you can actually choose the sorting algorithm that will be used to sort the iterators, making it possible to use a stable sorting algorithm instead. Note that the memory footprint of the algorithm is negligible when the size of the elements to sort is big since only iterators and booleans are stored. It makes mountain sort the ideal sorting algorithm when the objects to sort are huge and the comparisons are cheap (remember: you may want to sort fridges by price, or mountains by height). You can find a standalone implementation of mountain sort in [the dedicated repository][mountain_sort]. ### Improvements to poplar sort & poplar heap -**cpp-sort** ships an implementation of a poplar sort which roughly follows the algorithm as described by Coenraad Bron and Wim H. Hesselink in their paper *Smoothsort revisited*. However, I later managed to improve the space complexity of the algorithm by making the underlying poplar heap data structure a proper implicit data structure. You can read more about this experiment in the [dedicated project page][9]. The algorithm used by `poplar_heap` does not used the O(1) space complexity algorithm because it was measured to be slower on average that the old algorithm with O(log n) space complexity. +**cpp-sort** ships an implementation of a poplar sort which roughly follows the algorithm as described by Coenraad Bron and Wim H. Hesselink in their paper *Smoothsort revisited*. However, I later managed to improve the space complexity of the algorithm by making the underlying poplar heap data structure a proper implicit data structure. You can read more about this experiment in the [dedicated project page][poplar-heap]. The algorithm used by `poplar_heap` does not used the O(1) space complexity algorithm because it was measured to be slower on average that the old algorithm with O(log n) space complexity. + +I later realized that the data structure I called *poplar heap* had already been described by Nicholas J. A. Harvey and Kevin Zatloukal under the name [*post-order heap*][post-order-heap]. I however believe that some of the heap construction methods described in my repository are novel. + +### quick_merge_sort + +I borrowed some ideas from Edelkamp and Weiß QuickXsort and QuickMergesort algorithms, and came up with a version of QuickMergesort that uses a median-of-median selection algorithm to split the collection into two partitions respectively two thirds and one thirds of the original one. This allows to use an internal mergesort on the left partition while maximizing the number of elements merged in one pass. Then the algorithm is recursively called on the smaller partition, leading to an algorithm that can run in O(n log n) time and O(1) space on bidirectional iterators. -I later realized that the data structure I called *poplar heap* had already been described by Nicholas J. A. Harvey and Kevin Zatloukal under the name [*post-order heap*][10]. I however believe that some of the heap construction methods described in my repository are novel. +Somehow Edelkamp and Weiß eventually [published a paper][quick-merge-sort-arxiv] afew years later decribing the same flavour of QuickMergesort with properly computed algorithmic complexities. I have a [standalone implementation][quick-merge-sort] of `quick_merge_sort` in another repository, albeit currently lacking a proper explanation of how it works. It has the time and space complexity mentioned earlier, as opposed to the **cpp-sort** version of the algorithm where I chose to have theoretically worse algorithms from a complexity point of view, but that are nonetheless generally faster in practice. - [1]: https://github.com/orlp/pdqsort - [2]: https://github.com/Morwenn/vergesort - [3]: https://etd.ohiolink.edu/!etd.send_file?accession=kent1239814529 - [4]: https://en.wikipedia.org/wiki/Cycle_sort - [5]: http://www.geocities.ws/p356spt/ - [6]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#indirect_adapter - [7]: https://github.com/Morwenn/mountain-sort - [8]: http://www.dtic.mil/dtic/tr/fulltext/u2/737270.pdf - [9]: https://github.com/Morwenn/poplar-heap - [10]: https://people.csail.mit.edu/nickh/Publications/PostOrderHeap/FUN04-PostOrderHeap.pdf \ No newline at end of file + [better-sorting-networks]: https://etd.ohiolink.edu/!etd.send_file?accession=kent1239814529 + [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort + [divide-sort-merge-strategy]: http://www.dtic.mil/dtic/tr/fulltext/u2/737270.pdf + [exact-sort]: http://www.geocities.ws/p356spt/ + [indirect_adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#indirect_adapter + [morwenn-gist]: https://gist.github.com/Morwenn + [mountain_sort]: https://github.com/Morwenn/mountain-sort + [poplar-heap]: https://github.com/Morwenn/poplar-heap + [post-order-heap]: https://people.csail.mit.edu/nickh/Publications/PostOrderHeap/FUN04-PostOrderHeap.pdf + [quick-merge-sort]: https://github.com/Morwenn/quick_merge_sort + [quick-merge-sort-arxiv]: https://arxiv.org/pdf/1804.10062.pdf + [vergesort]: https://github.com/Morwenn/vergesort From 1d2b992e93647e47cfd024d48288840c09361520 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 25 Oct 2020 12:23:39 +0100 Subject: [PATCH 14/15] Change the number of allocated poplars in poplar_sort The new bound for the std::vector::reserve() in poplar_sort comes from the analysis performed by Harvey & Zatloukal in their work about the post-order heap. This should ensure that the poplars vector only ever performs a single memory allocation. --- include/cpp-sort/detail/poplar_sort.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/poplar_sort.h b/include/cpp-sort/detail/poplar_sort.h index 41ee57df..2c8d7e00 100644 --- a/include/cpp-sort/detail/poplar_sort.h +++ b/include/cpp-sort/detail/poplar_sort.h @@ -133,7 +133,9 @@ namespace detail if (size < 2) return; std::vector> poplars; - poplars.reserve(log2(size)); + // Harvey & Zatloukal, The Post-Order Heap: + // [...] the number of trees, k, is at most floor(lg(n + 1)) + 1 + poplars.reserve(log2(size + 1) + 1); // // Size of the biggest poplar in the array, which always is a number From 5f9b5b8fe1365d237f305cec665a4a0814e531a5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 25 Oct 2020 13:34:52 +0100 Subject: [PATCH 15/15] Preparing release 1.8.1 --- CMakeLists.txt | 2 +- README.md | 4 ++-- conanfile.py | 2 +- docs/Original-research.md | 2 +- include/cpp-sort/version.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 779d75d5..5e233b99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 1.8.0 LANGUAGES CXX) +project(cpp-sort VERSION 1.8.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index e047e83a..153ce318 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--sort%2F1.8.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases) -[![Conan Package](https://img.shields.io/badge/conan-1.8.0-blue.svg)](https://bintray.com/conan/conan-center/cpp-sort%3A_) +[![Latest Release](https://img.shields.io/badge/release-cpp--sort%2F1.8.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.8.1) +[![Conan Package](https://img.shields.io/badge/conan-1.8.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.8.1) [![Build Status](https://travis-ci.org/Morwenn/cpp-sort.svg?branch=master)](https://travis-ci.org/Morwenn/cpp-sort) [![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/master/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) diff --git a/conanfile.py b/conanfile.py index 4686d4c7..97e52c35 100644 --- a/conanfile.py +++ b/conanfile.py @@ -8,7 +8,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.8.0" + version = "1.8.1" description = "Additional sorting algorithms & related tools" topics = "conan", "cpp-sort", "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" diff --git a/docs/Original-research.md b/docs/Original-research.md index ea367f8a..ace9ab7a 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -23,7 +23,7 @@ The vergesort is a new sorting algorithm which combines merge operations on almo While vergesort has been designed to work with random-access iterators, there also exists a version that works with bidirectional iterators. The algorithm is slightly different and a bit slower because it can't as easily jump through the collection. The complexity of that bidirectional iterators algorithm can get down to the same time as the random-access one when `quick_merge_sort` is used as the fallback algorithm. -The actual cmplexity of the algorithm is O(n log n log log n), but interestingly it is only reached in one of its paths that is among the fastest in practice. +The actual cmplexity of the algorithm is O(n log n log log n), but interestingly it is only reached in one of its paths that is among the fastest in practice. It is also possible to make vergesort stable but tweaking its reverse runs detection algorithm and making it fallback to a stable sorting algorithm. This sorting algorithm has been split as a separate project. You can read more about in the [dedicated repository][vergesort]. diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index 4cf835b8..5536b524 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -9,6 +9,6 @@ #define CPPSORT_VERSION_MAJOR 1 #define CPPSORT_VERSION_MINOR 8 -#define CPPSORT_VERSION_PATCH 0 +#define CPPSORT_VERSION_PATCH 1 #endif // CPPSORT_VERSION_H_