From c99a56d6c956baa93780015bfe1f28f31f3acf45 Mon Sep 17 00:00:00 2001 From: "Fuji, Goro (gfx)" Date: Sun, 20 Apr 2014 09:57:10 +0900 Subject: [PATCH 001/137] add .travis.yml --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ba150ef --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +# https://github.com/embarkmobile/android-maven-example +language: cpp +compiler: + - clang + - gcc +script: + - make test From 0bc67a8741a42a60c0aa78ec745b2f8d0f574362 Mon Sep 17 00:00:00 2001 From: "Fuji, Goro (gfx)" Date: Sun, 20 Apr 2014 10:05:24 +0900 Subject: [PATCH 002/137] install libboost-dev --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ba150ef..9a9e75c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,7 @@ language: cpp compiler: - clang - gcc +before_install: + - sudo apt-get install boost libboost-dev script: - make test From 35604c3280ce1ae16c5b3baf7959047834c711a8 Mon Sep 17 00:00:00 2001 From: "Fuji, Goro (gfx)" Date: Sun, 20 Apr 2014 10:07:54 +0900 Subject: [PATCH 003/137] fix travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9a9e75c..f67ee5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,6 @@ compiler: - clang - gcc before_install: - - sudo apt-get install boost libboost-dev + - sudo apt-get install libboost-dev script: - make test From ee3dd0e40ecad43600bc51bd34463efd988909e9 Mon Sep 17 00:00:00 2001 From: "Fuji, Goro (gfx)" Date: Sun, 20 Apr 2014 10:11:18 +0900 Subject: [PATCH 004/137] fix linker flags --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7e9ecad..102eaeb 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMPILE := $(CXX) -I. -Wall -Wextra -g $(CXXFLAGS) OPTIMIZE := -DNDEBUG -O2 -LIB_BOOST_TEST := -lboost_unit_test_framework-mt +LIB_BOOST_TEST := -lboost_unit_test_framework all: @echo This library is a C++ header file only. From cb096d4ca109360a0ececc3b3e80bc9baeaef46e Mon Sep 17 00:00:00 2001 From: "Fuji, Goro (gfx)" Date: Sun, 20 Apr 2014 10:14:53 +0900 Subject: [PATCH 005/137] travis --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f67ee5b..bd3dc18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,8 @@ language: cpp compiler: - clang - gcc -before_install: - - sudo apt-get install libboost-dev +install: + - sudo apt-get -qq update + - sudo apt-get -qq install libboost-all-dev script: - make test From 664ea8660c75fed54f4c8b7bbaac565a98a3c344 Mon Sep 17 00:00:00 2001 From: "Fuji, Goro (gfx)" Date: Sun, 20 Apr 2014 10:19:49 +0900 Subject: [PATCH 006/137] no gcc --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bd3dc18..0bc83fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,8 @@ language: cpp compiler: - clang - - gcc install: - sudo apt-get -qq update - - sudo apt-get -qq install libboost-all-dev + - sudo apt-get -qq install time libboost-all-dev script: - make test From a13c890a4d5ea9fd0fa81b76b8394f74e0ad52c0 Mon Sep 17 00:00:00 2001 From: "Fuji, Goro (gfx)" Date: Sun, 20 Apr 2014 10:22:29 +0900 Subject: [PATCH 007/137] add travis status to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b25dd03..633a796 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -TimSort +TimSort [![Build Status](https://travis-ci.org/gfx/cpp-TimSort.svg?branch=master)](https://travis-ci.org/gfx/cpp-TimSort) ================== A C++ implementation of TimSort, O(n log n) in worst case and stable sort algorithm, ported from Python's and OpenJDK's. From fd3222da5db5b68b49069f203edd6c7da586bd0b Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sun, 22 Mar 2015 08:58:20 +0900 Subject: [PATCH 008/137] run all tests by `make test` --- Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 102eaeb..d172d99 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,9 @@ all: .bin: mkdir -p .bin -test: test/test.cpp timsort.hpp .bin +test: test-without-optimization test-with-optimization test-with-std-move + +test-without-optimization: test/test.cpp timsort.hpp .bin $(COMPILE) $(LIB_BOOST_TEST) $< -o .bin/$@ time ./.bin/$@ @@ -19,11 +21,11 @@ test-with-optimization: test/test.cpp timsort.hpp .bin time ./.bin/$@ test-with-std-move: test/test.cpp timsort.hpp .bin - $(COMPILE) $(LIB_BOOST_TEST) -std=c++11 -DENABLE_STD_MOVE $< -o .bin/$@ + $(COMPILE) $(OPTIMIZE) $(LIB_BOOST_TEST) -std=c++11 -DENABLE_STD_MOVE $< -o .bin/$@ time ./.bin/$@ bench: example/bench.cpp timsort.hpp .bin - $(COMPILE) $(OPTIMIZE) $< -o .bin/$@ + $(COMPILE) $(OPTIMIZE) -std=c++11 -DENABLE_STD_MOVE $< -o .bin/$@ $(CXX) -v ./.bin/$@ From 66c4c37b158b037e9dc20758346ab55794b147a2 Mon Sep 17 00:00:00 2001 From: Yimin Date: Wed, 29 Jul 2015 08:14:42 -0500 Subject: [PATCH 009/137] fix stackoverflow bug according to https://bugs.openjdk.java.net/browse/JDK-8072909 and http://envisage-project.eu/wp-content/uploads/2015/02/sorting.pdf --- timsort.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/timsort.hpp b/timsort.hpp index ac9d143..f5bba32 100644 --- a/timsort.hpp +++ b/timsort.hpp @@ -236,7 +236,8 @@ class TimSort { while( pending_.size() > 1 ) { diff_t n = pending_.size() - 2; - if(n > 0 && pending_[n - 1].len <= pending_[n].len + pending_[n + 1].len) { + if((n > 0 && pending_[n - 1].len <= pending_[n].len + pending_[n + 1].len) + || (n > 1 && pending_[n - 2].len <= pending_[n - 1].len + pending_[n].len)){ if(pending_[n - 1].len < pending_[n + 1].len) { --n; } From d723b49becfeeff1c268631b60b8fd66a665ac2c Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Mon, 5 Oct 2015 21:18:33 +0900 Subject: [PATCH 010/137] update benchmark results with Xcode 7.0.1 --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 633a796..a4668f2 100644 --- a/README.md +++ b/README.md @@ -34,42 +34,42 @@ BENCHMARK bench.cpp, invoked by `make bench`, is a simple benchmark. An example output is as follows (timing scale: sec.): - c++ -I. -Wall -Wextra -g -DNDEBUG -O2 example/bench.cpp -o .bin/bench + c++ -I. -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 -DENABLE_STD_MOVE example/bench.cpp -o .bin/bench c++ -v - Apple clang version 4.1 (tags/Apple/clang-421.11.66) (based on LLVM 3.1svn) - Target: x86_64-apple-darwin12.2.0 + Apple LLVM version 7.0.0 (clang-700.0.72) + Target: x86_64-apple-darwin14.5.0 Thread model: posix ./.bin/bench RANDOMIZED SEQUENCE [int] size 100000 - std::sort 0.667322 - std::stable_sort 0.895223 - timsort 1.274456 + std::sort 0.531996 + std::stable_sort 0.645782 + timsort 1.012254 [boost::rational] size 100000 - std::sort 4.152952 - std::stable_sort 5.136133 - timsort 5.781330 + std::sort 3.466250 + std::stable_sort 5.943234 + timsort 4.456835 REVERSED SEQUENCE [int] size 100000 - std::sort 0.087842 - std::stable_sort 0.234953 - timsort 0.017438 + std::sort 0.023546 + std::stable_sort 0.399995 + timsort 0.014056 [boost::rational] size 100000 - std::sort 2.114911 - std::stable_sort 2.247124 - timsort 0.281315 + std::sort 0.626102 + std::stable_sort 7.463993 + timsort 0.218232 SORTED SEQUENCE [int] size 100000 - std::sort 0.086000 - std::stable_sort 0.151913 - timsort 0.010536 + std::sort 0.015051 + std::stable_sort 0.074084 + timsort 0.007797 [boost::rational] size 100000 - std::sort 2.102378 - std::stable_sort 2.408591 - timsort 0.258270 + std::sort 0.371826 + std::stable_sort 1.290227 + timsort 0.216113 From 16b5c355a14f26d9ddafd187dc6faf32441fa44c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 9 Oct 2015 13:44:22 +0200 Subject: [PATCH 011/137] Make timsort work with move-only types. --- timsort.hpp | 64 +++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/timsort.hpp b/timsort.hpp index f5bba32..a28473f 100644 --- a/timsort.hpp +++ b/timsort.hpp @@ -44,8 +44,12 @@ #if ENABLE_STD_MOVE && __cplusplus >= 201103L #define GFX_TIMSORT_MOVE(x) std::move(x) +#define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::move((in1), (in2), (out)) +#define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::move_backward((in1), (in2), (out)) #else #define GFX_TIMSORT_MOVE(x) (x) +#define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::copy((in1), (in2), (out)) +#define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::copy_backward((in1), (in2), (out)) #endif namespace gfx { @@ -412,14 +416,14 @@ class TimSort { iter_t cursor2 = base2; iter_t dest = base1; - *(dest++) = *(cursor2++); + *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); if(--len2 == 0) { - std::copy(cursor1, cursor1 + len1, dest); + GFX_TIMSORT_MOVE_RANGE(cursor1, cursor1 + len1, dest); return; } if(len1 == 1) { - std::copy(cursor2, cursor2 + len2, dest); - *(dest + len2) = *cursor1; + GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + len2, dest); + *(dest + len2) = GFX_TIMSORT_MOVE(*cursor1); return; } @@ -435,7 +439,7 @@ class TimSort { assert( len1 > 1 && len2 > 0 ); if(comp_.lt(*cursor2, *cursor1)) { - *(dest++) = *(cursor2++); + *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); ++count2; count1 = 0; if(--len2 == 0) { @@ -444,7 +448,7 @@ class TimSort { } } else { - *(dest++) = *(cursor1++); + *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); ++count1; count2 = 0; if(--len1 == 1) { @@ -462,7 +466,7 @@ class TimSort { count1 = gallopRight(*cursor2, cursor1, len1, 0); if(count1 != 0) { - std::copy_backward(cursor1, cursor1 + count1, dest + count1); + GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + count1, dest + count1); dest += count1; cursor1 += count1; len1 -= count1; @@ -472,7 +476,7 @@ class TimSort { break; } } - *(dest++) = *(cursor2++); + *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); if(--len2 == 0) { break_outer = true; break; @@ -480,7 +484,7 @@ class TimSort { count2 = gallopLeft(*cursor1, cursor2, len2, 0); if(count2 != 0) { - std::copy(cursor2, cursor2 + count2, dest); + GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + count2, dest); dest += count2; cursor2 += count2; len2 -= count2; @@ -489,7 +493,7 @@ class TimSort { break; } } - *(dest++) = *(cursor1++); + *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); if(--len1 == 1) { break_outer = true; break; @@ -511,14 +515,14 @@ class TimSort { if(len1 == 1) { assert( len2 > 0 ); - std::copy(cursor2, cursor2 + len2, dest); - *(dest + len2) = *cursor1; + GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + len2, dest); + *(dest + len2) = GFX_TIMSORT_MOVE(*cursor1); } else { - assert( len1 != 0 && "Comparision function violates its general contract"); + assert( len1 != 0 && "Comparison function violates its general contract" ); assert( len2 == 0 ); assert( len1 > 1 ); - std::copy(cursor1, cursor1 + len1, dest); + GFX_TIMSORT_MOVE_RANGE(cursor1, cursor1 + len1, dest); } } @@ -531,16 +535,16 @@ class TimSort { tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); iter_t dest = base2 + (len2 - 1); - *(dest--) = *(cursor1--); + *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); if(--len1 == 0) { - std::copy(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); + GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); return; } if(len2 == 1) { dest -= len1; cursor1 -= len1; - std::copy_backward(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); - *dest = *cursor2; + GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); + *dest = GFX_TIMSORT_MOVE(*cursor2); return; } @@ -556,7 +560,7 @@ class TimSort { assert( len1 > 0 && len2 > 1 ); if(comp_.lt(*cursor2, *cursor1)) { - *(dest--) = *(cursor1--); + *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); ++count1; count2 = 0; if(--len1 == 0) { @@ -565,7 +569,7 @@ class TimSort { } } else { - *(dest--) = *(cursor2--); + *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); ++count2; count1 = 0; if(--len2 == 1) { @@ -586,14 +590,14 @@ class TimSort { dest -= count1; cursor1 -= count1; len1 -= count1; - std::copy_backward(cursor1 + 1, cursor1 + (1 + count1), dest + (1 + count1)); + GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + count1), dest + (1 + count1)); if(len1 == 0) { break_outer = true; break; } } - *(dest--) = *(cursor2--); + *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); if(--len2 == 1) { break_outer = true; break; @@ -604,13 +608,13 @@ class TimSort { dest -= count2; cursor2 -= count2; len2 -= count2; - std::copy(cursor2 + 1, cursor2 + (1 + count2), dest + 1); + GFX_TIMSORT_MOVE_RANGE(cursor2 + 1, cursor2 + (1 + count2), dest + 1); if(len2 <= 1) { break_outer = true; break; } } - *(dest--) = *(cursor1--); + *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); if(--len1 == 0) { break_outer = true; break; @@ -634,21 +638,21 @@ class TimSort { assert( len1 > 0 ); dest -= len1; cursor1 -= len1; - std::copy_backward(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); - *dest = *cursor2; + GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); + *dest = GFX_TIMSORT_MOVE(*cursor2); } else { - assert( len2 != 0 && "Comparision function violates its general contract"); + assert( len2 != 0 && "Comparison function violates its general contract" ); assert( len1 == 0 ); assert( len2 > 1 ); - std::copy(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); + GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); } } void copy_to_tmp(iter_t const begin, diff_t const len) { tmp_.clear(); tmp_.reserve(len); - std::copy(begin, begin + len, std::back_inserter(tmp_)); + GFX_TIMSORT_MOVE_RANGE(begin, begin + len, std::back_inserter(tmp_)); } // the only interface is the friend timsort() function @@ -671,5 +675,7 @@ inline void timsort(RandomAccessIterator const first, RandomAccessIterator const #undef GFX_TIMSORT_LOG #undef GFX_TIMSORT_MOVE +#undef GFX_TIMSORT_MOVE_RANGE +#undef GFX_TIMSORT_MOVE_BACKWARD #endif // GFX_TIMSORT_HPP From d937471a0058a5b914f328619c686922e0286e01 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sat, 17 Oct 2015 15:25:21 +0900 Subject: [PATCH 012/137] add tests on move only types for #9 --- test/test.cpp | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/test/test.cpp b/test/test.cpp index 4c6db3b..c5cae29 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -195,7 +195,7 @@ BOOST_AUTO_TEST_CASE( shuffle1023 ) { } BOOST_AUTO_TEST_CASE( shuffle1024 ) { - const int size = 1024; // even number of elements + const int size = 1024; // should be even number of elements std::vector a; for(int i = 0; i < size; ++i) { @@ -539,3 +539,83 @@ BOOST_AUTO_TEST_CASE( issue2_duplication ) { BOOST_CHECK_EQUAL(a[1001].first, expected[1001].first); BOOST_CHECK_EQUAL(a[1001].second, expected[1001].second); } + +#if ENABLE_STD_MOVE && __cplusplus >= 201103L +template +struct move_only +{ + // Constructors + + move_only() = delete; + move_only(const move_only&) = delete; + + move_only(const T& value): + can_read(true), + value(value) + {} + + move_only(move_only&& other): + can_read(true), + value(std::move(other.value)) + { + if (not exchange(other.can_read, false)) + { + std::cerr << "illegal read from a moved-from value\n"; + assert(false); + } + } + + // Assignment operators + + move_only& operator=(const move_only&) = delete; + + auto operator=(move_only&& other) + -> move_only& + { + if (&other != this) + { + if (not exchange(other.can_read, false)) + { + std::cerr << "illegal read from a moved-from value\n"; + assert(false); + } + can_read = true; + value = std::move(other.value); + } + return *this; + } + + // A C++11 backport of std::exchange() + + template U exchange(U& obj, U&& new_val) { + U old_val = std::move(obj); + obj = std::forward(new_val); + return old_val; + } + + // Whether the value can be read + bool can_read; + // Actual value + T value; +}; + +BOOST_AUTO_TEST_CASE( shuffle10k_for_move_only_types ) { + const int size = 1024 * 10; // should be even number of elements + + std::vector> a; + for(int i = 0; i < size; ++i) { + a.push_back((i+1) * 10); + } + + for(int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.end()); + + timsort(a.begin(), a.end(), [](const move_only& x, const move_only& y) { return x.value < y.value; }); + + for(int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL( a[i].value, (i+1) * 10 ); + } + } +} + +#endif // if std::move available From e699203b139fa6da2b3398678007dfae5d962ae3 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sat, 17 Oct 2015 15:36:34 +0900 Subject: [PATCH 013/137] mention to `-DENABLE_STD_MOVE=1` on README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4668f2..42415f2 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ TimSort [![Build Status](https://travis-ci.org/gfx/cpp-TimSort.svg?branch=master A C++ implementation of TimSort, O(n log n) in worst case and stable sort algorithm, ported from Python's and OpenJDK's. -This is a bit slower than `std::sort()` on randomized sequences, and much -faster on partially-sorted sequences. +According to benchmark, this is a bit slower than `std::sort()` on randomized sequences, but much faster on partially-sorted sequences. SYNOPSIS ================== @@ -22,6 +21,11 @@ TEST Run `make test` for testing and `make coverage` for test coverage. +COMPATIBILITY +================== + +This library is compatible with C++03, but if you give the `-DENABLE_STD_MOVE=1` flag to the compiler, you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). + SEE ALSO ================== From b54c29668fbb292c1e1e5dc9ac412b01074b4081 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sat, 17 Oct 2015 16:08:47 +0900 Subject: [PATCH 014/137] use std::string for benchmark this is because std::string supports move semantics whereas boost::rational not. --- Makefile | 2 +- README.md | 46 +++++++++++++++++++++++----------------------- example/bench.cpp | 6 +++--- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index d172d99..96d44d7 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,8 @@ test-with-std-move: test/test.cpp timsort.hpp .bin time ./.bin/$@ bench: example/bench.cpp timsort.hpp .bin - $(COMPILE) $(OPTIMIZE) -std=c++11 -DENABLE_STD_MOVE $< -o .bin/$@ $(CXX) -v + $(COMPILE) $(OPTIMIZE) -std=c++11 -DENABLE_STD_MOVE $< -o .bin/$@ ./.bin/$@ coverage: diff --git a/README.md b/README.md index 42415f2..748e425 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Run `make test` for testing and `make coverage` for test coverage. COMPATIBILITY ================== -This library is compatible with C++03, but if you give the `-DENABLE_STD_MOVE=1` flag to the compiler, you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). +This library is compatible with C++03, but if you give the `-DENABLE_STD_MOVE` flag to the compiler, you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). SEE ALSO ================== @@ -38,42 +38,42 @@ BENCHMARK bench.cpp, invoked by `make bench`, is a simple benchmark. An example output is as follows (timing scale: sec.): - c++ -I. -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 -DENABLE_STD_MOVE example/bench.cpp -o .bin/bench c++ -v Apple LLVM version 7.0.0 (clang-700.0.72) Target: x86_64-apple-darwin14.5.0 Thread model: posix + c++ -I. -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 -DENABLE_STD_MOVE example/bench.cpp -o .bin/bench ./.bin/bench RANDOMIZED SEQUENCE [int] size 100000 - std::sort 0.531996 - std::stable_sort 0.645782 - timsort 1.012254 - [boost::rational] + std::sort 0.695253 + std::stable_sort 0.868916 + timsort 1.255825 + [std::string] size 100000 - std::sort 3.466250 - std::stable_sort 5.943234 - timsort 4.456835 + std::sort 3.438217 + std::stable_sort 4.122629 + timsort 5.791845 REVERSED SEQUENCE [int] size 100000 - std::sort 0.023546 - std::stable_sort 0.399995 - timsort 0.014056 - [boost::rational] + std::sort 0.045461 + std::stable_sort 0.575431 + timsort 0.019139 + [std::string] size 100000 - std::sort 0.626102 - std::stable_sort 7.463993 - timsort 0.218232 + std::sort 0.586707 + std::stable_sort 2.715778 + timsort 0.345099 SORTED SEQUENCE [int] size 100000 - std::sort 0.015051 - std::stable_sort 0.074084 - timsort 0.007797 - [boost::rational] + std::sort 0.021876 + std::stable_sort 0.087993 + timsort 0.008042 + [std::string] size 100000 - std::sort 0.371826 - std::stable_sort 1.290227 - timsort 0.216113 + std::sort 0.402458 + std::stable_sort 2.436326 + timsort 0.298639 diff --git a/example/bench.cpp b/example/bench.cpp index 72cceea..5401cf1 100644 --- a/example/bench.cpp +++ b/example/bench.cpp @@ -23,7 +23,7 @@ static void bench(int const size, state_t const state) { std::vector a; for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + a.push_back(boost::lexical_cast((i+1) * 10)); } switch(state) { @@ -82,8 +82,8 @@ static void doit(int const n, state_t const state) { std::cerr << "[int]" << std::endl; bench(n, state); - std::cerr << "[boost::rational]" << std::endl; - bench< boost::rational >(n, state); + std::cerr << "[std::string]" << std::endl; + bench(n, state); } int main(int argc, const char *argv[]) { From d2fc1d1ca3c2e4d87e7c9a3195334fb792454a5b Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sat, 17 Oct 2015 16:48:53 +0900 Subject: [PATCH 015/137] Introduce clang-format --- .clang-format | 5 + example/bench.cpp | 23 +-- test/test.cpp | 411 ++++++++++++++++++++++------------------------ timsort.hpp | 396 +++++++++++++++++++++----------------------- 4 files changed, 400 insertions(+), 435 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8cb02a9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: LLVM +ColumnLimit: 120 +IndentWidth: 4 +AllowShortFunctionsOnASingleLine: false diff --git a/example/bench.cpp b/example/bench.cpp index 5401cf1..8dda35d 100644 --- a/example/bench.cpp +++ b/example/bench.cpp @@ -13,20 +13,17 @@ using namespace gfx; -enum state_t { - sorted, randomized, reversed -}; +enum state_t { sorted, randomized, reversed }; -template -static void bench(int const size, state_t const state) { +template static void bench(int const size, state_t const state) { std::cerr << "size\t" << size << std::endl; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back(boost::lexical_cast((i+1) * 10)); + for (int i = 0; i < size; ++i) { + a.push_back(boost::lexical_cast((i + 1) * 10)); } - switch(state) { + switch (state) { case randomized: std::random_shuffle(a.begin(), a.end()); break; @@ -45,7 +42,7 @@ static void bench(int const size, state_t const state) { std::vector b(a); boost::timer t; - for(int i = 0; i < 100; ++i) { + for (int i = 0; i < 100; ++i) { std::copy(a.begin(), a.end(), b.begin()); std::sort(b.begin(), b.end()); } @@ -57,7 +54,7 @@ static void bench(int const size, state_t const state) { std::vector b(a); boost::timer t; - for(int i = 0; i < 100; ++i) { + for (int i = 0; i < 100; ++i) { std::copy(a.begin(), a.end(), b.begin()); std::stable_sort(b.begin(), b.end()); } @@ -69,7 +66,7 @@ static void bench(int const size, state_t const state) { std::vector b(a); boost::timer t; - for(int i = 0; i < 100; ++i) { + for (int i = 0; i < 100; ++i) { std::copy(a.begin(), a.end(), b.begin()); timsort(b.begin(), b.end()); } @@ -87,9 +84,7 @@ static void doit(int const n, state_t const state) { } int main(int argc, const char *argv[]) { - const int N = argc > 1 - ? boost::lexical_cast(argv[1]) - : 100 * 1000; + const int N = argc > 1 ? boost::lexical_cast(argv[1]) : 100 * 1000; std::cerr << std::setprecision(6) << std::setiosflags(std::ios::fixed); diff --git a/test/test.cpp b/test/test.cpp index c5cae29..7869fd2 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -11,26 +11,26 @@ using namespace gfx; -BOOST_AUTO_TEST_CASE( simple0 ) { +BOOST_AUTO_TEST_CASE(simple0) { std::vector a; timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL( a.size(), 0 ); + BOOST_CHECK_EQUAL(a.size(), 0); } -BOOST_AUTO_TEST_CASE( simple1 ) { +BOOST_AUTO_TEST_CASE(simple1) { std::vector a; a.push_back(42); timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL( a.size(), 1 ); - BOOST_CHECK_EQUAL( a[0], 42 ); + BOOST_CHECK_EQUAL(a.size(), 1); + BOOST_CHECK_EQUAL(a[0], 42); } -BOOST_AUTO_TEST_CASE( simple2 ) { +BOOST_AUTO_TEST_CASE(simple2) { std::vector a; a.push_back(10); @@ -38,9 +38,9 @@ BOOST_AUTO_TEST_CASE( simple2 ) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL( a.size(), 2 ); - BOOST_CHECK_EQUAL( a[0], 10); - BOOST_CHECK_EQUAL( a[1], 20 ); + BOOST_CHECK_EQUAL(a.size(), 2); + BOOST_CHECK_EQUAL(a[0], 10); + BOOST_CHECK_EQUAL(a[1], 20); a.clear(); a.push_back(20); @@ -48,9 +48,9 @@ BOOST_AUTO_TEST_CASE( simple2 ) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL( a.size(), 2 ); - BOOST_CHECK_EQUAL( a[0], 10); - BOOST_CHECK_EQUAL( a[1], 20 ); + BOOST_CHECK_EQUAL(a.size(), 2); + BOOST_CHECK_EQUAL(a[0], 10); + BOOST_CHECK_EQUAL(a[1], 20); a.clear(); a.push_back(10); @@ -58,12 +58,12 @@ BOOST_AUTO_TEST_CASE( simple2 ) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL( a.size(), 2 ); - BOOST_CHECK_EQUAL( a[0], 10); - BOOST_CHECK_EQUAL( a[1], 10 ); + BOOST_CHECK_EQUAL(a.size(), 2); + BOOST_CHECK_EQUAL(a[0], 10); + BOOST_CHECK_EQUAL(a[1], 10); } -BOOST_AUTO_TEST_CASE( simple10 ) { +BOOST_AUTO_TEST_CASE(simple10) { std::vector a; a.push_back(60); a.push_back(50); @@ -78,265 +78,263 @@ BOOST_AUTO_TEST_CASE( simple10 ) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL( a[0], 10 ); - BOOST_CHECK_EQUAL( a[1], 10 ); - BOOST_CHECK_EQUAL( a[2], 20 ); - BOOST_CHECK_EQUAL( a[3], 30 ); - BOOST_CHECK_EQUAL( a[4], 40 ); - BOOST_CHECK_EQUAL( a[5], 50 ); - BOOST_CHECK_EQUAL( a[6], 60 ); - BOOST_CHECK_EQUAL( a[7], 70 ); - BOOST_CHECK_EQUAL( a[8], 80 ); - BOOST_CHECK_EQUAL( a[9], 90 ); + BOOST_CHECK_EQUAL(a[0], 10); + BOOST_CHECK_EQUAL(a[1], 10); + BOOST_CHECK_EQUAL(a[2], 20); + BOOST_CHECK_EQUAL(a[3], 30); + BOOST_CHECK_EQUAL(a[4], 40); + BOOST_CHECK_EQUAL(a[5], 50); + BOOST_CHECK_EQUAL(a[6], 60); + BOOST_CHECK_EQUAL(a[7], 70); + BOOST_CHECK_EQUAL(a[8], 80); + BOOST_CHECK_EQUAL(a[9], 90); std::reverse(a.begin(), a.end()); timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL( a[0], 10 ); - BOOST_CHECK_EQUAL( a[1], 10 ); - BOOST_CHECK_EQUAL( a[2], 20 ); - BOOST_CHECK_EQUAL( a[3], 30 ); - BOOST_CHECK_EQUAL( a[4], 40 ); - BOOST_CHECK_EQUAL( a[5], 50 ); - BOOST_CHECK_EQUAL( a[6], 60 ); - BOOST_CHECK_EQUAL( a[7], 70 ); - BOOST_CHECK_EQUAL( a[8], 80 ); - BOOST_CHECK_EQUAL( a[9], 90 ); + BOOST_CHECK_EQUAL(a[0], 10); + BOOST_CHECK_EQUAL(a[1], 10); + BOOST_CHECK_EQUAL(a[2], 20); + BOOST_CHECK_EQUAL(a[3], 30); + BOOST_CHECK_EQUAL(a[4], 40); + BOOST_CHECK_EQUAL(a[5], 50); + BOOST_CHECK_EQUAL(a[6], 60); + BOOST_CHECK_EQUAL(a[7], 70); + BOOST_CHECK_EQUAL(a[8], 80); + BOOST_CHECK_EQUAL(a[9], 90); } - -BOOST_AUTO_TEST_CASE( shuffle30 ) { +BOOST_AUTO_TEST_CASE(shuffle30) { const int size = 30; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end(), std::less()); BOOST_CHECK_EQUAL(a.size(), size); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } - -BOOST_AUTO_TEST_CASE( shuffle31 ) { +BOOST_AUTO_TEST_CASE(shuffle31) { const int size = 31; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end(), std::less()); BOOST_CHECK_EQUAL(a.size(), size); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } -BOOST_AUTO_TEST_CASE( shuffle32 ) { +BOOST_AUTO_TEST_CASE(shuffle32) { const int size = 32; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end(), std::less()); BOOST_CHECK_EQUAL(a.size(), size); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } -BOOST_AUTO_TEST_CASE( shuffle128 ) { +BOOST_AUTO_TEST_CASE(shuffle128) { const int size = 128; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end(), std::less()); BOOST_CHECK_EQUAL(a.size(), size); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } -BOOST_AUTO_TEST_CASE( shuffle1023 ) { +BOOST_AUTO_TEST_CASE(shuffle1023) { const int size = 1023; // odd number of elements std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } - for(int n = 0; n < 100; ++n) { + for (int n = 0; n < 100; ++n) { std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } } -BOOST_AUTO_TEST_CASE( shuffle1024 ) { +BOOST_AUTO_TEST_CASE(shuffle1024) { const int size = 1024; // should be even number of elements std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } - for(int n = 0; n < 100; ++n) { + for (int n = 0; n < 100; ++n) { std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } } -BOOST_AUTO_TEST_CASE( partial_shuffle1023 ) { +BOOST_AUTO_TEST_CASE(partial_shuffle1023) { const int size = 1023; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } // sorted-shuffled-sorted pattern - for(int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() + (size/3 * 1), a.begin() + (size/3 * 2)); + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } // shuffled-sorted-shuffled pattern - for(int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() , a.begin() + (size/3 * 1)); - std::random_shuffle(a.begin() + (size/3 * 2), a.end()); + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); + std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } } -BOOST_AUTO_TEST_CASE( partial_shuffle1024 ) { +BOOST_AUTO_TEST_CASE(partial_shuffle1024) { const int size = 1024; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } // sorted-shuffled-sorted pattern - for(int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() + (size/3 * 1), a.begin() + (size/3 * 2)); + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } // shuffled-sorted-shuffled pattern - for(int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() , a.begin() + (size/3 * 1)); - std::random_shuffle(a.begin() + (size/3 * 2), a.end()); + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); + std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } } -BOOST_AUTO_TEST_CASE( shuffle1024r ) { +BOOST_AUTO_TEST_CASE(shuffle1024r) { const int size = 1024; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } - for(int n = 0; n < 100; ++n) { + for (int n = 0; n < 100; ++n) { std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end(), std::greater()); int j = size; - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (--j+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (--j + 1) * 10); } } } -BOOST_AUTO_TEST_CASE( partial_reversed1023 ) { +BOOST_AUTO_TEST_CASE(partial_reversed1023) { const int size = 1023; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } - for(int n = 0; n < 100; ++n) { + for (int n = 0; n < 100; ++n) { std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } } -BOOST_AUTO_TEST_CASE( partial_reversed1024 ) { +BOOST_AUTO_TEST_CASE(partial_reversed1024) { const int size = 1024; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } - for(int n = 0; n < 100; ++n) { + for (int n = 0; n < 100; ++n) { std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed timsort(a.begin(), a.end(), std::less()); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } } -BOOST_AUTO_TEST_CASE( c_array ) { - int a[] = { 7, 1, 5, 3, 9 }; +BOOST_AUTO_TEST_CASE(c_array) { + int a[] = {7, 1, 5, 3, 9}; timsort(a, a + sizeof(a) / sizeof(int), std::less()); @@ -347,8 +345,8 @@ BOOST_AUTO_TEST_CASE( c_array ) { BOOST_CHECK_EQUAL(a[4], 9); } -BOOST_AUTO_TEST_CASE( string_array ) { - std::string a[] = { "7", "1", "5", "3", "9" }; +BOOST_AUTO_TEST_CASE(string_array) { + std::string a[] = {"7", "1", "5", "3", "9"}; timsort(a, a + sizeof(a) / sizeof(std::string), std::less()); @@ -359,21 +357,19 @@ BOOST_AUTO_TEST_CASE( string_array ) { BOOST_CHECK_EQUAL(a[4], "9"); } -struct NonDefaultConstructible -{ +struct NonDefaultConstructible { int i; - NonDefaultConstructible( int i_ ) - : i( i_ ) {} + NonDefaultConstructible(int i_) : i(i_) { + } - friend bool operator<( NonDefaultConstructible const& x, NonDefaultConstructible const& y ) { + friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { return x.i < y.i; } - }; -BOOST_AUTO_TEST_CASE( non_default_constructible ) { - NonDefaultConstructible a[] = { 7, 1, 5, 3, 9 }; +BOOST_AUTO_TEST_CASE(non_default_constructible) { + NonDefaultConstructible a[] = {7, 1, 5, 3, 9}; timsort(a, a + sizeof(a) / sizeof(a[0]), std::less()); @@ -384,20 +380,20 @@ BOOST_AUTO_TEST_CASE( non_default_constructible ) { BOOST_CHECK_EQUAL(a[4].i, 9); } -BOOST_AUTO_TEST_CASE( default_compare_function ) { +BOOST_AUTO_TEST_CASE(default_compare_function) { const int size = 128; std::vector a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } std::random_shuffle(a.begin(), a.end()); timsort(a.begin(), a.end()); BOOST_CHECK_EQUAL(a.size(), size); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i], (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } } @@ -407,86 +403,85 @@ bool less_in_first(pair_t x, pair_t y) { return x.first < y.first; } -BOOST_AUTO_TEST_CASE( stability ) { - std::vector< pair_t > a; +BOOST_AUTO_TEST_CASE(stability) { + std::vector a; - for(int i = 100; i >= 0; --i) { - a.push_back( std::make_pair(i, foo) ); - a.push_back( std::make_pair(i, bar) ); - a.push_back( std::make_pair(i, baz) ); + for (int i = 100; i >= 0; --i) { + a.push_back(std::make_pair(i, foo)); + a.push_back(std::make_pair(i, bar)); + a.push_back(std::make_pair(i, baz)); } timsort(a.begin(), a.end(), &less_in_first); - BOOST_CHECK_EQUAL(a[0].first, 0); + BOOST_CHECK_EQUAL(a[0].first, 0); BOOST_CHECK_EQUAL(a[0].second, foo); - BOOST_CHECK_EQUAL(a[1].first, 0); + BOOST_CHECK_EQUAL(a[1].first, 0); BOOST_CHECK_EQUAL(a[1].second, bar); - BOOST_CHECK_EQUAL(a[2].first, 0); + BOOST_CHECK_EQUAL(a[2].first, 0); BOOST_CHECK_EQUAL(a[2].second, baz); - BOOST_CHECK_EQUAL(a[3].first, 1); + BOOST_CHECK_EQUAL(a[3].first, 1); BOOST_CHECK_EQUAL(a[3].second, foo); - BOOST_CHECK_EQUAL(a[4].first, 1); + BOOST_CHECK_EQUAL(a[4].first, 1); BOOST_CHECK_EQUAL(a[4].second, bar); - BOOST_CHECK_EQUAL(a[5].first, 1); + BOOST_CHECK_EQUAL(a[5].first, 1); BOOST_CHECK_EQUAL(a[5].second, baz); - BOOST_CHECK_EQUAL(a[6].first, 2); + BOOST_CHECK_EQUAL(a[6].first, 2); BOOST_CHECK_EQUAL(a[6].second, foo); - BOOST_CHECK_EQUAL(a[7].first, 2); + BOOST_CHECK_EQUAL(a[7].first, 2); BOOST_CHECK_EQUAL(a[7].second, bar); - BOOST_CHECK_EQUAL(a[8].first, 2); + BOOST_CHECK_EQUAL(a[8].first, 2); BOOST_CHECK_EQUAL(a[8].second, baz); - BOOST_CHECK_EQUAL(a[9].first, 3); + BOOST_CHECK_EQUAL(a[9].first, 3); BOOST_CHECK_EQUAL(a[9].second, foo); - BOOST_CHECK_EQUAL(a[10].first, 3); + BOOST_CHECK_EQUAL(a[10].first, 3); BOOST_CHECK_EQUAL(a[10].second, bar); - BOOST_CHECK_EQUAL(a[11].first, 3); + BOOST_CHECK_EQUAL(a[11].first, 3); BOOST_CHECK_EQUAL(a[11].second, baz); } -bool less_in_pair(const std::pair& x, const std::pair& y) { +bool less_in_pair(const std::pair &x, const std::pair &y) { if (x.first == y.first) { return x.second < y.second; } return x.first < y.first; } -BOOST_AUTO_TEST_CASE( issue2_compare ) { +BOOST_AUTO_TEST_CASE(issue2_compare) { typedef std::pair p; - gfx::Compare< p, bool(*)(const p&, const p&) > c(&less_in_pair); + gfx::Compare c(&less_in_pair); + BOOST_CHECK_EQUAL(c.lt(std::make_pair(10, 10), std::make_pair(10, 9)), false); + BOOST_CHECK_EQUAL(c.lt(std::make_pair(10, 10), std::make_pair(10, 10)), false); + BOOST_CHECK_EQUAL(c.lt(std::make_pair(10, 10), std::make_pair(10, 11)), true); - BOOST_CHECK_EQUAL( c.lt(std::make_pair(10, 10), std::make_pair(10, 9)), false); - BOOST_CHECK_EQUAL( c.lt(std::make_pair(10, 10), std::make_pair(10, 10)), false); - BOOST_CHECK_EQUAL( c.lt(std::make_pair(10, 10), std::make_pair(10, 11)), true); + BOOST_CHECK_EQUAL(c.le(std::make_pair(10, 10), std::make_pair(10, 9)), false); + BOOST_CHECK_EQUAL(c.le(std::make_pair(10, 10), std::make_pair(10, 10)), true); + BOOST_CHECK_EQUAL(c.le(std::make_pair(10, 10), std::make_pair(10, 11)), true); - BOOST_CHECK_EQUAL( c.le(std::make_pair(10, 10), std::make_pair(10, 9)), false); - BOOST_CHECK_EQUAL( c.le(std::make_pair(10, 10), std::make_pair(10, 10)), true); - BOOST_CHECK_EQUAL( c.le(std::make_pair(10, 10), std::make_pair(10, 11)), true); + BOOST_CHECK_EQUAL(c.gt(std::make_pair(10, 10), std::make_pair(10, 9)), true); + BOOST_CHECK_EQUAL(c.gt(std::make_pair(10, 10), std::make_pair(10, 10)), false); + BOOST_CHECK_EQUAL(c.gt(std::make_pair(10, 10), std::make_pair(10, 11)), false); - BOOST_CHECK_EQUAL( c.gt(std::make_pair(10, 10), std::make_pair(10, 9)), true); - BOOST_CHECK_EQUAL( c.gt(std::make_pair(10, 10), std::make_pair(10, 10)), false); - BOOST_CHECK_EQUAL( c.gt(std::make_pair(10, 10), std::make_pair(10, 11)), false); - - BOOST_CHECK_EQUAL( c.ge(std::make_pair(10, 10), std::make_pair(10, 9)), true); - BOOST_CHECK_EQUAL( c.ge(std::make_pair(10, 10), std::make_pair(10, 10)), true); - BOOST_CHECK_EQUAL( c.ge(std::make_pair(10, 10), std::make_pair(10, 11)), false); + BOOST_CHECK_EQUAL(c.ge(std::make_pair(10, 10), std::make_pair(10, 9)), true); + BOOST_CHECK_EQUAL(c.ge(std::make_pair(10, 10), std::make_pair(10, 10)), true); + BOOST_CHECK_EQUAL(c.ge(std::make_pair(10, 10), std::make_pair(10, 11)), false); } -BOOST_AUTO_TEST_CASE( issue2_duplication ) { - std::vector< std::pair > a; +BOOST_AUTO_TEST_CASE(issue2_duplication) { + std::vector> a; for (int i = 0; i < 10000; ++i) { - int first = static_cast(rand()); + int first = static_cast(rand()); int second = static_cast(rand()); - a.push_back( std::make_pair(first, second) ); + a.push_back(std::make_pair(first, second)); } - std::vector< std::pair > expected(a); + std::vector> expected(a); std::sort(expected.begin(), expected.end(), &less_in_pair); timsort(a.begin(), a.end(), &less_in_pair); @@ -506,60 +501,52 @@ BOOST_AUTO_TEST_CASE( issue2_duplication ) { // test some points - BOOST_CHECK_EQUAL(a[0].first, expected[0].first); + BOOST_CHECK_EQUAL(a[0].first, expected[0].first); BOOST_CHECK_EQUAL(a[0].second, expected[0].second); - BOOST_CHECK_EQUAL(a[1].first, expected[1].first); + BOOST_CHECK_EQUAL(a[1].first, expected[1].first); BOOST_CHECK_EQUAL(a[1].second, expected[1].second); - BOOST_CHECK_EQUAL(a[10].first, expected[10].first); + BOOST_CHECK_EQUAL(a[10].first, expected[10].first); BOOST_CHECK_EQUAL(a[10].second, expected[10].second); - BOOST_CHECK_EQUAL(a[11].first, expected[11].first); + BOOST_CHECK_EQUAL(a[11].first, expected[11].first); BOOST_CHECK_EQUAL(a[11].second, expected[11].second); - BOOST_CHECK_EQUAL(a[100].first, expected[100].first); + BOOST_CHECK_EQUAL(a[100].first, expected[100].first); BOOST_CHECK_EQUAL(a[100].second, expected[100].second); - BOOST_CHECK_EQUAL(a[101].first, expected[101].first); + BOOST_CHECK_EQUAL(a[101].first, expected[101].first); BOOST_CHECK_EQUAL(a[101].second, expected[101].second); - BOOST_CHECK_EQUAL(a[111].first, expected[111].first); + BOOST_CHECK_EQUAL(a[111].first, expected[111].first); BOOST_CHECK_EQUAL(a[111].second, expected[111].second); - BOOST_CHECK_EQUAL(a[112].first, expected[112].first); + BOOST_CHECK_EQUAL(a[112].first, expected[112].first); BOOST_CHECK_EQUAL(a[112].second, expected[112].second); - BOOST_CHECK_EQUAL(a[999].first, expected[999].first); + BOOST_CHECK_EQUAL(a[999].first, expected[999].first); BOOST_CHECK_EQUAL(a[999].second, expected[999].second); - BOOST_CHECK_EQUAL(a[1000].first, expected[1000].first); + BOOST_CHECK_EQUAL(a[1000].first, expected[1000].first); BOOST_CHECK_EQUAL(a[1000].second, expected[1000].second); - BOOST_CHECK_EQUAL(a[1001].first, expected[1001].first); + BOOST_CHECK_EQUAL(a[1001].first, expected[1001].first); BOOST_CHECK_EQUAL(a[1001].second, expected[1001].second); } #if ENABLE_STD_MOVE && __cplusplus >= 201103L -template -struct move_only -{ +template struct move_only { // Constructors move_only() = delete; - move_only(const move_only&) = delete; - - move_only(const T& value): - can_read(true), - value(value) - {} - - move_only(move_only&& other): - can_read(true), - value(std::move(other.value)) - { - if (not exchange(other.can_read, false)) - { + move_only(const move_only &) = delete; + + move_only(const T &value) : can_read(true), value(value) { + } + + move_only(move_only &&other) : can_read(true), value(std::move(other.value)) { + if (not exchange(other.can_read, false)) { std::cerr << "illegal read from a moved-from value\n"; assert(false); } @@ -567,15 +554,11 @@ struct move_only // Assignment operators - move_only& operator=(const move_only&) = delete; + move_only &operator=(const move_only &) = delete; - auto operator=(move_only&& other) - -> move_only& - { - if (&other != this) - { - if (not exchange(other.can_read, false)) - { + auto operator=(move_only &&other) -> move_only & { + if (&other != this) { + if (not exchange(other.can_read, false)) { std::cerr << "illegal read from a moved-from value\n"; assert(false); } @@ -587,10 +570,10 @@ struct move_only // A C++11 backport of std::exchange() - template U exchange(U& obj, U&& new_val) { - U old_val = std::move(obj); - obj = std::forward(new_val); - return old_val; + template U exchange(U &obj, U &&new_val) { + U old_val = std::move(obj); + obj = std::forward(new_val); + return old_val; } // Whether the value can be read @@ -599,21 +582,21 @@ struct move_only T value; }; -BOOST_AUTO_TEST_CASE( shuffle10k_for_move_only_types ) { +BOOST_AUTO_TEST_CASE(shuffle10k_for_move_only_types) { const int size = 1024 * 10; // should be even number of elements std::vector> a; - for(int i = 0; i < size; ++i) { - a.push_back((i+1) * 10); + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); } - for(int n = 0; n < 100; ++n) { + for (int n = 0; n < 100; ++n) { std::random_shuffle(a.begin(), a.end()); - timsort(a.begin(), a.end(), [](const move_only& x, const move_only& y) { return x.value < y.value; }); + timsort(a.begin(), a.end(), [](const move_only &x, const move_only &y) { return x.value < y.value; }); - for(int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL( a[i].value, (i+1) * 10 ); + for (int i = 0; i < size; ++i) { + BOOST_CHECK_EQUAL(a[i].value, (i + 1) * 10); } } } diff --git a/timsort.hpp b/timsort.hpp index a28473f..6005a5b 100644 --- a/timsort.hpp +++ b/timsort.hpp @@ -61,57 +61,56 @@ namespace gfx { /** * Same as std::stable_sort(first, last). */ -template +template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last); /** * Same as std::stable_sort(first, last, c). */ -template +template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, LessFunction compare); - // --------------------------------------- // Implementation // --------------------------------------- template class Compare { - public: - typedef Value value_type; - typedef LessFunction func_type; + public: + typedef Value value_type; + typedef LessFunction func_type; - Compare(LessFunction f) - : less_(f) { } - Compare(const Compare& other) - : less_(other.less_) { } + Compare(LessFunction f) : less_(f) { + } + Compare(const Compare &other) : less_(other.less_) { + } - bool lt(value_type x, value_type y) { - return less_(x, y); - } - bool le(value_type x, value_type y) { - return less_(x, y) || !less_(y, x); - } - bool gt(value_type x, value_type y) { - return !less_(x, y) && less_(y, x); - } - bool ge(value_type x, value_type y) { - return !less_(x, y); - } + bool lt(value_type x, value_type y) { + return less_(x, y); + } + bool le(value_type x, value_type y) { + return less_(x, y) || !less_(y, x); + } + bool gt(value_type x, value_type y) { + return !less_(x, y) && less_(y, x); + } + bool ge(value_type x, value_type y) { + return !less_(x, y); + } - func_type& less_function() { - return less_; - } - private: - func_type less_; + func_type &less_function() { + return less_; + } + + private: + func_type less_; }; -template -class TimSort { +template class TimSort { typedef RandomAccessIterator iter_t; typedef typename std::iterator_traits::value_type value_t; typedef typename std::iterator_traits::reference ref_t; typedef typename std::iterator_traits::difference_type diff_t; - typedef Compare compare_t; + typedef Compare compare_t; static const int MIN_MERGE = 32; @@ -128,20 +127,20 @@ class TimSort { iter_t base; diff_t len; - run(iter_t const b, diff_t const l) : base(b), len(l) { } + run(iter_t const b, diff_t const l) : base(b), len(l) { + } }; std::vector pending_; - static - void sort(iter_t const lo, iter_t const hi, compare_t c) { - assert( lo <= hi ); + static void sort(iter_t const lo, iter_t const hi, compare_t c) { + assert(lo <= hi); diff_t nRemaining = (hi - lo); - if(nRemaining < 2) { + if (nRemaining < 2) { return; // nothing to do } - if(nRemaining < MIN_MERGE) { + if (nRemaining < MIN_MERGE) { diff_t const initRunLen = countRunAndMakeAscending(lo, hi, c); GFX_TIMSORT_LOG("initRunLen: " << initRunLen); binarySort(lo, hi, lo + initRunLen, c); @@ -150,12 +149,12 @@ class TimSort { TimSort ts(c); diff_t const minRun = minRunLength(nRemaining); - iter_t cur = lo; + iter_t cur = lo; do { diff_t runLen = countRunAndMakeAscending(cur, hi, c); - if(runLen < minRun) { - diff_t const force = std::min(nRemaining, minRun); + if (runLen < minRun) { + diff_t const force = std::min(nRemaining, minRun); binarySort(cur, cur + force, cur + runLen, c); runLen = force; } @@ -163,52 +162,50 @@ class TimSort { ts.pushRun(cur, runLen); ts.mergeCollapse(); - cur += runLen; + cur += runLen; nRemaining -= runLen; - } while(nRemaining != 0); + } while (nRemaining != 0); - assert( cur == hi ); + assert(cur == hi); ts.mergeForceCollapse(); - assert( ts.pending_.size() == 1 ); + assert(ts.pending_.size() == 1); - GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() << " pending_.size(): " << ts.pending_.size()); + GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() + << " pending_.size(): " << ts.pending_.size()); } // sort() - static - void binarySort(iter_t const lo, iter_t const hi, iter_t start, compare_t compare) { - assert( lo <= start && start <= hi ); - if(start == lo) { + static void binarySort(iter_t const lo, iter_t const hi, iter_t start, compare_t compare) { + assert(lo <= start && start <= hi); + if (start == lo) { ++start; } - for( ; start < hi; ++start ) { + for (; start < hi; ++start) { assert(lo <= start); /*const*/ value_t pivot = GFX_TIMSORT_MOVE(*start); iter_t const pos = std::upper_bound(lo, start, pivot, compare.less_function()); - for(iter_t p = start; p > pos; --p) { + for (iter_t p = start; p > pos; --p) { *p = GFX_TIMSORT_MOVE(*(p - 1)); } *pos = GFX_TIMSORT_MOVE(pivot); } } - static - diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, compare_t compare) { - assert( lo < hi ); + static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, compare_t compare) { + assert(lo < hi); iter_t runHi = lo + 1; - if( runHi == hi ) { + if (runHi == hi) { return 1; } - if(compare.lt(*(runHi++), *lo)) { // descending - while(runHi < hi && compare.lt(*runHi, *(runHi - 1))) { + if (compare.lt(*(runHi++), *lo)) { // descending + while (runHi < hi && compare.lt(*runHi, *(runHi - 1))) { ++runHi; } std::reverse(lo, runHi); - } - else { // ascending - while(runHi < hi && compare.ge(*runHi, *(runHi - 1))) { + } else { // ascending + while (runHi < hi && compare.ge(*runHi, *(runHi - 1))) { ++runHi; } } @@ -216,20 +213,18 @@ class TimSort { return runHi - lo; } - static - diff_t minRunLength(diff_t n) { - assert( n >= 0 ); + static diff_t minRunLength(diff_t n) { + assert(n >= 0); diff_t r = 0; - while(n >= MIN_MERGE) { + while (n >= MIN_MERGE) { r |= (n & 1); n >>= 1; } return n + r; } - TimSort(compare_t c) - : comp_(c), minGallop_(MIN_GALLOP) { + TimSort(compare_t c) : comp_(c), minGallop_(MIN_GALLOP) { } void pushRun(iter_t const runBase, diff_t const runLen) { @@ -237,30 +232,28 @@ class TimSort { } void mergeCollapse() { - while( pending_.size() > 1 ) { + while (pending_.size() > 1) { diff_t n = pending_.size() - 2; - if((n > 0 && pending_[n - 1].len <= pending_[n].len + pending_[n + 1].len) - || (n > 1 && pending_[n - 2].len <= pending_[n - 1].len + pending_[n].len)){ - if(pending_[n - 1].len < pending_[n + 1].len) { + if ((n > 0 && pending_[n - 1].len <= pending_[n].len + pending_[n + 1].len) || + (n > 1 && pending_[n - 2].len <= pending_[n - 1].len + pending_[n].len)) { + if (pending_[n - 1].len < pending_[n + 1].len) { --n; } mergeAt(n); - } - else if(pending_[n].len <= pending_[n + 1].len) { + } else if (pending_[n].len <= pending_[n + 1].len) { mergeAt(n); - } - else { + } else { break; } } } void mergeForceCollapse() { - while( pending_.size() > 1 ) { + while (pending_.size() > 1) { diff_t n = pending_.size() - 2; - if(n > 0 && pending_[n - 1].len < pending_[n + 1].len) { + if (n > 0 && pending_[n - 1].len < pending_[n + 1].len) { --n; } mergeAt(n); @@ -269,159 +262,154 @@ class TimSort { void mergeAt(diff_t const i) { diff_t const stackSize = pending_.size(); - assert( stackSize >= 2 ); - assert( i >= 0 ); - assert( i == stackSize - 2 || i == stackSize - 3 ); + assert(stackSize >= 2); + assert(i >= 0); + assert(i == stackSize - 2 || i == stackSize - 3); iter_t base1 = pending_[i].base; - diff_t len1 = pending_[i].len; + diff_t len1 = pending_[i].len; iter_t base2 = pending_[i + 1].base; - diff_t len2 = pending_[i + 1].len; + diff_t len2 = pending_[i + 1].len; - assert( len1 > 0 && len2 > 0 ); - assert( base1 + len1 == base2 ); + assert(len1 > 0 && len2 > 0); + assert(base1 + len1 == base2); pending_[i].len = len1 + len2; - if(i == stackSize - 3) { + if (i == stackSize - 3) { pending_[i + 1] = pending_[i + 2]; } pending_.pop_back(); diff_t const k = gallopRight(*base2, base1, len1, 0); - assert( k >= 0 ); + assert(k >= 0); base1 += k; - len1 -= k; + len1 -= k; - if(len1 == 0) { + if (len1 == 0) { return; } len2 = gallopLeft(*(base1 + (len1 - 1)), base2, len2, len2 - 1); - assert( len2 >= 0 ); - if(len2 == 0) { + assert(len2 >= 0); + if (len2 == 0) { return; } - if(len1 <= len2) { + if (len1 <= len2) { mergeLo(base1, len1, base2, len2); - } - else { + } else { mergeHi(base1, len1, base2, len2); } } - template - diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint) { - assert( len > 0 && hint >= 0 && hint < len ); + template diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint) { + assert(len > 0 && hint >= 0 && hint < len); diff_t lastOfs = 0; diff_t ofs = 1; - if(comp_.gt(key, *(base + hint))) { + if (comp_.gt(key, *(base + hint))) { diff_t const maxOfs = len - hint; - while(ofs < maxOfs && comp_.gt(key, *(base + (hint + ofs)))) { + while (ofs < maxOfs && comp_.gt(key, *(base + (hint + ofs)))) { lastOfs = ofs; - ofs = (ofs << 1) + 1; + ofs = (ofs << 1) + 1; - if(ofs <= 0) { // int overflow + if (ofs <= 0) { // int overflow ofs = maxOfs; } } - if(ofs > maxOfs) { + if (ofs > maxOfs) { ofs = maxOfs; } lastOfs += hint; - ofs += hint; - } - else { + ofs += hint; + } else { diff_t const maxOfs = hint + 1; - while(ofs < maxOfs && comp_.le(key, *(base + (hint - ofs)))) { + while (ofs < maxOfs && comp_.le(key, *(base + (hint - ofs)))) { lastOfs = ofs; - ofs = (ofs << 1) + 1; + ofs = (ofs << 1) + 1; - if(ofs <= 0) { + if (ofs <= 0) { ofs = maxOfs; } } - if(ofs > maxOfs) { + if (ofs > maxOfs) { ofs = maxOfs; } diff_t const tmp = lastOfs; - lastOfs = hint - ofs; - ofs = hint - tmp; + lastOfs = hint - ofs; + ofs = hint - tmp; } - assert( -1 <= lastOfs && lastOfs < ofs && ofs <= len ); + assert(-1 <= lastOfs && lastOfs < ofs && ofs <= len); - return std::lower_bound(base+(lastOfs+1), base+ofs, key, comp_.less_function()) - base; + return std::lower_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; } - template - diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint) { - assert( len > 0 && hint >= 0 && hint < len ); + template diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint) { + assert(len > 0 && hint >= 0 && hint < len); diff_t ofs = 1; diff_t lastOfs = 0; - if(comp_.lt(key, *(base + hint))) { + if (comp_.lt(key, *(base + hint))) { diff_t const maxOfs = hint + 1; - while(ofs < maxOfs && comp_.lt(key, *(base + (hint - ofs)))) { + while (ofs < maxOfs && comp_.lt(key, *(base + (hint - ofs)))) { lastOfs = ofs; - ofs = (ofs << 1) + 1; + ofs = (ofs << 1) + 1; - if(ofs <= 0) { + if (ofs <= 0) { ofs = maxOfs; } } - if(ofs > maxOfs) { + if (ofs > maxOfs) { ofs = maxOfs; } diff_t const tmp = lastOfs; - lastOfs = hint - ofs; - ofs = hint - tmp; - } - else { + lastOfs = hint - ofs; + ofs = hint - tmp; + } else { diff_t const maxOfs = len - hint; - while(ofs < maxOfs && comp_.ge(key, *(base + (hint + ofs)))) { + while (ofs < maxOfs && comp_.ge(key, *(base + (hint + ofs)))) { lastOfs = ofs; - ofs = (ofs << 1) + 1; + ofs = (ofs << 1) + 1; - if(ofs <= 0) { // int overflow + if (ofs <= 0) { // int overflow ofs = maxOfs; } } - if(ofs > maxOfs) { + if (ofs > maxOfs) { ofs = maxOfs; } lastOfs += hint; - ofs += hint; + ofs += hint; } - assert( -1 <= lastOfs && lastOfs < ofs && ofs <= len ); + assert(-1 <= lastOfs && lastOfs < ofs && ofs <= len); - return std::upper_bound(base+(lastOfs+1), base+ofs, key, comp_.less_function()) - base; + return std::upper_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; } void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { - assert( len1 > 0 && len2 > 0 && base1 + len1 == base2 ); + assert(len1 > 0 && len2 > 0 && base1 + len1 == base2); copy_to_tmp(base1, len1); tmp_iter_t cursor1 = tmp_.begin(); - iter_t cursor2 = base2; - iter_t dest = base1; + iter_t cursor2 = base2; + iter_t dest = base1; *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); - if(--len2 == 0) { + if (--len2 == 0) { GFX_TIMSORT_MOVE_RANGE(cursor1, cursor1 + len1, dest); return; } - if(len1 == 1) { + if (len1 == 1) { GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + len2, dest); *(dest + len2) = GFX_TIMSORT_MOVE(*cursor1); return; @@ -430,82 +418,81 @@ class TimSort { int minGallop(minGallop_); // outer: - while(true) { + while (true) { int count1 = 0; int count2 = 0; bool break_outer = false; do { - assert( len1 > 1 && len2 > 0 ); + assert(len1 > 1 && len2 > 0); - if(comp_.lt(*cursor2, *cursor1)) { + if (comp_.lt(*cursor2, *cursor1)) { *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); ++count2; count1 = 0; - if(--len2 == 0) { + if (--len2 == 0) { break_outer = true; break; } - } - else { + } else { *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); ++count1; count2 = 0; - if(--len1 == 1) { + if (--len1 == 1) { break_outer = true; break; } } - } while( (count1 | count2) < minGallop ); - if(break_outer) { + } while ((count1 | count2) < minGallop); + if (break_outer) { break; } do { - assert( len1 > 1 && len2 > 0 ); + assert(len1 > 1 && len2 > 0); count1 = gallopRight(*cursor2, cursor1, len1, 0); - if(count1 != 0) { + if (count1 != 0) { GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + count1, dest + count1); - dest += count1; + dest += count1; cursor1 += count1; - len1 -= count1; + len1 -= count1; - if(len1 <= 1) { + if (len1 <= 1) { break_outer = true; break; } } *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); - if(--len2 == 0) { + if (--len2 == 0) { break_outer = true; break; } count2 = gallopLeft(*cursor1, cursor2, len2, 0); - if(count2 != 0) { + if (count2 != 0) { GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + count2, dest); - dest += count2; + dest += count2; cursor2 += count2; - len2 -= count2; - if(len2 == 0) { + len2 -= count2; + if (len2 == 0) { break_outer = true; break; } } *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); - if(--len1 == 1) { + if (--len1 == 1) { break_outer = true; break; } --minGallop; - } while( (count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP) ); - if(break_outer) { + } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); + if (break_outer) { break; } - if(minGallop < 0) { + if (minGallop < 0) { minGallop = 0; } minGallop += 2; @@ -513,120 +500,118 @@ class TimSort { minGallop_ = std::min(minGallop, 1); - if(len1 == 1) { - assert( len2 > 0 ); + if (len1 == 1) { + assert(len2 > 0); GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + len2, dest); *(dest + len2) = GFX_TIMSORT_MOVE(*cursor1); - } - else { - assert( len1 != 0 && "Comparison function violates its general contract" ); - assert( len2 == 0 ); - assert( len1 > 1 ); + } else { + assert(len1 != 0 && "Comparison function violates its general contract"); + assert(len2 == 0); + assert(len1 > 1); GFX_TIMSORT_MOVE_RANGE(cursor1, cursor1 + len1, dest); } } void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { - assert( len1 > 0 && len2 > 0 && base1 + len1 == base2 ); + assert(len1 > 0 && len2 > 0 && base1 + len1 == base2); copy_to_tmp(base2, len2); - iter_t cursor1 = base1 + (len1 - 1); + iter_t cursor1 = base1 + (len1 - 1); tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); - iter_t dest = base2 + (len2 - 1); + iter_t dest = base2 + (len2 - 1); *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); - if(--len1 == 0) { + if (--len1 == 0) { GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); return; } - if(len2 == 1) { - dest -= len1; + if (len2 == 1) { + dest -= len1; cursor1 -= len1; GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); *dest = GFX_TIMSORT_MOVE(*cursor2); return; } - int minGallop( minGallop_ ); + int minGallop(minGallop_); // outer: - while(true) { + while (true) { int count1 = 0; int count2 = 0; bool break_outer = false; do { - assert( len1 > 0 && len2 > 1 ); + assert(len1 > 0 && len2 > 1); - if(comp_.lt(*cursor2, *cursor1)) { + if (comp_.lt(*cursor2, *cursor1)) { *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); ++count1; count2 = 0; - if(--len1 == 0) { + if (--len1 == 0) { break_outer = true; break; } - } - else { + } else { *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); ++count2; count1 = 0; - if(--len2 == 1) { + if (--len2 == 1) { break_outer = true; break; } } - } while( (count1 | count2) < minGallop ); - if(break_outer) { + } while ((count1 | count2) < minGallop); + if (break_outer) { break; } do { - assert( len1 > 0 && len2 > 1 ); + assert(len1 > 0 && len2 > 1); count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1); - if(count1 != 0) { - dest -= count1; + if (count1 != 0) { + dest -= count1; cursor1 -= count1; - len1 -= count1; + len1 -= count1; GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + count1), dest + (1 + count1)); - if(len1 == 0) { + if (len1 == 0) { break_outer = true; break; } } *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); - if(--len2 == 1) { + if (--len2 == 1) { break_outer = true; break; } count2 = len2 - gallopLeft(*cursor1, tmp_.begin(), len2, len2 - 1); - if(count2 != 0) { - dest -= count2; + if (count2 != 0) { + dest -= count2; cursor2 -= count2; - len2 -= count2; + len2 -= count2; GFX_TIMSORT_MOVE_RANGE(cursor2 + 1, cursor2 + (1 + count2), dest + 1); - if(len2 <= 1) { + if (len2 <= 1) { break_outer = true; break; } } *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); - if(--len1 == 0) { + if (--len1 == 0) { break_outer = true; break; } minGallop--; - } while( (count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP) ); - if(break_outer) { + } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); + if (break_outer) { break; } - if(minGallop < 0) { + if (minGallop < 0) { minGallop = 0; } minGallop += 2; @@ -634,17 +619,16 @@ class TimSort { minGallop_ = std::min(minGallop, 1); - if(len2 == 1) { - assert( len1 > 0 ); - dest -= len1; + if (len2 == 1) { + assert(len1 > 0); + dest -= len1; cursor1 -= len1; GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); *dest = GFX_TIMSORT_MOVE(*cursor2); - } - else { - assert( len2 != 0 && "Comparison function violates its general contract" ); - assert( len1 == 0 ); - assert( len2 > 1 ); + } else { + assert(len2 != 0 && "Comparison function violates its general contract"); + assert(len1 == 0); + assert(len2 > 1); GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); } } @@ -656,17 +640,16 @@ class TimSort { } // the only interface is the friend timsort() function - template - friend void timsort(IterT first, IterT last, LessT c); + template friend void timsort(IterT first, IterT last, LessT c); }; -template +template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { typedef typename std::iterator_traits::value_type value_type; timsort(first, last, std::less()); } -template +template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, LessFunction compare) { TimSort::sort(first, last, compare); } @@ -678,4 +661,3 @@ inline void timsort(RandomAccessIterator const first, RandomAccessIterator const #undef GFX_TIMSORT_MOVE_RANGE #undef GFX_TIMSORT_MOVE_BACKWARD #endif // GFX_TIMSORT_HPP - From 8084b2be2a6a379d6b93699be3b765d1481d130e Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sat, 17 Oct 2015 17:04:09 +0900 Subject: [PATCH 016/137] let .clang-format C++ compatible --- .clang-format | 1 + test/test.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.clang-format b/.clang-format index 8cb02a9..94c4b7c 100644 --- a/.clang-format +++ b/.clang-format @@ -3,3 +3,4 @@ BasedOnStyle: LLVM ColumnLimit: 120 IndentWidth: 4 AllowShortFunctionsOnASingleLine: false +Standard: Cpp03 diff --git a/test/test.cpp b/test/test.cpp index 7869fd2..7019422 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -472,7 +472,7 @@ BOOST_AUTO_TEST_CASE(issue2_compare) { } BOOST_AUTO_TEST_CASE(issue2_duplication) { - std::vector> a; + std::vector > a; for (int i = 0; i < 10000; ++i) { int first = static_cast(rand()); @@ -481,7 +481,7 @@ BOOST_AUTO_TEST_CASE(issue2_duplication) { a.push_back(std::make_pair(first, second)); } - std::vector> expected(a); + std::vector > expected(a); std::sort(expected.begin(), expected.end(), &less_in_pair); timsort(a.begin(), a.end(), &less_in_pair); @@ -585,7 +585,7 @@ template struct move_only { BOOST_AUTO_TEST_CASE(shuffle10k_for_move_only_types) { const int size = 1024 * 10; // should be even number of elements - std::vector> a; + std::vector > a; for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } From 2ef2054358a0a48aae17a8bb2cfe4278d81154af Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sun, 18 Oct 2015 13:39:37 +0900 Subject: [PATCH 017/137] use std::move by default --- Makefile | 18 +++++++++++++----- README.md | 6 ++++-- test/test.cpp | 6 ++++++ timsort.hpp | 6 +++++- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 96d44d7..867ccaf 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ all: .bin: mkdir -p .bin -test: test-without-optimization test-with-optimization test-with-std-move +test: test-without-optimization test-with-optimization test-with-cpp98 test-with-cpp11 test-with-cpp14 test-without-optimization: test/test.cpp timsort.hpp .bin $(COMPILE) $(LIB_BOOST_TEST) $< -o .bin/$@ @@ -20,17 +20,25 @@ test-with-optimization: test/test.cpp timsort.hpp .bin $(COMPILE) $(OPTIMIZE) $(LIB_BOOST_TEST) $< -o .bin/$@ time ./.bin/$@ -test-with-std-move: test/test.cpp timsort.hpp .bin - $(COMPILE) $(OPTIMIZE) $(LIB_BOOST_TEST) -std=c++11 -DENABLE_STD_MOVE $< -o .bin/$@ +test-with-cpp98: test/test.cpp timsort.hpp .bin + $(COMPILE) $(LIB_BOOST_TEST) -std=c++98 $< -o .bin/$@ + time ./.bin/$@ + +test-with-cpp11: test/test.cpp timsort.hpp .bin + $(COMPILE) $(LIB_BOOST_TEST) -std=c++11 $< -o .bin/$@ + time ./.bin/$@ + +test-with-cpp14: test/test.cpp timsort.hpp .bin + $(COMPILE) $(LIB_BOOST_TEST) -std=c++14 $< -o .bin/$@ time ./.bin/$@ bench: example/bench.cpp timsort.hpp .bin $(CXX) -v - $(COMPILE) $(OPTIMIZE) -std=c++11 -DENABLE_STD_MOVE $< -o .bin/$@ + $(COMPILE) $(OPTIMIZE) -std=c++11 $< -o .bin/$@ ./.bin/$@ coverage: - make test CXXFLAGS="-coverage -O0" + make test-with-cpp11 CXXFLAGS="-coverage -O0" gcov test.gcda | grep -A 1 "File './timsort.hpp'" mv timsort.hpp.gcov coverage.txt rm -rf *.gc* diff --git a/README.md b/README.md index 748e425..6af46f4 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ Run `make test` for testing and `make coverage` for test coverage. COMPATIBILITY ================== -This library is compatible with C++03, but if you give the `-DENABLE_STD_MOVE` flag to the compiler, you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). +This library is compatible with C++98, but if you give compile it with C++11 or later, this library uses `std::move()` instead of value copy and thus you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). + +You can disable use of `std::move()` by passing the macro '-DDISABLE_STD_MOVE'. SEE ALSO ================== @@ -42,7 +44,7 @@ An example output is as follows (timing scale: sec.): Apple LLVM version 7.0.0 (clang-700.0.72) Target: x86_64-apple-darwin14.5.0 Thread model: posix - c++ -I. -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 -DENABLE_STD_MOVE example/bench.cpp -o .bin/bench + c++ -I. -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 example/bench.cpp -o .bin/bench ./.bin/bench RANDOMIZED SEQUENCE [int] diff --git a/test/test.cpp b/test/test.cpp index 7019422..eab1783 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -9,6 +9,12 @@ #include "timsort.hpp" +#if ENABLE_STD_MOVE +#warning std::move() enabled +#else +#warning std::move() disabled +#endif + using namespace gfx; BOOST_AUTO_TEST_CASE(simple0) { diff --git a/timsort.hpp b/timsort.hpp index 6005a5b..67c55ba 100644 --- a/timsort.hpp +++ b/timsort.hpp @@ -42,7 +42,11 @@ #define GFX_TIMSORT_LOG(expr) ((void)0) #endif -#if ENABLE_STD_MOVE && __cplusplus >= 201103L +#if __cplusplus >= 201103L && !DISABLE_STD_MOVE +#define ENABLE_STD_MOVE 1 +#endif + +#if ENABLE_STD_MOVE #define GFX_TIMSORT_MOVE(x) std::move(x) #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::move((in1), (in2), (out)) #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::move_backward((in1), (in2), (out)) From 8b8f1df928dfe3161d4c5623244cba0c44f6d134 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sun, 18 Oct 2015 13:43:54 +0900 Subject: [PATCH 018/137] travis clang (v3.4) does not support -std=c++14 --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 867ccaf..7f89cc0 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ all: .bin: mkdir -p .bin -test: test-without-optimization test-with-optimization test-with-cpp98 test-with-cpp11 test-with-cpp14 +test: test-without-optimization test-with-optimization test-with-cpp98 test-with-cpp11 test-without-optimization: test/test.cpp timsort.hpp .bin $(COMPILE) $(LIB_BOOST_TEST) $< -o .bin/$@ @@ -28,10 +28,6 @@ test-with-cpp11: test/test.cpp timsort.hpp .bin $(COMPILE) $(LIB_BOOST_TEST) -std=c++11 $< -o .bin/$@ time ./.bin/$@ -test-with-cpp14: test/test.cpp timsort.hpp .bin - $(COMPILE) $(LIB_BOOST_TEST) -std=c++14 $< -o .bin/$@ - time ./.bin/$@ - bench: example/bench.cpp timsort.hpp .bin $(CXX) -v $(COMPILE) $(OPTIMIZE) -std=c++11 $< -o .bin/$@ From 4a325da19a35550a0a3eacf7291c3005b300964b Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sun, 18 Oct 2015 13:44:21 +0900 Subject: [PATCH 019/137] test with gcc on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0bc83fc..a60c79b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: cpp compiler: - clang + - gcc install: - sudo apt-get -qq update - sudo apt-get -qq install time libboost-all-dev From 6a6087b9e224ba5d21e581b537f01fa69642ce60 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sun, 18 Oct 2015 13:45:22 +0900 Subject: [PATCH 020/137] use container-based CI http://docs.travis-ci.com/user/migrating-from-legacy/ --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index a60c79b..3acaa9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ -# https://github.com/embarkmobile/android-maven-example +sudo: false language: cpp compiler: - clang - gcc -install: - - sudo apt-get -qq update - - sudo apt-get -qq install time libboost-all-dev +addons: + apt: + packages: + - time + - libboost-all-dev script: - make test From 5c6c748d512b19e3446ce494820a9fb1e00cc43d Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sun, 18 Oct 2015 14:00:21 +0900 Subject: [PATCH 021/137] fix gcc link errors --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 7f89cc0..657bdc6 100644 --- a/Makefile +++ b/Makefile @@ -13,19 +13,19 @@ all: test: test-without-optimization test-with-optimization test-with-cpp98 test-with-cpp11 test-without-optimization: test/test.cpp timsort.hpp .bin - $(COMPILE) $(LIB_BOOST_TEST) $< -o .bin/$@ + $(COMPILE) $< $(LIB_BOOST_TEST) -o .bin/$@ time ./.bin/$@ test-with-optimization: test/test.cpp timsort.hpp .bin - $(COMPILE) $(OPTIMIZE) $(LIB_BOOST_TEST) $< -o .bin/$@ + $(COMPILE) $(OPTIMIZE) $< $(LIB_BOOST_TEST) -o .bin/$@ time ./.bin/$@ test-with-cpp98: test/test.cpp timsort.hpp .bin - $(COMPILE) $(LIB_BOOST_TEST) -std=c++98 $< -o .bin/$@ + $(COMPILE) -std=c++98 $< $(LIB_BOOST_TEST) -o .bin/$@ time ./.bin/$@ test-with-cpp11: test/test.cpp timsort.hpp .bin - $(COMPILE) $(LIB_BOOST_TEST) -std=c++11 $< -o .bin/$@ + $(COMPILE) -std=c++11 $< $(LIB_BOOST_TEST) -o .bin/$@ time ./.bin/$@ bench: example/bench.cpp timsort.hpp .bin From 3af4b0fcef3d74d0c5e75c41e6ef94efceade198 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Sun, 18 Oct 2015 14:02:21 +0900 Subject: [PATCH 022/137] travis gcc (4.6) does not support -std=c++11 https://travis-ci.org/gfx/cpp-TimSort/jobs/85993141 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3acaa9d..ee4b005 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ sudo: false language: cpp compiler: - clang - - gcc addons: apt: packages: From 7e2fb8964f982575c59e1a1774acb4255b5df7ae Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Mon, 21 Mar 2016 23:07:59 +0900 Subject: [PATCH 023/137] add check code for #14 --- Makefile | 4 ++++ valgrind/issue14.cpp | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 valgrind/issue14.cpp diff --git a/Makefile b/Makefile index 657bdc6..f8ec243 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ test-with-cpp11: test/test.cpp timsort.hpp .bin $(COMPILE) -std=c++11 $< $(LIB_BOOST_TEST) -o .bin/$@ time ./.bin/$@ +test-issue14: valgrind/issue14.cpp + $(COMPILE) -std=c++11 $< -o .bin/$@ + valgrind ./.bin/$@ + bench: example/bench.cpp timsort.hpp .bin $(CXX) -v $(COMPILE) $(OPTIMIZE) -std=c++11 $< -o .bin/$@ diff --git a/valgrind/issue14.cpp b/valgrind/issue14.cpp new file mode 100644 index 0000000..68c308e --- /dev/null +++ b/valgrind/issue14.cpp @@ -0,0 +1,13 @@ +#include "timsort.hpp" +#include +#include + +int main() { + std::deque collection = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, + 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; + + gfx::timsort(std::begin(collection), std::end(collection)); + assert(std::is_sorted(std::begin(collection), std::end(collection))); + + return 0; +} From f5fc74abc8550ef6937632ee434ef36e841f2cc8 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Tue, 22 Mar 2016 00:01:47 +0900 Subject: [PATCH 024/137] use clang address sanitizer --- Makefile | 4 ++-- test/test.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f8ec243..d93530e 100644 --- a/Makefile +++ b/Makefile @@ -29,8 +29,8 @@ test-with-cpp11: test/test.cpp timsort.hpp .bin time ./.bin/$@ test-issue14: valgrind/issue14.cpp - $(COMPILE) -std=c++11 $< -o .bin/$@ - valgrind ./.bin/$@ + $(COMPILE) -DENABLE_TIMSORT_LOG -std=c++11 -fsanitize=address $< -o .bin/$@ + ./.bin/$@ bench: example/bench.cpp timsort.hpp .bin $(CXX) -v diff --git a/test/test.cpp b/test/test.cpp index eab1783..c97ecb1 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -607,4 +608,13 @@ BOOST_AUTO_TEST_CASE(shuffle10k_for_move_only_types) { } } +BOOST_AUTO_TEST_CASE(issue14) { + int a[] = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, + 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; + std::deque c(std::begin(a), std::end(a)); + + gfx::timsort(std::begin(c), std::end(c)); + BOOST_CHECK(std::is_sorted(std::begin(c), std::end(c))); +} + #endif // if std::move available From 71ae32768a69bbe948e6318d16058bb3b4ccfec7 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Tue, 22 Mar 2016 00:02:03 +0900 Subject: [PATCH 025/137] fix invalid memory access (#14) --- timsort.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/timsort.hpp b/timsort.hpp index 67c55ba..66599e4 100644 --- a/timsort.hpp +++ b/timsort.hpp @@ -626,8 +626,7 @@ template class TimSort { if (len2 == 1) { assert(len1 > 0); dest -= len1; - cursor1 -= len1; - GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); + GFX_TIMSORT_MOVE_BACKWARD(cursor1 + (1 - len1), cursor1 + 1, dest + (1 + len1)); *dest = GFX_TIMSORT_MOVE(*cursor2); } else { assert(len2 != 0 && "Comparison function violates its general contract"); From 2c1a5daba09e046d52ce80579577c557117041d8 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Tue, 22 Mar 2016 00:11:08 +0900 Subject: [PATCH 026/137] suppres warnings in tests --- Makefile | 2 +- test/test.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index d93530e..f7b93be 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -COMPILE := $(CXX) -I. -Wall -Wextra -g $(CXXFLAGS) +COMPILE := $(CXX) -I. -I/opt/brew/include -L/opt/brew/lib -Wall -Wextra -g $(CXXFLAGS) OPTIMIZE := -DNDEBUG -O2 LIB_BOOST_TEST := -lboost_unit_test_framework diff --git a/test/test.cpp b/test/test.cpp index c97ecb1..5efbcd7 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -23,7 +23,7 @@ BOOST_AUTO_TEST_CASE(simple0) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), 0); + BOOST_CHECK_EQUAL(a.size(), std::size_t(0)); } BOOST_AUTO_TEST_CASE(simple1) { @@ -33,7 +33,7 @@ BOOST_AUTO_TEST_CASE(simple1) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), 1); + BOOST_CHECK_EQUAL(a.size(), std::size_t(1)); BOOST_CHECK_EQUAL(a[0], 42); } @@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE(simple2) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), 2); + BOOST_CHECK_EQUAL(a.size(), std::size_t(2)); BOOST_CHECK_EQUAL(a[0], 10); BOOST_CHECK_EQUAL(a[1], 20); @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(simple2) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), 2); + BOOST_CHECK_EQUAL(a.size(), std::size_t(2)); BOOST_CHECK_EQUAL(a[0], 10); BOOST_CHECK_EQUAL(a[1], 20); @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(simple2) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), 2); + BOOST_CHECK_EQUAL(a.size(), std::size_t(2)); BOOST_CHECK_EQUAL(a[0], 10); BOOST_CHECK_EQUAL(a[1], 10); } @@ -123,7 +123,7 @@ BOOST_AUTO_TEST_CASE(shuffle30) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), size); + BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); for (int i = 0; i < size; ++i) { BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } @@ -140,7 +140,7 @@ BOOST_AUTO_TEST_CASE(shuffle31) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), size); + BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); for (int i = 0; i < size; ++i) { BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(shuffle32) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), size); + BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); for (int i = 0; i < size; ++i) { BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } @@ -174,7 +174,7 @@ BOOST_AUTO_TEST_CASE(shuffle128) { timsort(a.begin(), a.end(), std::less()); - BOOST_CHECK_EQUAL(a.size(), size); + BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); for (int i = 0; i < size; ++i) { BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } @@ -398,7 +398,7 @@ BOOST_AUTO_TEST_CASE(default_compare_function) { timsort(a.begin(), a.end()); - BOOST_CHECK_EQUAL(a.size(), size); + BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); for (int i = 0; i < size; ++i) { BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); } From e39ca95ee2ca4e717fce73e5a0088c82944d14dd Mon Sep 17 00:00:00 2001 From: mattreecebentley Date: Mon, 22 May 2017 15:13:40 +1200 Subject: [PATCH 027/137] C++03/move compliance, better support for compiler/library feature detection (#22) C++03/move compliance, better support for compiler/library feature detection --- timsort.hpp | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/timsort.hpp b/timsort.hpp index 66599e4..77dd7a2 100644 --- a/timsort.hpp +++ b/timsort.hpp @@ -5,7 +5,7 @@ * - http://svn.python.org/projects/python/trunk/Objects/listobject.c * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * - * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2011 Fuji, Goro (gfx) . C++03/move-compliance modifications by Matt Bentley 2017 (mattreecebentley@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -31,9 +31,8 @@ #include #include -#include -#include -#include +#include // std::copy +#include // std::less #ifdef ENABLE_TIMSORT_LOG #include @@ -42,20 +41,38 @@ #define GFX_TIMSORT_LOG(expr) ((void)0) #endif -#if __cplusplus >= 201103L && !DISABLE_STD_MOVE -#define ENABLE_STD_MOVE 1 -#endif - -#if ENABLE_STD_MOVE -#define GFX_TIMSORT_MOVE(x) std::move(x) -#define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::move((in1), (in2), (out)) -#define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::move_backward((in1), (in2), (out)) +// If compiler supports both type traits and move semantics - will cover most but not all compilers/std libraries: +#if (defined(_MSC_VER) && _MSC_VER >= 1700) || ((defined(__cplusplus) && __cplusplus >= 201103L && !defined(_LIBCPP_VERSION)) && ((!defined(__GNUC__) || __GNUC__ >= 5)) && (!defined(__GLIBCXX__) || __GLIBCXX__ >= 20150422)) + #include // iterator_traits + #include // std::move + + #define GFX_TIMSORT_MOVE(x) (std::is_move_constructible::value && std::is_move_assignable::value) ? std::move(x) : (x) + #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) \ + if (std::is_move_constructible::value && std::is_move_assignable::value) \ + { \ + std::move((in1), (in2), (out)); \ + } \ + else \ + { \ + std::copy((in1), (in2), (out)); \ + } + #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) \ + if (std::is_move_constructible::value && std::is_move_assignable::value) \ + { \ + std::move_backward((in1), (in2), (out)); \ + } \ + else \ + { \ + std::copy_backward((in1), (in2), (out)); \ + } #else -#define GFX_TIMSORT_MOVE(x) (x) -#define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::copy((in1), (in2), (out)) -#define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::copy_backward((in1), (in2), (out)) + #define GFX_TIMSORT_MOVE(x) (x) + #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::copy((in1), (in2), (out)); + #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::copy_backward((in1), (in2), (out)); #endif + + namespace gfx { // --------------------------------------- From a9353c51c05726dc894d590c488377fa9fb62adf Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 22 Sep 2019 22:14:54 +0200 Subject: [PATCH 028/137] Revamp tooling: initial commit --- .gitignore | 1 + .travis.yml | 176 +++++- CMakeLists.txt | 86 +++ Makefile | 49 -- README.md | 8 +- cmake/DownloadProject.CMakeLists.cmake.in | 17 + cmake/DownloadProject.cmake | 182 +++++++ cmake/gfx-timsort-config.cmake.in | 5 + examples/CMakeLists.txt | 6 + {example => examples}/bench.cpp | 0 timsort.hpp => include/gfx/timsort.hpp | 63 ++- test/test.cpp | 620 ---------------------- tests/CMakeLists.txt | 97 ++++ tests/cxx_11_tests.cpp | 87 +++ tests/cxx_98_tests.cpp | 537 +++++++++++++++++++ tests/main.cpp | 8 + valgrind/issue14.cpp | 13 - 17 files changed, 1228 insertions(+), 727 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Makefile create mode 100644 cmake/DownloadProject.CMakeLists.cmake.in create mode 100644 cmake/DownloadProject.cmake create mode 100644 cmake/gfx-timsort-config.cmake.in create mode 100644 examples/CMakeLists.txt rename {example => examples}/bench.cpp (100%) rename timsort.hpp => include/gfx/timsort.hpp (93%) delete mode 100644 test/test.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/cxx_11_tests.cpp create mode 100644 tests/cxx_98_tests.cpp create mode 100644 tests/main.cpp delete mode 100644 valgrind/issue14.cpp diff --git a/.gitignore b/.gitignore index 026851b..6ef5472 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .bin *.gc* coverage.txt +build diff --git a/.travis.yml b/.travis.yml index ee4b005..dbdfe22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,169 @@ -sudo: false language: cpp -compiler: - - clang -addons: - apt: - packages: - - time - - libboost-all-dev + +_packages: + - &clang clang-3.8 + - &gcc g++-5 + +_apt: &apt-common + sources: + - llvm-toolchain-trusty-3.8 + - ubuntu-toolchain-r-test + +matrix: + include: + + # Linux clang + - os: linux + sudo: required + env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *clang + - *gcc + - valgrind + compiler: clang + + - os: linux + sudo: required + env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *clang + - *gcc + compiler: clang + + - os: linux + sudo: required + env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *clang + - *gcc + compiler: clang + + - os: linux + sudo: required + env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *clang + - *gcc + compiler: clang + + # Linux gcc + - os: linux + sudo: false + env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *gcc + - valgrind + compiler: gcc + + - os: linux + sudo: false + env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *gcc + compiler: gcc + + - os: linux + sudo: false + env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *gcc + compiler: gcc + + - os: linux + sudo: false + env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" + addons: + apt: + <<: *apt-common + packages: + - *gcc + compiler: gcc + + # OSX clang + - os: osx + osx_image: xcode9.2 + env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Xcode" + addons: + homebrew: + update: true + packages: + - ccache + - cmake + - valgrind + compiler: clang + + - os: osx + osx_image: xcode8.3 + env: BUILD_TYPE=Release CMAKE_GENERATOR="Xcode" + addons: + homebrew: + update: true + packages: + - ccache + - cmake + compiler: clang + + # Windows GCC + - os: windows + language: sh + env: BUILD_TYPE=Debug CMAKE_GENERATOR="MinGW Makefiles" + compiler: gcc + + - os: windows + language: sh + env: BUILD_TYPE=Release CMAKE_GENERATOR="MinGW Makefiles" + compiler: gcc + +install: + - if [[ $CXX = "g++" ]]; then export CXX="g++-5"; fi + - if [[ $CXX = "clang++" ]]; then export CXX="clang++-3.8"; fi + script: - - make test + - cmake -H. -Bbuild + -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" + -DGFX_TIMSORT_SANITIZE="${SANITIZE}" + -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} + -G"${CMAKE_GENERATOR}" + -DCMAKE_SH="CMAKE_SH-NOTFOUND" + -DBUILD_EXAMPLES=ON + - if [[ $TRAVIS_OS_NAME = "osx" ]]; then + cmake --build build --config ${BUILD_TYPE} -j 2; + else + cmake --build build --config ${BUILD_TYPE} -- -j2; + fi + - cd build + - if [[ $USE_VALGRIND = true ]]; then + travis_wait 50 ctest -T memcheck -C ${BUILD_TYPE} --output-on-failure -j 4; + else + travis_wait ctest -C ${BUILD_TYPE} --output-on-failure; + fi + +after_failure: + - if [[ $USE_VALGRIND = true ]]; then + find ./Testing/Temporary -type f -name "MemoryChecker.*.log" -size +1300c | xargs cat; + fi + +notifications: + email: false diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a568f35 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.8.0) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +project(timsort VERSION 1.0.0 LANGUAGES CXX) + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +# Project options +option(BUILD_TESTING "Build the tests" ON) +option(BUILD_EXAMPLES "Build the examples" OFF) + +# Create gfx::timsort as an interface library +add_library(timsort INTERFACE) + +target_include_directories(timsort INTERFACE + $ + $ +) + +target_compile_features(timsort INTERFACE cxx_std_98) + +add_library(gfx::timsort ALIAS timsort) + +# Install targets and files +install( + TARGETS timsort + EXPORT gfx-timsort-targets + DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install( + EXPORT gfx-timsort-targets + NAMESPACE gfx:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/gfx +) + +install( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/gfx/timsort.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gfx +) + +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/gfx-timsort-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-config.cmake + INSTALL_DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/gfx +) + +# Bypass automatic architeture check introduced by CMake, +# use the ARCH_INDEPENDENT option for this in the future +set(GFX_TIMSORT_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) +unset(CMAKE_SIZEOF_VOID_P) +write_basic_package_version_file( + ${CMAKE_BINARY_DIR}/cmake/gfx-timsort-config-version.cmake + COMPATIBILITY + SameMajorVersion +) +set(CMAKE_SIZEOF_VOID_P ${GFX_TIMSORT_SIZEOF_VOID_P}) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/gfx +) + +# Export target so that it can be used in subdirectories +export( + EXPORT gfx-timsort-targets + FILE ${CMAKE_CURRENT_BINARY_DIR}/cmake/gfx-timsort-targets.cmake + NAMESPACE gfx:: +) + +# Build tests and/or examples if this is the main project +if (PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + if (BUILD_TESTING) + enable_testing() + add_subdirectory(tests) + endif() + + if (BUILD_EXAMPLES) + add_subdirectory(examples) + endif() +endif() diff --git a/Makefile b/Makefile deleted file mode 100644 index f7b93be..0000000 --- a/Makefile +++ /dev/null @@ -1,49 +0,0 @@ - -COMPILE := $(CXX) -I. -I/opt/brew/include -L/opt/brew/lib -Wall -Wextra -g $(CXXFLAGS) -OPTIMIZE := -DNDEBUG -O2 - -LIB_BOOST_TEST := -lboost_unit_test_framework - -all: - @echo This library is a C++ header file only. - -.bin: - mkdir -p .bin - -test: test-without-optimization test-with-optimization test-with-cpp98 test-with-cpp11 - -test-without-optimization: test/test.cpp timsort.hpp .bin - $(COMPILE) $< $(LIB_BOOST_TEST) -o .bin/$@ - time ./.bin/$@ - -test-with-optimization: test/test.cpp timsort.hpp .bin - $(COMPILE) $(OPTIMIZE) $< $(LIB_BOOST_TEST) -o .bin/$@ - time ./.bin/$@ - -test-with-cpp98: test/test.cpp timsort.hpp .bin - $(COMPILE) -std=c++98 $< $(LIB_BOOST_TEST) -o .bin/$@ - time ./.bin/$@ - -test-with-cpp11: test/test.cpp timsort.hpp .bin - $(COMPILE) -std=c++11 $< $(LIB_BOOST_TEST) -o .bin/$@ - time ./.bin/$@ - -test-issue14: valgrind/issue14.cpp - $(COMPILE) -DENABLE_TIMSORT_LOG -std=c++11 -fsanitize=address $< -o .bin/$@ - ./.bin/$@ - -bench: example/bench.cpp timsort.hpp .bin - $(CXX) -v - $(COMPILE) $(OPTIMIZE) -std=c++11 $< -o .bin/$@ - ./.bin/$@ - -coverage: - make test-with-cpp11 CXXFLAGS="-coverage -O0" - gcov test.gcda | grep -A 1 "File './timsort.hpp'" - mv timsort.hpp.gcov coverage.txt - rm -rf *.gc* - -clean: - rm -rf *~ .bin coverage.txt - -.PHONY: test bench coverage clean diff --git a/README.md b/README.md index 6af46f4..90c670a 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,16 @@ SYNOPSIS TEST ================== -Run `make test` for testing and `make coverage` for test coverage. +The tests are written with Catch (branch 1.x) and can be compiled with CMake. + +TODO: describe CMake support COMPATIBILITY ================== -This library is compatible with C++98, but if you give compile it with C++11 or later, this library uses `std::move()` instead of value copy and thus you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). +This library is compatible with C++98, but if you compile it with C++11 or later, this library uses `std::move()` instead of value copy and thus you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). -You can disable use of `std::move()` by passing the macro '-DDISABLE_STD_MOVE'. +You can explicity disable the use of `std::move()` by passing the macro '-DGFX_TIMSORT_DISABLE_STD_MOVE'. SEE ALSO ================== diff --git a/cmake/DownloadProject.CMakeLists.cmake.in b/cmake/DownloadProject.CMakeLists.cmake.in new file mode 100644 index 0000000..89be4fd --- /dev/null +++ b/cmake/DownloadProject.CMakeLists.cmake.in @@ -0,0 +1,17 @@ +# Distributed under the OSI-approved MIT License. See accompanying +# file LICENSE or https://github.com/Crascit/DownloadProject for details. + +cmake_minimum_required(VERSION 2.8.2) + +project(${DL_ARGS_PROJ}-download NONE) + +include(ExternalProject) +ExternalProject_Add(${DL_ARGS_PROJ}-download + ${DL_ARGS_UNPARSED_ARGUMENTS} + SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" + BINARY_DIR "${DL_ARGS_BINARY_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/cmake/DownloadProject.cmake b/cmake/DownloadProject.cmake new file mode 100644 index 0000000..e300f42 --- /dev/null +++ b/cmake/DownloadProject.cmake @@ -0,0 +1,182 @@ +# Distributed under the OSI-approved MIT License. See accompanying +# file LICENSE or https://github.com/Crascit/DownloadProject for details. +# +# MODULE: DownloadProject +# +# PROVIDES: +# download_project( PROJ projectName +# [PREFIX prefixDir] +# [DOWNLOAD_DIR downloadDir] +# [SOURCE_DIR srcDir] +# [BINARY_DIR binDir] +# [QUIET] +# ... +# ) +# +# Provides the ability to download and unpack a tarball, zip file, git repository, +# etc. at configure time (i.e. when the cmake command is run). How the downloaded +# and unpacked contents are used is up to the caller, but the motivating case is +# to download source code which can then be included directly in the build with +# add_subdirectory() after the call to download_project(). Source and build +# directories are set up with this in mind. +# +# The PROJ argument is required. The projectName value will be used to construct +# the following variables upon exit (obviously replace projectName with its actual +# value): +# +# projectName_SOURCE_DIR +# projectName_BINARY_DIR +# +# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically +# need to be provided. They can be specified if you want the downloaded source +# and build directories to be located in a specific place. The contents of +# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the +# locations used whether you provide SOURCE_DIR/BINARY_DIR or not. +# +# The DOWNLOAD_DIR argument does not normally need to be set. It controls the +# location of the temporary CMake build used to perform the download. +# +# The PREFIX argument can be provided to change the base location of the default +# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments +# are provided, then PREFIX will have no effect. The default value for PREFIX is +# CMAKE_BINARY_DIR. +# +# The QUIET option can be given if you do not want to show the output associated +# with downloading the specified project. +# +# In addition to the above, any other options are passed through unmodified to +# ExternalProject_Add() to perform the actual download, patch and update steps. +# The following ExternalProject_Add() options are explicitly prohibited (they +# are reserved for use by the download_project() command): +# +# CONFIGURE_COMMAND +# BUILD_COMMAND +# INSTALL_COMMAND +# TEST_COMMAND +# +# Only those ExternalProject_Add() arguments which relate to downloading, patching +# and updating of the project sources are intended to be used. Also note that at +# least one set of download-related arguments are required. +# +# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to +# prevent a check at the remote end for changes every time CMake is run +# after the first successful download. See the documentation of the ExternalProject +# module for more information. It is likely you will want to use this option if it +# is available to you. Note, however, that the ExternalProject implementation contains +# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when +# using the URL download method or when specifying a SOURCE_DIR with no download +# method. Fixes for these have been created, the last of which is scheduled for +# inclusion in CMake 3.8.0. Details can be found here: +# +# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c +# https://gitlab.kitware.com/cmake/cmake/issues/16428 +# +# If you experience build errors related to the update step, consider avoiding +# the use of UPDATE_DISCONNECTED. +# +# EXAMPLE USAGE: +# +# include(DownloadProject) +# download_project(PROJ googletest +# GIT_REPOSITORY https://github.com/google/googletest.git +# GIT_TAG master +# UPDATE_DISCONNECTED 1 +# QUIET +# ) +# +# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) +# +#======================================================================================== + + +set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") + +include(CMakeParseArguments) + +function(download_project) + + set(options QUIET) + set(oneValueArgs + PROJ + PREFIX + DOWNLOAD_DIR + SOURCE_DIR + BINARY_DIR + # Prevent the following from being passed through + CONFIGURE_COMMAND + BUILD_COMMAND + INSTALL_COMMAND + TEST_COMMAND + ) + set(multiValueArgs "") + + cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Hide output if requested + if (DL_ARGS_QUIET) + set(OUTPUT_QUIET "OUTPUT_QUIET") + else() + unset(OUTPUT_QUIET) + message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") + endif() + + # Set up where we will put our temporary CMakeLists.txt file and also + # the base point below which the default source and binary dirs will be. + # The prefix must always be an absolute path. + if (NOT DL_ARGS_PREFIX) + set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") + else() + get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if (NOT DL_ARGS_DOWNLOAD_DIR) + set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") + endif() + + # Ensure the caller can know where to find the source and build directories + if (NOT DL_ARGS_SOURCE_DIR) + set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") + endif() + if (NOT DL_ARGS_BINARY_DIR) + set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") + endif() + set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) + set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) + + # The way that CLion manages multiple configurations, it causes a copy of + # the CMakeCache.txt to be copied across due to it not expecting there to + # be a project within a project. This causes the hard-coded paths in the + # cache to be copied and builds to fail. To mitigate this, we simply + # remove the cache if it exists before we configure the new project. It + # is safe to do so because it will be re-generated. Since this is only + # executed at the configure step, it should not cause additional builds or + # downloads. + file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") + + # Create and build a separate CMake project to carry out the download. + # If we've already previously done these steps, they will not cause + # anything to be updated, so extra rebuilds of the project won't occur. + # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project + # has this set to something not findable on the PATH. + configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" + "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" + -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" + . + RESULT_VARIABLE result + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + if(result) + message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + if(result) + message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") + endif() + +endfunction() diff --git a/cmake/gfx-timsort-config.cmake.in b/cmake/gfx-timsort-config.cmake.in new file mode 100644 index 0000000..c2c847f --- /dev/null +++ b/cmake/gfx-timsort-config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +if (NOT TARGET gfx::timsort) + include(${CMAKE_CURRENT_LIST_DIR}/gfx-timsort-targets.cmake) +endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..dc98cb0 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,6 @@ + +foreach(filename bench.cpp) + get_filename_component(name ${filename} NAME_WE) + add_executable(${name} ${filename}) + target_link_libraries(${name} PRIVATE gfx::timsort) +endforeach() diff --git a/example/bench.cpp b/examples/bench.cpp similarity index 100% rename from example/bench.cpp rename to examples/bench.cpp diff --git a/timsort.hpp b/include/gfx/timsort.hpp similarity index 93% rename from timsort.hpp rename to include/gfx/timsort.hpp index 77dd7a2..1ba0a5d 100644 --- a/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -5,7 +5,8 @@ * - http://svn.python.org/projects/python/trunk/Objects/listobject.c * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * - * Copyright (c) 2011 Fuji, Goro (gfx) . C++03/move-compliance modifications by Matt Bentley 2017 (mattreecebentley@gmail.com) + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -29,50 +30,44 @@ #ifndef GFX_TIMSORT_HPP #define GFX_TIMSORT_HPP -#include +#include #include -#include // std::copy -#include // std::less +#include +#include +#include #ifdef ENABLE_TIMSORT_LOG -#include -#define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << expr << std::endl) +# include +# define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << expr << std::endl) #else -#define GFX_TIMSORT_LOG(expr) ((void)0) +# define GFX_TIMSORT_LOG(expr) ((void)0) #endif -// If compiler supports both type traits and move semantics - will cover most but not all compilers/std libraries: -#if (defined(_MSC_VER) && _MSC_VER >= 1700) || ((defined(__cplusplus) && __cplusplus >= 201103L && !defined(_LIBCPP_VERSION)) && ((!defined(__GNUC__) || __GNUC__ >= 5)) && (!defined(__GLIBCXX__) || __GLIBCXX__ >= 20150422)) - #include // iterator_traits - #include // std::move - - #define GFX_TIMSORT_MOVE(x) (std::is_move_constructible::value && std::is_move_assignable::value) ? std::move(x) : (x) - #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) \ - if (std::is_move_constructible::value && std::is_move_assignable::value) \ - { \ - std::move((in1), (in2), (out)); \ - } \ - else \ - { \ - std::copy((in1), (in2), (out)); \ - } - #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) \ - if (std::is_move_constructible::value && std::is_move_assignable::value) \ - { \ - std::move_backward((in1), (in2), (out)); \ - } \ - else \ - { \ - std::copy_backward((in1), (in2), (out)); \ - } +// Conditionally enable/disable move semantics + +#ifdef GFX_TIMSORT_DISABLE_STD_MOVE +# define GFX_TIMSORT_ENABLED_STD_MOVE 0 +# undef GFX_TIMSORT_DISABLE_STD_MOVE +#else +# if (defined(_MSC_VER) && _MSC_VER >= 1700) || ((defined(__cplusplus) && __cplusplus >= 201103L && !defined(_LIBCPP_VERSION)) && ((!defined(__GNUC__) || __GNUC__ >= 5)) && (!defined(__GLIBCXX__) || __GLIBCXX__ >= 20150422)) +# define GFX_TIMSORT_ENABLED_STD_MOVE 1 +# else +# define GFX_TIMSORT_ENABLED_STD_MOVE 0 +# endif +#endif // GFX_TIMSORT_DISABLE_STD_MOVE + +#if GFX_TIMSORT_ENABLED_STD_MOVE + #include + #define GFX_TIMSORT_MOVE(x) std::move(x) + #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::move((in1), (in2), (out)); + #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::move_backward((in1), (in2), (out)); #else #define GFX_TIMSORT_MOVE(x) (x) - #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::copy((in1), (in2), (out)); + #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::copy((in1), (in2), (out)); #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::copy_backward((in1), (in2), (out)); #endif - namespace gfx { // --------------------------------------- @@ -680,4 +675,6 @@ inline void timsort(RandomAccessIterator const first, RandomAccessIterator const #undef GFX_TIMSORT_MOVE #undef GFX_TIMSORT_MOVE_RANGE #undef GFX_TIMSORT_MOVE_BACKWARD +#undef GFX_TIMSORT_ENABLED_STD_MOVE + #endif // GFX_TIMSORT_HPP diff --git a/test/test.cpp b/test/test.cpp deleted file mode 100644 index 5efbcd7..0000000 --- a/test/test.cpp +++ /dev/null @@ -1,620 +0,0 @@ -#include -#include -#include -#include -#include - -#define BOOST_TEST_DYN_LINK -#define BOOST_TEST_MODULE TimSortTest -#include - -#include "timsort.hpp" - -#if ENABLE_STD_MOVE -#warning std::move() enabled -#else -#warning std::move() disabled -#endif - -using namespace gfx; - -BOOST_AUTO_TEST_CASE(simple0) { - std::vector a; - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(0)); -} - -BOOST_AUTO_TEST_CASE(simple1) { - std::vector a; - - a.push_back(42); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(1)); - BOOST_CHECK_EQUAL(a[0], 42); -} - -BOOST_AUTO_TEST_CASE(simple2) { - std::vector a; - - a.push_back(10); - a.push_back(20); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(2)); - BOOST_CHECK_EQUAL(a[0], 10); - BOOST_CHECK_EQUAL(a[1], 20); - - a.clear(); - a.push_back(20); - a.push_back(10); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(2)); - BOOST_CHECK_EQUAL(a[0], 10); - BOOST_CHECK_EQUAL(a[1], 20); - - a.clear(); - a.push_back(10); - a.push_back(10); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(2)); - BOOST_CHECK_EQUAL(a[0], 10); - BOOST_CHECK_EQUAL(a[1], 10); -} - -BOOST_AUTO_TEST_CASE(simple10) { - std::vector a; - a.push_back(60); - a.push_back(50); - a.push_back(10); - a.push_back(40); - a.push_back(80); - a.push_back(20); - a.push_back(30); - a.push_back(70); - a.push_back(10); - a.push_back(90); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a[0], 10); - BOOST_CHECK_EQUAL(a[1], 10); - BOOST_CHECK_EQUAL(a[2], 20); - BOOST_CHECK_EQUAL(a[3], 30); - BOOST_CHECK_EQUAL(a[4], 40); - BOOST_CHECK_EQUAL(a[5], 50); - BOOST_CHECK_EQUAL(a[6], 60); - BOOST_CHECK_EQUAL(a[7], 70); - BOOST_CHECK_EQUAL(a[8], 80); - BOOST_CHECK_EQUAL(a[9], 90); - - std::reverse(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a[0], 10); - BOOST_CHECK_EQUAL(a[1], 10); - BOOST_CHECK_EQUAL(a[2], 20); - BOOST_CHECK_EQUAL(a[3], 30); - BOOST_CHECK_EQUAL(a[4], 40); - BOOST_CHECK_EQUAL(a[5], 50); - BOOST_CHECK_EQUAL(a[6], 60); - BOOST_CHECK_EQUAL(a[7], 70); - BOOST_CHECK_EQUAL(a[8], 80); - BOOST_CHECK_EQUAL(a[9], 90); -} - -BOOST_AUTO_TEST_CASE(shuffle30) { - const int size = 30; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } -} - -BOOST_AUTO_TEST_CASE(shuffle31) { - const int size = 31; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } -} - -BOOST_AUTO_TEST_CASE(shuffle32) { - const int size = 32; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } -} - -BOOST_AUTO_TEST_CASE(shuffle128) { - const int size = 128; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } -} - -BOOST_AUTO_TEST_CASE(shuffle1023) { - const int size = 1023; // odd number of elements - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(shuffle1024) { - const int size = 1024; // should be even number of elements - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(partial_shuffle1023) { - const int size = 1023; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - // sorted-shuffled-sorted pattern - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } - - // shuffled-sorted-shuffled pattern - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); - std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(partial_shuffle1024) { - const int size = 1024; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - // sorted-shuffled-sorted pattern - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } - - // shuffled-sorted-shuffled pattern - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); - std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(shuffle1024r) { - const int size = 1024; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), std::greater()); - - int j = size; - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (--j + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(partial_reversed1023) { - const int size = 1023; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - for (int n = 0; n < 100; ++n) { - std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(partial_reversed1024) { - const int size = 1024; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - for (int n = 0; n < 100; ++n) { - std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed - - timsort(a.begin(), a.end(), std::less()); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(c_array) { - int a[] = {7, 1, 5, 3, 9}; - - timsort(a, a + sizeof(a) / sizeof(int), std::less()); - - BOOST_CHECK_EQUAL(a[0], 1); - BOOST_CHECK_EQUAL(a[1], 3); - BOOST_CHECK_EQUAL(a[2], 5); - BOOST_CHECK_EQUAL(a[3], 7); - BOOST_CHECK_EQUAL(a[4], 9); -} - -BOOST_AUTO_TEST_CASE(string_array) { - std::string a[] = {"7", "1", "5", "3", "9"}; - - timsort(a, a + sizeof(a) / sizeof(std::string), std::less()); - - BOOST_CHECK_EQUAL(a[0], "1"); - BOOST_CHECK_EQUAL(a[1], "3"); - BOOST_CHECK_EQUAL(a[2], "5"); - BOOST_CHECK_EQUAL(a[3], "7"); - BOOST_CHECK_EQUAL(a[4], "9"); -} - -struct NonDefaultConstructible { - int i; - - NonDefaultConstructible(int i_) : i(i_) { - } - - friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { - return x.i < y.i; - } -}; - -BOOST_AUTO_TEST_CASE(non_default_constructible) { - NonDefaultConstructible a[] = {7, 1, 5, 3, 9}; - - timsort(a, a + sizeof(a) / sizeof(a[0]), std::less()); - - BOOST_CHECK_EQUAL(a[0].i, 1); - BOOST_CHECK_EQUAL(a[1].i, 3); - BOOST_CHECK_EQUAL(a[2].i, 5); - BOOST_CHECK_EQUAL(a[3].i, 7); - BOOST_CHECK_EQUAL(a[4].i, 9); -} - -BOOST_AUTO_TEST_CASE(default_compare_function) { - const int size = 128; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end()); - - BOOST_CHECK_EQUAL(a.size(), std::size_t(size)); - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i], (i + 1) * 10); - } -} - -enum id { foo, bar, baz }; -typedef std::pair pair_t; -bool less_in_first(pair_t x, pair_t y) { - return x.first < y.first; -} - -BOOST_AUTO_TEST_CASE(stability) { - std::vector a; - - for (int i = 100; i >= 0; --i) { - a.push_back(std::make_pair(i, foo)); - a.push_back(std::make_pair(i, bar)); - a.push_back(std::make_pair(i, baz)); - } - - timsort(a.begin(), a.end(), &less_in_first); - - BOOST_CHECK_EQUAL(a[0].first, 0); - BOOST_CHECK_EQUAL(a[0].second, foo); - BOOST_CHECK_EQUAL(a[1].first, 0); - BOOST_CHECK_EQUAL(a[1].second, bar); - BOOST_CHECK_EQUAL(a[2].first, 0); - BOOST_CHECK_EQUAL(a[2].second, baz); - - BOOST_CHECK_EQUAL(a[3].first, 1); - BOOST_CHECK_EQUAL(a[3].second, foo); - BOOST_CHECK_EQUAL(a[4].first, 1); - BOOST_CHECK_EQUAL(a[4].second, bar); - BOOST_CHECK_EQUAL(a[5].first, 1); - BOOST_CHECK_EQUAL(a[5].second, baz); - - BOOST_CHECK_EQUAL(a[6].first, 2); - BOOST_CHECK_EQUAL(a[6].second, foo); - BOOST_CHECK_EQUAL(a[7].first, 2); - BOOST_CHECK_EQUAL(a[7].second, bar); - BOOST_CHECK_EQUAL(a[8].first, 2); - BOOST_CHECK_EQUAL(a[8].second, baz); - - BOOST_CHECK_EQUAL(a[9].first, 3); - BOOST_CHECK_EQUAL(a[9].second, foo); - BOOST_CHECK_EQUAL(a[10].first, 3); - BOOST_CHECK_EQUAL(a[10].second, bar); - BOOST_CHECK_EQUAL(a[11].first, 3); - BOOST_CHECK_EQUAL(a[11].second, baz); -} - -bool less_in_pair(const std::pair &x, const std::pair &y) { - if (x.first == y.first) { - return x.second < y.second; - } - return x.first < y.first; -} - -BOOST_AUTO_TEST_CASE(issue2_compare) { - typedef std::pair p; - gfx::Compare c(&less_in_pair); - - BOOST_CHECK_EQUAL(c.lt(std::make_pair(10, 10), std::make_pair(10, 9)), false); - BOOST_CHECK_EQUAL(c.lt(std::make_pair(10, 10), std::make_pair(10, 10)), false); - BOOST_CHECK_EQUAL(c.lt(std::make_pair(10, 10), std::make_pair(10, 11)), true); - - BOOST_CHECK_EQUAL(c.le(std::make_pair(10, 10), std::make_pair(10, 9)), false); - BOOST_CHECK_EQUAL(c.le(std::make_pair(10, 10), std::make_pair(10, 10)), true); - BOOST_CHECK_EQUAL(c.le(std::make_pair(10, 10), std::make_pair(10, 11)), true); - - BOOST_CHECK_EQUAL(c.gt(std::make_pair(10, 10), std::make_pair(10, 9)), true); - BOOST_CHECK_EQUAL(c.gt(std::make_pair(10, 10), std::make_pair(10, 10)), false); - BOOST_CHECK_EQUAL(c.gt(std::make_pair(10, 10), std::make_pair(10, 11)), false); - - BOOST_CHECK_EQUAL(c.ge(std::make_pair(10, 10), std::make_pair(10, 9)), true); - BOOST_CHECK_EQUAL(c.ge(std::make_pair(10, 10), std::make_pair(10, 10)), true); - BOOST_CHECK_EQUAL(c.ge(std::make_pair(10, 10), std::make_pair(10, 11)), false); -} - -BOOST_AUTO_TEST_CASE(issue2_duplication) { - std::vector > a; - - for (int i = 0; i < 10000; ++i) { - int first = static_cast(rand()); - int second = static_cast(rand()); - - a.push_back(std::make_pair(first, second)); - } - - std::vector > expected(a); - - std::sort(expected.begin(), expected.end(), &less_in_pair); - timsort(a.begin(), a.end(), &less_in_pair); - - if (false) { - for (std::size_t i = 0; i < a.size(); ++i) { - std::clog << i << " "; - std::clog << "(" << a[i].first << ", " << a[i].second << ")"; - std::clog << " "; - std::clog << "(" << expected[i].first << ", " << expected[i].second << ") "; - std::clog << "\n"; - } - return; - } - - BOOST_CHECK_EQUAL(a.size(), expected.size()); - - // test some points - - BOOST_CHECK_EQUAL(a[0].first, expected[0].first); - BOOST_CHECK_EQUAL(a[0].second, expected[0].second); - - BOOST_CHECK_EQUAL(a[1].first, expected[1].first); - BOOST_CHECK_EQUAL(a[1].second, expected[1].second); - - BOOST_CHECK_EQUAL(a[10].first, expected[10].first); - BOOST_CHECK_EQUAL(a[10].second, expected[10].second); - - BOOST_CHECK_EQUAL(a[11].first, expected[11].first); - BOOST_CHECK_EQUAL(a[11].second, expected[11].second); - - BOOST_CHECK_EQUAL(a[100].first, expected[100].first); - BOOST_CHECK_EQUAL(a[100].second, expected[100].second); - - BOOST_CHECK_EQUAL(a[101].first, expected[101].first); - BOOST_CHECK_EQUAL(a[101].second, expected[101].second); - - BOOST_CHECK_EQUAL(a[111].first, expected[111].first); - BOOST_CHECK_EQUAL(a[111].second, expected[111].second); - - BOOST_CHECK_EQUAL(a[112].first, expected[112].first); - BOOST_CHECK_EQUAL(a[112].second, expected[112].second); - - BOOST_CHECK_EQUAL(a[999].first, expected[999].first); - BOOST_CHECK_EQUAL(a[999].second, expected[999].second); - - BOOST_CHECK_EQUAL(a[1000].first, expected[1000].first); - BOOST_CHECK_EQUAL(a[1000].second, expected[1000].second); - - BOOST_CHECK_EQUAL(a[1001].first, expected[1001].first); - BOOST_CHECK_EQUAL(a[1001].second, expected[1001].second); -} - -#if ENABLE_STD_MOVE && __cplusplus >= 201103L -template struct move_only { - // Constructors - - move_only() = delete; - move_only(const move_only &) = delete; - - move_only(const T &value) : can_read(true), value(value) { - } - - move_only(move_only &&other) : can_read(true), value(std::move(other.value)) { - if (not exchange(other.can_read, false)) { - std::cerr << "illegal read from a moved-from value\n"; - assert(false); - } - } - - // Assignment operators - - move_only &operator=(const move_only &) = delete; - - auto operator=(move_only &&other) -> move_only & { - if (&other != this) { - if (not exchange(other.can_read, false)) { - std::cerr << "illegal read from a moved-from value\n"; - assert(false); - } - can_read = true; - value = std::move(other.value); - } - return *this; - } - - // A C++11 backport of std::exchange() - - template U exchange(U &obj, U &&new_val) { - U old_val = std::move(obj); - obj = std::forward(new_val); - return old_val; - } - - // Whether the value can be read - bool can_read; - // Actual value - T value; -}; - -BOOST_AUTO_TEST_CASE(shuffle10k_for_move_only_types) { - const int size = 1024 * 10; // should be even number of elements - - std::vector > a; - for (int i = 0; i < size; ++i) { - a.push_back((i + 1) * 10); - } - - for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); - - timsort(a.begin(), a.end(), [](const move_only &x, const move_only &y) { return x.value < y.value; }); - - for (int i = 0; i < size; ++i) { - BOOST_CHECK_EQUAL(a[i].value, (i + 1) * 10); - } - } -} - -BOOST_AUTO_TEST_CASE(issue14) { - int a[] = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, - 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; - std::deque c(std::begin(a), std::end(a)); - - gfx::timsort(std::begin(c), std::end(c)); - BOOST_CHECK(std::is_sorted(std::begin(c), std::end(c))); -} - -#endif // if std::move available diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..46f6628 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,97 @@ +include(DownloadProject) + +# Download and configure Catch2 for the tests +download_project(PROJ Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2 + GIT_TAG Catch1.x + UPDATE_DISCONNECTED 1 +) +list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/contrib) + +# Configure Catch2 so that it looks like a proper target +add_library(Catch2 INTERFACE) +add_library(Catch2::Catch2 ALIAS Catch2) +target_include_directories(Catch2 INTERFACE + $ + $ +) + +# Testsutie options +option(GFX_TIMSORT_USE_VALGRIND "Whether to run the tests with Valgrind" OFF) +set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") + +macro(configure_tests target) + # Add required dependencies to tests + target_link_libraries(${target} PRIVATE + Catch2::Catch2 + gfx::timsort + ) + + # Somewhat speed up Catch2 compile times + target_compile_definitions(${target} PRIVATE + CATCH_CONFIG_FAST_COMPILE + CATCH_CONFIG_DISABLE_MATCHERS + ) + + # Add warnings + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wcast-align -Winline -Wmissing-declarations -Wmissing-include-dirs + -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code + $<$:-Wlogical-op -Wuseless-cast -Wzero-as-null-pointer-constant> + ) + + # Configure optimization options + target_compile_options(${target} PRIVATE + $<$,$>:-O0> + $<$,$>:-Og> + ) + + # Use lld or the gold linker if possible + if (UNIX AND NOT APPLE) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -fuse-ld=lld") + else() + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -fuse-ld=gold") + endif() + endif() + + # Optionally enable sanitizers + if (UNIX AND GFX_TIMSORT_SANITIZE) + target_compile_options(${target} PRIVATE + -fsanitize=${GFX_TIMSORT_SANITIZE} + -fno-sanitize-recover=all + ) + set_property(TARGET ${target} + APPEND_STRING PROPERTY LINK_FLAGS + " -fsanitize=${GFX_TIMSORT_SANITIZE}" + ) + endif() +endmacro() + +# Tests that can run with C++98 +add_executable(cxx_98_tests + main.cpp + cxx_98_tests.cpp +) +configure_tests(cxx_98_tests) +target_compile_features(cxx_98_tests PRIVATE cxx_std_98) + +# Tests that require C++11 support to run +add_executable(cxx_11_tests + main.cpp + cxx_11_tests.cpp +) +configure_tests(cxx_11_tests) +target_compile_features(cxx_11_tests PRIVATE cxx_std_11) + +# Configure Valgrind +if (${GFX_TIMSORT_USE_VALGRIND}) + find_program(MEMORYCHECK_COMMAND valgrind) + set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") +endif() + +include(CTest) +include(ParseAndAddCatchTests) + +ParseAndAddCatchTests(cxx_98_tests) +ParseAndAddCatchTests(cxx_11_tests) diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp new file mode 100644 index 0000000..74d6133 --- /dev/null +++ b/tests/cxx_11_tests.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include + +template struct move_only { + // Constructors + + move_only() = delete; + move_only(const move_only &) = delete; + + move_only(const T &value) : can_read(true), value(value) { + } + + move_only(move_only &&other) : can_read(true), value(std::move(other.value)) { + if (not exchange(other.can_read, false)) { + std::cerr << "illegal read from a moved-from value\n"; + assert(false); + } + } + + // Assignment operators + + move_only &operator=(const move_only &) = delete; + + auto operator=(move_only &&other) -> move_only & { + if (&other != this) { + if (not exchange(other.can_read, false)) { + std::cerr << "illegal read from a moved-from value\n"; + assert(false); + } + can_read = true; + value = std::move(other.value); + } + return *this; + } + + // A C++11 backport of std::exchange() + + template U exchange(U &obj, U &&new_val) { + U old_val = std::move(obj); + obj = std::forward(new_val); + return old_val; + } + + // Whether the value can be read + bool can_read; + // Actual value + T value; +}; + +TEST_CASE( "shuffle10k_for_move_only_types" ) { + const int size = 1024 * 10; // should be even number of elements + + std::vector > a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), [](const move_only &x, const move_only &y) { return x.value < y.value; }); + + for (int i = 0; i < size; ++i) { + CHECK(a[i].value == (i + 1) * 10); + } + } +} + +TEST_CASE( "issue14" ) { + int a[] = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, + 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; + std::deque c(std::begin(a), std::end(a)); + + gfx::timsort(std::begin(c), std::end(c)); + CHECK(std::is_sorted(std::begin(c), std::end(c))); +} diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp new file mode 100644 index 0000000..ee05d1b --- /dev/null +++ b/tests/cxx_98_tests.cpp @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "simple0" ) { + std::vector a; + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(0)); +} + +TEST_CASE( "simple1" ) { + std::vector a; + + a.push_back(42); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(1)); + CHECK(a[0] == 42); +} + +TEST_CASE( "simple2" ) { + std::vector a; + + a.push_back(10); + a.push_back(20); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(2)); + CHECK(a[0] == 10); + CHECK(a[1] == 20); + + a.clear(); + a.push_back(20); + a.push_back(10); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(2)); + CHECK(a[0] == 10); + CHECK(a[1] == 20); + + a.clear(); + a.push_back(10); + a.push_back(10); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(2)); + CHECK(a[0] == 10); + CHECK(a[1] == 10); +} + +TEST_CASE( "simple10" ) { + std::vector a; + a.push_back(60); + a.push_back(50); + a.push_back(10); + a.push_back(40); + a.push_back(80); + a.push_back(20); + a.push_back(30); + a.push_back(70); + a.push_back(10); + a.push_back(90); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a[0] == 10); + CHECK(a[1] == 10); + CHECK(a[2] == 20); + CHECK(a[3] == 30); + CHECK(a[4] == 40); + CHECK(a[5] == 50); + CHECK(a[6] == 60); + CHECK(a[7] == 70); + CHECK(a[8] == 80); + CHECK(a[9] == 90); + + std::reverse(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a[0] == 10); + CHECK(a[1] == 10); + CHECK(a[2] == 20); + CHECK(a[3] == 30); + CHECK(a[4] == 40); + CHECK(a[5] == 50); + CHECK(a[6] == 60); + CHECK(a[7] == 70); + CHECK(a[8] == 80); + CHECK(a[9] == 90); +} + +TEST_CASE( "shuffle30" ) { + const int size = 30; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +TEST_CASE( "shuffle31" ) { + const int size = 31; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +TEST_CASE( "shuffle32" ) { + const int size = 32; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +TEST_CASE( "shuffle128" ) { + const int size = 128; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +TEST_CASE( "shuffle1023" ) { + const int size = 1023; // odd number of elements + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } +} + +TEST_CASE( "shuffle1024" ) { + const int size = 1024; // should be even number of elements + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } +} + +TEST_CASE( "partial_shuffle1023" ) { + const int size = 1023; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + // sorted-shuffled-sorted pattern + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } + + // shuffled-sorted-shuffled pattern + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); + std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } +} + +TEST_CASE( "partial_shuffle1024" ) { + const int size = 1024; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + // sorted-shuffled-sorted pattern + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } + + // shuffled-sorted-shuffled pattern + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); + std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } +} + +TEST_CASE( "shuffle1024r" ) { + const int size = 1024; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end(), std::greater()); + + int j = size; + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (--j + 1) * 10); + } + } +} + +TEST_CASE( "partial_reversed1023" ) { + const int size = 1023; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + for (int n = 0; n < 100; ++n) { + std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } +} + +TEST_CASE( "partial_reversed1024" ) { + const int size = 1024; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + for (int n = 0; n < 100; ++n) { + std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed + + gfx::timsort(a.begin(), a.end(), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } +} + +TEST_CASE( "c_array" ) { + int a[] = {7, 1, 5, 3, 9}; + + gfx::timsort(a, a + sizeof(a) / sizeof(int), std::less()); + + CHECK(a[0] == 1); + CHECK(a[1] == 3); + CHECK(a[2] == 5); + CHECK(a[3] == 7); + CHECK(a[4] == 9); +} + +TEST_CASE( "string_array" ) { + std::string a[] = {"7", "1", "5", "3", "9"}; + + gfx::timsort(a, a + sizeof(a) / sizeof(std::string), std::less()); + + CHECK(a[0] == "1"); + CHECK(a[1] == "3"); + CHECK(a[2] == "5"); + CHECK(a[3] == "7"); + CHECK(a[4] == "9"); +} + +struct NonDefaultConstructible { + int i; + + NonDefaultConstructible(int i_) : i(i_) { + } + + friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { + return x.i < y.i; + } +}; + +TEST_CASE( "non_default_constructible" ) { + NonDefaultConstructible a[] = {7, 1, 5, 3, 9}; + + gfx::timsort(a, a + sizeof(a) / sizeof(a[0]), std::less()); + + CHECK(a[0].i == 1); + CHECK(a[1].i == 3); + CHECK(a[2].i == 5); + CHECK(a[3].i == 7); + CHECK(a[4].i == 9); +} + +TEST_CASE( "default_compare_function" ) { + const int size = 128; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + std::random_shuffle(a.begin(), a.end()); + + gfx::timsort(a.begin(), a.end()); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +enum id { foo, bar, baz }; +typedef std::pair pair_t; +bool less_in_first(pair_t x, pair_t y) { + return x.first < y.first; +} + +TEST_CASE( "stability" ) { + std::vector a; + + for (int i = 100; i >= 0; --i) { + a.push_back(std::make_pair(i, foo)); + a.push_back(std::make_pair(i, bar)); + a.push_back(std::make_pair(i, baz)); + } + + gfx::timsort(a.begin(), a.end(), &less_in_first); + + CHECK(a[0].first == 0); + CHECK(a[0].second == foo); + CHECK(a[1].first == 0); + CHECK(a[1].second == bar); + CHECK(a[2].first == 0); + CHECK(a[2].second == baz); + + CHECK(a[3].first == 1); + CHECK(a[3].second == foo); + CHECK(a[4].first == 1); + CHECK(a[4].second == bar); + CHECK(a[5].first == 1); + CHECK(a[5].second == baz); + + CHECK(a[6].first == 2); + CHECK(a[6].second == foo); + CHECK(a[7].first == 2); + CHECK(a[7].second == bar); + CHECK(a[8].first == 2); + CHECK(a[8].second == baz); + + CHECK(a[9].first == 3); + CHECK(a[9].second == foo); + CHECK(a[10].first == 3); + CHECK(a[10].second == bar); + CHECK(a[11].first == 3); + CHECK(a[11].second == baz); +} + +bool less_in_pair(const std::pair &x, const std::pair &y) { + if (x.first == y.first) { + return x.second < y.second; + } + return x.first < y.first; +} + +TEST_CASE( "issue2_compare" ) { + typedef std::pair p; + gfx::Compare c(&less_in_pair); + + CHECK_FALSE(c.lt(std::make_pair(10, 10), std::make_pair(10, 9))); + CHECK_FALSE(c.lt(std::make_pair(10, 10), std::make_pair(10, 10))); + CHECK(c.lt(std::make_pair(10, 10), std::make_pair(10, 11))); + + CHECK_FALSE(c.le(std::make_pair(10, 10), std::make_pair(10, 9))); + CHECK(c.le(std::make_pair(10, 10), std::make_pair(10, 10))); + CHECK(c.le(std::make_pair(10, 10), std::make_pair(10, 11))); + + CHECK(c.gt(std::make_pair(10, 10), std::make_pair(10, 9))); + CHECK_FALSE(c.gt(std::make_pair(10, 10), std::make_pair(10, 10))); + CHECK_FALSE(c.gt(std::make_pair(10, 10), std::make_pair(10, 11))); + + CHECK(c.ge(std::make_pair(10, 10), std::make_pair(10, 9))); + CHECK(c.ge(std::make_pair(10, 10), std::make_pair(10, 10))); + CHECK_FALSE(c.ge(std::make_pair(10, 10), std::make_pair(10, 11))); +} + +TEST_CASE( "issue2_duplication" ) { + std::vector > a; + + for (int i = 0; i < 10000; ++i) { + int first = static_cast(rand()); + int second = static_cast(rand()); + + a.push_back(std::make_pair(first, second)); + } + + std::vector > expected(a); + + std::sort(expected.begin(), expected.end(), &less_in_pair); + gfx::timsort(a.begin(), a.end(), &less_in_pair); + + if (false) { + for (std::size_t i = 0; i < a.size(); ++i) { + std::clog << i << " "; + std::clog << "(" << a[i].first << ", " << a[i].second << ")"; + std::clog << " "; + std::clog << "(" << expected[i].first << ", " << expected[i].second << ") "; + std::clog << "\n"; + } + return; + } + + CHECK(a.size() == expected.size()); + + // test some points + + CHECK(a[0].first == expected[0].first); + CHECK(a[0].second == expected[0].second); + + CHECK(a[1].first == expected[1].first); + CHECK(a[1].second == expected[1].second); + + CHECK(a[10].first == expected[10].first); + CHECK(a[10].second == expected[10].second); + + CHECK(a[11].first == expected[11].first); + CHECK(a[11].second == expected[11].second); + + CHECK(a[100].first == expected[100].first); + CHECK(a[100].second == expected[100].second); + + CHECK(a[101].first == expected[101].first); + CHECK(a[101].second == expected[101].second); + + CHECK(a[111].first == expected[111].first); + CHECK(a[111].second == expected[111].second); + + CHECK(a[112].first == expected[112].first); + CHECK(a[112].second == expected[112].second); + + CHECK(a[999].first == expected[999].first); + CHECK(a[999].second == expected[999].second); + + CHECK(a[1000].first == expected[1000].first); + CHECK(a[1000].second == expected[1000].second); + + CHECK(a[1001].first == expected[1001].first); + CHECK(a[1001].second == expected[1001].second); +} diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..435b9a1 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#define CATCH_CONFIG_MAIN +#include diff --git a/valgrind/issue14.cpp b/valgrind/issue14.cpp deleted file mode 100644 index 68c308e..0000000 --- a/valgrind/issue14.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "timsort.hpp" -#include -#include - -int main() { - std::deque collection = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, - 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; - - gfx::timsort(std::begin(collection), std::end(collection)); - assert(std::is_sorted(std::begin(collection), std::end(collection))); - - return 0; -} From 8177c3e812d2cd45dc06dcb8299b03728c671ff3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 22 Sep 2019 22:32:17 +0200 Subject: [PATCH 029/137] Don't build examples (they need Boost) --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dbdfe22..2d2c8f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -147,7 +147,6 @@ script: -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} -G"${CMAKE_GENERATOR}" -DCMAKE_SH="CMAKE_SH-NOTFOUND" - -DBUILD_EXAMPLES=ON - if [[ $TRAVIS_OS_NAME = "osx" ]]; then cmake --build build --config ${BUILD_TYPE} -j 2; else From c26b2e032e782da0ba1971565d365b2cb2b344a0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 22 Sep 2019 23:07:12 +0200 Subject: [PATCH 030/137] Btter detection of move semantics --- include/gfx/timsort.hpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 1ba0a5d..62d82f4 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -48,13 +48,19 @@ #ifdef GFX_TIMSORT_DISABLE_STD_MOVE # define GFX_TIMSORT_ENABLED_STD_MOVE 0 # undef GFX_TIMSORT_DISABLE_STD_MOVE -#else -# if (defined(_MSC_VER) && _MSC_VER >= 1700) || ((defined(__cplusplus) && __cplusplus >= 201103L && !defined(_LIBCPP_VERSION)) && ((!defined(__GNUC__) || __GNUC__ >= 5)) && (!defined(__GLIBCXX__) || __GLIBCXX__ >= 20150422)) +#elif !defined(GFX_TIMSORT_ENABLED_STD_MOVE) +# if !(defined(__cplusplus) && __cplusplus >= 201103L) +# define GFX_TIMSORT_ENABLED_STD_MOVE 0 +# elif defined(_MSC_VER) && _MSC_VER >= 1700 +# define GFX_TIMSORT_ENABLED_STD_MOVE 1 +# elif defined(__CLANG__) +# define GFX_TIMSORT_ENABLED_STD_MOVE 1 +# elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && __GNUC_MINOR__ >= 6)) # define GFX_TIMSORT_ENABLED_STD_MOVE 1 # else # define GFX_TIMSORT_ENABLED_STD_MOVE 0 # endif -#endif // GFX_TIMSORT_DISABLE_STD_MOVE +#endif #if GFX_TIMSORT_ENABLED_STD_MOVE #include From b97bd63efd316fa08ac5e2eee8d034a532338181 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 22 Sep 2019 23:16:13 +0200 Subject: [PATCH 031/137] Fix Clang detection --- include/gfx/timsort.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 62d82f4..65df182 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -53,7 +53,7 @@ # define GFX_TIMSORT_ENABLED_STD_MOVE 0 # elif defined(_MSC_VER) && _MSC_VER >= 1700 # define GFX_TIMSORT_ENABLED_STD_MOVE 1 -# elif defined(__CLANG__) +# elif defined(__clang__) # define GFX_TIMSORT_ENABLED_STD_MOVE 1 # elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && __GNUC_MINOR__ >= 6)) # define GFX_TIMSORT_ENABLED_STD_MOVE 1 From b40bb2cadb6ac68c2279a61d7db1b77efbb87463 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 22 Sep 2019 23:32:48 +0200 Subject: [PATCH 032/137] Suppress Valgrind false positives on OSX --- tests/CMakeLists.txt | 3 +++ tests/valgrind-osx.supp | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/valgrind-osx.supp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46f6628..23dc3e2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,6 +88,9 @@ target_compile_features(cxx_11_tests PRIVATE cxx_std_11) if (${GFX_TIMSORT_USE_VALGRIND}) find_program(MEMORYCHECK_COMMAND valgrind) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") + if (APPLE) + set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/valgrind-osx.supp) + endif() endif() include(CTest) diff --git a/tests/valgrind-osx.supp b/tests/valgrind-osx.supp new file mode 100644 index 0000000..569fd28 --- /dev/null +++ b/tests/valgrind-osx.supp @@ -0,0 +1,29 @@ +{ + Mac-OS-X-System-Leaks + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + fun:map_images_nolock + fun:map_images + fun:_ZN4dyldL18notifyBatchPartialE17dyld_image_statesbPFPKcS0_jPK15dyld_image_infoEbb + fun:_ZN4dyld21registerObjCNotifiersEPFvjPKPKcPKPK11mach_headerEPFvS1_S6_ESC_ + fun:_dyld_objc_notify_register + fun:_objc_init + fun:_os_object_init + fun:libdispatch_init + fun:libSystem_initializer + fun:_ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE + fun:_ZN16ImageLoaderMachO16doInitializationERKN11ImageLoader11LinkContextE + fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE + fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE + fun:_ZN11ImageLoader19processInitializersERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE + fun:_ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE + fun:_ZN4dyld24initializeMainExecutableEv + fun:_ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm + fun:_ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm + fun:_dyld_start + obj:* + obj:* + obj:* + obj:* +} \ No newline at end of file From 563ed6a37640ef887769013cb3b317865ffdb3c5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 23 Sep 2019 22:30:15 +0200 Subject: [PATCH 033/137] Kill warnings in the tests --- tests/cxx_98_tests.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index ee05d1b..11560ef 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -399,8 +399,10 @@ TEST_CASE( "default_compare_function" ) { } enum id { foo, bar, baz }; + typedef std::pair pair_t; -bool less_in_first(pair_t x, pair_t y) { + +inline bool less_in_first(pair_t x, pair_t y) { return x.first < y.first; } @@ -444,7 +446,7 @@ TEST_CASE( "stability" ) { CHECK(a[11].second == baz); } -bool less_in_pair(const std::pair &x, const std::pair &y) { +inline bool less_in_pair(const std::pair &x, const std::pair &y) { if (x.first == y.first) { return x.second < y.second; } @@ -487,7 +489,7 @@ TEST_CASE( "issue2_duplication" ) { std::sort(expected.begin(), expected.end(), &less_in_pair); gfx::timsort(a.begin(), a.end(), &less_in_pair); - if (false) { +#if 0 for (std::size_t i = 0; i < a.size(); ++i) { std::clog << i << " "; std::clog << "(" << a[i].first << ", " << a[i].second << ")"; @@ -496,7 +498,7 @@ TEST_CASE( "issue2_duplication" ) { std::clog << "\n"; } return; - } +#endif CHECK(a.size() == expected.size()); From e5a19852e1ea72b47c2a2400446c7d6bc0e4620b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 24 Sep 2019 00:19:37 +0200 Subject: [PATCH 034/137] Enhance rvalue references detection, update README --- README.md | 44 ++++++++++++++++++++--------------------- include/gfx/timsort.hpp | 37 ++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 90c670a..1c92331 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,43 @@ -TimSort [![Build Status](https://travis-ci.org/gfx/cpp-TimSort.svg?branch=master)](https://travis-ci.org/gfx/cpp-TimSort) -================== +## TimSort [![Build Status](https://travis-ci.org/gfx/cpp-TimSort.svg?branch=master)](https://travis-ci.org/gfx/cpp-TimSort) -A C++ implementation of TimSort, O(n log n) in worst case and stable sort algorithm, ported from Python's and OpenJDK's. +A C++ implementation of TimSort, an O(n log n) stable sorting algorithm, ported from Python's and OpenJDK's. According to benchmark, this is a bit slower than `std::sort()` on randomized sequences, but much faster on partially-sorted sequences. -SYNOPSIS -================== +## SYNOPSIS - #include "timsort.hpp" +```cpp +#include +#include +#include - std::vector a; +std::vector a; +// ... initialize a ... +gfx::timsort(a.begin(), a.end(), std::less()); +``` - // initialize a +## TEST - gfx::timsort(a.begin(), a.end(), std::less()); - -TEST -================== - -The tests are written with Catch (branch 1.x) and can be compiled with CMake. +The tests are written with Catch2 (branch 1.x) and can be compiled with CMake and run through CTest. TODO: describe CMake support -COMPATIBILITY -================== +## COMPATIBILITY -This library is compatible with C++98, but if you compile it with C++11 or later, this library uses `std::move()` instead of value copy and thus you can sort move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). +This library is compatible with C++98, but if you compile it with C++11 or later it will try to use `std::move()` +when possible instead of copying vaues around, which notably allows to sort collections of move-only types (see +[#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). -You can explicity disable the use of `std::move()` by passing the macro '-DGFX_TIMSORT_DISABLE_STD_MOVE'. +You can explicity control the use of `std::move()` by setting the macro `GFX_TIMSORT_USE_STD_MOVE` to `0` or `1`. -SEE ALSO -================== +## SEE ALSO * http://svn.python.org/projects/python/trunk/Objects/listsort.txt * http://en.wikipedia.org/wiki/Timsort -BENCHMARK -================== +## BENCHMARK + +TODO: integrate benchmarks to CMake, update documentation bench.cpp, invoked by `make bench`, is a simple benchmark. An example output is as follows (timing scale: sec.): diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 65df182..2b359e5 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -43,26 +43,33 @@ # define GFX_TIMSORT_LOG(expr) ((void)0) #endif -// Conditionally enable/disable move semantics - -#ifdef GFX_TIMSORT_DISABLE_STD_MOVE -# define GFX_TIMSORT_ENABLED_STD_MOVE 0 -# undef GFX_TIMSORT_DISABLE_STD_MOVE -#elif !defined(GFX_TIMSORT_ENABLED_STD_MOVE) -# if !(defined(__cplusplus) && __cplusplus >= 201103L) -# define GFX_TIMSORT_ENABLED_STD_MOVE 0 +// If GFX_TIMSORT_USE_STD_MOVE is not defined, try to define it as follows: +// - Check standard feature-testing macro +// - Check non-standard feature-testing macro +// - Check C++ standard (disable if < C++11) +// - Check compiler-specific versions known to support move semantics + +#ifndef GFX_TIMSORT_USE_STD_MOVE +# if defined(__cpp_rvalue_references) +# define GFX_TIMSORT_USE_STD_MOVE 1 +# elif defined(__has_feature) +# if __has_feature(cxx_rvalue_references) +# define GFX_TIMSORT_USE_STD_MOVE 1 +# else +# define GFX_TIMSORT_USE_STD_MOVE 0 +# endif +# elif !(defined(__cplusplus) && __cplusplus >= 201103L) +# define GFX_TIMSORT_USE_STD_MOVE 0 # elif defined(_MSC_VER) && _MSC_VER >= 1700 -# define GFX_TIMSORT_ENABLED_STD_MOVE 1 -# elif defined(__clang__) -# define GFX_TIMSORT_ENABLED_STD_MOVE 1 +# define GFX_TIMSORT_USE_STD_MOVE 1 # elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && __GNUC_MINOR__ >= 6)) -# define GFX_TIMSORT_ENABLED_STD_MOVE 1 +# define GFX_TIMSORT_USE_STD_MOVE 1 # else -# define GFX_TIMSORT_ENABLED_STD_MOVE 0 +# define GFX_TIMSORT_USE_STD_MOVE 0 # endif #endif -#if GFX_TIMSORT_ENABLED_STD_MOVE +#if GFX_TIMSORT_USE_STD_MOVE #include #define GFX_TIMSORT_MOVE(x) std::move(x) #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::move((in1), (in2), (out)); @@ -681,6 +688,6 @@ inline void timsort(RandomAccessIterator const first, RandomAccessIterator const #undef GFX_TIMSORT_MOVE #undef GFX_TIMSORT_MOVE_RANGE #undef GFX_TIMSORT_MOVE_BACKWARD -#undef GFX_TIMSORT_ENABLED_STD_MOVE +#undef GFX_TIMSORT_USE_STD_MOVE #endif // GFX_TIMSORT_HPP From be562f2cf2c1fc5bcb20adf3c4bb7b547cb39fc6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 24 Sep 2019 12:21:33 +0200 Subject: [PATCH 035/137] Deactivate Valgrind build on OSX We need a suppression file on OSX to suppress some Valgrind false positives, but ParseAndAddCatchTests doesn't properly forward the suppression file information so we can't make the build pass anyway. The problem will be solved in the 2.0 version of the library thanks to catch_discover_tests, which does it properly. [ci skip] --- .travis.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2d2c8f2..c4110b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,17 +101,17 @@ matrix: compiler: gcc # OSX clang - - os: osx - osx_image: xcode9.2 - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Xcode" - addons: - homebrew: - update: true - packages: - - ccache - - cmake - - valgrind - compiler: clang +# - os: osx +# osx_image: xcode9.2 +# env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Xcode" +# addons: +# homebrew: +# update: true +# packages: +# - ccache +# - cmake +# - valgrind +# compiler: clang - os: osx osx_image: xcode8.3 From 2321465a49ab0bd2dfb9624811025eafb3c86d6f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 24 Sep 2019 15:43:59 +0200 Subject: [PATCH 036/137] MSVC support for tooling --- .travis.yml | 33 +++++++++++++++++++++++---------- tests/CMakeLists.txt | 12 +++++++----- tests/cxx_11_tests.cpp | 4 ++-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index c4110b3..7fb7b6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -126,28 +126,41 @@ matrix: # Windows GCC - os: windows - language: sh env: BUILD_TYPE=Debug CMAKE_GENERATOR="MinGW Makefiles" + language: sh compiler: gcc - os: windows - language: sh env: BUILD_TYPE=Release CMAKE_GENERATOR="MinGW Makefiles" + language: sh compiler: gcc + # Windows MSVC (using a pseudo-generator) + - os: windows + env: BUILD_TYPE=Debug CMAKE_GENERATOR="MSVC" + compiler: msvc + + - os: windows + env: BUILD_TYPE=Release CMAKE_GENERATOR="MSVC" + compiler: msvc + install: - if [[ $CXX = "g++" ]]; then export CXX="g++-5"; fi - if [[ $CXX = "clang++" ]]; then export CXX="clang++-3.8"; fi script: - - cmake -H. -Bbuild - -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" - -DGFX_TIMSORT_SANITIZE="${SANITIZE}" - -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} - -G"${CMAKE_GENERATOR}" - -DCMAKE_SH="CMAKE_SH-NOTFOUND" - - if [[ $TRAVIS_OS_NAME = "osx" ]]; then + - if [[ $CMAKE_GENERATOR = "MSVC" ]]; then + cmake -H. -Bbuild -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}"; + else + cmake -H. -Bbuild + -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" + -DGFX_TIMSORT_SANITIZE="${SANITIZE}" + -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} + -G"${CMAKE_GENERATOR}" + -DCMAKE_SH="CMAKE_SH-NOTFOUND"; + fi + - if [[ $CMAKE_GENERATOR = "MSVC" || $TRAVIS_OS_NAME = "osx" ]]; then cmake --build build --config ${BUILD_TYPE} -j 2; else cmake --build build --config ${BUILD_TYPE} -- -j2; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23dc3e2..2fd1956 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,11 +34,13 @@ macro(configure_tests target) ) # Add warnings - target_compile_options(${target} PRIVATE - -Wall -Wextra -Wcast-align -Winline -Wmissing-declarations -Wmissing-include-dirs - -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code - $<$:-Wlogical-op -Wuseless-cast -Wzero-as-null-pointer-constant> - ) + if (NOT MSVC) + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wcast-align -Winline -Wmissing-declarations -Wmissing-include-dirs + -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code + $<$:-Wlogical-op -Wuseless-cast -Wzero-as-null-pointer-constant> + ) + endif() # Configure optimization options target_compile_options(${target} PRIVATE diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index 74d6133..e15c926 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -22,7 +22,7 @@ template struct move_only { } move_only(move_only &&other) : can_read(true), value(std::move(other.value)) { - if (not exchange(other.can_read, false)) { + if (!exchange(other.can_read, false)) { std::cerr << "illegal read from a moved-from value\n"; assert(false); } @@ -34,7 +34,7 @@ template struct move_only { auto operator=(move_only &&other) -> move_only & { if (&other != this) { - if (not exchange(other.can_read, false)) { + if (!exchange(other.can_read, false)) { std::cerr << "illegal read from a moved-from value\n"; assert(false); } From 7b37de840f00ef067020b6ce1f5bfbed109d5167 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 25 Sep 2019 22:34:05 +0200 Subject: [PATCH 037/137] Make benchmarks dependent on the standard library only --- .travis.yml | 7 ++- CMakeLists.txt | 8 ++-- {examples => benchmarks}/CMakeLists.txt | 0 {examples => benchmarks}/bench.cpp | 63 +++++++++++++++++-------- 4 files changed, 53 insertions(+), 25 deletions(-) rename {examples => benchmarks}/CMakeLists.txt (100%) rename {examples => benchmarks}/bench.cpp (60%) diff --git a/.travis.yml b/.travis.yml index 7fb7b6c..54983a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -150,7 +150,9 @@ install: script: - if [[ $CMAKE_GENERATOR = "MSVC" ]]; then - cmake -H. -Bbuild -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}"; + cmake -H. -Bbuild + -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" + -DBUILD_BENCHMARKS=ON; else cmake -H. -Bbuild -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" @@ -158,7 +160,8 @@ script: -DGFX_TIMSORT_SANITIZE="${SANITIZE}" -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} -G"${CMAKE_GENERATOR}" - -DCMAKE_SH="CMAKE_SH-NOTFOUND"; + -DCMAKE_SH="CMAKE_SH-NOTFOUND" + -DBUILD_BENCHMARKS=ON; fi - if [[ $CMAKE_GENERATOR = "MSVC" || $TRAVIS_OS_NAME = "osx" ]]; then cmake --build build --config ${BUILD_TYPE} -j 2; diff --git a/CMakeLists.txt b/CMakeLists.txt index a568f35..74b82d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ include(GNUInstallDirs) # Project options option(BUILD_TESTING "Build the tests" ON) -option(BUILD_EXAMPLES "Build the examples" OFF) +option(BUILD_BENCHMARKS "Build the benchmarks" OFF) # Create gfx::timsort as an interface library add_library(timsort INTERFACE) @@ -73,14 +73,14 @@ export( NAMESPACE gfx:: ) -# Build tests and/or examples if this is the main project +# Build tests and/or benchmarks if this is the main project if (PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) if (BUILD_TESTING) enable_testing() add_subdirectory(tests) endif() - if (BUILD_EXAMPLES) - add_subdirectory(examples) + if (BUILD_BENCHMARKS) + add_subdirectory(benchmarks) endif() endif() diff --git a/examples/CMakeLists.txt b/benchmarks/CMakeLists.txt similarity index 100% rename from examples/CMakeLists.txt rename to benchmarks/CMakeLists.txt diff --git a/examples/bench.cpp b/benchmarks/bench.cpp similarity index 60% rename from examples/bench.cpp rename to benchmarks/bench.cpp index 8dda35d..6728c6a 100644 --- a/examples/bench.cpp +++ b/benchmarks/bench.cpp @@ -1,26 +1,48 @@ -#include -#include -#include -#include +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#include #include #include -#include -#include - -#include - -#include "timsort.hpp" +#include +#include +#include +#include +#include +#include using namespace gfx; enum state_t { sorted, randomized, reversed }; -template static void bench(int const size, state_t const state) { +template +struct convert_to +{ + static T from(int value) { + return T(value); + } +}; + +template <> +struct convert_to +{ + static std::string from(int value) { + std::ostringstream ss; + ss << value; + return ss.str(); + } +}; + +template +static void bench(int size, state_t const state) { std::cerr << "size\t" << size << std::endl; std::vector a; for (int i = 0; i < size; ++i) { - a.push_back(boost::lexical_cast((i + 1) * 10)); + a.push_back(convert_to::from((i + 1) * 10)); } switch (state) { @@ -40,38 +62,41 @@ template static void bench(int const size, state_t const stat { std::vector b(a); - boost::timer t; + std::clock_t start = std::clock(); for (int i = 0; i < 100; ++i) { std::copy(a.begin(), a.end(), b.begin()); std::sort(b.begin(), b.end()); } + std::clock_t stop = std::clock(); - std::cerr << "std::sort " << t.elapsed() << std::endl; + std::cerr << "std::sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; } { std::vector b(a); - boost::timer t; + std::clock_t start = std::clock(); for (int i = 0; i < 100; ++i) { std::copy(a.begin(), a.end(), b.begin()); std::stable_sort(b.begin(), b.end()); } + std::clock_t stop = clock(); - std::cerr << "std::stable_sort " << t.elapsed() << std::endl; + std::cerr << "std::stable_sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; } { std::vector b(a); - boost::timer t; + std::clock_t start = std::clock(); for (int i = 0; i < 100; ++i) { std::copy(a.begin(), a.end(), b.begin()); timsort(b.begin(), b.end()); } + std::clock_t stop = std::clock(); - std::cerr << "timsort " << t.elapsed() << std::endl; + std::cerr << "timsort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; } } @@ -84,7 +109,7 @@ static void doit(int const n, state_t const state) { } int main(int argc, const char *argv[]) { - const int N = argc > 1 ? boost::lexical_cast(argv[1]) : 100 * 1000; + const int N = argc > 1 ? std::atoi(argv[1]) : 100 * 1000; std::cerr << std::setprecision(6) << std::setiosflags(std::ios::fixed); From 9d37b76c2ecc18daf1ff60f0c271c858c44f2787 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 26 Sep 2019 23:28:09 +0200 Subject: [PATCH 038/137] Update README [ci skip] --- README.md | 51 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1c92331..81392b7 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,15 @@ A C++ implementation of TimSort, an O(n log n) stable sorting algorithm, ported from Python's and OpenJDK's. -According to benchmark, this is a bit slower than `std::sort()` on randomized sequences, but much faster on partially-sorted sequences. +See also the following links for a detailed description of TimSort: +* http://svn.python.org/projects/python/trunk/Objects/listsort.txt +* http://en.wikipedia.org/wiki/Timsort -## SYNOPSIS +According to the benchmarks, it is a bit slower than `std::sort()` on randomized sequences, but much faster on +partially-sorted ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_sort`, with the +difference that it's can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. + +## EXAMPLE ```cpp #include @@ -16,12 +22,6 @@ std::vector a; gfx::timsort(a.begin(), a.end(), std::less()); ``` -## TEST - -The tests are written with Catch2 (branch 1.x) and can be compiled with CMake and run through CTest. - -TODO: describe CMake support - ## COMPATIBILITY This library is compatible with C++98, but if you compile it with C++11 or later it will try to use `std::move()` @@ -30,17 +30,38 @@ when possible instead of copying vaues around, which notably allows to sort coll You can explicity control the use of `std::move()` by setting the macro `GFX_TIMSORT_USE_STD_MOVE` to `0` or `1`. -## SEE ALSO +The library has been tested with the following compilers: +* GCC 5 +* Clang 3.8 +* The Clang version that ships with Xcode 8.3 +* MSVC 2017 update 9 -* http://svn.python.org/projects/python/trunk/Objects/listsort.txt -* http://en.wikipedia.org/wiki/Timsort +It should also work with more recent compilers, and most likely with some older compilers too. + +The library can be installed on the system via CMake with the following commands: + +```sh +cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release +cd build +make install +``` + +## TESTS + +The tests are written with Catch2 (branch 1.x) and can be compiled with CMake and run through CTest. + +When using the project's main `CMakeLists.txt`, the CMake variable `BUILD_TESTING` is `ON` by default unless the +project is included as a subdirectory. The following CMake variables are available to change the way the tests are +built with CMake: +* `GFX_TIMSORT_USE_VALGRIND`: if `ON`, the tests will be run through Valgrind (`OFF` by default) +* `GFX_TIMSORT_SANITIZE`: this variable takes a comma-separated list of sanitizers options to run the tests (empty by default) -## BENCHMARK +## BENCHMARKS -TODO: integrate benchmarks to CMake, update documentation +Benchmarks are available in the `benchmarks` subdirectory, and can be constructed directly by passing `BUILD_BENCHMARKS=ON` +variable to CMake during the configuration step. -bench.cpp, invoked by `make bench`, is a simple benchmark. -An example output is as follows (timing scale: sec.): +Example output (timing scale: sec.): c++ -v Apple LLVM version 7.0.0 (clang-700.0.72) From 4bd3d9222fffcb5ee2eca2a5b69ff8c282334f67 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 28 Sep 2019 19:11:20 +0200 Subject: [PATCH 039/137] More complete move_only implementation for the tests --- tests/cxx_11_tests.cpp | 121 ++++++++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index e15c926..35554fb 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -7,56 +7,111 @@ #include #include #include +#include #include #include #include #include -template struct move_only { - // Constructors - - move_only() = delete; - move_only(const move_only &) = delete; +//////////////////////////////////////////////////////////// +// Move-only type for benchmarks +// +// std::sort and std::stable_sort are supposed to be able to +// sort collections of types that are move-only and that are +// not default-constructible. The class template move_only +// wraps such a type and can be fed to algorithms to check +// whether they still compile. +// +// Additionally, move_only detects attempts to read the value +// after a move has been performed and throws an exceptions +// when it happens. +// +// It also checks that no self-move is performed, since the +// standard algorithms can't rely on that to work either. +// + +namespace +{ + template struct move_only { + // Not default-constructible + move_only() = delete; + + // Move-only + move_only(const move_only &) = delete; + move_only& operator=(const move_only &) = delete; + + // Can be constructed from a T for convenience + move_only(const T &value) : can_read(true), value(value) { + } - move_only(const T &value) : can_read(true), value(value) { - } + // Move operators - move_only(move_only &&other) : can_read(true), value(std::move(other.value)) { - if (!exchange(other.can_read, false)) { - std::cerr << "illegal read from a moved-from value\n"; - assert(false); + move_only(move_only &&other) : can_read(true), value(std::move(other.value)) { + if (!exchange(other.can_read, false)) { + throw std::logic_error("illegal read from a moved-from value"); + } } - } - // Assignment operators + auto operator=(move_only &&other) -> move_only & { + // Self-move should be ok if the object is already in a moved-from + // state because it incurs no data loss, but should otherwise be + // frowned upon + if (&other == this && can_read) { + throw std::logic_error("illegal self-move was performed"); + } - move_only &operator=(const move_only &) = delete; + // Assign before overwriting other.can_read + can_read = other.can_read; + value = std::move(other.value); - auto operator=(move_only &&other) -> move_only & { - if (&other != this) { - if (!exchange(other.can_read, false)) { - std::cerr << "illegal read from a moved-from value\n"; - assert(false); + // If the two objects are not the same and we try to read from an + // object in a moved-from state, then it's a hard error because + // data might be lost + if (!exchange(other.can_read, false) && &other != this) { + throw std::logic_error("illegal read from a moved-from value"); } - can_read = true; - value = std::move(other.value); + + return *this; } - return *this; - } - // A C++11 backport of std::exchange() + // A C++11 backport of std::exchange() + template auto exchange(U &obj, U &&new_val) -> U { + U old_val = std::move(obj); + obj = std::forward(new_val); + return old_val; + } - template U exchange(U &obj, U &&new_val) { - U old_val = std::move(obj); - obj = std::forward(new_val); - return old_val; + // Whether the value can be read + bool can_read = false; + // Actual value + T value; + }; +} + +template +bool operator<(const move_only &lhs, const move_only &rhs) +{ + return lhs.value < rhs.value; +} + +template +void swap(move_only &lhs, move_only &rhs) +{ + // This function matters because we want to prevent self-moves + // but we don't want to prevent self-swaps because it is the + // responsibility of class authors to make sure that self-swap + // does the right thing, and not the responsibility of algorithm + // authors to prevent them from happening + + // Both operands need to be readable + if (!(lhs.can_read || rhs.can_read)) { + throw std::logic_error("illegal read from a moved-from value"); } - // Whether the value can be read - bool can_read; - // Actual value - T value; -}; + // Swapping the values is enough to preserve the preconditions + using std::swap; + swap(lhs.value, rhs.value); +} TEST_CASE( "shuffle10k_for_move_only_types" ) { const int size = 1024 * 10; // should be even number of elements From c1d3179388eedf8e57205182c70df35faddd3c4d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 28 Sep 2019 19:24:21 +0200 Subject: [PATCH 040/137] Remove unused header [ci skip] --- tests/cxx_11_tests.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index 35554fb..1fa1c0c 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -6,7 +6,6 @@ */ #include #include -#include #include #include #include From 9c2c45d6355aa66a59fbc71c421c7ad88cf3786b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 28 Sep 2019 19:38:16 +0200 Subject: [PATCH 041/137] Fix warning C4244 in MSVC --- include/gfx/timsort.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 2b359e5..fc37ed0 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -448,8 +448,8 @@ template class TimSort { // outer: while (true) { - int count1 = 0; - int count2 = 0; + diff_t count1 = 0; + diff_t count2 = 0; bool break_outer = false; do { @@ -567,8 +567,8 @@ template class TimSort { // outer: while (true) { - int count1 = 0; - int count2 = 0; + diff_t count1 = 0; + diff_t count2 = 0; bool break_outer = false; do { From 6cbbc4ebdb5f95216e1414d2545cf8631fb652e9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 10 Oct 2019 22:05:40 +0200 Subject: [PATCH 042/137] Refactor logs and assertions (both are now optional) --- README.md | 6 ++++ include/gfx/timsort.hpp | 79 +++++++++++++++++++++++------------------ 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 81392b7..fcf7102 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,12 @@ cd build make install ``` +## CONFIGURATION + +A few configuration macros allow gfx::timsort to emit diagnostic, which might be helpful to diagnose issues: +* Defining `GFX_TIMSORT_ENABLE_ASSERT` inserts assertions in key locations in the algorithm to avoid logic errors. +* Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the algorithm. + ## TESTS The tests are written with Catch2 (branch 1.x) and can be compiled with CMake and run through CTest. diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index fc37ed0..ad790a7 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -31,14 +31,20 @@ #define GFX_TIMSORT_HPP #include -#include #include #include #include -#ifdef ENABLE_TIMSORT_LOG +#ifdef GFX_TIMSORT_ENABLE_ASSERT +# include +# define GFX_TIMSORT_ASSERT(expr) assert(expr) +#else +# define GFX_TIMSORT_ASSERT(expr) ((void)0) +#endif + +#ifdef GFX_TIMSORT_ENABLE_LOG # include -# define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << expr << std::endl) +# define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << (expr) << std::endl) #else # define GFX_TIMSORT_LOG(expr) ((void)0) #endif @@ -162,7 +168,7 @@ template class TimSort { std::vector pending_; static void sort(iter_t const lo, iter_t const hi, compare_t c) { - assert(lo <= hi); + GFX_TIMSORT_ASSERT(lo <= hi); diff_t nRemaining = (hi - lo); if (nRemaining < 2) { @@ -195,21 +201,21 @@ template class TimSort { nRemaining -= runLen; } while (nRemaining != 0); - assert(cur == hi); + GFX_TIMSORT_ASSERT(cur == hi); ts.mergeForceCollapse(); - assert(ts.pending_.size() == 1); + GFX_TIMSORT_ASSERT(ts.pending_.size() == 1); GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() << " pending_.size(): " << ts.pending_.size()); } // sort() static void binarySort(iter_t const lo, iter_t const hi, iter_t start, compare_t compare) { - assert(lo <= start && start <= hi); + GFX_TIMSORT_ASSERT(lo <= start && start <= hi); if (start == lo) { ++start; } for (; start < hi; ++start) { - assert(lo <= start); + GFX_TIMSORT_ASSERT(lo <= start); /*const*/ value_t pivot = GFX_TIMSORT_MOVE(*start); iter_t const pos = std::upper_bound(lo, start, pivot, compare.less_function()); @@ -221,7 +227,7 @@ template class TimSort { } static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, compare_t compare) { - assert(lo < hi); + GFX_TIMSORT_ASSERT(lo < hi); iter_t runHi = lo + 1; if (runHi == hi) { @@ -243,7 +249,7 @@ template class TimSort { } static diff_t minRunLength(diff_t n) { - assert(n >= 0); + GFX_TIMSORT_ASSERT(n >= 0); diff_t r = 0; while (n >= MIN_MERGE) { @@ -291,17 +297,17 @@ template class TimSort { void mergeAt(diff_t const i) { diff_t const stackSize = pending_.size(); - assert(stackSize >= 2); - assert(i >= 0); - assert(i == stackSize - 2 || i == stackSize - 3); + GFX_TIMSORT_ASSERT(stackSize >= 2); + GFX_TIMSORT_ASSERT(i >= 0); + GFX_TIMSORT_ASSERT(i == stackSize - 2 || i == stackSize - 3); iter_t base1 = pending_[i].base; diff_t len1 = pending_[i].len; iter_t base2 = pending_[i + 1].base; diff_t len2 = pending_[i + 1].len; - assert(len1 > 0 && len2 > 0); - assert(base1 + len1 == base2); + GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0); + GFX_TIMSORT_ASSERT(base1 + len1 == base2); pending_[i].len = len1 + len2; @@ -312,7 +318,7 @@ template class TimSort { pending_.pop_back(); diff_t const k = gallopRight(*base2, base1, len1, 0); - assert(k >= 0); + GFX_TIMSORT_ASSERT(k >= 0); base1 += k; len1 -= k; @@ -322,7 +328,7 @@ template class TimSort { } len2 = gallopLeft(*(base1 + (len1 - 1)), base2, len2, len2 - 1); - assert(len2 >= 0); + GFX_TIMSORT_ASSERT(len2 >= 0); if (len2 == 0) { return; } @@ -335,7 +341,7 @@ template class TimSort { } template diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint) { - assert(len > 0 && hint >= 0 && hint < len); + GFX_TIMSORT_ASSERT(len > 0 && hint >= 0 && hint < len); diff_t lastOfs = 0; diff_t ofs = 1; @@ -374,13 +380,13 @@ template class TimSort { lastOfs = hint - ofs; ofs = hint - tmp; } - assert(-1 <= lastOfs && lastOfs < ofs && ofs <= len); + GFX_TIMSORT_ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len); return std::lower_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; } template diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint) { - assert(len > 0 && hint >= 0 && hint < len); + GFX_TIMSORT_ASSERT(len > 0 && hint >= 0 && hint < len); diff_t ofs = 1; diff_t lastOfs = 0; @@ -419,13 +425,13 @@ template class TimSort { lastOfs += hint; ofs += hint; } - assert(-1 <= lastOfs && lastOfs < ofs && ofs <= len); + GFX_TIMSORT_ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len); return std::upper_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; } void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { - assert(len1 > 0 && len2 > 0 && base1 + len1 == base2); + GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); copy_to_tmp(base1, len1); @@ -453,7 +459,7 @@ template class TimSort { bool break_outer = false; do { - assert(len1 > 1 && len2 > 0); + GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); if (comp_.lt(*cursor2, *cursor1)) { *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); @@ -478,7 +484,7 @@ template class TimSort { } do { - assert(len1 > 1 && len2 > 0); + GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); count1 = gallopRight(*cursor2, cursor1, len1, 0); if (count1 != 0) { @@ -530,19 +536,19 @@ template class TimSort { minGallop_ = std::min(minGallop, 1); if (len1 == 1) { - assert(len2 > 0); + GFX_TIMSORT_ASSERT(len2 > 0); GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + len2, dest); *(dest + len2) = GFX_TIMSORT_MOVE(*cursor1); } else { - assert(len1 != 0 && "Comparison function violates its general contract"); - assert(len2 == 0); - assert(len1 > 1); + GFX_TIMSORT_ASSERT(len1 != 0 && "Comparison function violates its general contract"); + GFX_TIMSORT_ASSERT(len2 == 0); + GFX_TIMSORT_ASSERT(len1 > 1); GFX_TIMSORT_MOVE_RANGE(cursor1, cursor1 + len1, dest); } } void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { - assert(len1 > 0 && len2 > 0 && base1 + len1 == base2); + GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); copy_to_tmp(base2, len2); @@ -572,7 +578,7 @@ template class TimSort { bool break_outer = false; do { - assert(len1 > 0 && len2 > 1); + GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); if (comp_.lt(*cursor2, *cursor1)) { *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); @@ -597,7 +603,7 @@ template class TimSort { } do { - assert(len1 > 0 && len2 > 1); + GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1); if (count1 != 0) { @@ -649,14 +655,14 @@ template class TimSort { minGallop_ = std::min(minGallop, 1); if (len2 == 1) { - assert(len1 > 0); + GFX_TIMSORT_ASSERT(len1 > 0); dest -= len1; GFX_TIMSORT_MOVE_BACKWARD(cursor1 + (1 - len1), cursor1 + 1, dest + (1 + len1)); *dest = GFX_TIMSORT_MOVE(*cursor2); } else { - assert(len2 != 0 && "Comparison function violates its general contract"); - assert(len1 == 0); - assert(len2 > 1); + GFX_TIMSORT_ASSERT(len2 != 0 && "Comparison function violates its general contract"); + GFX_TIMSORT_ASSERT(len1 == 0); + GFX_TIMSORT_ASSERT(len2 > 1); GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); } } @@ -684,6 +690,9 @@ inline void timsort(RandomAccessIterator const first, RandomAccessIterator const } // namespace gfx +#undef GFX_TIMSORT_ENABLE_ASSERT +#undef GFX_TIMSORT_ASSERT +#undef GFX_TIMSORT_ENABLE_LOG #undef GFX_TIMSORT_LOG #undef GFX_TIMSORT_MOVE #undef GFX_TIMSORT_MOVE_RANGE From bdb817dbee72f5f7993b8a5f3bc1451b6a81be75 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 11 Oct 2019 23:27:13 +0200 Subject: [PATCH 043/137] Avoid iterators pointing before begin in mergeHi (issue #30) --- include/gfx/timsort.hpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index ad790a7..aadfa66 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -552,11 +552,11 @@ template class TimSort { copy_to_tmp(base2, len2); - iter_t cursor1 = base1 + (len1 - 1); + iter_t cursor1 = base1 + len1; tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); iter_t dest = base2 + (len2 - 1); - *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); + *(dest--) = GFX_TIMSORT_MOVE(*(--cursor1)); if (--len1 == 0) { GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); return; @@ -564,7 +564,7 @@ template class TimSort { if (len2 == 1) { dest -= len1; cursor1 -= len1; - GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); + GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + len1, dest + (1 + len1)); *dest = GFX_TIMSORT_MOVE(*cursor2); return; } @@ -576,23 +576,31 @@ template class TimSort { diff_t count1 = 0; diff_t count2 = 0; + // The next loop is a hot path of the algorithm, so we decrement + // eagerly the cursor so that it always points directly to the value + // to compare, but we have to implement some trickier logic to make + // sure that it points to the next value again by the end of said loop + --cursor1; + bool break_outer = false; do { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); if (comp_.lt(*cursor2, *cursor1)) { - *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); + *(dest--) = GFX_TIMSORT_MOVE(*cursor1); ++count1; count2 = 0; if (--len1 == 0) { break_outer = true; break; } + --cursor1; } else { *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); ++count2; count1 = 0; if (--len2 == 1) { + ++cursor1; break_outer = true; break; } @@ -600,6 +608,8 @@ template class TimSort { } while ((count1 | count2) < minGallop); if (break_outer) { break; + } else { + ++cursor1; // See comment before the loop } do { @@ -610,7 +620,7 @@ template class TimSort { dest -= count1; cursor1 -= count1; len1 -= count1; - GFX_TIMSORT_MOVE_BACKWARD(cursor1 + 1, cursor1 + (1 + count1), dest + (1 + count1)); + GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + count1, dest + (1 + count1)); if (len1 == 0) { break_outer = true; @@ -623,7 +633,7 @@ template class TimSort { break; } - count2 = len2 - gallopLeft(*cursor1, tmp_.begin(), len2, len2 - 1); + count2 = len2 - gallopLeft(*(cursor1 - 1), tmp_.begin(), len2, len2 - 1); if (count2 != 0) { dest -= count2; cursor2 -= count2; @@ -634,7 +644,7 @@ template class TimSort { break; } } - *(dest--) = GFX_TIMSORT_MOVE(*(cursor1--)); + *(dest--) = GFX_TIMSORT_MOVE(*(--cursor1)); if (--len1 == 0) { break_outer = true; break; @@ -657,7 +667,7 @@ template class TimSort { if (len2 == 1) { GFX_TIMSORT_ASSERT(len1 > 0); dest -= len1; - GFX_TIMSORT_MOVE_BACKWARD(cursor1 + (1 - len1), cursor1 + 1, dest + (1 + len1)); + GFX_TIMSORT_MOVE_BACKWARD(cursor1 - len1, cursor1, dest + (1 + len1)); *dest = GFX_TIMSORT_MOVE(*cursor2); } else { GFX_TIMSORT_ASSERT(len2 != 0 && "Comparison function violates its general contract"); From 14c8cc4eff29635806870cbf49aee72e3f2f6fc6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 00:03:51 +0200 Subject: [PATCH 044/137] Try to fix MSVC builds --- .travis.yml | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 54983a8..21e93b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -135,35 +135,29 @@ matrix: language: sh compiler: gcc - # Windows MSVC (using a pseudo-generator) + # Windows MSVC - os: windows - env: BUILD_TYPE=Debug CMAKE_GENERATOR="MSVC" - compiler: msvc + env: BUILD_TYPE=Debug CMAKE_GENERATOR="Visual Studio 15 2017 Win64" + compiler: cl - os: windows - env: BUILD_TYPE=Release CMAKE_GENERATOR="MSVC" - compiler: msvc + env: BUILD_TYPE=Release CMAKE_GENERATOR="Visual Studio 15 2017 Win64" + compiler: cl install: - if [[ $CXX = "g++" ]]; then export CXX="g++-5"; fi - if [[ $CXX = "clang++" ]]; then export CXX="clang++-3.8"; fi script: - - if [[ $CMAKE_GENERATOR = "MSVC" ]]; then - cmake -H. -Bbuild - -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" - -DBUILD_BENCHMARKS=ON; - else - cmake -H. -Bbuild - -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" - -DGFX_TIMSORT_SANITIZE="${SANITIZE}" - -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} - -G"${CMAKE_GENERATOR}" - -DCMAKE_SH="CMAKE_SH-NOTFOUND" - -DBUILD_BENCHMARKS=ON; - fi - - if [[ $CMAKE_GENERATOR = "MSVC" || $TRAVIS_OS_NAME = "osx" ]]; then + - cmake -H. -Bbuild + -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" + -DGFX_TIMSORT_SANITIZE="${SANITIZE}" + -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} + -G"${CMAKE_GENERATOR}" + -DCMAKE_SH="CMAKE_SH-NOTFOUND" + -DBUILD_BENCHMARKS=ON + - if [[ $TRAVIS_OS_NAME = "windows" || $TRAVIS_OS_NAME = "osx" ]]; then cmake --build build --config ${BUILD_TYPE} -j 2; else cmake --build build --config ${BUILD_TYPE} -- -j2; From 2d34c5b835dac5cccbe26704d19b6a8d78392165 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 14:33:03 +0200 Subject: [PATCH 045/137] Fix GFX_TIMSORT_LOG that I accidentally broke [ci skip] --- include/gfx/timsort.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index aadfa66..5e7bdb8 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -44,7 +44,7 @@ #ifdef GFX_TIMSORT_ENABLE_LOG # include -# define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << (expr) << std::endl) +# define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << expr << std::endl) #else # define GFX_TIMSORT_LOG(expr) ((void)0) #endif From 3f8c2a2bda079c01a62350222e342d1b021c1252 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 14:56:49 +0200 Subject: [PATCH 046/137] Enable assertions in the test suite --- tests/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2fd1956..c679b92 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,10 +27,12 @@ macro(configure_tests target) gfx::timsort ) - # Somewhat speed up Catch2 compile times target_compile_definitions(${target} PRIVATE + # Somewhat speed up Catch2 compile times CATCH_CONFIG_FAST_COMPILE CATCH_CONFIG_DISABLE_MATCHERS + # Enable assertions for more thorough tests + GFX_TIMSORT_ENABLE_ASSERT ) # Add warnings From 91d35c9c7c69e7d39da62f29ced545da6eebaeb1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 15:42:22 +0200 Subject: [PATCH 047/137] Fix the CI badge and link [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fcf7102..b401e72 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## TimSort [![Build Status](https://travis-ci.org/gfx/cpp-TimSort.svg?branch=master)](https://travis-ci.org/gfx/cpp-TimSort) +## TimSort [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) A C++ implementation of TimSort, an O(n log n) stable sorting algorithm, ported from Python's and OpenJDK's. From bfca750be148cf2dfe9e402524e088d39bf17cbc Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 17:32:00 +0200 Subject: [PATCH 048/137] Make control flow clearer with goto --- include/gfx/timsort.hpp | 59 +++++++++++++---------------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 5e7bdb8..4498516 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -457,7 +457,6 @@ template class TimSort { diff_t count1 = 0; diff_t count2 = 0; - bool break_outer = false; do { GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); @@ -466,22 +465,17 @@ template class TimSort { ++count2; count1 = 0; if (--len2 == 0) { - break_outer = true; - break; + goto epilogue; } } else { *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); ++count1; count2 = 0; if (--len1 == 1) { - break_outer = true; - break; + goto epilogue; } } } while ((count1 | count2) < minGallop); - if (break_outer) { - break; - } do { GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); @@ -494,14 +488,12 @@ template class TimSort { len1 -= count1; if (len1 <= 1) { - break_outer = true; - break; + goto epilogue; } } *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); if (--len2 == 0) { - break_outer = true; - break; + goto epilogue; } count2 = gallopLeft(*cursor1, cursor2, len2, 0); @@ -511,21 +503,16 @@ template class TimSort { cursor2 += count2; len2 -= count2; if (len2 == 0) { - break_outer = true; - break; + goto epilogue; } } *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); if (--len1 == 1) { - break_outer = true; - break; + goto epilogue; } --minGallop; } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); - if (break_outer) { - break; - } if (minGallop < 0) { minGallop = 0; @@ -533,6 +520,8 @@ template class TimSort { minGallop += 2; } // end of "outer" loop + epilogue: // merge what is left from either cursor1 or cursor2 + minGallop_ = std::min(minGallop, 1); if (len1 == 1) { @@ -582,7 +571,6 @@ template class TimSort { // sure that it points to the next value again by the end of said loop --cursor1; - bool break_outer = false; do { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); @@ -591,8 +579,7 @@ template class TimSort { ++count1; count2 = 0; if (--len1 == 0) { - break_outer = true; - break; + goto epilogue; } --cursor1; } else { @@ -600,17 +587,12 @@ template class TimSort { ++count2; count1 = 0; if (--len2 == 1) { - ++cursor1; - break_outer = true; - break; + ++cursor1; // See comment before the loop + goto epilogue; } } } while ((count1 | count2) < minGallop); - if (break_outer) { - break; - } else { - ++cursor1; // See comment before the loop - } + ++cursor1; // See comment before the loop do { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); @@ -623,14 +605,12 @@ template class TimSort { GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + count1, dest + (1 + count1)); if (len1 == 0) { - break_outer = true; - break; + goto epilogue; } } *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); if (--len2 == 1) { - break_outer = true; - break; + goto epilogue; } count2 = len2 - gallopLeft(*(cursor1 - 1), tmp_.begin(), len2, len2 - 1); @@ -640,21 +620,16 @@ template class TimSort { len2 -= count2; GFX_TIMSORT_MOVE_RANGE(cursor2 + 1, cursor2 + (1 + count2), dest + 1); if (len2 <= 1) { - break_outer = true; - break; + goto epilogue; } } *(dest--) = GFX_TIMSORT_MOVE(*(--cursor1)); if (--len1 == 0) { - break_outer = true; - break; + goto epilogue; } minGallop--; } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); - if (break_outer) { - break; - } if (minGallop < 0) { minGallop = 0; @@ -662,6 +637,8 @@ template class TimSort { minGallop += 2; } // end of "outer" loop + epilogue: // merge what is left from either cursor1 or cursor2 + minGallop_ = std::min(minGallop, 1); if (len2 == 1) { From 8ffe8bc035af91a4f1e9d6ec2c3f4558a8d498e8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 18:47:06 +0200 Subject: [PATCH 049/137] Remove gfx::Compare, use the provided function directly gfx::Compare performed several unneeded operations and there are no places in timsort where we can actually take advantage of a three-way comparison, so it's generally better to use the provided comparison function directly (see PR #26 for more rationale). --- include/gfx/timsort.hpp | 138 +++++++++++++++------------------------- tests/cxx_98_tests.cpp | 21 ------ 2 files changed, 53 insertions(+), 106 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 4498516..20dce74 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -102,55 +102,20 @@ inline void timsort(RandomAccessIterator const first, RandomAccessIterator const /** * Same as std::stable_sort(first, last, c). */ -template -inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, LessFunction compare); +template +inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare); // --------------------------------------- // Implementation // --------------------------------------- -template class Compare { - public: - typedef Value value_type; - typedef LessFunction func_type; - - Compare(LessFunction f) : less_(f) { - } - Compare(const Compare &other) : less_(other.less_) { - } - - bool lt(value_type x, value_type y) { - return less_(x, y); - } - bool le(value_type x, value_type y) { - return less_(x, y) || !less_(y, x); - } - bool gt(value_type x, value_type y) { - return !less_(x, y) && less_(y, x); - } - bool ge(value_type x, value_type y) { - return !less_(x, y); - } - - func_type &less_function() { - return less_; - } - - private: - func_type less_; -}; - -template class TimSort { +template class TimSort { typedef RandomAccessIterator iter_t; typedef typename std::iterator_traits::value_type value_t; typedef typename std::iterator_traits::reference ref_t; typedef typename std::iterator_traits::difference_type diff_t; - typedef Compare compare_t; static const int MIN_MERGE = 32; - - compare_t comp_; - static const int MIN_GALLOP = 7; int minGallop_; // default to MIN_GALLOP @@ -167,7 +132,7 @@ template class TimSort { }; std::vector pending_; - static void sort(iter_t const lo, iter_t const hi, compare_t c) { + static void sort(iter_t const lo, iter_t const hi, Compare compare) { GFX_TIMSORT_ASSERT(lo <= hi); diff_t nRemaining = (hi - lo); @@ -176,40 +141,40 @@ template class TimSort { } if (nRemaining < MIN_MERGE) { - diff_t const initRunLen = countRunAndMakeAscending(lo, hi, c); + diff_t const initRunLen = countRunAndMakeAscending(lo, hi, compare); GFX_TIMSORT_LOG("initRunLen: " << initRunLen); - binarySort(lo, hi, lo + initRunLen, c); + binarySort(lo, hi, lo + initRunLen, compare); return; } - TimSort ts(c); + TimSort ts; diff_t const minRun = minRunLength(nRemaining); iter_t cur = lo; do { - diff_t runLen = countRunAndMakeAscending(cur, hi, c); + diff_t runLen = countRunAndMakeAscending(cur, hi, compare); if (runLen < minRun) { diff_t const force = std::min(nRemaining, minRun); - binarySort(cur, cur + force, cur + runLen, c); + binarySort(cur, cur + force, cur + runLen, compare); runLen = force; } ts.pushRun(cur, runLen); - ts.mergeCollapse(); + ts.mergeCollapse(compare); cur += runLen; nRemaining -= runLen; } while (nRemaining != 0); GFX_TIMSORT_ASSERT(cur == hi); - ts.mergeForceCollapse(); + ts.mergeForceCollapse(compare); GFX_TIMSORT_ASSERT(ts.pending_.size() == 1); GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() << " pending_.size(): " << ts.pending_.size()); } // sort() - static void binarySort(iter_t const lo, iter_t const hi, iter_t start, compare_t compare) { + static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare compare) { GFX_TIMSORT_ASSERT(lo <= start && start <= hi); if (start == lo) { ++start; @@ -218,7 +183,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(lo <= start); /*const*/ value_t pivot = GFX_TIMSORT_MOVE(*start); - iter_t const pos = std::upper_bound(lo, start, pivot, compare.less_function()); + iter_t const pos = std::upper_bound(lo, start, pivot, compare); for (iter_t p = start; p > pos; --p) { *p = GFX_TIMSORT_MOVE(*(p - 1)); } @@ -226,7 +191,7 @@ template class TimSort { } } - static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, compare_t compare) { + static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, Compare compare) { GFX_TIMSORT_ASSERT(lo < hi); iter_t runHi = lo + 1; @@ -234,13 +199,13 @@ template class TimSort { return 1; } - if (compare.lt(*(runHi++), *lo)) { // descending - while (runHi < hi && compare.lt(*runHi, *(runHi - 1))) { + if (compare(*(runHi++), *lo)) { // descending + while (runHi < hi && compare(*runHi, *(runHi - 1))) { ++runHi; } std::reverse(lo, runHi); } else { // ascending - while (runHi < hi && compare.ge(*runHi, *(runHi - 1))) { + while (runHi < hi && !compare(*runHi, *(runHi - 1))) { ++runHi; } } @@ -259,14 +224,14 @@ template class TimSort { return n + r; } - TimSort(compare_t c) : comp_(c), minGallop_(MIN_GALLOP) { + TimSort() : minGallop_(MIN_GALLOP) { } void pushRun(iter_t const runBase, diff_t const runLen) { pending_.push_back(run(runBase, runLen)); } - void mergeCollapse() { + void mergeCollapse(Compare compare) { while (pending_.size() > 1) { diff_t n = pending_.size() - 2; @@ -275,27 +240,27 @@ template class TimSort { if (pending_[n - 1].len < pending_[n + 1].len) { --n; } - mergeAt(n); + mergeAt(n, compare); } else if (pending_[n].len <= pending_[n + 1].len) { - mergeAt(n); + mergeAt(n, compare); } else { break; } } } - void mergeForceCollapse() { + void mergeForceCollapse(Compare compare) { while (pending_.size() > 1) { diff_t n = pending_.size() - 2; if (n > 0 && pending_[n - 1].len < pending_[n + 1].len) { --n; } - mergeAt(n); + mergeAt(n, compare); } } - void mergeAt(diff_t const i) { + void mergeAt(diff_t const i, Compare compare) { diff_t const stackSize = pending_.size(); GFX_TIMSORT_ASSERT(stackSize >= 2); GFX_TIMSORT_ASSERT(i >= 0); @@ -317,7 +282,7 @@ template class TimSort { pending_.pop_back(); - diff_t const k = gallopRight(*base2, base1, len1, 0); + diff_t const k = gallopRight(*base2, base1, len1, 0, compare); GFX_TIMSORT_ASSERT(k >= 0); base1 += k; @@ -327,28 +292,29 @@ template class TimSort { return; } - len2 = gallopLeft(*(base1 + (len1 - 1)), base2, len2, len2 - 1); + len2 = gallopLeft(*(base1 + (len1 - 1)), base2, len2, len2 - 1, compare); GFX_TIMSORT_ASSERT(len2 >= 0); if (len2 == 0) { return; } if (len1 <= len2) { - mergeLo(base1, len1, base2, len2); + mergeLo(base1, len1, base2, len2, compare); } else { - mergeHi(base1, len1, base2, len2); + mergeHi(base1, len1, base2, len2, compare); } } - template diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint) { + template + diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint, Compare compare) { GFX_TIMSORT_ASSERT(len > 0 && hint >= 0 && hint < len); diff_t lastOfs = 0; diff_t ofs = 1; - if (comp_.gt(key, *(base + hint))) { + if (compare(*(base + hint), key)) { diff_t const maxOfs = len - hint; - while (ofs < maxOfs && comp_.gt(key, *(base + (hint + ofs)))) { + while (ofs < maxOfs && compare(*(base + (hint + ofs)), key)) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -364,7 +330,7 @@ template class TimSort { ofs += hint; } else { diff_t const maxOfs = hint + 1; - while (ofs < maxOfs && comp_.le(key, *(base + (hint - ofs)))) { + while (ofs < maxOfs && !compare(*(base + (hint - ofs)), key)) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -382,18 +348,19 @@ template class TimSort { } GFX_TIMSORT_ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len); - return std::lower_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; + return std::lower_bound(base + (lastOfs + 1), base + ofs, key, compare) - base; } - template diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint) { + template + diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint, Compare compare) { GFX_TIMSORT_ASSERT(len > 0 && hint >= 0 && hint < len); diff_t ofs = 1; diff_t lastOfs = 0; - if (comp_.lt(key, *(base + hint))) { + if (compare(key, *(base + hint))) { diff_t const maxOfs = hint + 1; - while (ofs < maxOfs && comp_.lt(key, *(base + (hint - ofs)))) { + while (ofs < maxOfs && compare(key, *(base + (hint - ofs)))) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -410,7 +377,7 @@ template class TimSort { ofs = hint - tmp; } else { diff_t const maxOfs = len - hint; - while (ofs < maxOfs && comp_.ge(key, *(base + (hint + ofs)))) { + while (ofs < maxOfs && !compare(key, *(base + (hint + ofs)))) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -427,10 +394,10 @@ template class TimSort { } GFX_TIMSORT_ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len); - return std::upper_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; + return std::upper_bound(base + (lastOfs + 1), base + ofs, key, compare) - base; } - void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { + void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); copy_to_tmp(base1, len1); @@ -460,7 +427,7 @@ template class TimSort { do { GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); - if (comp_.lt(*cursor2, *cursor1)) { + if (compare(*cursor2, *cursor1)) { *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); ++count2; count1 = 0; @@ -480,7 +447,7 @@ template class TimSort { do { GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); - count1 = gallopRight(*cursor2, cursor1, len1, 0); + count1 = gallopRight(*cursor2, cursor1, len1, 0, compare); if (count1 != 0) { GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + count1, dest + count1); dest += count1; @@ -496,7 +463,7 @@ template class TimSort { goto epilogue; } - count2 = gallopLeft(*cursor1, cursor2, len2, 0); + count2 = gallopLeft(*cursor1, cursor2, len2, 0, compare); if (count2 != 0) { GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + count2, dest); dest += count2; @@ -536,7 +503,7 @@ template class TimSort { } } - void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { + void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); copy_to_tmp(base2, len2); @@ -574,7 +541,7 @@ template class TimSort { do { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); - if (comp_.lt(*cursor2, *cursor1)) { + if (compare(*cursor2, *cursor1)) { *(dest--) = GFX_TIMSORT_MOVE(*cursor1); ++count1; count2 = 0; @@ -597,7 +564,7 @@ template class TimSort { do { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); - count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1); + count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1, compare); if (count1 != 0) { dest -= count1; cursor1 -= count1; @@ -613,7 +580,7 @@ template class TimSort { goto epilogue; } - count2 = len2 - gallopLeft(*(cursor1 - 1), tmp_.begin(), len2, len2 - 1); + count2 = len2 - gallopLeft(*(cursor1 - 1), tmp_.begin(), len2, len2 - 1, compare); if (count2 != 0) { dest -= count2; cursor2 -= count2; @@ -661,7 +628,8 @@ template class TimSort { } // the only interface is the friend timsort() function - template friend void timsort(IterT first, IterT last, LessT c); + template + friend void timsort(IterT first, IterT last, LessT c); }; template @@ -670,9 +638,9 @@ inline void timsort(RandomAccessIterator const first, RandomAccessIterator const timsort(first, last, std::less()); } -template -inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, LessFunction compare) { - TimSort::sort(first, last, compare); +template +inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare) { + TimSort::sort(first, last, compare); } } // namespace gfx diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index 11560ef..394507a 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -453,27 +453,6 @@ inline bool less_in_pair(const std::pair &x, const std::pair return x.first < y.first; } -TEST_CASE( "issue2_compare" ) { - typedef std::pair p; - gfx::Compare c(&less_in_pair); - - CHECK_FALSE(c.lt(std::make_pair(10, 10), std::make_pair(10, 9))); - CHECK_FALSE(c.lt(std::make_pair(10, 10), std::make_pair(10, 10))); - CHECK(c.lt(std::make_pair(10, 10), std::make_pair(10, 11))); - - CHECK_FALSE(c.le(std::make_pair(10, 10), std::make_pair(10, 9))); - CHECK(c.le(std::make_pair(10, 10), std::make_pair(10, 10))); - CHECK(c.le(std::make_pair(10, 10), std::make_pair(10, 11))); - - CHECK(c.gt(std::make_pair(10, 10), std::make_pair(10, 9))); - CHECK_FALSE(c.gt(std::make_pair(10, 10), std::make_pair(10, 10))); - CHECK_FALSE(c.gt(std::make_pair(10, 10), std::make_pair(10, 11))); - - CHECK(c.ge(std::make_pair(10, 10), std::make_pair(10, 9))); - CHECK(c.ge(std::make_pair(10, 10), std::make_pair(10, 10))); - CHECK_FALSE(c.ge(std::make_pair(10, 10), std::make_pair(10, 11))); -} - TEST_CASE( "issue2_duplication" ) { std::vector > a; From 1531857377db908e8d9fd4b005e281a9840dbb12 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 19:50:09 +0200 Subject: [PATCH 050/137] Small cleanup --- include/gfx/timsort.hpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 20dce74..61797f7 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -97,13 +97,13 @@ namespace gfx { * Same as std::stable_sort(first, last). */ template -inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last); +void timsort(RandomAccessIterator const first, RandomAccessIterator const last); /** - * Same as std::stable_sort(first, last, c). + * Same as std::stable_sort(first, last, compare). */ template -inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare); +void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare); // --------------------------------------- // Implementation @@ -172,7 +172,7 @@ template class TimSort { GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() << " pending_.size(): " << ts.pending_.size()); - } // sort() + } static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare compare) { GFX_TIMSORT_ASSERT(lo <= start && start <= hi); @@ -181,7 +181,7 @@ template class TimSort { } for (; start < hi; ++start) { GFX_TIMSORT_ASSERT(lo <= start); - /*const*/ value_t pivot = GFX_TIMSORT_MOVE(*start); + value_t pivot = GFX_TIMSORT_MOVE(*start); iter_t const pos = std::upper_bound(lo, start, pivot, compare); for (iter_t p = start; p > pos; --p) { @@ -199,12 +199,12 @@ template class TimSort { return 1; } - if (compare(*(runHi++), *lo)) { // descending + if (compare(*(runHi++), *lo)) { // decreasing while (runHi < hi && compare(*runHi, *(runHi - 1))) { ++runHi; } std::reverse(lo, runHi); - } else { // ascending + } else { // non-decreasing while (runHi < hi && !compare(*runHi, *(runHi - 1))) { ++runHi; } @@ -627,19 +627,18 @@ template class TimSort { GFX_TIMSORT_MOVE_RANGE(begin, begin + len, std::back_inserter(tmp_)); } - // the only interface is the friend timsort() function template friend void timsort(IterT first, IterT last, LessT c); }; template -inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { +void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { typedef typename std::iterator_traits::value_type value_type; timsort(first, last, std::less()); } template -inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare) { +void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare) { TimSort::sort(first, last, compare); } From 5c46b9e0d6cf2a49a65ecd1d08b3357aa78a80f5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 20:34:00 +0200 Subject: [PATCH 051/137] Better implementation of copy_to_tmp --- include/gfx/timsort.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 61797f7..5da9d75 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -621,10 +621,13 @@ template class TimSort { } } - void copy_to_tmp(iter_t const begin, diff_t const len) { - tmp_.clear(); - tmp_.reserve(len); - GFX_TIMSORT_MOVE_RANGE(begin, begin + len, std::back_inserter(tmp_)); + void copy_to_tmp(iter_t const begin, diff_t len) { +#if GFX_TIMSORT_USE_STD_MOVE + tmp_.assign(std::make_move_iterator(begin), + std::make_move_iterator(begin + len)); +#else + tmp_.assign(begin, begin + len); +#endif } template From 7105bff1cc9cae982e41af377c79b4efad4168ae Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 22:37:19 +0200 Subject: [PATCH 052/137] Optimise the 1-element case for mergeLo/MergeHi --- include/gfx/timsort.hpp | 52 +++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 5da9d75..7f86888 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -397,9 +397,32 @@ template class TimSort { return std::upper_bound(base + (lastOfs + 1), base + ofs, key, compare) - base; } + static void rotateLeft(iter_t first, iter_t last) + { + value_t tmp = GFX_TIMSORT_MOVE(*first); + iter_t last_1 = GFX_TIMSORT_MOVE_RANGE(first + 1, last, first); + *last_1 = GFX_TIMSORT_MOVE(tmp); + } + + static void rotateRight(iter_t first, iter_t last) + { + iter_t last_1 = last - 1; + value_t tmp = GFX_TIMSORT_MOVE(*last_1); + GFX_TIMSORT_MOVE_BACKWARD(first, last_1, last); + *first = GFX_TIMSORT_MOVE(tmp); + } + + void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); + if (len1 == 1) { + return rotateLeft(base1, base2 + len2); + } + if (len2 == 1) { + return rotateRight(base1, base2 + len2); + } + copy_to_tmp(base1, len1); tmp_iter_t cursor1 = tmp_.begin(); @@ -407,15 +430,7 @@ template class TimSort { iter_t dest = base1; *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); - if (--len2 == 0) { - GFX_TIMSORT_MOVE_RANGE(cursor1, cursor1 + len1, dest); - return; - } - if (len1 == 1) { - GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + len2, dest); - *(dest + len2) = GFX_TIMSORT_MOVE(*cursor1); - return; - } + --len2; int minGallop(minGallop_); @@ -506,6 +521,13 @@ template class TimSort { void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); + if (len1 == 1) { + return rotateLeft(base1, base2 + len2); + } + if (len2 == 1) { + return rotateRight(base1, base2 + len2); + } + copy_to_tmp(base2, len2); iter_t cursor1 = base1 + len1; @@ -513,17 +535,7 @@ template class TimSort { iter_t dest = base2 + (len2 - 1); *(dest--) = GFX_TIMSORT_MOVE(*(--cursor1)); - if (--len1 == 0) { - GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); - return; - } - if (len2 == 1) { - dest -= len1; - cursor1 -= len1; - GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + len1, dest + (1 + len1)); - *dest = GFX_TIMSORT_MOVE(*cursor2); - return; - } + --len1; int minGallop(minGallop_); From 6c200e21978b12fcb9990015ee49b665155ec49d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 12 Oct 2019 23:09:22 +0200 Subject: [PATCH 053/137] Silence GCC -Winline warnings --- include/gfx/timsort.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 7f86888..b834cdb 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -227,6 +227,9 @@ template class TimSort { TimSort() : minGallop_(MIN_GALLOP) { } + // Silence GCC -Winline warning + ~TimSort() {} + void pushRun(iter_t const runBase, diff_t const runLen) { pending_.push_back(run(runBase, runLen)); } From 6da604e1f0e04a7eb85c5598444ff29991d06d19 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 16 Oct 2019 21:33:19 +0200 Subject: [PATCH 054/137] Put everything except timsort() in detail:: namespace --- include/gfx/timsort.hpp | 146 ++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index b834cdb..55077a2 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -90,24 +90,20 @@ namespace gfx { // --------------------------------------- -// Declaration +// Implementation details // --------------------------------------- -/** - * Same as std::stable_sort(first, last). - */ -template -void timsort(RandomAccessIterator const first, RandomAccessIterator const last); +namespace detail { -/** - * Same as std::stable_sort(first, last, compare). - */ -template -void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare); +template struct run { + typedef typename std::iterator_traits::difference_type diff_t; -// --------------------------------------- -// Implementation -// --------------------------------------- + Iterator base; + diff_t len; + + run(Iterator b, diff_t l) : base(b), len(l) { + } +}; template class TimSort { typedef RandomAccessIterator iter_t; @@ -123,56 +119,7 @@ template class TimSort { std::vector tmp_; // temp storage for merges typedef typename std::vector::iterator tmp_iter_t; - struct run { - iter_t base; - diff_t len; - - run(iter_t const b, diff_t const l) : base(b), len(l) { - } - }; - std::vector pending_; - - static void sort(iter_t const lo, iter_t const hi, Compare compare) { - GFX_TIMSORT_ASSERT(lo <= hi); - - diff_t nRemaining = (hi - lo); - if (nRemaining < 2) { - return; // nothing to do - } - - if (nRemaining < MIN_MERGE) { - diff_t const initRunLen = countRunAndMakeAscending(lo, hi, compare); - GFX_TIMSORT_LOG("initRunLen: " << initRunLen); - binarySort(lo, hi, lo + initRunLen, compare); - return; - } - - TimSort ts; - diff_t const minRun = minRunLength(nRemaining); - iter_t cur = lo; - do { - diff_t runLen = countRunAndMakeAscending(cur, hi, compare); - - if (runLen < minRun) { - diff_t const force = std::min(nRemaining, minRun); - binarySort(cur, cur + force, cur + runLen, compare); - runLen = force; - } - - ts.pushRun(cur, runLen); - ts.mergeCollapse(compare); - - cur += runLen; - nRemaining -= runLen; - } while (nRemaining != 0); - - GFX_TIMSORT_ASSERT(cur == hi); - ts.mergeForceCollapse(compare); - GFX_TIMSORT_ASSERT(ts.pending_.size() == 1); - - GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() - << " pending_.size(): " << ts.pending_.size()); - } + std::vector > pending_; static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare compare) { GFX_TIMSORT_ASSERT(lo <= start && start <= hi); @@ -231,7 +178,7 @@ template class TimSort { ~TimSort() {} void pushRun(iter_t const runBase, diff_t const runLen) { - pending_.push_back(run(runBase, runLen)); + pending_.push_back(run(runBase, runLen)); } void mergeCollapse(Compare compare) { @@ -645,19 +592,72 @@ template class TimSort { #endif } - template - friend void timsort(IterT first, IterT last, LessT c); +public: + + static void sort(iter_t const lo, iter_t const hi, Compare compare) { + GFX_TIMSORT_ASSERT(lo <= hi); + + diff_t nRemaining = (hi - lo); + if (nRemaining < 2) { + return; // nothing to do + } + + if (nRemaining < MIN_MERGE) { + diff_t const initRunLen = countRunAndMakeAscending(lo, hi, compare); + GFX_TIMSORT_LOG("initRunLen: " << initRunLen); + binarySort(lo, hi, lo + initRunLen, compare); + return; + } + + TimSort ts; + diff_t const minRun = minRunLength(nRemaining); + iter_t cur = lo; + do { + diff_t runLen = countRunAndMakeAscending(cur, hi, compare); + + if (runLen < minRun) { + diff_t const force = std::min(nRemaining, minRun); + binarySort(cur, cur + force, cur + runLen, compare); + runLen = force; + } + + ts.pushRun(cur, runLen); + ts.mergeCollapse(compare); + + cur += runLen; + nRemaining -= runLen; + } while (nRemaining != 0); + + GFX_TIMSORT_ASSERT(cur == hi); + ts.mergeForceCollapse(compare); + GFX_TIMSORT_ASSERT(ts.pending_.size() == 1); + + GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() + << " pending_.size(): " << ts.pending_.size()); + } }; -template -void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { - typedef typename std::iterator_traits::value_type value_type; - timsort(first, last, std::less()); -} +} // namespace detail +// --------------------------------------- +// Public interface implementation +// --------------------------------------- + +/** + * Same as std::stable_sort(first, last, compare). + */ template void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare) { - TimSort::sort(first, last, compare); + detail::TimSort::sort(first, last, compare); +} + +/** + * Same as std::stable_sort(first, last). + */ +template +void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { + typedef typename std::iterator_traits::value_type value_type; + gfx::timsort(first, last, std::less()); } } // namespace gfx From e451bbb4d6ac0e3d2977c5bc7d4b416d56beb292 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 18 Oct 2019 21:13:34 +0200 Subject: [PATCH 055/137] Release 1.0.0 --- README.md | 17 +++++++++++++++-- include/gfx/timsort.hpp | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b401e72..b0f187c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -## TimSort [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) +[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F1.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) +[![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) +[![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) + +## TimSort A C++ implementation of TimSort, an O(n log n) stable sorting algorithm, ported from Python's and OpenJDK's. @@ -46,12 +50,21 @@ cd build make install ``` -## CONFIGURATION +## DIAGNOSTICS & INFORMATION A few configuration macros allow gfx::timsort to emit diagnostic, which might be helpful to diagnose issues: * Defining `GFX_TIMSORT_ENABLE_ASSERT` inserts assertions in key locations in the algorithm to avoid logic errors. * Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the algorithm. +**cpp-TimSort** follows semantic versioning and provides the following macros to retrieve the current major, minor +and patch versions: + +```cpp +GFX_TIMSORT_VERSION_MAJOR +GFX_TIMSORT_VERSION_MINOR +GFX_TIMSORT_VERSION_PATCH +``` + ## TESTS The tests are written with Catch2 (branch 1.x) and can be compiled with CMake and run through CTest. diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 55077a2..02dac34 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -35,6 +35,14 @@ #include #include +// Semantic versioning macros + +#define GFX_TIMSORT_VERSION_MAJOR 1 +#define GFX_TIMSORT_VERSION_MINOR 0 +#define GFX_TIMSORT_VERSION_PATCH 0 + +// Diagnostic selection macros + #ifdef GFX_TIMSORT_ENABLE_ASSERT # include # define GFX_TIMSORT_ASSERT(expr) assert(expr) From f61add23217edef42af7fff06f936090c585e929 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 3 Nov 2019 17:02:33 +0100 Subject: [PATCH 056/137] Split assertions for more precise diagnostics --- include/gfx/timsort.hpp | 42 +++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 02dac34..ef9a7d3 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -130,7 +130,8 @@ template class TimSort { std::vector > pending_; static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare compare) { - GFX_TIMSORT_ASSERT(lo <= start && start <= hi); + GFX_TIMSORT_ASSERT(lo <= start); + GFX_TIMSORT_ASSERT(start <= hi); if (start == lo) { ++start; } @@ -229,7 +230,8 @@ template class TimSort { iter_t base2 = pending_[i + 1].base; diff_t len2 = pending_[i + 1].len; - GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0); + GFX_TIMSORT_ASSERT(len1 > 0); + GFX_TIMSORT_ASSERT(len2 > 0); GFX_TIMSORT_ASSERT(base1 + len1 == base2); pending_[i].len = len1 + len2; @@ -265,7 +267,9 @@ template class TimSort { template diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint, Compare compare) { - GFX_TIMSORT_ASSERT(len > 0 && hint >= 0 && hint < len); + GFX_TIMSORT_ASSERT(len > 0); + GFX_TIMSORT_ASSERT(hint >= 0); + GFX_TIMSORT_ASSERT(hint < len); diff_t lastOfs = 0; diff_t ofs = 1; @@ -304,14 +308,18 @@ template class TimSort { lastOfs = hint - ofs; ofs = hint - tmp; } - GFX_TIMSORT_ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len); + GFX_TIMSORT_ASSERT(-1 <= lastOfs); + GFX_TIMSORT_ASSERT(lastOfs < ofs); + GFX_TIMSORT_ASSERT(ofs <= len); return std::lower_bound(base + (lastOfs + 1), base + ofs, key, compare) - base; } template diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint, Compare compare) { - GFX_TIMSORT_ASSERT(len > 0 && hint >= 0 && hint < len); + GFX_TIMSORT_ASSERT(len > 0); + GFX_TIMSORT_ASSERT(hint >= 0); + GFX_TIMSORT_ASSERT(hint < len); diff_t ofs = 1; diff_t lastOfs = 0; @@ -350,7 +358,9 @@ template class TimSort { lastOfs += hint; ofs += hint; } - GFX_TIMSORT_ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len); + GFX_TIMSORT_ASSERT(-1 <= lastOfs); + GFX_TIMSORT_ASSERT(lastOfs < ofs); + GFX_TIMSORT_ASSERT(ofs <= len); return std::upper_bound(base + (lastOfs + 1), base + ofs, key, compare) - base; } @@ -372,7 +382,9 @@ template class TimSort { void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { - GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); + GFX_TIMSORT_ASSERT(len1 > 0); + GFX_TIMSORT_ASSERT(len2 > 0); + GFX_TIMSORT_ASSERT(base1 + len1 == base2); if (len1 == 1) { return rotateLeft(base1, base2 + len2); @@ -398,7 +410,8 @@ template class TimSort { diff_t count2 = 0; do { - GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); + GFX_TIMSORT_ASSERT(len1 > 1); + GFX_TIMSORT_ASSERT(len2 > 0); if (compare(*cursor2, *cursor1)) { *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); @@ -418,7 +431,8 @@ template class TimSort { } while ((count1 | count2) < minGallop); do { - GFX_TIMSORT_ASSERT(len1 > 1 && len2 > 0); + GFX_TIMSORT_ASSERT(len1 > 1); + GFX_TIMSORT_ASSERT(len2 > 0); count1 = gallopRight(*cursor2, cursor1, len1, 0, compare); if (count1 != 0) { @@ -477,7 +491,9 @@ template class TimSort { } void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { - GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2); + GFX_TIMSORT_ASSERT(len1 > 0); + GFX_TIMSORT_ASSERT(len2 > 0); + GFX_TIMSORT_ASSERT(base1 + len1 == base2); if (len1 == 1) { return rotateLeft(base1, base2 + len2); @@ -509,7 +525,8 @@ template class TimSort { --cursor1; do { - GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); + GFX_TIMSORT_ASSERT(len1 > 0); + GFX_TIMSORT_ASSERT(len2 > 1); if (compare(*cursor2, *cursor1)) { *(dest--) = GFX_TIMSORT_MOVE(*cursor1); @@ -532,7 +549,8 @@ template class TimSort { ++cursor1; // See comment before the loop do { - GFX_TIMSORT_ASSERT(len1 > 0 && len2 > 1); + GFX_TIMSORT_ASSERT(len1 > 0); + GFX_TIMSORT_ASSERT(len2 > 1); count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1, compare); if (count1 != 0) { From 838aea0ffb2b8b4a3859a589c66f8b6fd6f41eba Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 16 Nov 2019 22:50:54 +0100 Subject: [PATCH 057/137] Support simple projections --- README.md | 19 ++++++++++--- include/gfx/timsort.hpp | 61 +++++++++++++++++++++++++++++++++++++++-- tests/cxx_98_tests.cpp | 15 ++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b0f187c..68814a2 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,29 @@ See also the following links for a detailed description of TimSort: According to the benchmarks, it is a bit slower than `std::sort()` on randomized sequences, but much faster on partially-sorted ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_sort`, with the -difference that it's can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. +difference that it can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. + +Additionally `gfx::timsort` can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) +after the comparison function. The support is a bit rougher than in the linked article: only instances of types +callable with parenthesis can be used, there is no support for pointer to members. ## EXAMPLE +Example using timsort with a comparison function and a projection function to sort a vector of strings by length: + ```cpp #include #include #include -std::vector a; -// ... initialize a ... -gfx::timsort(a.begin(), a.end(), std::less()); +size_t len(const std::string& str) { + return str.size(); +} + +// Sort a vector of strings by length +std::vector vec; +// ... fill vec ... +gfx::timsort(vec.begin(), vec.end(), std::less(), &len); ``` ## COMPATIBILITY diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index ef9a7d3..5f3bc58 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -103,6 +103,52 @@ namespace gfx { namespace detail { +// Equivalent to C++20 std::identity +struct identity { +#if GFX_TIMSORT_USE_STD_MOVE + template + T&& operator()(T&& value) const + { + return std::forward(value); + } +#else + template + T& operator()(T& value) const { + return value; + } + + template + T const& operator()(T const& value) const { + return value; + } +#endif +}; + +// Merge a predicate and a projection function +template +struct projection_compare { + projection_compare(Compare comp, Projection proj) : compare(comp), projection(proj) { + } + +#if GFX_TIMSORT_USE_STD_MOVE + template + bool operator()(T &&lhs, U &&rhs) { + return static_cast(compare( + projection(std::forward(lhs)), + projection(std::forward(rhs)) + )); + } +#else + template + bool operator()(T &lhs, U &rhs) { + return static_cast(compare(projection(lhs), projection(rhs))); + } +#endif + + Compare compare; + Projection projection; +}; + template struct run { typedef typename std::iterator_traits::difference_type diff_t; @@ -669,12 +715,23 @@ template class TimSort { // Public interface implementation // --------------------------------------- +/** + * Stably sorts a range with a comparison function and a projection function. + */ +template +void timsort(RandomAccessIterator const first, RandomAccessIterator const last, + Compare compare, Projection projection) { + typedef detail::projection_compare compare_t; + compare_t comp(compare, projection); + detail::TimSort::sort(first, last, comp); +} + /** * Same as std::stable_sort(first, last, compare). */ template void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare) { - detail::TimSort::sort(first, last, compare); + gfx::timsort(first, last, compare, detail::identity()); } /** @@ -683,7 +740,7 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last, template void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { typedef typename std::iterator_traits::value_type value_type; - gfx::timsort(first, last, std::less()); + gfx::timsort(first, last, std::less(), detail::identity()); } } // namespace gfx diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index 394507a..4dc4a7e 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -516,3 +516,18 @@ TEST_CASE( "issue2_duplication" ) { CHECK(a[1001].first == expected[1001].first); CHECK(a[1001].second == expected[1001].second); } + +TEST_CASE( "projection" ) { + const int size = 128; + + std::vector vec; + for (int i = 0; i < size; ++i) { + vec.push_back(i - 40); + } + std::random_shuffle(vec.begin(), vec.end()); + + gfx::timsort(vec.begin(), vec.end(), std::greater(), std::negate()); + for (int i = 0; i < size; ++i) { + CHECK(vec[i] == i - 40); + } +} From 2110849289beaeda8e8710bc84ad4b0102fe5478 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 17 Nov 2019 12:09:54 +0100 Subject: [PATCH 058/137] Fix DEBUG -> Debug when passing flags in CMake --- tests/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c679b92..0fc296c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,8 +46,8 @@ macro(configure_tests target) # Configure optimization options target_compile_options(${target} PRIVATE - $<$,$>:-O0> - $<$,$>:-Og> + $<$,$>:-O0> + $<$,$>:-Og> ) # Use lld or the gold linker if possible From 6fd48e9b8d79cbc4bfcc4b71c6de8ce118b00cd0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 17 Nov 2019 16:25:31 +0100 Subject: [PATCH 059/137] Supposedly final tweaks --- .travis.yml | 12 ------------ tests/CMakeLists.txt | 17 +++++++---------- tests/valgrind-osx.supp | 29 ----------------------------- 3 files changed, 7 insertions(+), 51 deletions(-) delete mode 100644 tests/valgrind-osx.supp diff --git a/.travis.yml b/.travis.yml index 21e93b6..2db0093 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,18 +101,6 @@ matrix: compiler: gcc # OSX clang -# - os: osx -# osx_image: xcode9.2 -# env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Xcode" -# addons: -# homebrew: -# update: true -# packages: -# - ccache -# - cmake -# - valgrind -# compiler: clang - - os: osx osx_image: xcode8.3 env: BUILD_TYPE=Release CMAKE_GENERATOR="Xcode" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0fc296c..1b4732f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,6 +20,12 @@ target_include_directories(Catch2 INTERFACE option(GFX_TIMSORT_USE_VALGRIND "Whether to run the tests with Valgrind" OFF) set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") +# Configure Valgrind +if (${GFX_TIMSORT_USE_VALGRIND}) + find_program(MEMORYCHECK_COMMAND valgrind) + set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") +endif() + macro(configure_tests target) # Add required dependencies to tests target_link_libraries(${target} PRIVATE @@ -80,7 +86,7 @@ add_executable(cxx_98_tests configure_tests(cxx_98_tests) target_compile_features(cxx_98_tests PRIVATE cxx_std_98) -# Tests that require C++11 support to run +# Tests that require C++11 support add_executable(cxx_11_tests main.cpp cxx_11_tests.cpp @@ -88,15 +94,6 @@ add_executable(cxx_11_tests configure_tests(cxx_11_tests) target_compile_features(cxx_11_tests PRIVATE cxx_std_11) -# Configure Valgrind -if (${GFX_TIMSORT_USE_VALGRIND}) - find_program(MEMORYCHECK_COMMAND valgrind) - set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") - if (APPLE) - set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/valgrind-osx.supp) - endif() -endif() - include(CTest) include(ParseAndAddCatchTests) diff --git a/tests/valgrind-osx.supp b/tests/valgrind-osx.supp deleted file mode 100644 index 569fd28..0000000 --- a/tests/valgrind-osx.supp +++ /dev/null @@ -1,29 +0,0 @@ -{ - Mac-OS-X-System-Leaks - Memcheck:Leak - match-leak-kinds: possible - fun:calloc - fun:map_images_nolock - fun:map_images - fun:_ZN4dyldL18notifyBatchPartialE17dyld_image_statesbPFPKcS0_jPK15dyld_image_infoEbb - fun:_ZN4dyld21registerObjCNotifiersEPFvjPKPKcPKPK11mach_headerEPFvS1_S6_ESC_ - fun:_dyld_objc_notify_register - fun:_objc_init - fun:_os_object_init - fun:libdispatch_init - fun:libSystem_initializer - fun:_ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE - fun:_ZN16ImageLoaderMachO16doInitializationERKN11ImageLoader11LinkContextE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader19processInitializersERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE - fun:_ZN4dyld24initializeMainExecutableEv - fun:_ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm - fun:_ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm - fun:_dyld_start - obj:* - obj:* - obj:* - obj:* -} \ No newline at end of file From 3c76dc650e52cf8a52a139b9dcf0b390328e6ce1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 17 Nov 2019 17:06:43 +0100 Subject: [PATCH 060/137] Release 1.1.0 --- README.md | 18 +++++++++--------- include/gfx/timsort.hpp | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 68814a2..527ca55 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F1.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) +[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F1.1.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) [![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) @@ -10,17 +10,17 @@ See also the following links for a detailed description of TimSort: * http://svn.python.org/projects/python/trunk/Objects/listsort.txt * http://en.wikipedia.org/wiki/Timsort -According to the benchmarks, it is a bit slower than `std::sort()` on randomized sequences, but much faster on -partially-sorted ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_sort`, with the -difference that it can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. +According to the benchmarks, it is slower than `std::sort()` on randomized sequences, but faster on partially-sorted +ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_sort`, with the difference that it +can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. Additionally `gfx::timsort` can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) -after the comparison function. The support is a bit rougher than in the linked article: only instances of types -callable with parenthesis can be used, there is no support for pointer to members. +after the comparison function. The support is a bit rougher than in the linked article or the C++20 stadard library: +only instances of types callable with parentheses can be used, there is no support for pointer to members. ## EXAMPLE -Example using timsort with a comparison function and a projection function to sort a vector of strings by length: +Example of using timsort with a comparison function and a projection function to sort a vector of strings by length: ```cpp #include @@ -39,7 +39,7 @@ gfx::timsort(vec.begin(), vec.end(), std::less(), &len); ## COMPATIBILITY -This library is compatible with C++98, but if you compile it with C++11 or later it will try to use `std::move()` +This library is compatible with C++98, but if you compile it with C++11 or higher it will try to use `std::move()` when possible instead of copying vaues around, which notably allows to sort collections of move-only types (see [#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). @@ -48,7 +48,7 @@ You can explicity control the use of `std::move()` by setting the macro `GFX_TIM The library has been tested with the following compilers: * GCC 5 * Clang 3.8 -* The Clang version that ships with Xcode 8.3 +* The AppleClang version that ships with Xcode 8.3 * MSVC 2017 update 9 It should also work with more recent compilers, and most likely with some older compilers too. diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 5f3bc58..8af9d44 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -38,7 +38,7 @@ // Semantic versioning macros #define GFX_TIMSORT_VERSION_MAJOR 1 -#define GFX_TIMSORT_VERSION_MINOR 0 +#define GFX_TIMSORT_VERSION_MINOR 1 #define GFX_TIMSORT_VERSION_PATCH 0 // Diagnostic selection macros From 61556cfa81faf84b6abced897fca07ffb41786dc Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 18 Nov 2019 20:52:02 +0100 Subject: [PATCH 061/137] Guard std::min calls against windows.h inclusion --- include/gfx/timsort.hpp | 6 +++--- tests/CMakeLists.txt | 13 +++++++++++++ tests/windows.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 tests/windows.cpp diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 8af9d44..ec3a299 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -522,7 +522,7 @@ template class TimSort { epilogue: // merge what is left from either cursor1 or cursor2 - minGallop_ = std::min(minGallop, 1); + minGallop_ = (std::min)(minGallop, 1); if (len1 == 1) { GFX_TIMSORT_ASSERT(len2 > 0); @@ -640,7 +640,7 @@ template class TimSort { epilogue: // merge what is left from either cursor1 or cursor2 - minGallop_ = std::min(minGallop, 1); + minGallop_ = (std::min)(minGallop, 1); if (len2 == 1) { GFX_TIMSORT_ASSERT(len1 > 0); @@ -688,7 +688,7 @@ template class TimSort { diff_t runLen = countRunAndMakeAscending(cur, hi, compare); if (runLen < minRun) { - diff_t const force = std::min(nRemaining, minRun); + diff_t const force = (std::min)(nRemaining, minRun); binarySort(cur, cur + force, cur + runLen, compare); runLen = force; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1b4732f..ee8e825 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -94,8 +94,21 @@ add_executable(cxx_11_tests configure_tests(cxx_11_tests) target_compile_features(cxx_11_tests PRIVATE cxx_std_11) +# Windows-specific tests +if (WIN32) + add_executable(windows_tests + main.cpp + windows.cpp + ) + configure_tests(windows_tests) + target_compile_features(windows_tests PRIVATE cxx_std_98) +endif() + include(CTest) include(ParseAndAddCatchTests) ParseAndAddCatchTests(cxx_98_tests) ParseAndAddCatchTests(cxx_11_tests) +if (WIN32) + ParseAndAddCatchTests(windows_tests) +endif() diff --git a/tests/windows.cpp b/tests/windows.cpp new file mode 100644 index 0000000..750f7ef --- /dev/null +++ b/tests/windows.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "check inclusion of windows.h" ) { + const int size = 100; + + std::vector vec; + for (int i = 0; i < size; ++i) { + vec.push_back(i); + } + + std::random_shuffle(vec.begin(), vec.end()); + gfx::timsort(vec.begin(), vec.end()); + + for (int i = 0; i < size; ++i) { + CHECK(vec[i] == i); + } +} From 72043c7cf72a0322e6b01f360a69baa1abe829d6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 28 Nov 2019 00:09:34 +0100 Subject: [PATCH 062/137] Make timsort work without postfix ++ and -- on iterators --- README.md | 4 + include/gfx/timsort.hpp | 49 ++++++---- tests/cxx_98_tests.cpp | 200 ++++++++++++++++++++++++++++++++++------ 3 files changed, 209 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 527ca55..961cc93 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ Additionally `gfx::timsort` can take a [projection function](https://ezoeryou.gi after the comparison function. The support is a bit rougher than in the linked article or the C++20 stadard library: only instances of types callable with parentheses can be used, there is no support for pointer to members. +This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents +are used, which means that timsort will work even if the postfix operators are not present or return an incompatible +type such as `void`. + ## EXAMPLE Example of using timsort with a comparison function and a projection function to sort a vector of strings by length: diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index ec3a299..154964f 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -201,15 +201,15 @@ template class TimSort { return 1; } - if (compare(*(runHi++), *lo)) { // decreasing - while (runHi < hi && compare(*runHi, *(runHi - 1))) { + if (compare(*runHi, *lo)) { // decreasing + do { ++runHi; - } + } while (runHi < hi && compare(*runHi, *(runHi - 1))); std::reverse(lo, runHi); } else { // non-decreasing - while (runHi < hi && !compare(*runHi, *(runHi - 1))) { + do { ++runHi; - } + } while (runHi < hi && !compare(*runHi, *(runHi - 1))); } return runHi - lo; @@ -445,7 +445,9 @@ template class TimSort { iter_t cursor2 = base2; iter_t dest = base1; - *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); + *dest = GFX_TIMSORT_MOVE(*cursor2); + ++cursor2; + ++dest; --len2; int minGallop(minGallop_); @@ -460,14 +462,18 @@ template class TimSort { GFX_TIMSORT_ASSERT(len2 > 0); if (compare(*cursor2, *cursor1)) { - *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); + *dest = GFX_TIMSORT_MOVE(*cursor2); + ++cursor2; + ++dest; ++count2; count1 = 0; if (--len2 == 0) { goto epilogue; } } else { - *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); + *dest = GFX_TIMSORT_MOVE(*cursor1); + ++cursor1; + ++dest; ++count1; count2 = 0; if (--len1 == 1) { @@ -491,7 +497,9 @@ template class TimSort { goto epilogue; } } - *(dest++) = GFX_TIMSORT_MOVE(*(cursor2++)); + *dest = GFX_TIMSORT_MOVE(*cursor2); + ++cursor2; + ++dest; if (--len2 == 0) { goto epilogue; } @@ -506,7 +514,9 @@ template class TimSort { goto epilogue; } } - *(dest++) = GFX_TIMSORT_MOVE(*(cursor1++)); + *dest = GFX_TIMSORT_MOVE(*cursor1); + ++cursor1; + ++dest; if (--len1 == 1) { goto epilogue; } @@ -554,7 +564,8 @@ template class TimSort { tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); iter_t dest = base2 + (len2 - 1); - *(dest--) = GFX_TIMSORT_MOVE(*(--cursor1)); + *dest = GFX_TIMSORT_MOVE(*(--cursor1)); + --dest; --len1; int minGallop(minGallop_); @@ -575,7 +586,8 @@ template class TimSort { GFX_TIMSORT_ASSERT(len2 > 1); if (compare(*cursor2, *cursor1)) { - *(dest--) = GFX_TIMSORT_MOVE(*cursor1); + *dest = GFX_TIMSORT_MOVE(*cursor1); + --dest; ++count1; count2 = 0; if (--len1 == 0) { @@ -583,7 +595,9 @@ template class TimSort { } --cursor1; } else { - *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); + *dest = GFX_TIMSORT_MOVE(*cursor2); + --cursor2; + --dest; ++count2; count1 = 0; if (--len2 == 1) { @@ -609,7 +623,9 @@ template class TimSort { goto epilogue; } } - *(dest--) = GFX_TIMSORT_MOVE(*(cursor2--)); + *dest = GFX_TIMSORT_MOVE(*cursor2); + --cursor2; + --dest; if (--len2 == 1) { goto epilogue; } @@ -624,12 +640,13 @@ template class TimSort { goto epilogue; } } - *(dest--) = GFX_TIMSORT_MOVE(*(--cursor1)); + *dest = GFX_TIMSORT_MOVE(*(--cursor1)); + --dest; if (--len1 == 0) { goto epilogue; } - minGallop--; + --minGallop; } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); if (minGallop < 0) { diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index 4dc4a7e..05e0f7f 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -12,6 +12,168 @@ #include #include +namespace { + // Helper types for the tests + + //////////////////////////////////////////////////////////// + // Timsort should work with types that are not + // default-constructible + + struct NonDefaultConstructible { + int i; + + NonDefaultConstructible(int i_) : i(i_) { + } + + friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { + return x.i < y.i; + } + }; + + //////////////////////////////////////////////////////////// + // Tools to test the stability of the sort + + enum id { foo, bar, baz }; + + typedef std::pair pair_t; + + inline bool less_in_first(pair_t x, pair_t y) { + return x.first < y.first; + } + + //////////////////////////////////////////////////////////// + // Timsort should work with iterators that don't have a + // post-increment or post-decrement operation + + template + class NoPostIterator + { + public: + + //////////////////////////////////////////////////////////// + // Public types + + typedef typename std::iterator_traits::iterator_category iterator_category; + typedef Iterator iterator_type; + typedef typename std::iterator_traits::value_type value_type; + typedef typename std::iterator_traits::difference_type difference_type; + typedef typename std::iterator_traits::pointer pointer; + typedef typename std::iterator_traits::reference reference; + + //////////////////////////////////////////////////////////// + // Constructors + + NoPostIterator() : _it() { + } + + explicit NoPostIterator(Iterator it) : _it(it) { + } + + //////////////////////////////////////////////////////////// + // Members access + + iterator_type base() const { + return _it; + } + + //////////////////////////////////////////////////////////// + // Element access + + reference operator*() const { + return *base(); + } + + pointer operator->() const { + return &(operator*()); + } + + //////////////////////////////////////////////////////////// + // Increment/decrement operators + + NoPostIterator& operator++() { + ++_it; + return *this; + } + + NoPostIterator& operator--() { + --_it; + return *this; + } + + NoPostIterator& operator+=(difference_type increment) { + _it += increment; + return *this; + } + + NoPostIterator& operator-=(difference_type increment) { + _it -= increment; + return *this; + } + + //////////////////////////////////////////////////////////// + // Comparison operators + + friend bool operator==(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() == rhs.base(); + } + + friend bool operator!=(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() != rhs.base(); + } + + //////////////////////////////////////////////////////////// + // Relational operators + + friend bool operator<(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() < rhs.base(); + } + + friend bool operator<=(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() <= rhs.base(); + } + + friend bool operator>(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() > rhs.base(); + } + + friend bool operator>=(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() >= rhs.base(); + } + + //////////////////////////////////////////////////////////// + // Arithmetic operators + + friend NoPostIterator operator+(NoPostIterator it, difference_type size) { + return it += size; + } + + friend NoPostIterator operator+(difference_type size, NoPostIterator it) { + return it += size; + } + + friend NoPostIterator operator-(NoPostIterator it, difference_type size) { + return it -= size; + } + + friend difference_type operator-(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() - rhs.base(); + } + + private: + + Iterator _it; + }; + + //////////////////////////////////////////////////////////// + // Construction function + + template + NoPostIterator make_no_post_iterator(Iterator it) { + return NoPostIterator(it); + } + +} + TEST_CASE( "simple0" ) { std::vector a; @@ -358,17 +520,6 @@ TEST_CASE( "string_array" ) { CHECK(a[4] == "9"); } -struct NonDefaultConstructible { - int i; - - NonDefaultConstructible(int i_) : i(i_) { - } - - friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { - return x.i < y.i; - } -}; - TEST_CASE( "non_default_constructible" ) { NonDefaultConstructible a[] = {7, 1, 5, 3, 9}; @@ -398,14 +549,6 @@ TEST_CASE( "default_compare_function" ) { } } -enum id { foo, bar, baz }; - -typedef std::pair pair_t; - -inline bool less_in_first(pair_t x, pair_t y) { - return x.first < y.first; -} - TEST_CASE( "stability" ) { std::vector a; @@ -446,13 +589,6 @@ TEST_CASE( "stability" ) { CHECK(a[11].second == baz); } -inline bool less_in_pair(const std::pair &x, const std::pair &y) { - if (x.first == y.first) { - return x.second < y.second; - } - return x.first < y.first; -} - TEST_CASE( "issue2_duplication" ) { std::vector > a; @@ -465,8 +601,8 @@ TEST_CASE( "issue2_duplication" ) { std::vector > expected(a); - std::sort(expected.begin(), expected.end(), &less_in_pair); - gfx::timsort(a.begin(), a.end(), &less_in_pair); + std::sort(expected.begin(), expected.end()); + gfx::timsort(a.begin(), a.end()); #if 0 for (std::size_t i = 0; i < a.size(); ++i) { @@ -531,3 +667,11 @@ TEST_CASE( "projection" ) { CHECK(vec[i] == i - 40); } } + +TEST_CASE( "iterator without post-increment or post-decrement" ) { + std::vector a; + + gfx::timsort(make_no_post_iterator(a.begin()), make_no_post_iterator(a.end())); + + CHECK(a.size() == std::size_t(0)); +} From 81b54106833164865fc95589c7f7253a2ff1e9f3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 29 Nov 2019 21:40:42 +0100 Subject: [PATCH 063/137] Generalized support callables when std::invoke is available --- README.md | 5 +++-- include/gfx/timsort.hpp | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 961cc93..ffe87e6 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@ ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_ can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. Additionally `gfx::timsort` can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) -after the comparison function. The support is a bit rougher than in the linked article or the C++20 stadard library: -only instances of types callable with parentheses can be used, there is no support for pointer to members. +after the comparison function. The support is a bit rougher than in the linked article or the C++20 standard library: +unless `std::invoke` is available, only instances of types callable with parentheses can be used, there is no support +for pointer to members. This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents are used, which means that timsort will work even if the postfix operators are not present or return an incompatible diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 154964f..94944b9 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -133,10 +133,17 @@ struct projection_compare { #if GFX_TIMSORT_USE_STD_MOVE template bool operator()(T &&lhs, U &&rhs) { +# ifdef __cpp_lib_invoke + return static_cast(std::invoke(compare, + std::invoke(projection, std::forward(lhs)), + std::invoke(projection, std::forward(rhs)) + )); +# else return static_cast(compare( projection(std::forward(lhs)), projection(std::forward(rhs)) )); +# endif } #else template From d35ab83513b6e7baf52e3593c6c2c58cb5f04c44 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 29 Nov 2019 22:30:18 +0100 Subject: [PATCH 064/137] Release 1.2.0 --- README.md | 2 +- include/gfx/timsort.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ffe87e6..4c02073 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F1.1.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) +[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F1.2.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) [![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 94944b9..2766659 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -38,7 +38,7 @@ // Semantic versioning macros #define GFX_TIMSORT_VERSION_MAJOR 1 -#define GFX_TIMSORT_VERSION_MINOR 1 +#define GFX_TIMSORT_VERSION_MINOR 2 #define GFX_TIMSORT_VERSION_PATCH 0 // Diagnostic selection macros From 1698be4ddd23091f2605a7ef65eb4d995f3f8b90 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Nov 2019 12:23:10 +0100 Subject: [PATCH 065/137] Release 2.0.0 will be C++11-only --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 74b82d7..6bd4181 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ target_include_directories(timsort INTERFACE $ ) -target_compile_features(timsort INTERFACE cxx_std_98) +target_compile_features(timsort INTERFACE cxx_std_11) add_library(gfx::timsort ALIAS timsort) From 38ab433c7809d94dd976a1bc4cf2656e261592bb Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Nov 2019 13:12:28 +0100 Subject: [PATCH 066/137] Move to Catch 2.x for the tests --- tests/CMakeLists.txt | 19 ++++++------------- tests/cxx_11_tests.cpp | 2 +- tests/cxx_98_tests.cpp | 2 +- tests/main.cpp | 2 +- tests/windows.cpp | 2 +- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee8e825..6e63977 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,19 +3,12 @@ include(DownloadProject) # Download and configure Catch2 for the tests download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG Catch1.x + GIT_TAG master UPDATE_DISCONNECTED 1 ) +add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/contrib) -# Configure Catch2 so that it looks like a proper target -add_library(Catch2 INTERFACE) -add_library(Catch2::Catch2 ALIAS Catch2) -target_include_directories(Catch2 INTERFACE - $ - $ -) - # Testsutie options option(GFX_TIMSORT_USE_VALGRIND "Whether to run the tests with Valgrind" OFF) set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") @@ -105,10 +98,10 @@ if (WIN32) endif() include(CTest) -include(ParseAndAddCatchTests) +include(Catch) -ParseAndAddCatchTests(cxx_98_tests) -ParseAndAddCatchTests(cxx_11_tests) +catch_discover_tests(cxx_98_tests) +catch_discover_tests(cxx_11_tests) if (WIN32) - ParseAndAddCatchTests(windows_tests) + catch_discover_tests(windows_tests) endif() diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index 1fa1c0c..a525806 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include //////////////////////////////////////////////////////////// diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index 05e0f7f..7762f21 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include namespace { diff --git a/tests/main.cpp b/tests/main.cpp index 435b9a1..6890559 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -5,4 +5,4 @@ * SPDX-License-Identifier: MIT */ #define CATCH_CONFIG_MAIN -#include +#include diff --git a/tests/windows.cpp b/tests/windows.cpp index 750f7ef..991cd77 100644 --- a/tests/windows.cpp +++ b/tests/windows.cpp @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include #include TEST_CASE( "check inclusion of windows.h" ) { From 6af8bfec2e748bb5ef21a5e3ac6ab5caaca3274b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Nov 2019 15:06:47 +0100 Subject: [PATCH 067/137] Unconditionally use move semantics --- README.md | 8 +-- include/gfx/timsort.hpp | 131 +++++++++++----------------------------- 2 files changed, 36 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 4c02073..0ff8f6a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ See also the following links for a detailed description of TimSort: * http://svn.python.org/projects/python/trunk/Objects/listsort.txt * http://en.wikipedia.org/wiki/Timsort +This library is compatible with C++11. If you need a C++98 version, you can check the 1.x.y branch of this repository. + According to the benchmarks, it is slower than `std::sort()` on randomized sequences, but faster on partially-sorted ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_sort`, with the difference that it can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. @@ -44,12 +46,6 @@ gfx::timsort(vec.begin(), vec.end(), std::less(), &len); ## COMPATIBILITY -This library is compatible with C++98, but if you compile it with C++11 or higher it will try to use `std::move()` -when possible instead of copying vaues around, which notably allows to sort collections of move-only types (see -[#9](https://github.com/gfx/cpp-TimSort/pull/9) for details). - -You can explicity control the use of `std::move()` by setting the macro `GFX_TIMSORT_USE_STD_MOVE` to `0` or `1`. - The library has been tested with the following compilers: * GCC 5 * Clang 3.8 diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 2766659..895098d 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include // Semantic versioning macros @@ -57,43 +58,6 @@ # define GFX_TIMSORT_LOG(expr) ((void)0) #endif -// If GFX_TIMSORT_USE_STD_MOVE is not defined, try to define it as follows: -// - Check standard feature-testing macro -// - Check non-standard feature-testing macro -// - Check C++ standard (disable if < C++11) -// - Check compiler-specific versions known to support move semantics - -#ifndef GFX_TIMSORT_USE_STD_MOVE -# if defined(__cpp_rvalue_references) -# define GFX_TIMSORT_USE_STD_MOVE 1 -# elif defined(__has_feature) -# if __has_feature(cxx_rvalue_references) -# define GFX_TIMSORT_USE_STD_MOVE 1 -# else -# define GFX_TIMSORT_USE_STD_MOVE 0 -# endif -# elif !(defined(__cplusplus) && __cplusplus >= 201103L) -# define GFX_TIMSORT_USE_STD_MOVE 0 -# elif defined(_MSC_VER) && _MSC_VER >= 1700 -# define GFX_TIMSORT_USE_STD_MOVE 1 -# elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && __GNUC_MINOR__ >= 6)) -# define GFX_TIMSORT_USE_STD_MOVE 1 -# else -# define GFX_TIMSORT_USE_STD_MOVE 0 -# endif -#endif - -#if GFX_TIMSORT_USE_STD_MOVE - #include - #define GFX_TIMSORT_MOVE(x) std::move(x) - #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::move((in1), (in2), (out)); - #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::move_backward((in1), (in2), (out)); -#else - #define GFX_TIMSORT_MOVE(x) (x) - #define GFX_TIMSORT_MOVE_RANGE(in1, in2, out) std::copy((in1), (in2), (out)); - #define GFX_TIMSORT_MOVE_BACKWARD(in1, in2, out) std::copy_backward((in1), (in2), (out)); -#endif - namespace gfx { @@ -105,23 +69,11 @@ namespace detail { // Equivalent to C++20 std::identity struct identity { -#if GFX_TIMSORT_USE_STD_MOVE template - T&& operator()(T&& value) const + constexpr T&& operator()(T&& value) const noexcept { return std::forward(value); } -#else - template - T& operator()(T& value) const { - return value; - } - - template - T const& operator()(T const& value) const { - return value; - } -#endif }; // Merge a predicate and a projection function @@ -130,27 +82,20 @@ struct projection_compare { projection_compare(Compare comp, Projection proj) : compare(comp), projection(proj) { } -#if GFX_TIMSORT_USE_STD_MOVE template bool operator()(T &&lhs, U &&rhs) { -# ifdef __cpp_lib_invoke +#ifdef __cpp_lib_invoke return static_cast(std::invoke(compare, std::invoke(projection, std::forward(lhs)), std::invoke(projection, std::forward(rhs)) )); -# else +#else return static_cast(compare( projection(std::forward(lhs)), projection(std::forward(rhs)) )); -# endif - } -#else - template - bool operator()(T &lhs, U &rhs) { - return static_cast(compare(projection(lhs), projection(rhs))); - } #endif + } Compare compare; Projection projection; @@ -190,13 +135,13 @@ template class TimSort { } for (; start < hi; ++start) { GFX_TIMSORT_ASSERT(lo <= start); - value_t pivot = GFX_TIMSORT_MOVE(*start); + value_t pivot = std::move(*start); iter_t const pos = std::upper_bound(lo, start, pivot, compare); for (iter_t p = start; p > pos; --p) { - *p = GFX_TIMSORT_MOVE(*(p - 1)); + *p = std::move(*(p - 1)); } - *pos = GFX_TIMSORT_MOVE(pivot); + *pos = std::move(pivot); } } @@ -420,17 +365,17 @@ template class TimSort { static void rotateLeft(iter_t first, iter_t last) { - value_t tmp = GFX_TIMSORT_MOVE(*first); - iter_t last_1 = GFX_TIMSORT_MOVE_RANGE(first + 1, last, first); - *last_1 = GFX_TIMSORT_MOVE(tmp); + value_t tmp = std::move(*first); + iter_t last_1 = std::move(first + 1, last, first); + *last_1 = std::move(tmp); } static void rotateRight(iter_t first, iter_t last) { iter_t last_1 = last - 1; - value_t tmp = GFX_TIMSORT_MOVE(*last_1); - GFX_TIMSORT_MOVE_BACKWARD(first, last_1, last); - *first = GFX_TIMSORT_MOVE(tmp); + value_t tmp = std::move(*last_1); + std::move_backward(first, last_1, last); + *first = std::move(tmp); } @@ -452,7 +397,7 @@ template class TimSort { iter_t cursor2 = base2; iter_t dest = base1; - *dest = GFX_TIMSORT_MOVE(*cursor2); + *dest = std::move(*cursor2); ++cursor2; ++dest; --len2; @@ -469,7 +414,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(len2 > 0); if (compare(*cursor2, *cursor1)) { - *dest = GFX_TIMSORT_MOVE(*cursor2); + *dest = std::move(*cursor2); ++cursor2; ++dest; ++count2; @@ -478,7 +423,7 @@ template class TimSort { goto epilogue; } } else { - *dest = GFX_TIMSORT_MOVE(*cursor1); + *dest = std::move(*cursor1); ++cursor1; ++dest; ++count1; @@ -495,7 +440,7 @@ template class TimSort { count1 = gallopRight(*cursor2, cursor1, len1, 0, compare); if (count1 != 0) { - GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + count1, dest + count1); + std::move_backward(cursor1, cursor1 + count1, dest + count1); dest += count1; cursor1 += count1; len1 -= count1; @@ -504,7 +449,7 @@ template class TimSort { goto epilogue; } } - *dest = GFX_TIMSORT_MOVE(*cursor2); + *dest = std::move(*cursor2); ++cursor2; ++dest; if (--len2 == 0) { @@ -513,7 +458,7 @@ template class TimSort { count2 = gallopLeft(*cursor1, cursor2, len2, 0, compare); if (count2 != 0) { - GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + count2, dest); + std::move(cursor2, cursor2 + count2, dest); dest += count2; cursor2 += count2; len2 -= count2; @@ -521,7 +466,7 @@ template class TimSort { goto epilogue; } } - *dest = GFX_TIMSORT_MOVE(*cursor1); + *dest = std::move(*cursor1); ++cursor1; ++dest; if (--len1 == 1) { @@ -543,13 +488,13 @@ template class TimSort { if (len1 == 1) { GFX_TIMSORT_ASSERT(len2 > 0); - GFX_TIMSORT_MOVE_RANGE(cursor2, cursor2 + len2, dest); - *(dest + len2) = GFX_TIMSORT_MOVE(*cursor1); + std::move(cursor2, cursor2 + len2, dest); + *(dest + len2) = std::move(*cursor1); } else { GFX_TIMSORT_ASSERT(len1 != 0 && "Comparison function violates its general contract"); GFX_TIMSORT_ASSERT(len2 == 0); GFX_TIMSORT_ASSERT(len1 > 1); - GFX_TIMSORT_MOVE_RANGE(cursor1, cursor1 + len1, dest); + std::move(cursor1, cursor1 + len1, dest); } } @@ -571,7 +516,7 @@ template class TimSort { tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); iter_t dest = base2 + (len2 - 1); - *dest = GFX_TIMSORT_MOVE(*(--cursor1)); + *dest = std::move(*(--cursor1)); --dest; --len1; @@ -593,7 +538,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(len2 > 1); if (compare(*cursor2, *cursor1)) { - *dest = GFX_TIMSORT_MOVE(*cursor1); + *dest = std::move(*cursor1); --dest; ++count1; count2 = 0; @@ -602,7 +547,7 @@ template class TimSort { } --cursor1; } else { - *dest = GFX_TIMSORT_MOVE(*cursor2); + *dest = std::move(*cursor2); --cursor2; --dest; ++count2; @@ -624,13 +569,13 @@ template class TimSort { dest -= count1; cursor1 -= count1; len1 -= count1; - GFX_TIMSORT_MOVE_BACKWARD(cursor1, cursor1 + count1, dest + (1 + count1)); + std::move_backward(cursor1, cursor1 + count1, dest + (1 + count1)); if (len1 == 0) { goto epilogue; } } - *dest = GFX_TIMSORT_MOVE(*cursor2); + *dest = std::move(*cursor2); --cursor2; --dest; if (--len2 == 1) { @@ -642,12 +587,12 @@ template class TimSort { dest -= count2; cursor2 -= count2; len2 -= count2; - GFX_TIMSORT_MOVE_RANGE(cursor2 + 1, cursor2 + (1 + count2), dest + 1); + std::move(cursor2 + 1, cursor2 + (1 + count2), dest + 1); if (len2 <= 1) { goto epilogue; } } - *dest = GFX_TIMSORT_MOVE(*(--cursor1)); + *dest = std::move(*(--cursor1)); --dest; if (--len1 == 0) { goto epilogue; @@ -669,23 +614,19 @@ template class TimSort { if (len2 == 1) { GFX_TIMSORT_ASSERT(len1 > 0); dest -= len1; - GFX_TIMSORT_MOVE_BACKWARD(cursor1 - len1, cursor1, dest + (1 + len1)); - *dest = GFX_TIMSORT_MOVE(*cursor2); + std::move_backward(cursor1 - len1, cursor1, dest + (1 + len1)); + *dest = std::move(*cursor2); } else { GFX_TIMSORT_ASSERT(len2 != 0 && "Comparison function violates its general contract"); GFX_TIMSORT_ASSERT(len1 == 0); GFX_TIMSORT_ASSERT(len2 > 1); - GFX_TIMSORT_MOVE_RANGE(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); + std::move(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); } } void copy_to_tmp(iter_t const begin, diff_t len) { -#if GFX_TIMSORT_USE_STD_MOVE tmp_.assign(std::make_move_iterator(begin), std::make_move_iterator(begin + len)); -#else - tmp_.assign(begin, begin + len); -#endif } public: @@ -773,9 +714,5 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last) #undef GFX_TIMSORT_ASSERT #undef GFX_TIMSORT_ENABLE_LOG #undef GFX_TIMSORT_LOG -#undef GFX_TIMSORT_MOVE -#undef GFX_TIMSORT_MOVE_RANGE -#undef GFX_TIMSORT_MOVE_BACKWARD -#undef GFX_TIMSORT_USE_STD_MOVE #endif // GFX_TIMSORT_HPP From 38a3a2def40127741b3c3c95475099ff831ad5a0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Nov 2019 16:18:55 +0100 Subject: [PATCH 068/137] Add range overloads to gfx::timsort --- README.md | 42 +++++++++++++++++++++++++++++++---------- include/gfx/timsort.hpp | 29 ++++++++++++++++++++++++++-- tests/cxx_11_tests.cpp | 24 +++++++++++++++++++++++ 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0ff8f6a..ef3d55a 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,37 @@ According to the benchmarks, it is slower than `std::sort()` on randomized seque ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_sort`, with the difference that it can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. -Additionally `gfx::timsort` can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) -after the comparison function. The support is a bit rougher than in the linked article or the C++20 standard library: -unless `std::invoke` is available, only instances of types callable with parentheses can be used, there is no support -for pointer to members. +`gfx::timsort` also has a few additional features and guarantees compared to `std::stable_sort`: +* It can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) + after the comparison function. The support is a bit rougher than in the linked article or the C++20 standard library: + unless `std::invoke` is available, only instances of types callable with parentheses can be used, there is no support + for pointer to members. +* It can also be passed a range instead of a pair of iterators, in which case it will sort the whole range. +* This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents + are used, which means that timsort will work even if the postfix operators are not present or return an incompatible + type such as `void`. -This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents -are used, which means that timsort will work even if the postfix operators are not present or return an incompatible -type such as `void`. + +The full list of available signatures is as follows (in namespace `gfx`): + +```cpp +// Overloads taking a pair of iterators +template +void timsort(RandomAccessIterator const first, RandomAccessIterator const last); +template +void timsort(RandomAccessIterator const first, RandomAccessIterator const last, + Compare compare); +template +void timsort(RandomAccessIterator const first, RandomAccessIterator const last, + Compare compare, Projection projection); +// Overloads taking a range +template +void timsort(RandomAccessRange &range); +template +void timsort(RandomAccessRange &range, Compare compare); +template +void timsort(RandomAccessRange &range, Compare compare, Projection projection); +``` ## EXAMPLE @@ -39,9 +62,8 @@ size_t len(const std::string& str) { } // Sort a vector of strings by length -std::vector vec; -// ... fill vec ... -gfx::timsort(vec.begin(), vec.end(), std::less(), &len); +std::vector collection = { /* ... */ }; +gfx::timsort(collection, std::less{}, &len); ``` ## COMPATIBILITY diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 895098d..56393d4 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -676,6 +676,7 @@ template class TimSort { } // namespace detail + // --------------------------------------- // Public interface implementation // --------------------------------------- @@ -687,8 +688,8 @@ template void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare, Projection projection) { typedef detail::projection_compare compare_t; - compare_t comp(compare, projection); - detail::TimSort::sort(first, last, comp); + compare_t comp(std::move(compare), std::move(projection)); + detail::TimSort::sort(first, last, std::move(comp)); } /** @@ -708,6 +709,30 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last) gfx::timsort(first, last, std::less(), detail::identity()); } +/** + * Stably sorts a range with a comparison function and a projection function. + */ +template +void timsort(RandomAccessRange &range, Compare compare, Projection projection) { + gfx::timsort(std::begin(range), std::end(range), compare, projection); +} + +/** + * Same as std::stable_sort(std::begin(range), std::end(range), compare). + */ +template +void timsort(RandomAccessRange &range, Compare compare) { + gfx::timsort(std::begin(range), std::end(range), compare); +} + +/** + * Same as std::stable_sort(std::begin(range), std::end(range)). + */ +template +void timsort(RandomAccessRange &range) { + gfx::timsort(std::begin(range), std::end(range)); +} + } // namespace gfx #undef GFX_TIMSORT_ENABLE_ASSERT diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index a525806..6a5d8ed 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -6,6 +6,7 @@ */ #include #include +#include #include #include #include @@ -139,3 +140,26 @@ TEST_CASE( "issue14" ) { gfx::timsort(std::begin(c), std::end(c)); CHECK(std::is_sorted(std::begin(c), std::end(c))); } + +TEST_CASE( "range signatures" ) { + std::vector vec(50, 0); + std::iota(vec.begin(), vec.end(), -25); + std::random_shuffle(vec.begin(), vec.end()); + + SECTION( "range only" ) { + gfx::timsort(vec); + CHECK(std::is_sorted(vec.begin(), vec.end())); + } + + SECTION( "range with a comparison function" ) { + using value_type = std::vector::value_type; + gfx::timsort(vec, std::greater{}); + CHECK(std::is_sorted(vec.begin(), vec.end(), std::greater{})); + } + + SECTION( "range with comparison and projection functions" ) { + using value_type = std::vector::value_type; + gfx::timsort(vec, std::greater{}, std::negate{}); + CHECK(std::is_sorted(vec.begin(), vec.end())); + } +} From ea34507db5d068f60f46ad9624a9d3c1d0d0678b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Nov 2019 16:53:19 +0100 Subject: [PATCH 069/137] Add tests for generalized callables --- tests/CMakeLists.txt | 11 +++++++- tests/cxx_17_tests.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/cxx_17_tests.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e63977..582ee31 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -79,7 +79,7 @@ add_executable(cxx_98_tests configure_tests(cxx_98_tests) target_compile_features(cxx_98_tests PRIVATE cxx_std_98) -# Tests that require C++11 support +# Tests requiring C++11 support add_executable(cxx_11_tests main.cpp cxx_11_tests.cpp @@ -87,6 +87,14 @@ add_executable(cxx_11_tests configure_tests(cxx_11_tests) target_compile_features(cxx_11_tests PRIVATE cxx_std_11) +# Tests requiring C++17 support +add_executable(cxx_17_tests + main.cpp + cxx_17_tests.cpp +) +configure_tests(cxx_17_tests) +target_compile_features(cxx_17_tests PRIVATE cxx_std_17) + # Windows-specific tests if (WIN32) add_executable(windows_tests @@ -102,6 +110,7 @@ include(Catch) catch_discover_tests(cxx_98_tests) catch_discover_tests(cxx_11_tests) +catch_discover_tests(cxx_17_tests) if (WIN32) catch_discover_tests(windows_tests) endif() diff --git a/tests/cxx_17_tests.cpp b/tests/cxx_17_tests.cpp new file mode 100644 index 0000000..6afbe0d --- /dev/null +++ b/tests/cxx_17_tests.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + struct wrapper { + wrapper() = default; + wrapper(wrapper const&) = default; + wrapper(wrapper&&) = default; + + wrapper(int val) : value(val) { + } + + wrapper& operator=(wrapper const&) = default; + wrapper& operator=(wrapper&&) = default; + + wrapper& operator=(int val) { + value = val; + return *this; + } + + bool compare_to(wrapper const& other) const { + return value < other.value; + } + + int value = 0; + }; +} + +#ifdef __cpp_lib_invoke + +TEST_CASE( "generalized callables" ) { + std::vector vec(50); + std::iota(vec.begin(), vec.end(), -25); + std::mt19937 gen(123456); // fixed seed is enough + std::shuffle(vec.begin(), vec.end(), gen); + + SECTION( "for comparisons" ) { + gfx::timsort(vec, &wrapper::compare_to); + CHECK(std::is_sorted(vec.begin(), vec.end(), [](wrapper const& lhs, wrapper const& rhs) { + return lhs.value < rhs.value; + })); + } + + SECTION( "for projections" ) { + gfx::timsort(vec, std::less<>{}, &wrapper::value); + CHECK(std::is_sorted(vec.begin(), vec.end(), [](wrapper const& lhs, wrapper const& rhs) { + return lhs.value < rhs.value; + })); + } +} + +#endif // __cpp_lib_invoke From 1ef3d422124bb4043575c8801b3e4a55e605d83c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Nov 2019 17:28:49 +0100 Subject: [PATCH 070/137] Enable Valgrind tests on OSX --- .travis.yml | 12 ++++++++++++ tests/CMakeLists.txt | 3 +++ tests/valgrind-osx.supp | 29 +++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 tests/valgrind-osx.supp diff --git a/.travis.yml b/.travis.yml index 2db0093..fb001db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,6 +101,18 @@ matrix: compiler: gcc # OSX clang + - os: osx + osx_image: xcode9.2 + env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Xcode" + addons: + homebrew: + update: true + packages: + - ccache + - cmake + - valgrind + compiler: clang + - os: osx osx_image: xcode8.3 env: BUILD_TYPE=Release CMAKE_GENERATOR="Xcode" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 582ee31..053570f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,9 @@ set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pas if (${GFX_TIMSORT_USE_VALGRIND}) find_program(MEMORYCHECK_COMMAND valgrind) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") + if (APPLE) + set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/valgrind-osx.supp) + endif() endif() macro(configure_tests target) diff --git a/tests/valgrind-osx.supp b/tests/valgrind-osx.supp new file mode 100644 index 0000000..569fd28 --- /dev/null +++ b/tests/valgrind-osx.supp @@ -0,0 +1,29 @@ +{ + Mac-OS-X-System-Leaks + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + fun:map_images_nolock + fun:map_images + fun:_ZN4dyldL18notifyBatchPartialE17dyld_image_statesbPFPKcS0_jPK15dyld_image_infoEbb + fun:_ZN4dyld21registerObjCNotifiersEPFvjPKPKcPKPK11mach_headerEPFvS1_S6_ESC_ + fun:_dyld_objc_notify_register + fun:_objc_init + fun:_os_object_init + fun:libdispatch_init + fun:libSystem_initializer + fun:_ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE + fun:_ZN16ImageLoaderMachO16doInitializationERKN11ImageLoader11LinkContextE + fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE + fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE + fun:_ZN11ImageLoader19processInitializersERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE + fun:_ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE + fun:_ZN4dyld24initializeMainExecutableEv + fun:_ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm + fun:_ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm + fun:_dyld_start + obj:* + obj:* + obj:* + obj:* +} \ No newline at end of file From f63671c9ba17199108d2f59a8cea29e3762d6ab1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Nov 2019 18:25:32 +0100 Subject: [PATCH 071/137] Release 2.0.0 --- README.md | 11 +++++++++-- include/gfx/timsort.hpp | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ef3d55a..56196a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F1.2.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) +[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F2.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) -[![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) +[![License](https://img.shields.io/:license-mit-yellow.svg)](https://doge.mit-license.org) ## TimSort @@ -31,19 +31,26 @@ The full list of available signatures is as follows (in namespace `gfx`): ```cpp // Overloads taking a pair of iterators + template void timsort(RandomAccessIterator const first, RandomAccessIterator const last); + template void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare); + template void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare, Projection projection); + // Overloads taking a range + template void timsort(RandomAccessRange &range); + template void timsort(RandomAccessRange &range, Compare compare); + template void timsort(RandomAccessRange &range, Compare compare, Projection projection); ``` diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 56393d4..b42797c 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -38,8 +38,8 @@ // Semantic versioning macros -#define GFX_TIMSORT_VERSION_MAJOR 1 -#define GFX_TIMSORT_VERSION_MINOR 2 +#define GFX_TIMSORT_VERSION_MAJOR 2 +#define GFX_TIMSORT_VERSION_MINOR 0 #define GFX_TIMSORT_VERSION_PATCH 0 // Diagnostic selection macros From 298928ce37e2954956320e764a862b9b1dbc51a7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 30 Dec 2019 18:41:20 +0100 Subject: [PATCH 072/137] cpp-TimSort is now available on Conan Center [ci skip] --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 56196a5..8d8cba3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F2.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) +[![Conan Package](https://img.shields.io/badge/conan-2.0.0-blue.svg)](https://bintray.com/conan/conan-center/timsort%3A_) [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) [![License](https://img.shields.io/:license-mit-yellow.svg)](https://doge.mit-license.org) @@ -73,7 +74,7 @@ std::vector collection = { /* ... */ }; gfx::timsort(collection, std::less{}, &len); ``` -## COMPATIBILITY +## INSTALLATION & COMPATIBILITY The library has been tested with the following compilers: * GCC 5 @@ -91,6 +92,13 @@ cd build make install ``` +Alternatively the library is also available on conan-center-index and can be installed in your local Conan cache via +the following command: + +```sh +conan install timsort/2.0.0 +``` + ## DIAGNOSTICS & INFORMATION A few configuration macros allow gfx::timsort to emit diagnostic, which might be helpful to diagnose issues: From f257f56d72c7fcedc3e6a919610de4eec178aa01 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 11 Oct 2020 21:39:33 +0200 Subject: [PATCH 073/137] Change loop condition in minRunLength (fixes #33) For some reason the loop condition in minRunLength was MIN_MERGE=32 while the original CPython implementation uses 64 there. This commit changes that condition to 2*MIN_MERGE to bring the condition back to 64 at it should probably have been. Thanks @weeyizhi for identifying and the fix. --- include/gfx/timsort.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index b42797c..3822abf 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -171,7 +171,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(n >= 0); diff_t r = 0; - while (n >= MIN_MERGE) { + while (n >= 2 * MIN_MERGE) { r |= (n & 1); n >>= 1; } From 740da301f0d774f2a5efff6a1af1d6acdabdca4c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 11 Oct 2020 22:18:45 +0200 Subject: [PATCH 074/137] Travis: bump XCode version to 9.2 --- .travis.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fb001db..024c89d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -114,7 +114,7 @@ matrix: compiler: clang - os: osx - osx_image: xcode8.3 + osx_image: xcode9.2 env: BUILD_TYPE=Release CMAKE_GENERATOR="Xcode" addons: homebrew: diff --git a/README.md b/README.md index 8d8cba3..d8eb469 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ gfx::timsort(collection, std::less{}, &len); The library has been tested with the following compilers: * GCC 5 * Clang 3.8 -* The AppleClang version that ships with Xcode 8.3 +* Xcode 9.2 AppleClang * MSVC 2017 update 9 It should also work with more recent compilers, and most likely with some older compilers too. From 1c1a4a2f13c064166c51ffe7e18bc50424ce0054 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 17 Oct 2020 14:31:16 +0200 Subject: [PATCH 075/137] Change Conan badge to point to https://conan.io/ [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8eb469..fd3c26a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F2.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) -[![Conan Package](https://img.shields.io/badge/conan-2.0.0-blue.svg)](https://bintray.com/conan/conan-center/timsort%3A_) +[![Conan Package](https://img.shields.io/badge/conan-2.0.0-blue.svg)](https://conan.io/center/timsort?version=2.0.0) [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) [![License](https://img.shields.io/:license-mit-yellow.svg)](https://doge.mit-license.org) From 9bef4ea8545bf1777accf4c6f564658477928ef9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 17 Oct 2020 15:00:48 +0200 Subject: [PATCH 076/137] Release 2.0.1 --- CMakeLists.txt | 2 +- README.md | 6 +++--- include/gfx/timsort.hpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bd4181..e5c3b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(timsort VERSION 1.0.0 LANGUAGES CXX) +project(timsort VERSION 2.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index fd3c26a..a50b987 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F2.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases) -[![Conan Package](https://img.shields.io/badge/conan-2.0.0-blue.svg)](https://conan.io/center/timsort?version=2.0.0) +[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F2.0.1-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.0.1) +[![Conan Package](https://img.shields.io/badge/conan-2.0.1-blue.svg)](https://conan.io/center/timsort?version=2.0.1) [![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) [![License](https://img.shields.io/:license-mit-yellow.svg)](https://doge.mit-license.org) @@ -96,7 +96,7 @@ Alternatively the library is also available on conan-center-index and can be ins the following command: ```sh -conan install timsort/2.0.0 +conan install timsort/2.0.1 ``` ## DIAGNOSTICS & INFORMATION diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 3822abf..fa2b462 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -6,7 +6,7 @@ * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. + * Copyright (c) 2019-2020 Morwenn. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -40,7 +40,7 @@ #define GFX_TIMSORT_VERSION_MAJOR 2 #define GFX_TIMSORT_VERSION_MINOR 0 -#define GFX_TIMSORT_VERSION_PATCH 0 +#define GFX_TIMSORT_VERSION_PATCH 1 // Diagnostic selection macros From e8647951f8c276701cd079a20397a5d99eaffa40 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sun, 10 Jan 2021 19:41:06 +0200 Subject: [PATCH 077/137] Use existing Catch2 v2.x git branch instead of deleted master Tests do not compile against the default Catch2 branch devel (where the next major version, v3, of Catch2 is being developed). --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 053570f..f50dfd5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,7 @@ include(DownloadProject) # Download and configure Catch2 for the tests download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG master + GIT_TAG v2.x UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) From 5431f2f774211ea8e6ad16afe3544f5ca1ce8789 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sun, 10 Jan 2021 15:20:16 +0200 Subject: [PATCH 078/137] Make TimSort::gallop* static These member functions do not use data members and don't need a pointer to a TimSort object. --- include/gfx/timsort.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index fa2b462..5eb1c41 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -264,7 +264,8 @@ template class TimSort { } template - diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint, Compare compare) { + static diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, + diff_t const hint, Compare compare) { GFX_TIMSORT_ASSERT(len > 0); GFX_TIMSORT_ASSERT(hint >= 0); GFX_TIMSORT_ASSERT(hint < len); @@ -314,7 +315,8 @@ template class TimSort { } template - diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint, Compare compare) { + static diff_t gallopRight(ref_t key, Iter const base, diff_t const len, + diff_t const hint, Compare compare) { GFX_TIMSORT_ASSERT(len > 0); GFX_TIMSORT_ASSERT(hint >= 0); GFX_TIMSORT_ASSERT(hint < len); From 6d9c1f44a7999c111ed34b28c43561cc06034bf9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 12 Jan 2021 14:00:56 +0100 Subject: [PATCH 079/137] Replace Travis CI with GitHub Actions CI The coverage matrix is roughly the same: some additional configurations, some removed ones depending on what's available in the new virtual environments. On good side effect is the removal of valgrind-osx.supp from the project's sources. --- .github/workflows/build-macos.yml | 65 ++++++++++ .github/workflows/build-ubuntu.yml | 82 +++++++++++++ .github/workflows/build-windows.yml | 53 +++++++++ .travis.yml | 178 ---------------------------- README.md | 1 - tests/valgrind-osx.supp | 29 ----- 6 files changed, 200 insertions(+), 208 deletions(-) create mode 100644 .github/workflows/build-macos.yml create mode 100644 .github/workflows/build-ubuntu.yml create mode 100644 .github/workflows/build-windows.yml delete mode 100644 .travis.yml delete mode 100644 tests/valgrind-osx.supp diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 0000000..422bced --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: MacOS Builds + +on: + push: + paths: + - '.github/workflows/build-macos.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + pull_request: + paths: + - '.github/workflows/build-macos.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + +jobs: + build: + runs-on: macos-10.15 + + strategy: + fail-fast: false + matrix: + cxx: + - g++-9 + - $(brew --prefix llvm)/bin/clang++ # Clang 11 + config: + # Release build + - build_type: Release + # Debug builds + - build_type: Debug + sanitize: address + - build_type: Debug + sanitize: undefined + + steps: + - uses: actions/checkout@v2 + - uses: seanmiddleditch/gha-setup-ninja@master + + - name: Configure CMake + working-directory: ${{runner.workspace}} + run: | + export CXX=${{matrix.cxx}} + cmake -H${{github.event.repository.name}} -Bbuild \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ + -DGFX_TIMSORT_SANITIZE=${{matrix.config.sanitize}} \ + -GNinja \ + -DBUILD_BENCHMARKS=ON + + - name: Build the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.config.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.config.build_type}} diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml new file mode 100644 index 0000000..48e2f84 --- /dev/null +++ b/.github/workflows/build-ubuntu.yml @@ -0,0 +1,82 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: Ubuntu Builds + +on: + push: + paths: + - '.github/workflows/build-ubuntu.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + pull_request: + paths: + - '.github/workflows/build-ubuntu.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + +jobs: + build: + runs-on: ubuntu-16.04 + + strategy: + fail-fast: false + matrix: + cxx: + - g++-5 + - clang++-6.0 + config: + # Release build + - build_type: Release + # Debug builds + - build_type: Debug + valgrind: ON + - build_type: Debug + sanitize: address + - build_type: Debug + sanitize: undefined + + steps: + - uses: actions/checkout@v2 + + - name: Install Valgrind + if: ${{matrix.config.valgrind == 'ON'}} + run: sudo apt install -y valgrind + + - name: Configure CMake + working-directory: ${{runner.workspace}} + env: + CXX: ${{matrix.cxx}} + run: | + cmake -H${{github.event.repository.name}} -Bbuild \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ + -DGFX_TIMSORT_SANITIZE=${{matrix.config.sanitize}} \ + -DGFX_TIMSORT_USE_VALGRIND=${{matrix.config.valgrind}} \ + -G"Unix Makefiles" \ + -DBUILD_BENCHMARKS=ON + + - name: Build the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.config.build_type}} -j 2 + + - name: Run the test suite + if: ${{matrix.config.valgrind != 'ON'}} + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.config.build_type}} + + - name: Run the test suite with Memcheck + if: ${{matrix.config.valgrind == 'ON'}} + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: | + ctest -T memcheck -C ${{matrix.config.build_type}} -j 2 + find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 0000000..c7ce066 --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,53 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: Windows Builds + +on: + push: + paths: + - '.github/workflows/build-windows.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + pull_request: + paths: + - '.github/workflows/build-windows.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + +jobs: + build: + runs-on: windows-2016 + + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + cmake_generator: ['MinGW Makefiles', 'Visual Studio 15 2017 Win64'] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + shell: pwsh + working-directory: ${{runner.workspace}} + run: | + cmake -H${{github.event.repository.name}} -Bbuild ` + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` + -G"${{matrix.cmake_generator}}" ` + -DBUILD_BENCHMARKS=ON + + - name: Build the test suite + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 024c89d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,178 +0,0 @@ -language: cpp - -_packages: - - &clang clang-3.8 - - &gcc g++-5 - -_apt: &apt-common - sources: - - llvm-toolchain-trusty-3.8 - - ubuntu-toolchain-r-test - -matrix: - include: - - # Linux clang - - os: linux - sudo: required - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - - valgrind - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - # Linux gcc - - os: linux - sudo: false - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - - valgrind - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - # OSX clang - - os: osx - osx_image: xcode9.2 - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Xcode" - addons: - homebrew: - update: true - packages: - - ccache - - cmake - - valgrind - compiler: clang - - - os: osx - osx_image: xcode9.2 - env: BUILD_TYPE=Release CMAKE_GENERATOR="Xcode" - addons: - homebrew: - update: true - packages: - - ccache - - cmake - compiler: clang - - # Windows GCC - - os: windows - env: BUILD_TYPE=Debug CMAKE_GENERATOR="MinGW Makefiles" - language: sh - compiler: gcc - - - os: windows - env: BUILD_TYPE=Release CMAKE_GENERATOR="MinGW Makefiles" - language: sh - compiler: gcc - - # Windows MSVC - - os: windows - env: BUILD_TYPE=Debug CMAKE_GENERATOR="Visual Studio 15 2017 Win64" - compiler: cl - - - os: windows - env: BUILD_TYPE=Release CMAKE_GENERATOR="Visual Studio 15 2017 Win64" - compiler: cl - -install: - - if [[ $CXX = "g++" ]]; then export CXX="g++-5"; fi - - if [[ $CXX = "clang++" ]]; then export CXX="clang++-3.8"; fi - -script: - - cmake -H. -Bbuild - -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" - -DGFX_TIMSORT_SANITIZE="${SANITIZE}" - -DGFX_TIMSORT_USE_VALGRIND=${USE_VALGRIND} - -G"${CMAKE_GENERATOR}" - -DCMAKE_SH="CMAKE_SH-NOTFOUND" - -DBUILD_BENCHMARKS=ON - - if [[ $TRAVIS_OS_NAME = "windows" || $TRAVIS_OS_NAME = "osx" ]]; then - cmake --build build --config ${BUILD_TYPE} -j 2; - else - cmake --build build --config ${BUILD_TYPE} -- -j2; - fi - - cd build - - if [[ $USE_VALGRIND = true ]]; then - travis_wait 50 ctest -T memcheck -C ${BUILD_TYPE} --output-on-failure -j 4; - else - travis_wait ctest -C ${BUILD_TYPE} --output-on-failure; - fi - -after_failure: - - if [[ $USE_VALGRIND = true ]]; then - find ./Testing/Temporary -type f -name "MemoryChecker.*.log" -size +1300c | xargs cat; - fi - -notifications: - email: false diff --git a/README.md b/README.md index a50b987..4368bd4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ [![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F2.0.1-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.0.1) [![Conan Package](https://img.shields.io/badge/conan-2.0.1-blue.svg)](https://conan.io/center/timsort?version=2.0.1) -[![Build Status](https://travis-ci.org/timsort/cpp-TimSort.svg?branch=master)](https://travis-ci.org/timsort/cpp-TimSort) [![License](https://img.shields.io/:license-mit-yellow.svg)](https://doge.mit-license.org) ## TimSort diff --git a/tests/valgrind-osx.supp b/tests/valgrind-osx.supp deleted file mode 100644 index 569fd28..0000000 --- a/tests/valgrind-osx.supp +++ /dev/null @@ -1,29 +0,0 @@ -{ - Mac-OS-X-System-Leaks - Memcheck:Leak - match-leak-kinds: possible - fun:calloc - fun:map_images_nolock - fun:map_images - fun:_ZN4dyldL18notifyBatchPartialE17dyld_image_statesbPFPKcS0_jPK15dyld_image_infoEbb - fun:_ZN4dyld21registerObjCNotifiersEPFvjPKPKcPKPK11mach_headerEPFvS1_S6_ESC_ - fun:_dyld_objc_notify_register - fun:_objc_init - fun:_os_object_init - fun:libdispatch_init - fun:libSystem_initializer - fun:_ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE - fun:_ZN16ImageLoaderMachO16doInitializationERKN11ImageLoader11LinkContextE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader19processInitializersERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE - fun:_ZN4dyld24initializeMainExecutableEv - fun:_ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm - fun:_ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm - fun:_dyld_start - obj:* - obj:* - obj:* - obj:* -} \ No newline at end of file From 2b9365ffe56c5fd3c03a4d714c850351044bd760 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 12 Jan 2021 14:19:04 +0100 Subject: [PATCH 080/137] Tweak badges and compiler support in the README --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4368bd4..0327765 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--TimSort%2F2.0.1-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.0.1) -[![Conan Package](https://img.shields.io/badge/conan-2.0.1-blue.svg)](https://conan.io/center/timsort?version=2.0.1) -[![License](https://img.shields.io/:license-mit-yellow.svg)](https://doge.mit-license.org) +[![Latest Release](https://img.shields.io/badge/release-2.0.1-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.0.1) +[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F2.0.1-blue.svg)](https://conan.io/center/timsort?version=2.0.1) ## TimSort @@ -75,13 +74,16 @@ gfx::timsort(collection, std::less{}, &len); ## INSTALLATION & COMPATIBILITY +![Ubuntu builds status](https://github.com/timsort/cpp-TimSort/workflows/Ubuntu%20Builds/badge.svg?branch=master) +![Windows builds status](https://github.com/timsort/cpp-TimSort/workflows/Windows%20Builds/badge.svg?branch=master) +![MacOS builds status](https://github.com/timsort/cpp-TimSort/workflows/MacOS%20Builds/badge.svg?branch=master) + The library has been tested with the following compilers: -* GCC 5 -* Clang 3.8 -* Xcode 9.2 AppleClang -* MSVC 2017 update 9 +* GCC 5.5 +* Clang 6 +* MSVC 2017 -It should also work with more recent compilers, and most likely with some older compilers too. +It should also work with more recent compilers, and most likely with some older compilers too. We used to guarantee support as far back as Clang 3.8, but the new continuous integration environment doesn't go that far. The library can be installed on the system via CMake with the following commands: From 16564232c2c5cb7de4ca9d1ed7f2d7fd1080e863 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 12 Jan 2021 17:33:45 +0100 Subject: [PATCH 081/137] Release 2.0.2 --- CMakeLists.txt | 2 +- README.md | 6 +++--- include/gfx/timsort.hpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5c3b94..94b1cb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(timsort VERSION 2.0.1 LANGUAGES CXX) +project(timsort VERSION 2.0.2 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index 0327765..cd4ba4a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-2.0.1-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.0.1) -[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F2.0.1-blue.svg)](https://conan.io/center/timsort?version=2.0.1) +[![Latest Release](https://img.shields.io/badge/release-2.0.2-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.0.2) +[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F2.0.2-blue.svg)](https://conan.io/center/timsort?version=2.0.2) ## TimSort @@ -97,7 +97,7 @@ Alternatively the library is also available on conan-center-index and can be ins the following command: ```sh -conan install timsort/2.0.1 +conan install timsort/2.0.2 ``` ## DIAGNOSTICS & INFORMATION diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 5eb1c41..f8e704e 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -6,7 +6,7 @@ * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2020 Morwenn. + * Copyright (c) 2019-2021 Morwenn. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -40,7 +40,7 @@ #define GFX_TIMSORT_VERSION_MAJOR 2 #define GFX_TIMSORT_VERSION_MINOR 0 -#define GFX_TIMSORT_VERSION_PATCH 1 +#define GFX_TIMSORT_VERSION_PATCH 2 // Diagnostic selection macros From 764c2d40364e5988d5f7cfdeaaaec53456f2bcdc Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sun, 17 Jan 2021 18:42:00 +0200 Subject: [PATCH 082/137] Expose gfx::timmerge in the public interface (#36) * Expose gfx::timmerge in the public interface * Improve the timmerge performance description * Use markdown code in README * Improve TimSort::merge() logging * Make setting initial vector size more efficient and explicit * AVERAGE => approx. average (trapezoidal rule) * Add a Wiki link to detailed bench_merge results * Fix MSVC build * Do not duplicate Result size * Optimize TimSort::merge * timmerge: do not skip assertions --- README.md | 57 ++++- benchmarks/CMakeLists.txt | 2 +- benchmarks/bench.cpp | 126 ----------- benchmarks/bench_merge.cpp | 130 +++++++++++ benchmarks/bench_sort.cpp | 63 ++++++ benchmarks/benchmarker.hpp | 103 +++++++++ include/gfx/timsort.hpp | 59 ++++- tests/CMakeLists.txt | 9 + tests/cxx_11_tests.cpp | 48 +++- tests/cxx_17_tests.cpp | 36 ++- tests/cxx_98_test_helpers.hpp | 175 ++++++++++++++ tests/cxx_98_tests.cpp | 163 +------------ tests/merge_cxx_11_tests.cpp | 415 ++++++++++++++++++++++++++++++++++ 13 files changed, 1078 insertions(+), 308 deletions(-) delete mode 100644 benchmarks/bench.cpp create mode 100644 benchmarks/bench_merge.cpp create mode 100644 benchmarks/bench_sort.cpp create mode 100644 benchmarks/benchmarker.hpp create mode 100644 tests/cxx_98_test_helpers.hpp create mode 100644 tests/merge_cxx_11_tests.cpp diff --git a/README.md b/README.md index cd4ba4a..1e8057c 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,19 @@ can't fallback to a O(n log² n) algorithm when there isn't enough extra heap me type such as `void`. +Merging sorted ranges efficiently is an important part of the TimSort algorithm. This library exposes its merge +algorithm in the public API. According to the benchmarks, `gfx::timmerge` is slower than `std::inplace_merge` on +heavily/randomly overlapping subranges of simple elements, but it is faster for complex elements such as `std::string` +and on sparsely overlapping subranges. `gfx::timmerge` should be usable as a drop-in replacement for +`std::inplace_merge`, with the difference that it can't fallback to a O(n log n) algorithm when there isn't enough +extra heap memory available. Like `gfx::timsort`, `gfx::timmerge` can take a projection function and avoids using the +postfix `++` or `--` operators. + + The full list of available signatures is as follows (in namespace `gfx`): ```cpp -// Overloads taking a pair of iterators +// timsort overloads taking a pair of iterators template void timsort(RandomAccessIterator const first, RandomAccessIterator const last); @@ -42,7 +51,7 @@ template void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare, Projection projection); -// Overloads taking a range +// timsort overloads taking a range template void timsort(RandomAccessRange &range); @@ -52,6 +61,20 @@ void timsort(RandomAccessRange &range, Compare compare); template void timsort(RandomAccessRange &range, Compare compare, Projection projection); + +// timmerge overloads + +template +void timmerge(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last); + +template +void timmerge(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last, Compare compare); + +template +void timmerge(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last, Compare compare, Projection projection); ``` ## EXAMPLE @@ -102,7 +125,7 @@ conan install timsort/2.0.2 ## DIAGNOSTICS & INFORMATION -A few configuration macros allow gfx::timsort to emit diagnostic, which might be helpful to diagnose issues: +A few configuration macros allow `gfx::timsort` and `gfx::timmerge` to emit diagnostic, which might be helpful to diagnose issues: * Defining `GFX_TIMSORT_ENABLE_ASSERT` inserts assertions in key locations in the algorithm to avoid logic errors. * Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the algorithm. @@ -130,7 +153,7 @@ built with CMake: Benchmarks are available in the `benchmarks` subdirectory, and can be constructed directly by passing `BUILD_BENCHMARKS=ON` variable to CMake during the configuration step. -Example output (timing scale: sec.): +Example bench_sort output (timing scale: sec.): c++ -v Apple LLVM version 7.0.0 (clang-700.0.72) @@ -171,3 +194,29 @@ Example output (timing scale: sec.): std::sort 0.402458 std::stable_sort 2.436326 timsort 0.298639 + +Example bench_merge output (timing scale: milliseconds; omitted detailed results for different +middle iterator positions, reformatted to improve readability): + + c++ -v + Using built-in specs. + ... + Target: x86_64-pc-linux-gnu + ... + gcc version 10.2.0 (GCC) + c++ -I ../include -Wall -Wextra -g -DNDEBUG -O2 -std=c++11 bench_merge.cpp -o bench_merge + ./bench_merge + size 100000 + element type\algorithm: std::inplace_merge timmerge + RANDOMIZED SEQUENCE + [int] approx. average 33.404430 37.047990 + [std::string] approx. average 324.964249 210.297207 + REVERSED SEQUENCE + [int] approx. average 11.441404 4.017482 + [std::string] approx. average 305.649503 114.773898 + SORTED SEQUENCE + [int] approx. average 4.291098 0.105571 + [std::string] approx. average 158.238114 0.273858 + +Detailed bench_merge results for different middle iterator positions can be found at +https://github.com/timsort/cpp-TimSort/wiki/Benchmark-results diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index dc98cb0..ec47dbf 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,5 +1,5 @@ -foreach(filename bench.cpp) +foreach(filename bench_merge.cpp bench_sort.cpp) get_filename_component(name ${filename} NAME_WE) add_executable(${name} ${filename}) target_link_libraries(${name} PRIVATE gfx::timsort) diff --git a/benchmarks/bench.cpp b/benchmarks/bench.cpp deleted file mode 100644 index 6728c6a..0000000 --- a/benchmarks/bench.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. - * - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gfx; - -enum state_t { sorted, randomized, reversed }; - -template -struct convert_to -{ - static T from(int value) { - return T(value); - } -}; - -template <> -struct convert_to -{ - static std::string from(int value) { - std::ostringstream ss; - ss << value; - return ss.str(); - } -}; - -template -static void bench(int size, state_t const state) { - std::cerr << "size\t" << size << std::endl; - - std::vector a; - for (int i = 0; i < size; ++i) { - a.push_back(convert_to::from((i + 1) * 10)); - } - - switch (state) { - case randomized: - std::random_shuffle(a.begin(), a.end()); - break; - case reversed: - std::stable_sort(a.begin(), a.end()); - std::reverse(a.begin(), a.end()); - break; - case sorted: - std::stable_sort(a.begin(), a.end()); - break; - default: - assert(!"not reached"); - } - - { - std::vector b(a); - - std::clock_t start = std::clock(); - for (int i = 0; i < 100; ++i) { - std::copy(a.begin(), a.end(), b.begin()); - std::sort(b.begin(), b.end()); - } - std::clock_t stop = std::clock(); - - std::cerr << "std::sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; - } - - { - std::vector b(a); - - std::clock_t start = std::clock(); - for (int i = 0; i < 100; ++i) { - std::copy(a.begin(), a.end(), b.begin()); - std::stable_sort(b.begin(), b.end()); - } - std::clock_t stop = clock(); - - std::cerr << "std::stable_sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; - } - - { - std::vector b(a); - - std::clock_t start = std::clock(); - for (int i = 0; i < 100; ++i) { - std::copy(a.begin(), a.end(), b.begin()); - timsort(b.begin(), b.end()); - } - std::clock_t stop = std::clock(); - - std::cerr << "timsort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; - } -} - -static void doit(int const n, state_t const state) { - std::cerr << "[int]" << std::endl; - bench(n, state); - - std::cerr << "[std::string]" << std::endl; - bench(n, state); -} - -int main(int argc, const char *argv[]) { - const int N = argc > 1 ? std::atoi(argv[1]) : 100 * 1000; - - std::cerr << std::setprecision(6) << std::setiosflags(std::ios::fixed); - - std::srand(0); - - std::cerr << "RANDOMIZED SEQUENCE" << std::endl; - doit(N, randomized); - - std::cerr << "REVERSED SEQUENCE" << std::endl; - doit(N, reversed); - - std::cerr << "SORTED SEQUENCE" << std::endl; - doit(N, sorted); -} diff --git a/benchmarks/bench_merge.cpp b/benchmarks/bench_merge.cpp new file mode 100644 index 0000000..797ab8a --- /dev/null +++ b/benchmarks/bench_merge.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Igor Kushnir . + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "benchmarker.hpp" + +namespace +{ + std::vector generate_middle_positions(int size) { + std::vector result = { + 0, 1, 2, 5, 100, size/100, size/20, size/5, size/3, size/2, 3*size/4, + 6*size/7, 24*size/25, 90*size/91, size-85, size-8, size-2, size-1, size + }; + + // The code below can remove or reorder elements if size is small. + + auto logical_end = std::remove_if(result.begin(), result.end(), [size](int middle) { + return middle < 0 || middle > size; + }); + result.erase(logical_end, result.end()); + + std::sort(result.begin(), result.end()); + logical_end = std::unique(result.begin(), result.end()); + result.erase(logical_end, result.end()); + + return result; + } + + using Result = std::valarray; + Result zeroResult() { return Result(2); } +} + +template +struct Bench { + void operator()(const std::vector &source) const { + const int size = static_cast(source.size()); + const auto middle_positions = generate_middle_positions(size); + + int prev_middle = 0; + auto prev_result = zeroResult(); + auto result_sum = zeroResult(); + + std::cerr << "middle\\algorithm:\tstd::inplace_merge\ttimmerge" << std::endl; + constexpr int width = 10; + constexpr const char* padding = " \t"; + + std::vector a(source.size()); + for (auto middle : middle_positions) { + std::copy(source.begin(), source.end(), a.begin()); + std::sort(a.begin(), a.begin() + middle); + std::sort(a.begin() + middle, a.end()); + const auto result = run(a, middle); + + if (middle != prev_middle) { + // Trapezoidal rule for approximating the definite integral. + result_sum += 0.5 * (result + prev_result) + * static_cast(middle - prev_middle); + prev_middle = middle; + } + prev_result = result; + + std::cerr << std::setw(width) << middle + << " \t" << std::setw(width) << result[0] + << padding << std::setw(width) << result[1] + << std::endl; + } + + if (size != 0) { + result_sum /= static_cast(size); + std::cerr << "approx. average" + << " \t" << std::setw(width) << result_sum[0] + << padding << std::setw(width) << result_sum[1] + << std::endl; + } + } + +private: + static Result run(const std::vector &a, const int middle) { + std::vector b(a.size()); + const auto assert_is_sorted = [&b] { + if (!std::is_sorted(b.cbegin(), b.cend())) { + std::cerr << "Not sorted!" << std::endl; + std::abort(); + } + }; + + auto result = zeroResult(); + for (auto *total_time_ms : { &result[0], &result[1] }) { + using Clock = std::chrono::steady_clock; + decltype(Clock::now() - Clock::now()) total_time{0}; + + for (int i = 0; i < 100; ++i) { + std::copy(a.begin(), a.end(), b.begin()); + const auto time_begin = Clock::now(); + + if (total_time_ms == &result[0]) { + std::inplace_merge(b.begin(), b.begin() + middle, b.end()); + } else { + gfx::timmerge(b.begin(), b.begin() + middle, b.end()); + } + + const auto time_end = Clock::now(); + total_time += time_end - time_begin; + + // Verifying that b is sorted should prevent the compiler from optimizing anything out. + assert_is_sorted(); + } + + *total_time_ms = std::chrono::duration_cast< + std::chrono::microseconds>(total_time).count() / 1000.0; + } + return result; + } +}; + +int main(int argc, const char *argv[]) { + const int size = argc > 1 ? std::stoi(argv[1]) : 100 * 1000; + Benchmarker benchmarker(size); + benchmarker.run(); +} diff --git a/benchmarks/bench_sort.cpp b/benchmarks/bench_sort.cpp new file mode 100644 index 0000000..4f9539d --- /dev/null +++ b/benchmarks/bench_sort.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include "benchmarker.hpp" + +template +struct Bench { + void operator()(const std::vector &a) const { + { + std::vector b(a); + + std::clock_t start = std::clock(); + for (int i = 0; i < 100; ++i) { + std::copy(a.begin(), a.end(), b.begin()); + std::sort(b.begin(), b.end()); + } + std::clock_t stop = std::clock(); + + std::cerr << "std::sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; + } + + { + std::vector b(a); + + std::clock_t start = std::clock(); + for (int i = 0; i < 100; ++i) { + std::copy(a.begin(), a.end(), b.begin()); + std::stable_sort(b.begin(), b.end()); + } + std::clock_t stop = clock(); + + std::cerr << "std::stable_sort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; + } + + { + std::vector b(a); + + std::clock_t start = std::clock(); + for (int i = 0; i < 100; ++i) { + std::copy(a.begin(), a.end(), b.begin()); + gfx::timsort(b.begin(), b.end()); + } + std::clock_t stop = std::clock(); + + std::cerr << "timsort " << (double(stop - start) / CLOCKS_PER_SEC) << std::endl; + } + } +}; + +int main(int argc, const char *argv[]) { + const int size = argc > 1 ? std::atoi(argv[1]) : 100 * 1000; + Benchmarker benchmarker(size); + benchmarker.run(); +} diff --git a/benchmarks/benchmarker.hpp b/benchmarks/benchmarker.hpp new file mode 100644 index 0000000..00c0770 --- /dev/null +++ b/benchmarks/benchmarker.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * Copyright (c) 2021 Igor Kushnir . + * + * SPDX-License-Identifier: MIT + */ + +#ifndef GFX_TIMSORT_BENCHMARKER_HPP +#define GFX_TIMSORT_BENCHMARKER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BenchmarkerHelpers { + template + struct convert_to + { + static T from(int value) { + return T(value); + } + }; + + template <> + struct convert_to + { + static std::string from(int value) { + std::ostringstream ss; + ss << value; + return ss.str(); + } + }; +} + +template class Bench> +class Benchmarker { +private: + enum state_t { sorted, randomized, reversed }; + + template + void bench(state_t const state) { + std::vector a; + for (int i = 0; i < size_; ++i) { + a.push_back(BenchmarkerHelpers::convert_to::from((i + 1) * 10)); + } + + switch (state) { + case randomized: + std::random_shuffle(a.begin(), a.end()); + break; + case reversed: + std::stable_sort(a.begin(), a.end()); + std::reverse(a.begin(), a.end()); + break; + case sorted: + std::stable_sort(a.begin(), a.end()); + break; + default: + std::abort(); // unreachable + } + + Bench()(a); + } + + void doit(state_t const state) { + std::cerr << "[int]" << std::endl; + bench(state); + + std::cerr << "[std::string]" << std::endl; + bench(state); + } + + const int size_; + +public: + explicit Benchmarker(int size) : size_(size) { + } + + void run() { + std::cerr << "size\t" << size_ << std::endl; + + std::cerr << std::setprecision(6) << std::setiosflags(std::ios::fixed); + + std::srand(0); + + std::cerr << "RANDOMIZED SEQUENCE" << std::endl; + doit(randomized); + + std::cerr << "REVERSED SEQUENCE" << std::endl; + doit(reversed); + + std::cerr << "SORTED SEQUENCE" << std::endl; + doit(sorted); + } +}; + +#endif // GFX_TIMSORT_BENCHMARKER_HPP diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index f8e704e..f564289 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -7,6 +7,7 @@ * * Copyright (c) 2011 Fuji, Goro (gfx) . * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2021 Igor Kushnir . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -228,10 +229,6 @@ template class TimSort { iter_t base2 = pending_[i + 1].base; diff_t len2 = pending_[i + 1].len; - GFX_TIMSORT_ASSERT(len1 > 0); - GFX_TIMSORT_ASSERT(len2 > 0); - GFX_TIMSORT_ASSERT(base1 + len1 == base2); - pending_[i].len = len1 + len2; if (i == stackSize - 3) { @@ -240,6 +237,14 @@ template class TimSort { pending_.pop_back(); + mergeConsecutiveRuns(base1, len1, base2, len2, std::move(compare)); + } + + void mergeConsecutiveRuns(iter_t base1, diff_t len1, iter_t base2, diff_t len2, Compare compare) { + GFX_TIMSORT_ASSERT(len1 > 0); + GFX_TIMSORT_ASSERT(len2 > 0); + GFX_TIMSORT_ASSERT(base1 + len1 == base2); + diff_t const k = gallopRight(*base2, base1, len1, 0, compare); GFX_TIMSORT_ASSERT(k >= 0); @@ -633,6 +638,21 @@ template class TimSort { public: + static void merge(iter_t const lo, iter_t const mid, iter_t const hi, Compare compare) { + GFX_TIMSORT_ASSERT(lo <= mid); + GFX_TIMSORT_ASSERT(mid <= hi); + + if (lo == mid || mid == hi) { + return; // nothing to do + } + + TimSort ts; + ts.mergeConsecutiveRuns(lo, mid - lo, mid, hi - mid, std::move(compare)); + + GFX_TIMSORT_LOG("1st size: " << (mid - lo) << "; 2nd size: " << (hi - mid) + << "; tmp_.size(): " << ts.tmp_.size()); + } + static void sort(iter_t const lo, iter_t const hi, Compare compare) { GFX_TIMSORT_ASSERT(lo <= hi); @@ -683,6 +703,37 @@ template class TimSort { // Public interface implementation // --------------------------------------- +/** + * Stably merges two consecutive sorted ranges [first, middle) and [middle, last) into one + * sorted range [first, last) with a comparison function and a projection function. + */ +template +void timmerge(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last, Compare compare, Projection projection) { + typedef detail::projection_compare compare_t; + compare_t comp(std::move(compare), std::move(projection)); + detail::TimSort::merge(first, middle, last, std::move(comp)); +} + +/** + * Same as std::inplace_merge(first, middle, last, compare). + */ +template +void timmerge(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last, Compare compare) { + gfx::timmerge(first, middle, last, compare, detail::identity()); +} + +/** + * Same as std::inplace_merge(first, middle, last). + */ +template +void timmerge(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last) { + typedef typename std::iterator_traits::value_type value_type; + gfx::timmerge(first, middle, last, std::less(), detail::identity()); +} + /** * Stably sorts a range with a comparison function and a projection function. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f50dfd5..4c9f441 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -108,6 +108,14 @@ if (WIN32) target_compile_features(windows_tests PRIVATE cxx_std_98) endif() +# timmerge tests requiring C++11 support +add_executable(merge_cxx_11_tests + main.cpp + merge_cxx_11_tests.cpp +) +configure_tests(merge_cxx_11_tests) +target_compile_features(merge_cxx_11_tests PRIVATE cxx_std_11) + include(CTest) include(Catch) @@ -117,3 +125,4 @@ catch_discover_tests(cxx_17_tests) if (WIN32) catch_discover_tests(windows_tests) endif() +catch_discover_tests(merge_cxx_11_tests) diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index 6a5d8ed..855b1b5 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -132,13 +132,53 @@ TEST_CASE( "shuffle10k_for_move_only_types" ) { } } +TEST_CASE( "merge_shuffle10k_for_move_only_types" ) { + const int size = 1024 * 10; // should be even number of elements + + std::vector > a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + for (int n = 0; n < 100; ++n) { + std::random_shuffle(a.begin(), a.end()); + + const auto compare = [](const move_only &x, const move_only &y) { return x.value < y.value; }; + const auto middle = a.begin() + rand() % size; + gfx::timsort(a.begin(), middle, compare); + gfx::timsort(middle, a.end(), compare); + gfx::timmerge(a.begin(), middle, a.end(), compare); + + for (int i = 0; i < size; ++i) { + CHECK(a[i].value == (i + 1) * 10); + } + } +} + TEST_CASE( "issue14" ) { - int a[] = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, - 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; + const int a[] = {15, 7, 16, 20, 25, 28, 13, 27, 34, 24, 19, 1, 6, 30, 32, 29, 10, 9, + 3, 31, 21, 26, 8, 2, 22, 14, 4, 12, 5, 0, 23, 33, 11, 17, 18}; std::deque c(std::begin(a), std::end(a)); - gfx::timsort(std::begin(c), std::end(c)); - CHECK(std::is_sorted(std::begin(c), std::end(c))); + SECTION( "timsort" ) { + gfx::timsort(std::begin(c), std::end(c)); + CHECK(std::is_sorted(std::begin(c), std::end(c))); + } + + SECTION( "timmerge" ) { + for (auto middle = c.begin(); ; ++middle) { + std::copy(std::begin(a), std::end(a), c.begin()); + + gfx::timsort(c.begin(), middle); + gfx::timsort(middle, c.end()); + gfx::timmerge(c.begin(), middle, c.end()); + CHECK(std::is_sorted(c.cbegin(), c.cend())); + + if (middle == c.end()) { + break; + } + } + } } TEST_CASE( "range signatures" ) { diff --git a/tests/cxx_17_tests.cpp b/tests/cxx_17_tests.cpp index 6afbe0d..56f1ca6 100644 --- a/tests/cxx_17_tests.cpp +++ b/tests/cxx_17_tests.cpp @@ -46,18 +46,38 @@ TEST_CASE( "generalized callables" ) { std::mt19937 gen(123456); // fixed seed is enough std::shuffle(vec.begin(), vec.end(), gen); - SECTION( "for comparisons" ) { - gfx::timsort(vec, &wrapper::compare_to); - CHECK(std::is_sorted(vec.begin(), vec.end(), [](wrapper const& lhs, wrapper const& rhs) { + const auto is_vec_sorted = [&vec] { + return std::is_sorted(vec.begin(), vec.end(), [](wrapper const& lhs, wrapper const& rhs) { return lhs.value < rhs.value; - })); + }); + }; + + SECTION( "timsort for comparisons" ) { + gfx::timsort(vec, &wrapper::compare_to); + CHECK(is_vec_sorted()); } - SECTION( "for projections" ) { + SECTION( "timsort for projections" ) { gfx::timsort(vec, std::less<>{}, &wrapper::value); - CHECK(std::is_sorted(vec.begin(), vec.end(), [](wrapper const& lhs, wrapper const& rhs) { - return lhs.value < rhs.value; - })); + CHECK(is_vec_sorted()); + } + + std::uniform_int_distribution random_middle(0, vec.size()); + + SECTION( "timmerge for comparisons" ) { + const auto middle = vec.begin() + random_middle(gen); + gfx::timsort(vec.begin(), middle, &wrapper::compare_to); + gfx::timsort(middle, vec.end(), &wrapper::compare_to); + gfx::timmerge(vec.begin(), middle, vec.end(), &wrapper::compare_to); + CHECK(is_vec_sorted()); + } + + SECTION( "timmerge for projections" ) { + const auto middle = vec.begin() + random_middle(gen); + gfx::timsort(vec.begin(), middle, std::less<>{}, &wrapper::value); + gfx::timsort(middle, vec.end(), std::less<>{}, &wrapper::value); + gfx::timmerge(vec.begin(), middle, vec.end(), std::less<>{}, &wrapper::value); + CHECK(is_vec_sorted()); } } diff --git a/tests/cxx_98_test_helpers.hpp b/tests/cxx_98_test_helpers.hpp new file mode 100644 index 0000000..340367f --- /dev/null +++ b/tests/cxx_98_test_helpers.hpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef GFX_TIMSORT_CXX_98_TEST_HELPERS_HPP +#define GFX_TIMSORT_CXX_98_TEST_HELPERS_HPP + +#include +#include + +namespace test_helpers { + // Helper types for the tests + + //////////////////////////////////////////////////////////// + // Timsort should work with types that are not + // default-constructible + + struct NonDefaultConstructible { + int i; + + NonDefaultConstructible(int i_) : i(i_) { + } + + friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { + return x.i < y.i; + } + }; + + //////////////////////////////////////////////////////////// + // Tools to test the stability of the sort + + enum id { foo, bar, baz }; + + typedef std::pair pair_t; + + inline bool less_in_first(pair_t x, pair_t y) { + return x.first < y.first; + } + + //////////////////////////////////////////////////////////// + // Timsort should work with iterators that don't have a + // post-increment or post-decrement operation + + template + class NoPostIterator + { + public: + + //////////////////////////////////////////////////////////// + // Public types + + typedef typename std::iterator_traits::iterator_category iterator_category; + typedef Iterator iterator_type; + typedef typename std::iterator_traits::value_type value_type; + typedef typename std::iterator_traits::difference_type difference_type; + typedef typename std::iterator_traits::pointer pointer; + typedef typename std::iterator_traits::reference reference; + + //////////////////////////////////////////////////////////// + // Constructors + + NoPostIterator() : _it() { + } + + explicit NoPostIterator(Iterator it) : _it(it) { + } + + //////////////////////////////////////////////////////////// + // Members access + + iterator_type base() const { + return _it; + } + + //////////////////////////////////////////////////////////// + // Element access + + reference operator*() const { + return *base(); + } + + pointer operator->() const { + return &(operator*()); + } + + //////////////////////////////////////////////////////////// + // Increment/decrement operators + + NoPostIterator& operator++() { + ++_it; + return *this; + } + + NoPostIterator& operator--() { + --_it; + return *this; + } + + NoPostIterator& operator+=(difference_type increment) { + _it += increment; + return *this; + } + + NoPostIterator& operator-=(difference_type increment) { + _it -= increment; + return *this; + } + + //////////////////////////////////////////////////////////// + // Comparison operators + + friend bool operator==(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() == rhs.base(); + } + + friend bool operator!=(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() != rhs.base(); + } + + //////////////////////////////////////////////////////////// + // Relational operators + + friend bool operator<(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() < rhs.base(); + } + + friend bool operator<=(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() <= rhs.base(); + } + + friend bool operator>(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() > rhs.base(); + } + + friend bool operator>=(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() >= rhs.base(); + } + + //////////////////////////////////////////////////////////// + // Arithmetic operators + + friend NoPostIterator operator+(NoPostIterator it, difference_type size) { + return it += size; + } + + friend NoPostIterator operator+(difference_type size, NoPostIterator it) { + return it += size; + } + + friend NoPostIterator operator-(NoPostIterator it, difference_type size) { + return it -= size; + } + + friend difference_type operator-(NoPostIterator const& lhs, NoPostIterator const& rhs) { + return lhs.base() - rhs.base(); + } + + private: + + Iterator _it; + }; + + //////////////////////////////////////////////////////////// + // Construction function + + template + NoPostIterator make_no_post_iterator(Iterator it) { + return NoPostIterator(it); + } +} + +#endif // GFX_TIMSORT_CXX_98_TEST_HELPERS_HPP diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index 7762f21..11b4e84 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -11,168 +11,9 @@ #include #include #include +#include "cxx_98_test_helpers.hpp" -namespace { - // Helper types for the tests - - //////////////////////////////////////////////////////////// - // Timsort should work with types that are not - // default-constructible - - struct NonDefaultConstructible { - int i; - - NonDefaultConstructible(int i_) : i(i_) { - } - - friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) { - return x.i < y.i; - } - }; - - //////////////////////////////////////////////////////////// - // Tools to test the stability of the sort - - enum id { foo, bar, baz }; - - typedef std::pair pair_t; - - inline bool less_in_first(pair_t x, pair_t y) { - return x.first < y.first; - } - - //////////////////////////////////////////////////////////// - // Timsort should work with iterators that don't have a - // post-increment or post-decrement operation - - template - class NoPostIterator - { - public: - - //////////////////////////////////////////////////////////// - // Public types - - typedef typename std::iterator_traits::iterator_category iterator_category; - typedef Iterator iterator_type; - typedef typename std::iterator_traits::value_type value_type; - typedef typename std::iterator_traits::difference_type difference_type; - typedef typename std::iterator_traits::pointer pointer; - typedef typename std::iterator_traits::reference reference; - - //////////////////////////////////////////////////////////// - // Constructors - - NoPostIterator() : _it() { - } - - explicit NoPostIterator(Iterator it) : _it(it) { - } - - //////////////////////////////////////////////////////////// - // Members access - - iterator_type base() const { - return _it; - } - - //////////////////////////////////////////////////////////// - // Element access - - reference operator*() const { - return *base(); - } - - pointer operator->() const { - return &(operator*()); - } - - //////////////////////////////////////////////////////////// - // Increment/decrement operators - - NoPostIterator& operator++() { - ++_it; - return *this; - } - - NoPostIterator& operator--() { - --_it; - return *this; - } - - NoPostIterator& operator+=(difference_type increment) { - _it += increment; - return *this; - } - - NoPostIterator& operator-=(difference_type increment) { - _it -= increment; - return *this; - } - - //////////////////////////////////////////////////////////// - // Comparison operators - - friend bool operator==(NoPostIterator const& lhs, NoPostIterator const& rhs) { - return lhs.base() == rhs.base(); - } - - friend bool operator!=(NoPostIterator const& lhs, NoPostIterator const& rhs) { - return lhs.base() != rhs.base(); - } - - //////////////////////////////////////////////////////////// - // Relational operators - - friend bool operator<(NoPostIterator const& lhs, NoPostIterator const& rhs) { - return lhs.base() < rhs.base(); - } - - friend bool operator<=(NoPostIterator const& lhs, NoPostIterator const& rhs) { - return lhs.base() <= rhs.base(); - } - - friend bool operator>(NoPostIterator const& lhs, NoPostIterator const& rhs) { - return lhs.base() > rhs.base(); - } - - friend bool operator>=(NoPostIterator const& lhs, NoPostIterator const& rhs) { - return lhs.base() >= rhs.base(); - } - - //////////////////////////////////////////////////////////// - // Arithmetic operators - - friend NoPostIterator operator+(NoPostIterator it, difference_type size) { - return it += size; - } - - friend NoPostIterator operator+(difference_type size, NoPostIterator it) { - return it += size; - } - - friend NoPostIterator operator-(NoPostIterator it, difference_type size) { - return it -= size; - } - - friend difference_type operator-(NoPostIterator const& lhs, NoPostIterator const& rhs) { - return lhs.base() - rhs.base(); - } - - private: - - Iterator _it; - }; - - //////////////////////////////////////////////////////////// - // Construction function - - template - NoPostIterator make_no_post_iterator(Iterator it) { - return NoPostIterator(it); - } - -} +using namespace test_helpers; TEST_CASE( "simple0" ) { std::vector a; diff --git a/tests/merge_cxx_11_tests.cpp b/tests/merge_cxx_11_tests.cpp new file mode 100644 index 0000000..8aba79d --- /dev/null +++ b/tests/merge_cxx_11_tests.cpp @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2011 Fuji, Goro (gfx) . + * Copyright (c) 2019 Morwenn. + * Copyright (c) 2021 Igor Kushnir . + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "cxx_98_test_helpers.hpp" + +using namespace test_helpers; + +namespace +{ +template +void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle, Compare compare) { + const auto first = std::begin(range); + const auto last = std::end(range); + gfx::timsort(first, middle, compare); + gfx::timsort(middle, last, compare); + gfx::timmerge(first, middle, last, compare); +} + +template +void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle) { + typedef typename std::iterator_traits::value_type value_type; + sort_and_merge(range, middle, std::less()); +} + +std::mt19937 random_engine(2581470); // fixed seed is enough + +template +void shuffle(RandomAccessIterator first, RandomAccessIterator last) +{ + std::shuffle(first, last, random_engine); +} + +template +void shuffle(RandomAccessRange &range) +{ + shuffle(std::begin(range), std::end(range)); +} +} + +TEST_CASE( "merge_simple0" ) { + std::vector a; + + gfx::timmerge(a.begin(), a.end(), a.end()); + + CHECK(a.size() == std::size_t(0)); +} + +TEST_CASE( "merge_simple1" ) { + std::vector a; + + a.push_back(-54); + + gfx::timmerge(a.begin(), a.end(), a.end(), std::greater()); + + CHECK(a.size() == std::size_t(1)); + CHECK(a[0] == -54); +} + +TEST_CASE( "merge_simple2" ) { + std::vector a; + + a.push_back(10); + a.push_back(20); + + gfx::timmerge(a.begin(), a.begin() + 1, a.end()); + + CHECK(a.size() == std::size_t(2)); + CHECK(a[0] == 10); + CHECK(a[1] == 20); + + a.clear(); + a.push_back(20); + a.push_back(10); + + gfx::timmerge(a.begin(), a.begin() + 1, a.end(), std::less()); + + CHECK(a.size() == std::size_t(2)); + CHECK(a[0] == 10); + CHECK(a[1] == 20); + + a.clear(); + a.push_back(10); + a.push_back(10); + + gfx::timmerge(a.begin(), a.begin() + 1, a.end(), std::less()); + + CHECK(a.size() == std::size_t(2)); + CHECK(a[0] == 10); + CHECK(a[1] == 10); +} + +TEST_CASE( "merge_simple10" ) { + std::vector a; + a.push_back(60); + a.push_back(50); + a.push_back(10); + a.push_back(40); + a.push_back(80); + a.push_back(20); + a.push_back(30); + a.push_back(70); + a.push_back(10); + a.push_back(90); + + sort_and_merge(a, a.begin() + 5, std::less()); + + CHECK(a[0] == 10); + CHECK(a[1] == 10); + CHECK(a[2] == 20); + CHECK(a[3] == 30); + CHECK(a[4] == 40); + CHECK(a[5] == 50); + CHECK(a[6] == 60); + CHECK(a[7] == 70); + CHECK(a[8] == 80); + CHECK(a[9] == 90); + + std::reverse(a.begin(), a.end()); + + sort_and_merge(a, a.begin() + 2, std::less()); + + CHECK(a[0] == 10); + CHECK(a[1] == 10); + CHECK(a[2] == 20); + CHECK(a[3] == 30); + CHECK(a[4] == 40); + CHECK(a[5] == 50); + CHECK(a[6] == 60); + CHECK(a[7] == 70); + CHECK(a[8] == 80); + CHECK(a[9] == 90); +} + +TEST_CASE( "merge_shuffle30" ) { + const int size = 30; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + shuffle(a); + + sort_and_merge(a, a.begin() + 24); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +TEST_CASE( "merge_shuffle128" ) { + const int size = 128; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + shuffle(a); + + sort_and_merge(a, a.begin() + 51); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +TEST_CASE( "merge_shuffle204x" ) { + for (int size : {2047, 2048}) { + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + std::uniform_int_distribution random_middle(0, size); + for (int n = 0; n < 30; ++n) { + shuffle(a); + + sort_and_merge(a, a.begin() + random_middle(random_engine)); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } + } +} + +TEST_CASE( "merge_partial_shuffle102x" ) { + for (int size : {1023, 1024}) { + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + std::uniform_int_distribution random_middle(0, size); + + // sorted-shuffled-sorted pattern + for (int n = 0; n < 100; ++n) { + shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); + + sort_and_merge(a, a.begin() + random_middle(random_engine), std::less()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } + + // shuffled-sorted-shuffled pattern + for (int n = 0; n < 100; ++n) { + shuffle(a.begin(), a.begin() + (size / 3 * 1)); + shuffle(a.begin() + (size / 3 * 2), a.end()); + + sort_and_merge(a, a.begin() + random_middle(random_engine)); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } + } +} + +TEST_CASE( "merge_shuffle1025r" ) { + const int size = 1025; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + std::uniform_int_distribution random_middle(0, size); + for (int n = 0; n < 100; ++n) { + shuffle(a); + + sort_and_merge(a, a.begin() + random_middle(random_engine), std::greater()); + + int j = size; + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (--j + 1) * 10); + } + } +} + +TEST_CASE( "merge_partial_reversed307x" ) { + for (int size : {3071, 3072}) { + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + + std::uniform_int_distribution random_middle(0, size); + for (int n = 0; n < 20; ++n) { + std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed + + sort_and_merge(a, a.begin() + random_middle(random_engine)); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } + } + } +} + +TEST_CASE( "merge_c_array" ) { + int a[] = {7, 1, 5, 3, 9}; + + sort_and_merge(a, a + 2); + + CHECK(a[0] == 1); + CHECK(a[1] == 3); + CHECK(a[2] == 5); + CHECK(a[3] == 7); + CHECK(a[4] == 9); +} + +TEST_CASE( "merge_string_array" ) { + std::string a[] = {"7", "1", "5", "3", "9"}; + + sort_and_merge(a, a + 3); + + CHECK(a[0] == "1"); + CHECK(a[1] == "3"); + CHECK(a[2] == "5"); + CHECK(a[3] == "7"); + CHECK(a[4] == "9"); +} + +TEST_CASE( "merge_non_default_constructible" ) { + NonDefaultConstructible a[] = {7, 1, 5, 3, 9}; + + sort_and_merge(a, a + 1); + + CHECK(a[0].i == 1); + CHECK(a[1].i == 3); + CHECK(a[2].i == 5); + CHECK(a[3].i == 7); + CHECK(a[4].i == 9); +} + +TEST_CASE( "merge_default_compare_function" ) { + const int size = 128; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back((i + 1) * 10); + } + shuffle(a); + + const auto middle = a.begin() + a.size() / 3; + gfx::timsort(a.begin(), middle); + gfx::timsort(middle, a.end()); + gfx::timmerge(a.begin(), middle, a.end()); + + CHECK(a.size() == std::size_t(size)); + for (int i = 0; i < size; ++i) { + CHECK(a[i] == (i + 1) * 10); + } +} + +TEST_CASE( "merge_stability" ) { + std::vector a; + + for (int i = 100; i >= 0; --i) { + a.push_back(std::make_pair(i, foo)); + a.push_back(std::make_pair(i, bar)); + a.push_back(std::make_pair(i, baz)); + } + + sort_and_merge(a, a.begin() + 117, &less_in_first); + + CHECK(a[0].first == 0); + CHECK(a[0].second == foo); + CHECK(a[1].first == 0); + CHECK(a[1].second == bar); + CHECK(a[2].first == 0); + CHECK(a[2].second == baz); + + CHECK(a[3].first == 1); + CHECK(a[3].second == foo); + CHECK(a[4].first == 1); + CHECK(a[4].second == bar); + CHECK(a[5].first == 1); + CHECK(a[5].second == baz); + + CHECK(a[6].first == 2); + CHECK(a[6].second == foo); + CHECK(a[7].first == 2); + CHECK(a[7].second == bar); + CHECK(a[8].first == 2); + CHECK(a[8].second == baz); + + CHECK(a[9].first == 3); + CHECK(a[9].second == foo); + CHECK(a[10].first == 3); + CHECK(a[10].second == bar); + CHECK(a[11].first == 3); + CHECK(a[11].second == baz); +} + +TEST_CASE( "merge_issue2_duplication" ) { + std::vector > a; + + for (int i = 0; i < 10000; ++i) { + int first = static_cast(rand()); + int second = static_cast(rand()); + + a.push_back(std::make_pair(first, second)); + } + + std::vector > expected(a); + + std::sort(expected.begin(), expected.end()); + sort_and_merge(a, a.begin() + 824); + + CHECK(a == expected); +} + +TEST_CASE( "merge_projection" ) { + const int size = 128; + + std::vector a; + for (int i = 0; i < size; ++i) { + a.push_back(i - 40); + } + shuffle(a); + + const auto middle = a.begin() + 43; + gfx::timsort(a.begin(), middle, std::greater(), std::negate()); + gfx::timsort(middle, a.end(), std::greater(), std::negate()); + gfx::timmerge(a.begin(), middle, a.end(), std::greater(), std::negate()); + + for (int i = 0; i < size; ++i) { + CHECK(a[i] == i - 40); + } +} + +TEST_CASE( "merge_iterator without post-increment or post-decrement" ) { + std::vector a; + + gfx::timmerge(make_no_post_iterator(a.begin()), make_no_post_iterator(a.begin()), + make_no_post_iterator(a.end())); + + CHECK(a.size() == std::size_t(0)); +} From 88124ab499e628dc3462fe303c9449fb4a8d3ef6 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Mon, 18 Jan 2021 13:30:49 +0200 Subject: [PATCH 083/137] Add an audit option to verify is_sorted (off by default) (#37) --- README.md | 2 ++ include/gfx/timsort.hpp | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e8057c..6d4dfa7 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ conan install timsort/2.0.2 A few configuration macros allow `gfx::timsort` and `gfx::timmerge` to emit diagnostic, which might be helpful to diagnose issues: * Defining `GFX_TIMSORT_ENABLE_ASSERT` inserts assertions in key locations in the algorithm to avoid logic errors. +* Defining `GFX_TIMSORT_ENABLE_AUDIT` inserts assertions that verify pre- and postconditions. These verifications can slow the + algorithm down significantly. Enable the audit only while testing or debugging. * Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the algorithm. **cpp-TimSort** follows semantic versioning and provides the following macros to retrieve the current major, minor diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index f564289..249dc6f 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -45,13 +45,22 @@ // Diagnostic selection macros -#ifdef GFX_TIMSORT_ENABLE_ASSERT +#if defined(GFX_TIMSORT_ENABLE_ASSERT) || defined(GFX_TIMSORT_ENABLE_AUDIT) # include +#endif + +#ifdef GFX_TIMSORT_ENABLE_ASSERT # define GFX_TIMSORT_ASSERT(expr) assert(expr) #else # define GFX_TIMSORT_ASSERT(expr) ((void)0) #endif +#ifdef GFX_TIMSORT_ENABLE_AUDIT +# define GFX_TIMSORT_AUDIT(expr) assert(expr) +#else +# define GFX_TIMSORT_AUDIT(expr) ((void)0) +#endif + #ifdef GFX_TIMSORT_ENABLE_LOG # include # define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << expr << std::endl) @@ -712,7 +721,10 @@ void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Compare compare, Projection projection) { typedef detail::projection_compare compare_t; compare_t comp(std::move(compare), std::move(projection)); - detail::TimSort::merge(first, middle, last, std::move(comp)); + GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp) && "Precondition"); + GFX_TIMSORT_AUDIT(std::is_sorted(middle, last, comp) && "Precondition"); + detail::TimSort::merge(first, middle, last, comp); + GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp) && "Postcondition"); } /** @@ -742,7 +754,8 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare, Projection projection) { typedef detail::projection_compare compare_t; compare_t comp(std::move(compare), std::move(projection)); - detail::TimSort::sort(first, last, std::move(comp)); + detail::TimSort::sort(first, last, comp); + GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp) && "Postcondition"); } /** @@ -790,6 +803,8 @@ void timsort(RandomAccessRange &range) { #undef GFX_TIMSORT_ENABLE_ASSERT #undef GFX_TIMSORT_ASSERT +#undef GFX_TIMSORT_ENABLE_AUDIT +#undef GFX_TIMSORT_AUDIT #undef GFX_TIMSORT_ENABLE_LOG #undef GFX_TIMSORT_LOG From 60d53f54f9ecf2547a25a056e4c8ea61b9a2124f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 18 Jan 2021 18:24:51 +0100 Subject: [PATCH 084/137] Release 2.1.0 --- CMakeLists.txt | 2 +- README.md | 6 +++--- include/gfx/timsort.hpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94b1cb7..2614ccc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(timsort VERSION 2.0.2 LANGUAGES CXX) +project(timsort VERSION 2.1.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index 6d4dfa7..665b0e8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-2.0.2-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.0.2) -[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F2.0.2-blue.svg)](https://conan.io/center/timsort?version=2.0.2) +[![Latest Release](https://img.shields.io/badge/release-2.1.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.1.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F2.1.0-blue.svg)](https://conan.io/center/timsort?version=2.1.0) ## TimSort @@ -120,7 +120,7 @@ Alternatively the library is also available on conan-center-index and can be ins the following command: ```sh -conan install timsort/2.0.2 +conan install timsort/2.1.0 ``` ## DIAGNOSTICS & INFORMATION diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 249dc6f..aad5ce3 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -40,8 +40,8 @@ // Semantic versioning macros #define GFX_TIMSORT_VERSION_MAJOR 2 -#define GFX_TIMSORT_VERSION_MINOR 0 -#define GFX_TIMSORT_VERSION_PATCH 2 +#define GFX_TIMSORT_VERSION_MINOR 1 +#define GFX_TIMSORT_VERSION_PATCH 0 // Diagnostic selection macros From 161d89e7fc1d570d51fe0576c816e561d3f9f979 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 15 Feb 2021 16:13:14 +0100 Subject: [PATCH 085/137] Add a Pitchfork Layout badge to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 665b0e8..f2d3eca 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Latest Release](https://img.shields.io/badge/release-2.1.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.1.0) [![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F2.1.0-blue.svg)](https://conan.io/center/timsort?version=2.1.0) +[![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork) ## TimSort From a82e47da8a2fe77fdef2604eb95b8fdff9dd8235 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 May 2021 12:54:04 +0200 Subject: [PATCH 086/137] Test suite maintenance The C++98 branch will only receive minor updates if any in the future, and there are no plans to overhaul its test suite ever. Therefore, the branches targetting newer standards should not be held back by decisions made for the C++98 branch, and compatibility between both can be broken when it makes sense. This commit replaces std::random_shuffle with a shuffle function based on std::shuffle to be forward-compatible with standards where std::random_shuffle has been removed. The rest is mostly on-the-fly minor cleanups since I was working in the same area. --- tests/cxx_11_tests.cpp | 9 +-- tests/cxx_98_tests.cpp | 34 +++++----- tests/merge_cxx_11_tests.cpp | 63 ++++++------------- ...x_98_test_helpers.hpp => test_helpers.hpp} | 34 +++++++--- 4 files changed, 65 insertions(+), 75 deletions(-) rename tests/{cxx_98_test_helpers.hpp => test_helpers.hpp} (87%) diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index 855b1b5..1201c3a 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. + * Copyright (c) 2019-2021 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -12,6 +12,7 @@ #include #include #include +#include "test_helpers.hpp" //////////////////////////////////////////////////////////// // Move-only type for benchmarks @@ -122,7 +123,7 @@ TEST_CASE( "shuffle10k_for_move_only_types" ) { } for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), [](const move_only &x, const move_only &y) { return x.value < y.value; }); @@ -141,7 +142,7 @@ TEST_CASE( "merge_shuffle10k_for_move_only_types" ) { } for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); const auto compare = [](const move_only &x, const move_only &y) { return x.value < y.value; }; const auto middle = a.begin() + rand() % size; @@ -184,7 +185,7 @@ TEST_CASE( "issue14" ) { TEST_CASE( "range signatures" ) { std::vector vec(50, 0); std::iota(vec.begin(), vec.end(), -25); - std::random_shuffle(vec.begin(), vec.end()); + test_helpers::shuffle(vec.begin(), vec.end()); SECTION( "range only" ) { gfx::timsort(vec); diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index 11b4e84..1bd76b5 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. + * Copyright (c) 2019-2021 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -11,7 +11,7 @@ #include #include #include -#include "cxx_98_test_helpers.hpp" +#include "test_helpers.hpp" using namespace test_helpers; @@ -116,7 +116,7 @@ TEST_CASE( "shuffle30" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -133,7 +133,7 @@ TEST_CASE( "shuffle31" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -150,7 +150,7 @@ TEST_CASE( "shuffle32" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -167,7 +167,7 @@ TEST_CASE( "shuffle128" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -186,7 +186,7 @@ TEST_CASE( "shuffle1023" ) { } for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -205,7 +205,7 @@ TEST_CASE( "shuffle1024" ) { } for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -225,7 +225,7 @@ TEST_CASE( "partial_shuffle1023" ) { // sorted-shuffled-sorted pattern for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); + test_helpers::shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); gfx::timsort(a.begin(), a.end(), std::less()); @@ -236,8 +236,8 @@ TEST_CASE( "partial_shuffle1023" ) { // shuffled-sorted-shuffled pattern for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); - std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); + test_helpers::shuffle(a.begin(), a.begin() + (size / 3 * 1)); + test_helpers::shuffle(a.begin() + (size / 3 * 2), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -257,7 +257,7 @@ TEST_CASE( "partial_shuffle1024" ) { // sorted-shuffled-sorted pattern for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); + test_helpers::shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); gfx::timsort(a.begin(), a.end(), std::less()); @@ -268,8 +268,8 @@ TEST_CASE( "partial_shuffle1024" ) { // shuffled-sorted-shuffled pattern for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.begin() + (size / 3 * 1)); - std::random_shuffle(a.begin() + (size / 3 * 2), a.end()); + test_helpers::shuffle(a.begin(), a.begin() + (size / 3 * 1)); + test_helpers::shuffle(a.begin() + (size / 3 * 2), a.end()); gfx::timsort(a.begin(), a.end(), std::less()); @@ -288,7 +288,7 @@ TEST_CASE( "shuffle1024r" ) { } for (int n = 0; n < 100; ++n) { - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end(), std::greater()); @@ -380,7 +380,7 @@ TEST_CASE( "default_compare_function" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - std::random_shuffle(a.begin(), a.end()); + test_helpers::shuffle(a.begin(), a.end()); gfx::timsort(a.begin(), a.end()); @@ -501,7 +501,7 @@ TEST_CASE( "projection" ) { for (int i = 0; i < size; ++i) { vec.push_back(i - 40); } - std::random_shuffle(vec.begin(), vec.end()); + test_helpers::shuffle(vec.begin(), vec.end()); gfx::timsort(vec.begin(), vec.end(), std::greater(), std::negate()); for (int i = 0; i < size; ++i) { diff --git a/tests/merge_cxx_11_tests.cpp b/tests/merge_cxx_11_tests.cpp index 8aba79d..89f0d0b 100644 --- a/tests/merge_cxx_11_tests.cpp +++ b/tests/merge_cxx_11_tests.cpp @@ -1,11 +1,13 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. + * Copyright (c) 2019-2021 Morwenn. * Copyright (c) 2021 Igor Kushnir . * * SPDX-License-Identifier: MIT */ #include +#include +#include #include #include #include @@ -13,7 +15,7 @@ #include #include #include -#include "cxx_98_test_helpers.hpp" +#include "test_helpers.hpp" using namespace test_helpers; @@ -30,23 +32,11 @@ void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle, template void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle) { - typedef typename std::iterator_traits::value_type value_type; + using value_type = typename std::iterator_traits::value_type; sort_and_merge(range, middle, std::less()); } std::mt19937 random_engine(2581470); // fixed seed is enough - -template -void shuffle(RandomAccessIterator first, RandomAccessIterator last) -{ - std::shuffle(first, last, random_engine); -} - -template -void shuffle(RandomAccessRange &range) -{ - shuffle(std::begin(range), std::end(range)); -} } TEST_CASE( "merge_simple0" ) { @@ -69,10 +59,7 @@ TEST_CASE( "merge_simple1" ) { } TEST_CASE( "merge_simple2" ) { - std::vector a; - - a.push_back(10); - a.push_back(20); + std::vector a = { 10, 20 }; gfx::timmerge(a.begin(), a.begin() + 1, a.end()); @@ -80,9 +67,7 @@ TEST_CASE( "merge_simple2" ) { CHECK(a[0] == 10); CHECK(a[1] == 20); - a.clear(); - a.push_back(20); - a.push_back(10); + a = { 20, 10 }; gfx::timmerge(a.begin(), a.begin() + 1, a.end(), std::less()); @@ -90,9 +75,7 @@ TEST_CASE( "merge_simple2" ) { CHECK(a[0] == 10); CHECK(a[1] == 20); - a.clear(); - a.push_back(10); - a.push_back(10); + a = { 10, 10 }; gfx::timmerge(a.begin(), a.begin() + 1, a.end(), std::less()); @@ -102,17 +85,7 @@ TEST_CASE( "merge_simple2" ) { } TEST_CASE( "merge_simple10" ) { - std::vector a; - a.push_back(60); - a.push_back(50); - a.push_back(10); - a.push_back(40); - a.push_back(80); - a.push_back(20); - a.push_back(30); - a.push_back(70); - a.push_back(10); - a.push_back(90); + std::vector a = { 60, 50, 10, 40, 80, 20, 30, 70, 10, 90 }; sort_and_merge(a, a.begin() + 5, std::less()); @@ -150,7 +123,7 @@ TEST_CASE( "merge_shuffle30" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - shuffle(a); + test_helpers::shuffle(a); sort_and_merge(a, a.begin() + 24); @@ -167,7 +140,7 @@ TEST_CASE( "merge_shuffle128" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - shuffle(a); + test_helpers::shuffle(a); sort_and_merge(a, a.begin() + 51); @@ -186,7 +159,7 @@ TEST_CASE( "merge_shuffle204x" ) { std::uniform_int_distribution random_middle(0, size); for (int n = 0; n < 30; ++n) { - shuffle(a); + test_helpers::shuffle(a); sort_and_merge(a, a.begin() + random_middle(random_engine)); @@ -208,7 +181,7 @@ TEST_CASE( "merge_partial_shuffle102x" ) { // sorted-shuffled-sorted pattern for (int n = 0; n < 100; ++n) { - shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); + test_helpers::shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); sort_and_merge(a, a.begin() + random_middle(random_engine), std::less()); @@ -219,8 +192,8 @@ TEST_CASE( "merge_partial_shuffle102x" ) { // shuffled-sorted-shuffled pattern for (int n = 0; n < 100; ++n) { - shuffle(a.begin(), a.begin() + (size / 3 * 1)); - shuffle(a.begin() + (size / 3 * 2), a.end()); + test_helpers::shuffle(a.begin(), a.begin() + (size / 3 * 1)); + test_helpers::shuffle(a.begin() + (size / 3 * 2), a.end()); sort_and_merge(a, a.begin() + random_middle(random_engine)); @@ -241,7 +214,7 @@ TEST_CASE( "merge_shuffle1025r" ) { std::uniform_int_distribution random_middle(0, size); for (int n = 0; n < 100; ++n) { - shuffle(a); + test_helpers::shuffle(a); sort_and_merge(a, a.begin() + random_middle(random_engine), std::greater()); @@ -315,7 +288,7 @@ TEST_CASE( "merge_default_compare_function" ) { for (int i = 0; i < size; ++i) { a.push_back((i + 1) * 10); } - shuffle(a); + test_helpers::shuffle(a); const auto middle = a.begin() + a.size() / 3; gfx::timsort(a.begin(), middle); @@ -393,7 +366,7 @@ TEST_CASE( "merge_projection" ) { for (int i = 0; i < size; ++i) { a.push_back(i - 40); } - shuffle(a); + test_helpers::shuffle(a); const auto middle = a.begin() + 43; gfx::timsort(a.begin(), middle, std::greater(), std::negate()); diff --git a/tests/cxx_98_test_helpers.hpp b/tests/test_helpers.hpp similarity index 87% rename from tests/cxx_98_test_helpers.hpp rename to tests/test_helpers.hpp index 340367f..3dffe0f 100644 --- a/tests/cxx_98_test_helpers.hpp +++ b/tests/test_helpers.hpp @@ -1,14 +1,16 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. + * Copyright (c) 2019-2021 Morwenn. * * SPDX-License-Identifier: MIT */ -#ifndef GFX_TIMSORT_CXX_98_TEST_HELPERS_HPP -#define GFX_TIMSORT_CXX_98_TEST_HELPERS_HPP +#ifndef GFX_TIMSORT_TEST_HELPERS_HPP +#define GFX_TIMSORT_TEST_HELPERS_HPP +#include #include +#include #include namespace test_helpers { @@ -44,7 +46,7 @@ namespace test_helpers { // Timsort should work with iterators that don't have a // post-increment or post-decrement operation - template + template class NoPostIterator { public: @@ -163,13 +165,27 @@ namespace test_helpers { Iterator _it; }; - //////////////////////////////////////////////////////////// - // Construction function - - template + template NoPostIterator make_no_post_iterator(Iterator it) { return NoPostIterator(it); } + + //////////////////////////////////////////////////////////// + // Shuffle function + + template + void shuffle(RandomAccessIterator first, RandomAccessIterator last) + { + thread_local std::mt19937 random_engine(2581470); // fixed seed is enough + + std::shuffle(first, last, random_engine); + } + + template + void shuffle(RandomAccessRange &range) + { + test_helpers::shuffle(std::begin(range), std::end(range)); + } } -#endif // GFX_TIMSORT_CXX_98_TEST_HELPERS_HPP +#endif // GFX_TIMSORT_TEST_HELPERS_HPP From 7c08f2bc30ab71081ada53b4194b9f060425ccc0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 May 2021 13:27:43 +0200 Subject: [PATCH 087/137] GitHub Actions: manually install Clang --- .github/workflows/build-ubuntu.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 48e2f84..f62c7ed 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -43,9 +43,13 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Install Clang + if: ${{matrix.cxx == 'clang++-6.0'}} + run: sudo apt install -y clang-6.0 lld-6.0 + - name: Install Valgrind if: ${{matrix.config.valgrind == 'ON'}} - run: sudo apt install -y valgrind + run: sudo apt update && sudo apt install -y valgrind - name: Configure CMake working-directory: ${{runner.workspace}} From 39222dfcf9fa7a2d9ae72d892b8a99274285863c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 May 2021 15:16:47 +0200 Subject: [PATCH 088/137] Actually randomize random tests --- tests/CMakeLists.txt | 16 ++++++++++------ tests/merge_cxx_11_tests.cpp | 2 +- tests/test_helpers.hpp | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c9f441..486dfbb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (c) 2019-2021 Morwenn +# SPDX-License-Identifier: MIT + include(DownloadProject) # Download and configure Catch2 for the tests @@ -9,7 +12,7 @@ download_project(PROJ Catch2 add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/contrib) -# Testsutie options +# Test suite options option(GFX_TIMSORT_USE_VALGRIND "Whether to run the tests with Valgrind" OFF) set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") @@ -119,10 +122,11 @@ target_compile_features(merge_cxx_11_tests PRIVATE cxx_std_11) include(CTest) include(Catch) -catch_discover_tests(cxx_98_tests) -catch_discover_tests(cxx_11_tests) -catch_discover_tests(cxx_17_tests) +string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) +catch_discover_tests(cxx_98_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) +catch_discover_tests(cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) +catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) if (WIN32) - catch_discover_tests(windows_tests) + catch_discover_tests(windows_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) endif() -catch_discover_tests(merge_cxx_11_tests) +catch_discover_tests(merge_cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) diff --git a/tests/merge_cxx_11_tests.cpp b/tests/merge_cxx_11_tests.cpp index 89f0d0b..c7f4aa3 100644 --- a/tests/merge_cxx_11_tests.cpp +++ b/tests/merge_cxx_11_tests.cpp @@ -36,7 +36,7 @@ void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle) sort_and_merge(range, middle, std::less()); } -std::mt19937 random_engine(2581470); // fixed seed is enough +std::mt19937 random_engine(Catch::rngSeed()); } TEST_CASE( "merge_simple0" ) { diff --git a/tests/test_helpers.hpp b/tests/test_helpers.hpp index 3dffe0f..8ff0ed7 100644 --- a/tests/test_helpers.hpp +++ b/tests/test_helpers.hpp @@ -176,7 +176,7 @@ namespace test_helpers { template void shuffle(RandomAccessIterator first, RandomAccessIterator last) { - thread_local std::mt19937 random_engine(2581470); // fixed seed is enough + thread_local std::mt19937 random_engine(Catch::rngSeed()); std::shuffle(first, last, random_engine); } From 0d3332efd49a05c1312ed0b7aef5c1ffedf9076e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 1 Jul 2021 14:12:36 +0200 Subject: [PATCH 089/137] Remove more occurrences of std::random_shuffle --- benchmarks/benchmarker.hpp | 7 +++++-- tests/windows.cpp | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/benchmarks/benchmarker.hpp b/benchmarks/benchmarker.hpp index 00c0770..c93c7e6 100644 --- a/benchmarks/benchmarker.hpp +++ b/benchmarks/benchmarker.hpp @@ -9,11 +9,12 @@ #ifndef GFX_TIMSORT_BENCHMARKER_HPP #define GFX_TIMSORT_BENCHMARKER_HPP +#include #include #include -#include #include #include +#include #include #include #include @@ -45,6 +46,8 @@ class Benchmarker { template void bench(state_t const state) { + thread_local std::mt19937 random_engine(2581470); // fixed seed is enough + std::vector a; for (int i = 0; i < size_; ++i) { a.push_back(BenchmarkerHelpers::convert_to::from((i + 1) * 10)); @@ -52,7 +55,7 @@ class Benchmarker { switch (state) { case randomized: - std::random_shuffle(a.begin(), a.end()); + std::shuffle(a.begin(), a.end(), random_engine); break; case reversed: std::stable_sort(a.begin(), a.end()); diff --git a/tests/windows.cpp b/tests/windows.cpp index 991cd77..6cd98ad 100644 --- a/tests/windows.cpp +++ b/tests/windows.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. + * Copyright (c) 2019-2021 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -12,6 +12,7 @@ #include #include #include +#include "test_helpers.hpp" TEST_CASE( "check inclusion of windows.h" ) { const int size = 100; @@ -21,7 +22,7 @@ TEST_CASE( "check inclusion of windows.h" ) { vec.push_back(i); } - std::random_shuffle(vec.begin(), vec.end()); + test_helpers::shuffle(vec); gfx::timsort(vec.begin(), vec.end()); for (int i = 0; i < size; ++i) { From 83be89e77dd4655688c165703d44c7f25fae9c1b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 16 Sep 2021 17:04:58 +0200 Subject: [PATCH 090/137] CI: bump Ubuntu version from 16.04 to 18.04 --- .github/workflows/build-ubuntu.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index f62c7ed..31f99a2 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -21,7 +21,7 @@ on: jobs: build: - runs-on: ubuntu-16.04 + runs-on: ubuntu-18.04 strategy: fail-fast: false @@ -43,6 +43,10 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Install GCC + if: ${{matrix.cxx == 'g++-5'}} + run: sudo apt-get install -y g++-5 + - name: Install Clang if: ${{matrix.cxx == 'clang++-6.0'}} run: sudo apt install -y clang-6.0 lld-6.0 From 865ada88d7d95458888c90765f4016e8fc06fba7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 16 Sep 2021 17:11:34 +0200 Subject: [PATCH 091/137] Remove remnants of Valgrind support on OSX --- tests/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 486dfbb..ff166c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,9 +20,6 @@ set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pas if (${GFX_TIMSORT_USE_VALGRIND}) find_program(MEMORYCHECK_COMMAND valgrind) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") - if (APPLE) - set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/valgrind-osx.supp) - endif() endif() macro(configure_tests target) From 552d9e587a7a38b28519be9cc100d5ce7854c670 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Oct 2021 12:04:17 +0200 Subject: [PATCH 092/137] Simplify timsrot and timmerge signatures Use default arguments and default template argments for the comparison and projection functions. This reduces the number of functions to write and to document, which will matter soon enough when new algorithms will be added to the library. This commit also cleans the README a bit as a drive-by fix, ensuring no more than 120 characters per line, and adding link to cppreference when standard library components are mentioned. --- README.md | 108 +++++++++++++++++++++------------------- include/gfx/timsort.hpp | 80 ++++++++--------------------- 2 files changed, 77 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index f2d3eca..cf75f05 100644 --- a/README.md +++ b/README.md @@ -10,74 +10,66 @@ See also the following links for a detailed description of TimSort: * http://svn.python.org/projects/python/trunk/Objects/listsort.txt * http://en.wikipedia.org/wiki/Timsort -This library is compatible with C++11. If you need a C++98 version, you can check the 1.x.y branch of this repository. +This library requires at least C++11. If you need a C++98 version, you can check the 1.x.y branch of this repository. -According to the benchmarks, it is slower than `std::sort()` on randomized sequences, but faster on partially-sorted -ones. `gfx::timsort` should be usable as a drop-in replacement for `std::stable_sort`, with the difference that it -can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. +According to the benchmarks, `gfx::timsort` is slower than [`std::sort()`][std-sort] on randomized sequences, but +faster on partially-sorted ones. It can be used as a drop-in replacement for [`std::stable_sort`][std-stable-sort], +with the difference that it can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory +available. `gfx::timsort` also has a few additional features and guarantees compared to `std::stable_sort`: -* It can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) - after the comparison function. The support is a bit rougher than in the linked article or the C++20 standard library: - unless `std::invoke` is available, only instances of types callable with parentheses can be used, there is no support - for pointer to members. -* It can also be passed a range instead of a pair of iterators, in which case it will sort the whole range. +* It can take a [projection function][projection] after the comparison function. The support is a bit rougher than in + the linked article or the C++20 standard library: unless [`std::invoke`][std-invoke] is available, only instances of + types callable with parentheses can be used, there is no support for pointer to members. +* It can be passed a range instead of a pair of iterators, in which case it will sort the whole range. * This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents are used, which means that timsort will work even if the postfix operators are not present or return an incompatible type such as `void`. +Merging sorted ranges efficiently is an important part of the TimSort algorithm. This library exposes `gfx::timmerge` +in the public API, a drop-in replacement for [`std::inplace_merge`][std-inplace-merge] with the difference that it +can't fallback to a O(n log n) algorithm when there isn't enough extra heap memory available. According to the +benchmarks, `gfx::timmerge` is slower than `std::inplace_merge` on heavily/randomly overlapping subranges of simple +elements, but it is faster for complex elements such as `std::string` and on sparsely overlapping subranges. -Merging sorted ranges efficiently is an important part of the TimSort algorithm. This library exposes its merge -algorithm in the public API. According to the benchmarks, `gfx::timmerge` is slower than `std::inplace_merge` on -heavily/randomly overlapping subranges of simple elements, but it is faster for complex elements such as `std::string` -and on sparsely overlapping subranges. `gfx::timmerge` should be usable as a drop-in replacement for -`std::inplace_merge`, with the difference that it can't fallback to a O(n log n) algorithm when there isn't enough -extra heap memory available. Like `gfx::timsort`, `gfx::timmerge` can take a projection function and avoids using the -postfix `++` or `--` operators. +Just like `gfx::timsort`, `gfx::timmerge` can take a projection function and avoids using the postfix `++` and `--` +operators. - -The full list of available signatures is as follows (in namespace `gfx`): +The list of available signatures is as follows (in namespace `gfx`): ```cpp -// timsort overloads taking a pair of iterators - -template -void timsort(RandomAccessIterator const first, RandomAccessIterator const last); - -template -void timsort(RandomAccessIterator const first, RandomAccessIterator const last, - Compare compare); +// timsort -template +template < + typename RandomAccessIterator, + typename Compare = /* see below (1) */, + typename Projection = /* see below (2) */ +> void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare, Projection projection); -// timsort overloads taking a range - -template -void timsort(RandomAccessRange &range); - -template -void timsort(RandomAccessRange &range, Compare compare); - -template +template < + typename RandomAccessRange, + typename Compare = /* see below (1) */, + typename Projection = /* see below (2) */ +> void timsort(RandomAccessRange &range, Compare compare, Projection projection); -// timmerge overloads +// timmerge -template -void timmerge(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last); - -template -void timmerge(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last, Compare compare); - -template +template < + typename RandomAccessIterator, + typename Compare = /* see below (1) */, + typename Projection = /* see below (2) */ +> void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Compare compare, Projection projection); ``` +In the signatures above: +- (1) [`std::less`][std-less] specialization for the `value_type` of the passed range or iterator. +- (2) Custom class equivalent to [`std::identity`][std-identity]. + ## EXAMPLE Example of using timsort with a comparison function and a projection function to sort a vector of strings by length: @@ -107,7 +99,8 @@ The library has been tested with the following compilers: * Clang 6 * MSVC 2017 -It should also work with more recent compilers, and most likely with some older compilers too. We used to guarantee support as far back as Clang 3.8, but the new continuous integration environment doesn't go that far. +It should also work with more recent compilers, and most likely with some older compilers too. We used to guarantee +support as far back as Clang 3.8, but the new continuous integration environment doesn't go that far back. The library can be installed on the system via CMake with the following commands: @@ -126,11 +119,13 @@ conan install timsort/2.1.0 ## DIAGNOSTICS & INFORMATION -A few configuration macros allow `gfx::timsort` and `gfx::timmerge` to emit diagnostic, which might be helpful to diagnose issues: +The following configuration macros allow `gfx::timsort` and `gfx::timmerge` to emit diagnostics, which can be helpful +to diagnose issues: * Defining `GFX_TIMSORT_ENABLE_ASSERT` inserts assertions in key locations in the algorithm to avoid logic errors. -* Defining `GFX_TIMSORT_ENABLE_AUDIT` inserts assertions that verify pre- and postconditions. These verifications can slow the - algorithm down significantly. Enable the audit only while testing or debugging. -* Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the algorithm. +* Defining `GFX_TIMSORT_ENABLE_AUDIT` inserts assertions that verify pre- and postconditions. These verifications can + slow the algorithm down significantly. Enable the audits only while testing or debugging. +* Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the + algorithm. **cpp-TimSort** follows semantic versioning and provides the following macros to retrieve the current major, minor and patch versions: @@ -143,7 +138,7 @@ GFX_TIMSORT_VERSION_PATCH ## TESTS -The tests are written with Catch2 (branch 1.x) and can be compiled with CMake and run through CTest. +The tests are written with Catch2 and can be compiled with CMake and run through CTest. When using the project's main `CMakeLists.txt`, the CMake variable `BUILD_TESTING` is `ON` by default unless the project is included as a subdirectory. The following CMake variables are available to change the way the tests are @@ -223,3 +218,12 @@ middle iterator positions, reformatted to improve readability): Detailed bench_merge results for different middle iterator positions can be found at https://github.com/timsort/cpp-TimSort/wiki/Benchmark-results + + + [projection]: https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html + [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity + [std-inplace-merge]: https://en.cppreference.com/w/cpp/algorithm/inplace_merge + [std-invoke]: https://en.cppreference.com/w/cpp/utility/functional/invoke + [std-less]: https://en.cppreference.com/w/cpp/utility/functional/less + [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort + [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/stable_sort diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index aad5ce3..b92b594 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -716,9 +716,13 @@ template class TimSort { * Stably merges two consecutive sorted ranges [first, middle) and [middle, last) into one * sorted range [first, last) with a comparison function and a projection function. */ -template -void timmerge(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last, Compare compare, Projection projection) { +template < + typename RandomAccessIterator, + typename Compare = std::less::value_type>, + typename Projection = detail::identity +> +void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, + Compare compare={}, Projection projection={}) { typedef detail::projection_compare compare_t; compare_t comp(std::move(compare), std::move(projection)); GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp) && "Precondition"); @@ -727,78 +731,36 @@ void timmerge(RandomAccessIterator first, RandomAccessIterator middle, GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp) && "Postcondition"); } -/** - * Same as std::inplace_merge(first, middle, last, compare). - */ -template -void timmerge(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last, Compare compare) { - gfx::timmerge(first, middle, last, compare, detail::identity()); -} - -/** - * Same as std::inplace_merge(first, middle, last). - */ -template -void timmerge(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last) { - typedef typename std::iterator_traits::value_type value_type; - gfx::timmerge(first, middle, last, std::less(), detail::identity()); -} - /** * Stably sorts a range with a comparison function and a projection function. */ -template +template < + typename RandomAccessIterator, + typename Compare = std::less::value_type>, + typename Projection = detail::identity +> void timsort(RandomAccessIterator const first, RandomAccessIterator const last, - Compare compare, Projection projection) { + Compare compare={}, Projection projection={}) { typedef detail::projection_compare compare_t; compare_t comp(std::move(compare), std::move(projection)); detail::TimSort::sort(first, last, comp); GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp) && "Postcondition"); } -/** - * Same as std::stable_sort(first, last, compare). - */ -template -void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare) { - gfx::timsort(first, last, compare, detail::identity()); -} - -/** - * Same as std::stable_sort(first, last). - */ -template -void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { - typedef typename std::iterator_traits::value_type value_type; - gfx::timsort(first, last, std::less(), detail::identity()); -} - /** * Stably sorts a range with a comparison function and a projection function. */ -template -void timsort(RandomAccessRange &range, Compare compare, Projection projection) { +template < + typename RandomAccessRange, + typename Compare = std::less())) + >::value_type>, + typename Projection = detail::identity +> +void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}) { gfx::timsort(std::begin(range), std::end(range), compare, projection); } -/** - * Same as std::stable_sort(std::begin(range), std::end(range), compare). - */ -template -void timsort(RandomAccessRange &range, Compare compare) { - gfx::timsort(std::begin(range), std::end(range), compare); -} - -/** - * Same as std::stable_sort(std::begin(range), std::end(range)). - */ -template -void timsort(RandomAccessRange &range) { - gfx::timsort(std::begin(range), std::end(range)); -} - } // namespace gfx #undef GFX_TIMSORT_ENABLE_ASSERT From 98b2808f3a72e799aa44c84cce1942805fabcfe0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 30 Jan 2022 14:07:43 +0100 Subject: [PATCH 093/137] Fix merge_cxx_11_tests not running (issue #38) Probably due to some concurrent access to the PRNG, I'm not sure. I had similar issues in cpp-sort and fixed it the same way: random_engine() is now a thread_local Meyers singleton. --- tests/merge_cxx_11_tests.cpp | 44 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/tests/merge_cxx_11_tests.cpp b/tests/merge_cxx_11_tests.cpp index c7f4aa3..837a4c4 100644 --- a/tests/merge_cxx_11_tests.cpp +++ b/tests/merge_cxx_11_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2019-2022 Morwenn. * Copyright (c) 2021 Igor Kushnir . * * SPDX-License-Identifier: MIT @@ -21,22 +21,26 @@ using namespace test_helpers; namespace { -template -void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle, Compare compare) { - const auto first = std::begin(range); - const auto last = std::end(range); - gfx::timsort(first, middle, compare); - gfx::timsort(middle, last, compare); - gfx::timmerge(first, middle, last, compare); -} + template + void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle, Compare compare) { + const auto first = std::begin(range); + const auto last = std::end(range); + gfx::timsort(first, middle, compare); + gfx::timsort(middle, last, compare); + gfx::timmerge(first, middle, last, compare); + } -template -void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle) { - using value_type = typename std::iterator_traits::value_type; - sort_and_merge(range, middle, std::less()); -} + template + void sort_and_merge(RandomAccessRange &range, decltype(std::end(range)) middle) { + using value_type = typename std::iterator_traits::value_type; + sort_and_merge(range, middle, std::less()); + } -std::mt19937 random_engine(Catch::rngSeed()); + inline std::mt19937& random_engine() + { + thread_local std::mt19937 res(Catch::rngSeed()); + return res; + } } TEST_CASE( "merge_simple0" ) { @@ -161,7 +165,7 @@ TEST_CASE( "merge_shuffle204x" ) { for (int n = 0; n < 30; ++n) { test_helpers::shuffle(a); - sort_and_merge(a, a.begin() + random_middle(random_engine)); + sort_and_merge(a, a.begin() + random_middle(random_engine())); for (int i = 0; i < size; ++i) { CHECK(a[i] == (i + 1) * 10); @@ -183,7 +187,7 @@ TEST_CASE( "merge_partial_shuffle102x" ) { for (int n = 0; n < 100; ++n) { test_helpers::shuffle(a.begin() + (size / 3 * 1), a.begin() + (size / 3 * 2)); - sort_and_merge(a, a.begin() + random_middle(random_engine), std::less()); + sort_and_merge(a, a.begin() + random_middle(random_engine()), std::less()); for (int i = 0; i < size; ++i) { CHECK(a[i] == (i + 1) * 10); @@ -195,7 +199,7 @@ TEST_CASE( "merge_partial_shuffle102x" ) { test_helpers::shuffle(a.begin(), a.begin() + (size / 3 * 1)); test_helpers::shuffle(a.begin() + (size / 3 * 2), a.end()); - sort_and_merge(a, a.begin() + random_middle(random_engine)); + sort_and_merge(a, a.begin() + random_middle(random_engine())); for (int i = 0; i < size; ++i) { CHECK(a[i] == (i + 1) * 10); @@ -216,7 +220,7 @@ TEST_CASE( "merge_shuffle1025r" ) { for (int n = 0; n < 100; ++n) { test_helpers::shuffle(a); - sort_and_merge(a, a.begin() + random_middle(random_engine), std::greater()); + sort_and_merge(a, a.begin() + random_middle(random_engine()), std::greater()); int j = size; for (int i = 0; i < size; ++i) { @@ -236,7 +240,7 @@ TEST_CASE( "merge_partial_reversed307x" ) { for (int n = 0; n < 20; ++n) { std::reverse(a.begin(), a.begin() + (size / 2)); // partial reversed - sort_and_merge(a, a.begin() + random_middle(random_engine)); + sort_and_merge(a, a.begin() + random_middle(random_engine())); for (int i = 0; i < size; ++i) { CHECK(a[i] == (i + 1) * 10); From e782512a1032430d1eb285634a91d2c86a25b3d7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 30 Jan 2022 16:08:13 +0100 Subject: [PATCH 094/137] Make a single C++11 test executable --- tests/CMakeLists.txt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ff166c8..ebeec87 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2021 Morwenn +# Copyright (c) 2019-2022 Morwenn # SPDX-License-Identifier: MIT include(DownloadProject) @@ -85,6 +85,7 @@ target_compile_features(cxx_98_tests PRIVATE cxx_std_98) # Tests requiring C++11 support add_executable(cxx_11_tests main.cpp + merge_cxx_11_tests.cpp cxx_11_tests.cpp ) configure_tests(cxx_11_tests) @@ -108,14 +109,6 @@ if (WIN32) target_compile_features(windows_tests PRIVATE cxx_std_98) endif() -# timmerge tests requiring C++11 support -add_executable(merge_cxx_11_tests - main.cpp - merge_cxx_11_tests.cpp -) -configure_tests(merge_cxx_11_tests) -target_compile_features(merge_cxx_11_tests PRIVATE cxx_std_11) - include(CTest) include(Catch) @@ -126,4 +119,3 @@ catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) if (WIN32) catch_discover_tests(windows_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) endif() -catch_discover_tests(merge_cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) From fa20051ef8c718c5c8f464602c61c344c0a22e0a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 23 Apr 2022 23:29:11 +0200 Subject: [PATCH 095/137] Add default arguments to function declarations in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cf75f05..1f9a407 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,14 @@ template < typename Projection = /* see below (2) */ > void timsort(RandomAccessIterator const first, RandomAccessIterator const last, - Compare compare, Projection projection); + Compare compare={}, Projection projection={}); template < typename RandomAccessRange, typename Compare = /* see below (1) */, typename Projection = /* see below (2) */ > -void timsort(RandomAccessRange &range, Compare compare, Projection projection); +void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}); // timmerge @@ -63,7 +63,7 @@ template < typename Projection = /* see below (2) */ > void timmerge(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last, Compare compare, Projection projection); + RandomAccessIterator last, Compare compare={}, Projection projection={}); ``` In the signatures above: From 6cedbdd3cbbc021427f5c0a3dbfbea109563c18c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 15 May 2021 15:59:07 +0200 Subject: [PATCH 096/137] Compile with C++20 --- .github/workflows/build-macos.yml | 4 +- .../{build-windows.yml => build-mingw.yml} | 11 ++-- .github/workflows/build-msvc.yml | 52 +++++++++++++++++++ .github/workflows/build-ubuntu.yml | 6 +-- CMakeLists.txt | 6 +-- include/gfx/timsort.hpp | 4 +- 6 files changed, 67 insertions(+), 16 deletions(-) rename .github/workflows/{build-windows.yml => build-mingw.yml} (81%) create mode 100644 .github/workflows/build-msvc.yml diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 422bced..a05fef6 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -27,8 +27,8 @@ jobs: fail-fast: false matrix: cxx: - - g++-9 - - $(brew --prefix llvm)/bin/clang++ # Clang 11 + - g++-10 + - $(brew --prefix llvm)/bin/clang++ # Clang 12 config: # Release build - build_type: Release diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-mingw.yml similarity index 81% rename from .github/workflows/build-windows.yml rename to .github/workflows/build-mingw.yml index c7ce066..779edad 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-mingw.yml @@ -1,19 +1,19 @@ # Copyright (c) 2021 Morwenn # SPDX-License-Identifier: MIT -name: Windows Builds +name: MinGW-w64 Builds on: push: paths: - - '.github/workflows/build-windows.yml' + - '.github/workflows/build-mingw.yml' - 'CMakeLists.txt' - 'cmake/**' - 'include/gfx/timsort.hpp' - 'tests/*' pull_request: paths: - - '.github/workflows/build-windows.yml' + - '.github/workflows/build-mingw.yml' - 'CMakeLists.txt' - 'cmake/**' - 'include/gfx/timsort.hpp' @@ -21,13 +21,12 @@ on: jobs: build: - runs-on: windows-2016 + runs-on: windows-2019 strategy: fail-fast: false matrix: build_type: [Debug, Release] - cmake_generator: ['MinGW Makefiles', 'Visual Studio 15 2017 Win64'] steps: - uses: actions/checkout@v2 @@ -39,7 +38,7 @@ jobs: cmake -H${{github.event.repository.name}} -Bbuild ` -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` - -G"${{matrix.cmake_generator}}" ` + -G"MinGW Makefiles" ` -DBUILD_BENCHMARKS=ON - name: Build the test suite diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml new file mode 100644 index 0000000..f57ed5e --- /dev/null +++ b/.github/workflows/build-msvc.yml @@ -0,0 +1,52 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: MSVC Builds + +on: + push: + paths: + - '.github/workflows/build-msvc.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + pull_request: + paths: + - '.github/workflows/build-msvc.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'include/gfx/timsort.hpp' + - 'tests/*' + +jobs: + build: + runs-on: windows-2019 + + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + shell: pwsh + working-directory: ${{runner.workspace}} + run: | + cmake -H${{github.event.repository.name}} -Bbuild ` + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` + -G"Visual Studio 16 2019" -A x64 ` + -DBUILD_BENCHMARKS=ON + + - name: Build the test suite + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 31f99a2..e1081a0 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -21,14 +21,14 @@ on: jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: cxx: - - g++-5 - - clang++-6.0 + - g++-10 + - clang++-11 config: # Release build - build_type: Release diff --git a/CMakeLists.txt b/CMakeLists.txt index 2614ccc..7b2fccb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ -cmake_minimum_required(VERSION 3.8.0) +cmake_minimum_required(VERSION 3.12.4) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(timsort VERSION 2.1.0 LANGUAGES CXX) +project(timsort VERSION 3.0.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) @@ -19,7 +19,7 @@ target_include_directories(timsort INTERFACE $ ) -target_compile_features(timsort INTERFACE cxx_std_11) +target_compile_features(timsort INTERFACE cxx_std_20) add_library(gfx::timsort ALIAS timsort) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index b92b594..1fe81b7 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -39,8 +39,8 @@ // Semantic versioning macros -#define GFX_TIMSORT_VERSION_MAJOR 2 -#define GFX_TIMSORT_VERSION_MINOR 1 +#define GFX_TIMSORT_VERSION_MAJOR 3 +#define GFX_TIMSORT_VERSION_MINOR 0 #define GFX_TIMSORT_VERSION_PATCH 0 // Diagnostic selection macros From 9e08b74df4154ab45427dd90ba82bf4c0b7dbf86 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 1 Jul 2021 13:16:09 +0200 Subject: [PATCH 097/137] Solve some build issues --- .github/workflows/build-ubuntu.yml | 8 -------- tests/CMakeLists.txt | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index e1081a0..da54687 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -43,14 +43,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install GCC - if: ${{matrix.cxx == 'g++-5'}} - run: sudo apt-get install -y g++-5 - - - name: Install Clang - if: ${{matrix.cxx == 'clang++-6.0'}} - run: sudo apt install -y clang-6.0 lld-6.0 - - name: Install Valgrind if: ${{matrix.config.valgrind == 'ON'}} run: sudo apt update && sudo apt install -y valgrind diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ebeec87..2a06482 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,7 +42,7 @@ macro(configure_tests target) target_compile_options(${target} PRIVATE -Wall -Wextra -Wcast-align -Winline -Wmissing-declarations -Wmissing-include-dirs -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code - $<$:-Wlogical-op -Wuseless-cast -Wzero-as-null-pointer-constant> + $<$:-Wlogical-op -Wuseless-cast> ) endif() @@ -115,6 +115,7 @@ include(Catch) string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) catch_discover_tests(cxx_98_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) +catch_discover_tests(merge_cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) if (WIN32) catch_discover_tests(windows_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) From 9f94c3bfc4bc3edfb73c13a543407ed77a7f9b55 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 1 Jul 2021 16:46:13 +0200 Subject: [PATCH 098/137] Unconditionally use std::invoke --- README.md | 7 +++---- include/gfx/timsort.hpp | 7 ------- tests/cxx_17_tests.cpp | 6 +----- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1f9a407..651a934 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,9 @@ with the difference that it can't fallback to a O(n log² n) algorithm when ther available. `gfx::timsort` also has a few additional features and guarantees compared to `std::stable_sort`: -* It can take a [projection function][projection] after the comparison function. The support is a bit rougher than in - the linked article or the C++20 standard library: unless [`std::invoke`][std-invoke] is available, only instances of - types callable with parentheses can be used, there is no support for pointer to members. -* It can be passed a range instead of a pair of iterators, in which case it will sort the whole range. +* It can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) + after the comparison function. +* It can also be passed a range instead of a pair of iterators, in which case it will sort the whole range. * This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents are used, which means that timsort will work even if the postfix operators are not present or return an incompatible type such as `void`. diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 1fe81b7..c2ee821 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -94,17 +94,10 @@ struct projection_compare { template bool operator()(T &&lhs, U &&rhs) { -#ifdef __cpp_lib_invoke return static_cast(std::invoke(compare, std::invoke(projection, std::forward(lhs)), std::invoke(projection, std::forward(rhs)) )); -#else - return static_cast(compare( - projection(std::forward(lhs)), - projection(std::forward(rhs)) - )); -#endif } Compare compare; diff --git a/tests/cxx_17_tests.cpp b/tests/cxx_17_tests.cpp index 56f1ca6..71513f7 100644 --- a/tests/cxx_17_tests.cpp +++ b/tests/cxx_17_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. + * Copyright (c) 2019-2021 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -38,8 +38,6 @@ namespace }; } -#ifdef __cpp_lib_invoke - TEST_CASE( "generalized callables" ) { std::vector vec(50); std::iota(vec.begin(), vec.end(), -25); @@ -80,5 +78,3 @@ TEST_CASE( "generalized callables" ) { CHECK(is_vec_sorted()); } } - -#endif // __cpp_lib_invoke From c569225fe0b56d63be9d5dd6ee2de538fdb9fa1b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 1 Jul 2021 17:13:16 +0200 Subject: [PATCH 099/137] Use std::identity --- include/gfx/timsort.hpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index c2ee821..798bdfa 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -77,15 +77,6 @@ namespace gfx { namespace detail { -// Equivalent to C++20 std::identity -struct identity { - template - constexpr T&& operator()(T&& value) const noexcept - { - return std::forward(value); - } -}; - // Merge a predicate and a projection function template struct projection_compare { @@ -712,7 +703,7 @@ template class TimSort { template < typename RandomAccessIterator, typename Compare = std::less::value_type>, - typename Projection = detail::identity + typename Projection = std::identity > void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Compare compare={}, Projection projection={}) { @@ -730,7 +721,7 @@ void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAcc template < typename RandomAccessIterator, typename Compare = std::less::value_type>, - typename Projection = detail::identity + typename Projection = std::identity > void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare={}, Projection projection={}) { @@ -748,7 +739,7 @@ template < typename Compare = std::less())) >::value_type>, - typename Projection = detail::identity + typename Projection = std::identity > void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}) { gfx::timsort(std::begin(range), std::end(range), compare, projection); From 31157d11dc405d66285c200a942df3466e35c0fd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 23 Apr 2022 22:36:34 +0200 Subject: [PATCH 100/137] CI: bump Clang to version 13 --- .github/workflows/build-macos.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index a05fef6..5717b1e 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Morwenn +# Copyright (c) 2021-2022 Morwenn # SPDX-License-Identifier: MIT name: MacOS Builds @@ -21,14 +21,14 @@ on: jobs: build: - runs-on: macos-10.15 + runs-on: macos-11 strategy: fail-fast: false matrix: cxx: - g++-10 - - $(brew --prefix llvm)/bin/clang++ # Clang 12 + - $(brew --prefix llvm)/bin/clang++ # Clang 13 config: # Release build - build_type: Release From b45be9a95ce732c25f30d5a4ed88ab2dc74f5823 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 23 Apr 2022 22:55:21 +0200 Subject: [PATCH 101/137] CI: bump MinGW-w64 version --- .github/workflows/build-mingw.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-mingw.yml b/.github/workflows/build-mingw.yml index 779edad..c8b256d 100644 --- a/.github/workflows/build-mingw.yml +++ b/.github/workflows/build-mingw.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Morwenn +# Copyright (c) 2021-2022 Morwenn # SPDX-License-Identifier: MIT name: MinGW-w64 Builds @@ -39,6 +39,8 @@ jobs: -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` -G"MinGW Makefiles" ` + -DCMAKE_C_COMPILER=C:/msys64/mingw64/bin/gcc.exe ` + -DCMAKE_CXX_COMPILER=C:/msys64/mingw64/bin/g++.exe ` -DBUILD_BENCHMARKS=ON - name: Build the test suite From 980fc517d3000598b190c9adbfd8d7da17f9cc2d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 23 Apr 2022 23:12:41 +0200 Subject: [PATCH 102/137] Fix rebase issue --- tests/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2a06482..54c6085 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -115,7 +115,6 @@ include(Catch) string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) catch_discover_tests(cxx_98_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) -catch_discover_tests(merge_cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) if (WIN32) catch_discover_tests(windows_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) From 7a03419b3b445ef4ef083561138368d1f2069fe2 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 23 Apr 2022 23:59:56 +0200 Subject: [PATCH 103/137] Replace std::less with std::less --- README.md | 16 ++++++---------- include/gfx/timsort.hpp | 10 ++++------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 651a934..85510e0 100644 --- a/README.md +++ b/README.md @@ -41,16 +41,16 @@ The list of available signatures is as follows (in namespace `gfx`): template < typename RandomAccessIterator, - typename Compare = /* see below (1) */, - typename Projection = /* see below (2) */ + typename Compare = std::less<>, + typename Projection = std::identity > void timsort(RandomAccessIterator const first, RandomAccessIterator const last, Compare compare={}, Projection projection={}); template < typename RandomAccessRange, - typename Compare = /* see below (1) */, - typename Projection = /* see below (2) */ + typename Compare = std::less<>, + typename Projection = std::identity > void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}); @@ -58,17 +58,13 @@ void timsort(RandomAccessRange &range, Compare compare={}, Projection projection template < typename RandomAccessIterator, - typename Compare = /* see below (1) */, - typename Projection = /* see below (2) */ + typename Compare = std::less<>, + typename Projection = std::identity > void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Compare compare={}, Projection projection={}); ``` -In the signatures above: -- (1) [`std::less`][std-less] specialization for the `value_type` of the passed range or iterator. -- (2) Custom class equivalent to [`std::identity`][std-identity]. - ## EXAMPLE Example of using timsort with a comparison function and a projection function to sort a vector of strings by length: diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 798bdfa..450b24d 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -6,7 +6,7 @@ * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2019-2022 Morwenn. * Copyright (c) 2021 Igor Kushnir . * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -702,7 +702,7 @@ template class TimSort { */ template < typename RandomAccessIterator, - typename Compare = std::less::value_type>, + typename Compare = std::less<>, typename Projection = std::identity > void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, @@ -720,7 +720,7 @@ void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAcc */ template < typename RandomAccessIterator, - typename Compare = std::less::value_type>, + typename Compare = std::less<>, typename Projection = std::identity > void timsort(RandomAccessIterator const first, RandomAccessIterator const last, @@ -736,9 +736,7 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last, */ template < typename RandomAccessRange, - typename Compare = std::less())) - >::value_type>, + typename Compare = std::less<>, typename Projection = std::identity > void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}) { From f56c1341ed5cec2ae64f941469f3c3f0fbbf82b3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 24 Apr 2022 01:16:51 +0200 Subject: [PATCH 104/137] Propagate projections manually This has a number of different effects: - Fewer projections are performed - projection_compare is gone - switched to std::ranges::(upper|lower)_bound - support dropped for iterators without postfix ++ and -- --- include/gfx/timsort.hpp | 154 ++++++++++++++++------------------- tests/cxx_98_tests.cpp | 10 +-- tests/merge_cxx_11_tests.cpp | 9 -- 3 files changed, 72 insertions(+), 101 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 450b24d..fdf7070 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -77,24 +77,6 @@ namespace gfx { namespace detail { -// Merge a predicate and a projection function -template -struct projection_compare { - projection_compare(Compare comp, Projection proj) : compare(comp), projection(proj) { - } - - template - bool operator()(T &&lhs, U &&rhs) { - return static_cast(std::invoke(compare, - std::invoke(projection, std::forward(lhs)), - std::invoke(projection, std::forward(rhs)) - )); - } - - Compare compare; - Projection projection; -}; - template struct run { typedef typename std::iterator_traits::difference_type diff_t; @@ -105,10 +87,9 @@ template struct run { } }; -template class TimSort { +template class TimSort { typedef RandomAccessIterator iter_t; typedef typename std::iterator_traits::value_type value_t; - typedef typename std::iterator_traits::reference ref_t; typedef typename std::iterator_traits::difference_type diff_t; static const int MIN_MERGE = 32; @@ -121,7 +102,7 @@ template class TimSort { std::vector > pending_; - static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare compare) { + static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= start); GFX_TIMSORT_ASSERT(start <= hi); if (start == lo) { @@ -131,7 +112,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(lo <= start); value_t pivot = std::move(*start); - iter_t const pos = std::upper_bound(lo, start, pivot, compare); + iter_t const pos = std::ranges::upper_bound(lo, start, std::invoke(proj, pivot), comp, proj); for (iter_t p = start; p > pos; --p) { *p = std::move(*(p - 1)); } @@ -139,7 +120,8 @@ template class TimSort { } } - static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, Compare compare) { + static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo < hi); iter_t runHi = lo + 1; @@ -147,15 +129,19 @@ template class TimSort { return 1; } - if (compare(*runHi, *lo)) { // decreasing + if (std::invoke(comp, std::invoke(proj, *runHi), std::invoke(proj, *lo))) { // decreasing do { ++runHi; - } while (runHi < hi && compare(*runHi, *(runHi - 1))); + } while (runHi < hi && std::invoke(comp, + std::invoke(proj, *runHi), + std::invoke(proj, *(runHi - 1)))); std::reverse(lo, runHi); } else { // non-decreasing do { ++runHi; - } while (runHi < hi && !compare(*runHi, *(runHi - 1))); + } while (runHi < hi && !std::invoke(comp, + std::invoke(proj, *runHi), + std::invoke(proj, *(runHi - 1)))); } return runHi - lo; @@ -182,7 +168,7 @@ template class TimSort { pending_.push_back(run(runBase, runLen)); } - void mergeCollapse(Compare compare) { + void mergeCollapse(Compare comp, Projection proj) { while (pending_.size() > 1) { diff_t n = pending_.size() - 2; @@ -191,27 +177,27 @@ template class TimSort { if (pending_[n - 1].len < pending_[n + 1].len) { --n; } - mergeAt(n, compare); + mergeAt(n, comp, proj); } else if (pending_[n].len <= pending_[n + 1].len) { - mergeAt(n, compare); + mergeAt(n, comp, proj); } else { break; } } } - void mergeForceCollapse(Compare compare) { + void mergeForceCollapse(Compare comp, Projection proj) { while (pending_.size() > 1) { diff_t n = pending_.size() - 2; if (n > 0 && pending_[n - 1].len < pending_[n + 1].len) { --n; } - mergeAt(n, compare); + mergeAt(n, comp, proj); } } - void mergeAt(diff_t const i, Compare compare) { + void mergeAt(diff_t const i, Compare comp, Projection proj) { diff_t const stackSize = pending_.size(); GFX_TIMSORT_ASSERT(stackSize >= 2); GFX_TIMSORT_ASSERT(i >= 0); @@ -230,15 +216,16 @@ template class TimSort { pending_.pop_back(); - mergeConsecutiveRuns(base1, len1, base2, len2, std::move(compare)); + mergeConsecutiveRuns(base1, len1, base2, len2, std::move(comp), std::move(proj)); } - void mergeConsecutiveRuns(iter_t base1, diff_t len1, iter_t base2, diff_t len2, Compare compare) { + void mergeConsecutiveRuns(iter_t base1, diff_t len1, iter_t base2, diff_t len2, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len1 > 0); GFX_TIMSORT_ASSERT(len2 > 0); GFX_TIMSORT_ASSERT(base1 + len1 == base2); - diff_t const k = gallopRight(*base2, base1, len1, 0, compare); + diff_t const k = gallopRight(std::invoke(proj, *base2), base1, len1, 0, comp, proj); GFX_TIMSORT_ASSERT(k >= 0); base1 += k; @@ -248,22 +235,22 @@ template class TimSort { return; } - len2 = gallopLeft(*(base1 + (len1 - 1)), base2, len2, len2 - 1, compare); + len2 = gallopLeft(std::invoke(proj, base1[len1 - 1]), base2, len2, len2 - 1, comp, proj); GFX_TIMSORT_ASSERT(len2 >= 0); if (len2 == 0) { return; } if (len1 <= len2) { - mergeLo(base1, len1, base2, len2, compare); + mergeLo(base1, len1, base2, len2, comp, proj); } else { - mergeHi(base1, len1, base2, len2, compare); + mergeHi(base1, len1, base2, len2, comp, proj); } } - template - static diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, - diff_t const hint, Compare compare) { + template + static diff_t gallopLeft(T const& key, Iter const base, diff_t const len, diff_t const hint, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len > 0); GFX_TIMSORT_ASSERT(hint >= 0); GFX_TIMSORT_ASSERT(hint < len); @@ -271,9 +258,9 @@ template class TimSort { diff_t lastOfs = 0; diff_t ofs = 1; - if (compare(*(base + hint), key)) { + if (std::invoke(comp, std::invoke(proj, base[hint]), key)) { diff_t const maxOfs = len - hint; - while (ofs < maxOfs && compare(*(base + (hint + ofs)), key)) { + while (ofs < maxOfs && std::invoke(comp, std::invoke(proj, base[hint + ofs]), key)) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -289,7 +276,7 @@ template class TimSort { ofs += hint; } else { diff_t const maxOfs = hint + 1; - while (ofs < maxOfs && !compare(*(base + (hint - ofs)), key)) { + while (ofs < maxOfs && !std::invoke(comp, std::invoke(proj, base[hint - ofs]), key)) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -309,12 +296,12 @@ template class TimSort { GFX_TIMSORT_ASSERT(lastOfs < ofs); GFX_TIMSORT_ASSERT(ofs <= len); - return std::lower_bound(base + (lastOfs + 1), base + ofs, key, compare) - base; + return std::ranges::lower_bound(base + (lastOfs + 1), base + ofs, key, comp, proj) - base; } - template - static diff_t gallopRight(ref_t key, Iter const base, diff_t const len, - diff_t const hint, Compare compare) { + template + static diff_t gallopRight(T const& key, Iter const base, diff_t const len, diff_t const hint, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len > 0); GFX_TIMSORT_ASSERT(hint >= 0); GFX_TIMSORT_ASSERT(hint < len); @@ -322,9 +309,9 @@ template class TimSort { diff_t ofs = 1; diff_t lastOfs = 0; - if (compare(key, *(base + hint))) { + if (std::invoke(comp, key, std::invoke(proj, base[hint]))) { diff_t const maxOfs = hint + 1; - while (ofs < maxOfs && compare(key, *(base + (hint - ofs)))) { + while (ofs < maxOfs && std::invoke(comp, key, std::invoke(proj, base[hint - ofs]))) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -341,7 +328,7 @@ template class TimSort { ofs = hint - tmp; } else { diff_t const maxOfs = len - hint; - while (ofs < maxOfs && !compare(key, *(base + (hint + ofs)))) { + while (ofs < maxOfs && !std::invoke(comp, key, std::invoke(proj, base[hint + ofs]))) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -360,7 +347,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(lastOfs < ofs); GFX_TIMSORT_ASSERT(ofs <= len); - return std::upper_bound(base + (lastOfs + 1), base + ofs, key, compare) - base; + return std::ranges::upper_bound(base + (lastOfs + 1), base + ofs, key, comp, proj) - base; } static void rotateLeft(iter_t first, iter_t last) @@ -379,7 +366,8 @@ template class TimSort { } - void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { + void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len1 > 0); GFX_TIMSORT_ASSERT(len2 > 0); GFX_TIMSORT_ASSERT(base1 + len1 == base2); @@ -413,7 +401,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(len1 > 1); GFX_TIMSORT_ASSERT(len2 > 0); - if (compare(*cursor2, *cursor1)) { + if (std::invoke(comp, std::invoke(proj, *cursor2), std::invoke(proj, *cursor1))) { *dest = std::move(*cursor2); ++cursor2; ++dest; @@ -438,7 +426,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(len1 > 1); GFX_TIMSORT_ASSERT(len2 > 0); - count1 = gallopRight(*cursor2, cursor1, len1, 0, compare); + count1 = gallopRight(std::invoke(proj, *cursor2), cursor1, len1, 0, comp, proj); if (count1 != 0) { std::move_backward(cursor1, cursor1 + count1, dest + count1); dest += count1; @@ -456,7 +444,7 @@ template class TimSort { goto epilogue; } - count2 = gallopLeft(*cursor1, cursor2, len2, 0, compare); + count2 = gallopLeft(std::invoke(proj, *cursor1), cursor2, len2, 0, comp, proj); if (count2 != 0) { std::move(cursor2, cursor2 + count2, dest); dest += count2; @@ -498,7 +486,8 @@ template class TimSort { } } - void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare compare) { + void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len1 > 0); GFX_TIMSORT_ASSERT(len2 > 0); GFX_TIMSORT_ASSERT(base1 + len1 == base2); @@ -537,7 +526,7 @@ template class TimSort { GFX_TIMSORT_ASSERT(len1 > 0); GFX_TIMSORT_ASSERT(len2 > 1); - if (compare(*cursor2, *cursor1)) { + if (std::invoke(comp, std::invoke(proj, *cursor2), std::invoke(proj, *cursor1))) { *dest = std::move(*cursor1); --dest; ++count1; @@ -564,7 +553,8 @@ template class TimSort { GFX_TIMSORT_ASSERT(len1 > 0); GFX_TIMSORT_ASSERT(len2 > 1); - count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1, compare); + count1 = len1 - gallopRight(std::invoke(proj, *cursor2), + base1, len1, len1 - 1, comp, proj); if (count1 != 0) { dest -= count1; cursor1 -= count1; @@ -582,7 +572,8 @@ template class TimSort { goto epilogue; } - count2 = len2 - gallopLeft(*(cursor1 - 1), tmp_.begin(), len2, len2 - 1, compare); + count2 = len2 - gallopLeft(std::invoke(proj, *(cursor1 - 1)), + tmp_.begin(), len2, len2 - 1, comp, proj); if (count2 != 0) { dest -= count2; cursor2 -= count2; @@ -631,7 +622,8 @@ template class TimSort { public: - static void merge(iter_t const lo, iter_t const mid, iter_t const hi, Compare compare) { + static void merge(iter_t const lo, iter_t const mid, iter_t const hi, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= mid); GFX_TIMSORT_ASSERT(mid <= hi); @@ -640,13 +632,13 @@ template class TimSort { } TimSort ts; - ts.mergeConsecutiveRuns(lo, mid - lo, mid, hi - mid, std::move(compare)); + ts.mergeConsecutiveRuns(lo, mid - lo, mid, hi - mid, std::move(comp), std::move(proj)); GFX_TIMSORT_LOG("1st size: " << (mid - lo) << "; 2nd size: " << (hi - mid) << "; tmp_.size(): " << ts.tmp_.size()); } - static void sort(iter_t const lo, iter_t const hi, Compare compare) { + static void sort(iter_t const lo, iter_t const hi, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= hi); diff_t nRemaining = (hi - lo); @@ -655,9 +647,9 @@ template class TimSort { } if (nRemaining < MIN_MERGE) { - diff_t const initRunLen = countRunAndMakeAscending(lo, hi, compare); + diff_t const initRunLen = countRunAndMakeAscending(lo, hi, comp, proj); GFX_TIMSORT_LOG("initRunLen: " << initRunLen); - binarySort(lo, hi, lo + initRunLen, compare); + binarySort(lo, hi, lo + initRunLen, comp, proj); return; } @@ -665,23 +657,23 @@ template class TimSort { diff_t const minRun = minRunLength(nRemaining); iter_t cur = lo; do { - diff_t runLen = countRunAndMakeAscending(cur, hi, compare); + diff_t runLen = countRunAndMakeAscending(cur, hi, comp, proj); if (runLen < minRun) { diff_t const force = (std::min)(nRemaining, minRun); - binarySort(cur, cur + force, cur + runLen, compare); + binarySort(cur, cur + force, cur + runLen, comp, proj); runLen = force; } ts.pushRun(cur, runLen); - ts.mergeCollapse(compare); + ts.mergeCollapse(comp, proj); cur += runLen; nRemaining -= runLen; } while (nRemaining != 0); GFX_TIMSORT_ASSERT(cur == hi); - ts.mergeForceCollapse(compare); + ts.mergeForceCollapse(comp, proj); GFX_TIMSORT_ASSERT(ts.pending_.size() == 1); GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() @@ -706,13 +698,11 @@ template < typename Projection = std::identity > void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, - Compare compare={}, Projection projection={}) { - typedef detail::projection_compare compare_t; - compare_t comp(std::move(compare), std::move(projection)); - GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp) && "Precondition"); - GFX_TIMSORT_AUDIT(std::is_sorted(middle, last, comp) && "Precondition"); - detail::TimSort::merge(first, middle, last, comp); - GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp) && "Postcondition"); + Compare comp={}, Projection proj={}) { + GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp, proj) && "Precondition"); + GFX_TIMSORT_AUDIT(std::is_sorted(middle, last, comp, proj) && "Precondition"); + detail::TimSort::merge(first, middle, last, comp, proj); + GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp, proj) && "Postcondition"); } /** @@ -724,11 +714,9 @@ template < typename Projection = std::identity > void timsort(RandomAccessIterator const first, RandomAccessIterator const last, - Compare compare={}, Projection projection={}) { - typedef detail::projection_compare compare_t; - compare_t comp(std::move(compare), std::move(projection)); - detail::TimSort::sort(first, last, comp); - GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp) && "Postcondition"); + Compare comp={}, Projection proj={}) { + detail::TimSort::sort(first, last, comp, proj); + GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp, proj) && "Postcondition"); } /** @@ -739,8 +727,8 @@ template < typename Compare = std::less<>, typename Projection = std::identity > -void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}) { - gfx::timsort(std::begin(range), std::end(range), compare, projection); +void timsort(RandomAccessRange &range, Compare comp={}, Projection proj={}) { + gfx::timsort(std::begin(range), std::end(range), comp, proj); } } // namespace gfx diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index 1bd76b5..c3fc6eb 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2019-2022 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -508,11 +508,3 @@ TEST_CASE( "projection" ) { CHECK(vec[i] == i - 40); } } - -TEST_CASE( "iterator without post-increment or post-decrement" ) { - std::vector a; - - gfx::timsort(make_no_post_iterator(a.begin()), make_no_post_iterator(a.end())); - - CHECK(a.size() == std::size_t(0)); -} diff --git a/tests/merge_cxx_11_tests.cpp b/tests/merge_cxx_11_tests.cpp index 837a4c4..237b123 100644 --- a/tests/merge_cxx_11_tests.cpp +++ b/tests/merge_cxx_11_tests.cpp @@ -381,12 +381,3 @@ TEST_CASE( "merge_projection" ) { CHECK(a[i] == i - 40); } } - -TEST_CASE( "merge_iterator without post-increment or post-decrement" ) { - std::vector a; - - gfx::timmerge(make_no_post_iterator(a.begin()), make_no_post_iterator(a.begin()), - make_no_post_iterator(a.end())); - - CHECK(a.size() == std::size_t(0)); -} From 5e4f715e296c9ce4d5a6781597d6fb1ae2f24129 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 24 Apr 2022 11:27:51 +0200 Subject: [PATCH 105/137] Don't mention standard C++20 features in README --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 85510e0..e601d01 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,12 @@ faster on partially-sorted ones. It can be used as a drop-in replacement for [`s with the difference that it can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory available. -`gfx::timsort` also has a few additional features and guarantees compared to `std::stable_sort`: -* It can take a [projection function](https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html) - after the comparison function. -* It can also be passed a range instead of a pair of iterators, in which case it will sort the whole range. -* This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents - are used, which means that timsort will work even if the postfix operators are not present or return an incompatible - type such as `void`. - Merging sorted ranges efficiently is an important part of the TimSort algorithm. This library exposes `gfx::timmerge` in the public API, a drop-in replacement for [`std::inplace_merge`][std-inplace-merge] with the difference that it can't fallback to a O(n log n) algorithm when there isn't enough extra heap memory available. According to the benchmarks, `gfx::timmerge` is slower than `std::inplace_merge` on heavily/randomly overlapping subranges of simple elements, but it is faster for complex elements such as `std::string` and on sparsely overlapping subranges. -Just like `gfx::timsort`, `gfx::timmerge` can take a projection function and avoids using the postfix `++` and `--` -operators. - The list of available signatures is as follows (in namespace `gfx`): ```cpp From 55ec977472a3d41db3565fb0e36754821d9d0a48 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 16 Jan 2024 22:24:43 +0100 Subject: [PATCH 106/137] Replace std::less<> with std::ranges::less --- README.md | 12 ++++-------- include/gfx/timsort.hpp | 6 +++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e601d01..05e0ec9 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The list of available signatures is as follows (in namespace `gfx`): template < typename RandomAccessIterator, - typename Compare = std::less<>, + typename Compare = std::ranges::less, typename Projection = std::identity > void timsort(RandomAccessIterator const first, RandomAccessIterator const last, @@ -38,7 +38,7 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last, template < typename RandomAccessRange, - typename Compare = std::less<>, + typename Compare = std::ranges::less, typename Projection = std::identity > void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}); @@ -47,7 +47,7 @@ void timsort(RandomAccessRange &range, Compare compare={}, Projection projection template < typename RandomAccessIterator, - typename Compare = std::less<>, + typename Compare = std::ranges::less, typename Projection = std::identity > void timmerge(RandomAccessIterator first, RandomAccessIterator middle, @@ -69,7 +69,7 @@ size_t len(const std::string& str) { // Sort a vector of strings by length std::vector collection = { /* ... */ }; -gfx::timsort(collection, std::less{}, &len); +gfx::timsort(collection, {}, &len); ``` ## INSTALLATION & COMPATIBILITY @@ -204,10 +204,6 @@ Detailed bench_merge results for different middle iterator positions can be foun https://github.com/timsort/cpp-TimSort/wiki/Benchmark-results - [projection]: https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html - [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity [std-inplace-merge]: https://en.cppreference.com/w/cpp/algorithm/inplace_merge - [std-invoke]: https://en.cppreference.com/w/cpp/utility/functional/invoke - [std-less]: https://en.cppreference.com/w/cpp/utility/functional/less [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/stable_sort diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index fdf7070..b7c0f9a 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -6,7 +6,7 @@ * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2022 Morwenn. + * Copyright (c) 2019-2024 Morwenn. * Copyright (c) 2021 Igor Kushnir . * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -710,7 +710,7 @@ void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAcc */ template < typename RandomAccessIterator, - typename Compare = std::less<>, + typename Compare = std::ranges::less, typename Projection = std::identity > void timsort(RandomAccessIterator const first, RandomAccessIterator const last, @@ -724,7 +724,7 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last, */ template < typename RandomAccessRange, - typename Compare = std::less<>, + typename Compare = std::ranges::less, typename Projection = std::identity > void timsort(RandomAccessRange &range, Compare comp={}, Projection proj={}) { From b9eabb0368cae71b99eae9504479f6afed28d781 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 16 Jan 2024 22:35:30 +0100 Subject: [PATCH 107/137] CI: upgrade Windows image & MSVC --- .github/workflows/build-msvc.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index f57ed5e..779b4f7 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Morwenn +# Copyright (c) 2021-2024 Morwenn # SPDX-License-Identifier: MIT name: MSVC Builds @@ -21,7 +21,7 @@ on: jobs: build: - runs-on: windows-2019 + runs-on: windows-2022 strategy: fail-fast: false @@ -38,7 +38,7 @@ jobs: cmake -H${{github.event.repository.name}} -Bbuild ` -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` - -G"Visual Studio 16 2019" -A x64 ` + -G"Visual Studio 17 2022" -A x64 ` -DBUILD_BENCHMARKS=ON - name: Build the test suite From 5e502fae3bcddd39761460435d9c78af14e23bb1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 16 Jan 2024 22:43:48 +0100 Subject: [PATCH 108/137] CI: upgrade action/checkout to v4 --- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/build-mingw.yml | 4 ++-- .github/workflows/build-msvc.yml | 2 +- .github/workflows/build-ubuntu.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 5717b1e..8e4f900 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2022 Morwenn +# Copyright (c) 2021-2024 Morwenn # SPDX-License-Identifier: MIT name: MacOS Builds @@ -39,7 +39,7 @@ jobs: sanitize: undefined steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: seanmiddleditch/gha-setup-ninja@master - name: Configure CMake diff --git a/.github/workflows/build-mingw.yml b/.github/workflows/build-mingw.yml index c8b256d..0985576 100644 --- a/.github/workflows/build-mingw.yml +++ b/.github/workflows/build-mingw.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2022 Morwenn +# Copyright (c) 2021-2024 Morwenn # SPDX-License-Identifier: MIT name: MinGW-w64 Builds @@ -29,7 +29,7 @@ jobs: build_type: [Debug, Release] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Configure CMake shell: pwsh diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 779b4f7..7d519b6 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -29,7 +29,7 @@ jobs: build_type: [Debug, Release] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Configure CMake shell: pwsh diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index da54687..005099b 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Morwenn +# Copyright (c) 2021-2024 Morwenn # SPDX-License-Identifier: MIT name: Ubuntu Builds @@ -41,7 +41,7 @@ jobs: sanitize: undefined steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Valgrind if: ${{matrix.config.valgrind == 'ON'}} From 5235a89f047a9bb733d6ac34da62af3a15029788 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 16 Jan 2024 23:17:50 +0100 Subject: [PATCH 109/137] Constrain main entry points with concepts --- README.md | 17 ++++++++++------- include/gfx/timsort.hpp | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 05e0ec9..9d0f26b 100644 --- a/README.md +++ b/README.md @@ -29,29 +29,32 @@ The list of available signatures is as follows (in namespace `gfx`): // timsort template < - typename RandomAccessIterator, + std::random_access_iterator Iterator, typename Compare = std::ranges::less, typename Projection = std::identity > -void timsort(RandomAccessIterator const first, RandomAccessIterator const last, + requires std::sortable +void timsort(Iterator first, Iterator last, Compare compare={}, Projection projection={}); template < - typename RandomAccessRange, + std::ranges::random_access_range Range, typename Compare = std::ranges::less, typename Projection = std::identity > -void timsort(RandomAccessRange &range, Compare compare={}, Projection projection={}); + requires std::sortable, Compare, Projection> +void timsort(Range &range, Compare compare={}, Projection projection={}); // timmerge template < - typename RandomAccessIterator, + std::random_access_iterator Iterator, typename Compare = std::ranges::less, typename Projection = std::identity > -void timmerge(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last, Compare compare={}, Projection projection={}); + requires std::sortable +void timmerge(Iterator first, Iterator middle, Iterator last, + Compare compare={}, Projection projection={}); ``` ## EXAMPLE diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index b7c0f9a..34f19ee 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -693,15 +694,16 @@ template * sorted range [first, last) with a comparison function and a projection function. */ template < - typename RandomAccessIterator, + std::random_access_iterator Iterator, typename Compare = std::less<>, typename Projection = std::identity > -void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, + requires std::sortable +void timmerge(Iterator first, Iterator middle, Iterator last, Compare comp={}, Projection proj={}) { GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp, proj) && "Precondition"); GFX_TIMSORT_AUDIT(std::is_sorted(middle, last, comp, proj) && "Precondition"); - detail::TimSort::merge(first, middle, last, comp, proj); + detail::TimSort::merge(first, middle, last, comp, proj); GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp, proj) && "Postcondition"); } @@ -709,13 +711,14 @@ void timmerge(RandomAccessIterator first, RandomAccessIterator middle, RandomAcc * Stably sorts a range with a comparison function and a projection function. */ template < - typename RandomAccessIterator, + std::random_access_iterator Iterator, typename Compare = std::ranges::less, typename Projection = std::identity > -void timsort(RandomAccessIterator const first, RandomAccessIterator const last, + requires std::sortable +void timsort(Iterator first, Iterator last, Compare comp={}, Projection proj={}) { - detail::TimSort::sort(first, last, comp, proj); + detail::TimSort::sort(first, last, comp, proj); GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp, proj) && "Postcondition"); } @@ -723,11 +726,12 @@ void timsort(RandomAccessIterator const first, RandomAccessIterator const last, * Stably sorts a range with a comparison function and a projection function. */ template < - typename RandomAccessRange, + std::ranges::random_access_range Range, typename Compare = std::ranges::less, typename Projection = std::identity > -void timsort(RandomAccessRange &range, Compare comp={}, Projection proj={}) { + requires std::sortable, Compare, Projection> +void timsort(Range &range, Compare comp={}, Projection proj={}) { gfx::timsort(std::begin(range), std::end(range), comp, proj); } From 1125adec4e092ff86a7ffb36ec1c3a40d2320d2e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 16 Jan 2024 23:53:28 +0100 Subject: [PATCH 110/137] Fix MinGW build --- .github/workflows/build-mingw.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build-mingw.yml b/.github/workflows/build-mingw.yml index 0985576..49d0ab7 100644 --- a/.github/workflows/build-mingw.yml +++ b/.github/workflows/build-mingw.yml @@ -21,7 +21,7 @@ on: jobs: build: - runs-on: windows-2019 + runs-on: windows-2022 strategy: fail-fast: false @@ -36,11 +36,8 @@ jobs: working-directory: ${{runner.workspace}} run: | cmake -H${{github.event.repository.name}} -Bbuild ` - -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` -G"MinGW Makefiles" ` - -DCMAKE_C_COMPILER=C:/msys64/mingw64/bin/gcc.exe ` - -DCMAKE_CXX_COMPILER=C:/msys64/mingw64/bin/g++.exe ` -DBUILD_BENCHMARKS=ON - name: Build the test suite From e59cdd3cc6c4d1ca10d29ab0b4523becf9ad432c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Jan 2024 00:25:48 +0100 Subject: [PATCH 111/137] Upgrade Catch2 to v3 --- tests/CMakeLists.txt | 13 ++++--------- tests/cxx_11_tests.cpp | 4 ++-- tests/cxx_17_tests.cpp | 4 ++-- tests/cxx_98_tests.cpp | 4 ++-- tests/main.cpp | 8 -------- tests/merge_cxx_11_tests.cpp | 4 ++-- tests/test_helpers.hpp | 10 +++++++++- tests/windows.cpp | 4 ++-- 8 files changed, 23 insertions(+), 28 deletions(-) delete mode 100644 tests/main.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 54c6085..773ea74 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2022 Morwenn +# Copyright (c) 2019-2024 Morwenn # SPDX-License-Identifier: MIT include(DownloadProject) @@ -6,11 +6,11 @@ include(DownloadProject) # Download and configure Catch2 for the tests download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v2.x + GIT_TAG v3.5.2 UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) -list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/contrib) +list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) # Test suite options option(GFX_TIMSORT_USE_VALGRIND "Whether to run the tests with Valgrind" OFF) @@ -25,14 +25,13 @@ endif() macro(configure_tests target) # Add required dependencies to tests target_link_libraries(${target} PRIVATE - Catch2::Catch2 + Catch2::Catch2WithMain gfx::timsort ) target_compile_definitions(${target} PRIVATE # Somewhat speed up Catch2 compile times CATCH_CONFIG_FAST_COMPILE - CATCH_CONFIG_DISABLE_MATCHERS # Enable assertions for more thorough tests GFX_TIMSORT_ENABLE_ASSERT ) @@ -76,7 +75,6 @@ endmacro() # Tests that can run with C++98 add_executable(cxx_98_tests - main.cpp cxx_98_tests.cpp ) configure_tests(cxx_98_tests) @@ -84,7 +82,6 @@ target_compile_features(cxx_98_tests PRIVATE cxx_std_98) # Tests requiring C++11 support add_executable(cxx_11_tests - main.cpp merge_cxx_11_tests.cpp cxx_11_tests.cpp ) @@ -93,7 +90,6 @@ target_compile_features(cxx_11_tests PRIVATE cxx_std_11) # Tests requiring C++17 support add_executable(cxx_17_tests - main.cpp cxx_17_tests.cpp ) configure_tests(cxx_17_tests) @@ -102,7 +98,6 @@ target_compile_features(cxx_17_tests PRIVATE cxx_std_17) # Windows-specific tests if (WIN32) add_executable(windows_tests - main.cpp windows.cpp ) configure_tests(windows_tests) diff --git a/tests/cxx_11_tests.cpp b/tests/cxx_11_tests.cpp index 1201c3a..f84f73f 100644 --- a/tests/cxx_11_tests.cpp +++ b/tests/cxx_11_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2019-2024 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "test_helpers.hpp" diff --git a/tests/cxx_17_tests.cpp b/tests/cxx_17_tests.cpp index 71513f7..735c0c2 100644 --- a/tests/cxx_17_tests.cpp +++ b/tests/cxx_17_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2019-2024 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include namespace diff --git a/tests/cxx_98_tests.cpp b/tests/cxx_98_tests.cpp index c3fc6eb..6699cac 100644 --- a/tests/cxx_98_tests.cpp +++ b/tests/cxx_98_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2022 Morwenn. + * Copyright (c) 2019-2024 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include "test_helpers.hpp" diff --git a/tests/main.cpp b/tests/main.cpp deleted file mode 100644 index 6890559..0000000 --- a/tests/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019 Morwenn. - * - * SPDX-License-Identifier: MIT - */ -#define CATCH_CONFIG_MAIN -#include diff --git a/tests/merge_cxx_11_tests.cpp b/tests/merge_cxx_11_tests.cpp index 237b123..dab22d8 100644 --- a/tests/merge_cxx_11_tests.cpp +++ b/tests/merge_cxx_11_tests.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2022 Morwenn. + * Copyright (c) 2019-2024 Morwenn. * Copyright (c) 2021 Igor Kushnir . * * SPDX-License-Identifier: MIT @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include "test_helpers.hpp" diff --git a/tests/test_helpers.hpp b/tests/test_helpers.hpp index 8ff0ed7..c81b74b 100644 --- a/tests/test_helpers.hpp +++ b/tests/test_helpers.hpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2019-2024 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -13,6 +13,14 @@ #include #include +namespace Catch +{ + // This functions is only available in an internal header that + // drags a lot of dependencies, it's cheaper to just declare + // it ourselves in this wrapper + unsigned int rngSeed(); +} + namespace test_helpers { // Helper types for the tests diff --git a/tests/windows.cpp b/tests/windows.cpp index 6cd98ad..2f5b510 100644 --- a/tests/windows.cpp +++ b/tests/windows.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2019-2021 Morwenn. + * Copyright (c) 2019-2024 Morwenn. * * SPDX-License-Identifier: MIT */ @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "test_helpers.hpp" From dd31776de7056bcb9c55b6e997a39644a9f76a51 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Jan 2024 14:14:20 +0100 Subject: [PATCH 112/137] CMake: replace DownloadProject with FetchContent --- cmake/DownloadProject.CMakeLists.cmake.in | 17 -- cmake/DownloadProject.cmake | 182 ---------------------- tests/CMakeLists.txt | 23 +-- 3 files changed, 14 insertions(+), 208 deletions(-) delete mode 100644 cmake/DownloadProject.CMakeLists.cmake.in delete mode 100644 cmake/DownloadProject.cmake diff --git a/cmake/DownloadProject.CMakeLists.cmake.in b/cmake/DownloadProject.CMakeLists.cmake.in deleted file mode 100644 index 89be4fd..0000000 --- a/cmake/DownloadProject.CMakeLists.cmake.in +++ /dev/null @@ -1,17 +0,0 @@ -# Distributed under the OSI-approved MIT License. See accompanying -# file LICENSE or https://github.com/Crascit/DownloadProject for details. - -cmake_minimum_required(VERSION 2.8.2) - -project(${DL_ARGS_PROJ}-download NONE) - -include(ExternalProject) -ExternalProject_Add(${DL_ARGS_PROJ}-download - ${DL_ARGS_UNPARSED_ARGUMENTS} - SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" - BINARY_DIR "${DL_ARGS_BINARY_DIR}" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) diff --git a/cmake/DownloadProject.cmake b/cmake/DownloadProject.cmake deleted file mode 100644 index e300f42..0000000 --- a/cmake/DownloadProject.cmake +++ /dev/null @@ -1,182 +0,0 @@ -# Distributed under the OSI-approved MIT License. See accompanying -# file LICENSE or https://github.com/Crascit/DownloadProject for details. -# -# MODULE: DownloadProject -# -# PROVIDES: -# download_project( PROJ projectName -# [PREFIX prefixDir] -# [DOWNLOAD_DIR downloadDir] -# [SOURCE_DIR srcDir] -# [BINARY_DIR binDir] -# [QUIET] -# ... -# ) -# -# Provides the ability to download and unpack a tarball, zip file, git repository, -# etc. at configure time (i.e. when the cmake command is run). How the downloaded -# and unpacked contents are used is up to the caller, but the motivating case is -# to download source code which can then be included directly in the build with -# add_subdirectory() after the call to download_project(). Source and build -# directories are set up with this in mind. -# -# The PROJ argument is required. The projectName value will be used to construct -# the following variables upon exit (obviously replace projectName with its actual -# value): -# -# projectName_SOURCE_DIR -# projectName_BINARY_DIR -# -# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically -# need to be provided. They can be specified if you want the downloaded source -# and build directories to be located in a specific place. The contents of -# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the -# locations used whether you provide SOURCE_DIR/BINARY_DIR or not. -# -# The DOWNLOAD_DIR argument does not normally need to be set. It controls the -# location of the temporary CMake build used to perform the download. -# -# The PREFIX argument can be provided to change the base location of the default -# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments -# are provided, then PREFIX will have no effect. The default value for PREFIX is -# CMAKE_BINARY_DIR. -# -# The QUIET option can be given if you do not want to show the output associated -# with downloading the specified project. -# -# In addition to the above, any other options are passed through unmodified to -# ExternalProject_Add() to perform the actual download, patch and update steps. -# The following ExternalProject_Add() options are explicitly prohibited (they -# are reserved for use by the download_project() command): -# -# CONFIGURE_COMMAND -# BUILD_COMMAND -# INSTALL_COMMAND -# TEST_COMMAND -# -# Only those ExternalProject_Add() arguments which relate to downloading, patching -# and updating of the project sources are intended to be used. Also note that at -# least one set of download-related arguments are required. -# -# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to -# prevent a check at the remote end for changes every time CMake is run -# after the first successful download. See the documentation of the ExternalProject -# module for more information. It is likely you will want to use this option if it -# is available to you. Note, however, that the ExternalProject implementation contains -# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when -# using the URL download method or when specifying a SOURCE_DIR with no download -# method. Fixes for these have been created, the last of which is scheduled for -# inclusion in CMake 3.8.0. Details can be found here: -# -# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c -# https://gitlab.kitware.com/cmake/cmake/issues/16428 -# -# If you experience build errors related to the update step, consider avoiding -# the use of UPDATE_DISCONNECTED. -# -# EXAMPLE USAGE: -# -# include(DownloadProject) -# download_project(PROJ googletest -# GIT_REPOSITORY https://github.com/google/googletest.git -# GIT_TAG master -# UPDATE_DISCONNECTED 1 -# QUIET -# ) -# -# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) -# -#======================================================================================== - - -set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") - -include(CMakeParseArguments) - -function(download_project) - - set(options QUIET) - set(oneValueArgs - PROJ - PREFIX - DOWNLOAD_DIR - SOURCE_DIR - BINARY_DIR - # Prevent the following from being passed through - CONFIGURE_COMMAND - BUILD_COMMAND - INSTALL_COMMAND - TEST_COMMAND - ) - set(multiValueArgs "") - - cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - # Hide output if requested - if (DL_ARGS_QUIET) - set(OUTPUT_QUIET "OUTPUT_QUIET") - else() - unset(OUTPUT_QUIET) - message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") - endif() - - # Set up where we will put our temporary CMakeLists.txt file and also - # the base point below which the default source and binary dirs will be. - # The prefix must always be an absolute path. - if (NOT DL_ARGS_PREFIX) - set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") - else() - get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE - BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") - endif() - if (NOT DL_ARGS_DOWNLOAD_DIR) - set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") - endif() - - # Ensure the caller can know where to find the source and build directories - if (NOT DL_ARGS_SOURCE_DIR) - set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") - endif() - if (NOT DL_ARGS_BINARY_DIR) - set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") - endif() - set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) - set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) - - # The way that CLion manages multiple configurations, it causes a copy of - # the CMakeCache.txt to be copied across due to it not expecting there to - # be a project within a project. This causes the hard-coded paths in the - # cache to be copied and builds to fail. To mitigate this, we simply - # remove the cache if it exists before we configure the new project. It - # is safe to do so because it will be re-generated. Since this is only - # executed at the configure step, it should not cause additional builds or - # downloads. - file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") - - # Create and build a separate CMake project to carry out the download. - # If we've already previously done these steps, they will not cause - # anything to be updated, so extra rebuilds of the project won't occur. - # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project - # has this set to something not findable on the PATH. - configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" - "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" - -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" - . - RESULT_VARIABLE result - ${OUTPUT_QUIET} - WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" - ) - if(result) - message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") - endif() - execute_process(COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - ${OUTPUT_QUIET} - WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" - ) - if(result) - message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") - endif() - -endfunction() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 773ea74..f8c7f84 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,21 +1,26 @@ # Copyright (c) 2019-2024 Morwenn # SPDX-License-Identifier: MIT -include(DownloadProject) +cmake_minimum_required(VERSION 3.24.0) -# Download and configure Catch2 for the tests -download_project(PROJ Catch2 - GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v3.5.2 - UPDATE_DISCONNECTED 1 -) -add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) -list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) +include(FetchContent) # Test suite options option(GFX_TIMSORT_USE_VALGRIND "Whether to run the tests with Valgrind" OFF) set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") +# Find/download Catch2 +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2 + GIT_TAG v3.5.0 + SYSTEM + FIND_PACKAGE_ARGS 3.1.0 +) +FetchContent_MakeAvailable(Catch2) +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +include(Catch) + # Configure Valgrind if (${GFX_TIMSORT_USE_VALGRIND}) find_program(MEMORYCHECK_COMMAND valgrind) From c7ea6656ad862a14672d32b17a38eaf9a4acd7d0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Jan 2024 14:22:13 +0100 Subject: [PATCH 113/137] CMake: do not generate seeds containing 0 Catch2 does not support RNG seeds that start with 0, the simplest solution is to avoid generating seeds that contain 0 altogether. --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f8c7f84..83ce62f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -112,7 +112,7 @@ endif() include(CTest) include(Catch) -string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) +string(RANDOM LENGTH 5 ALPHABET 123456789 RNG_SEED) catch_discover_tests(cxx_98_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) From 36606d66912bc919dd3500e89ef2e645d085ef44 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Jan 2024 14:29:03 +0100 Subject: [PATCH 114/137] CMake: do not compile test suite with -Winline The project exclusively uses inline for ODR compliance, therefore the warnings are mostly bogus as we never actually care whether functions are inline or not. --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 83ce62f..d99b51f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,7 +44,7 @@ macro(configure_tests target) # Add warnings if (NOT MSVC) target_compile_options(${target} PRIVATE - -Wall -Wextra -Wcast-align -Winline -Wmissing-declarations -Wmissing-include-dirs + -Wall -Wextra -Wcast-align -Wmissing-declarations -Wmissing-include-dirs -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code $<$:-Wlogical-op -Wuseless-cast> ) From 464a7bc38f826dee3b6740a8680b745095da5f5b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Jan 2024 18:48:16 +0100 Subject: [PATCH 115/137] CMake: use ARCH_INDEPENDENT for write_basic_package_version_file --- CMakeLists.txt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b2fccb..19c6fbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12.4) +cmake_minimum_required(VERSION 3.14.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -48,16 +48,11 @@ configure_package_config_file( ${CMAKE_INSTALL_LIBDIR}/cmake/gfx ) -# Bypass automatic architeture check introduced by CMake, -# use the ARCH_INDEPENDENT option for this in the future -set(GFX_TIMSORT_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) -unset(CMAKE_SIZEOF_VOID_P) write_basic_package_version_file( ${CMAKE_BINARY_DIR}/cmake/gfx-timsort-config-version.cmake - COMPATIBILITY - SameMajorVersion + COMPATIBILITY SameMajorVersion + ARCH_INDEPENDENT ) -set(CMAKE_SIZEOF_VOID_P ${GFX_TIMSORT_SIZEOF_VOID_P}) install( FILES From ebeabaebc467911d8f95e3fe0e5f856a97028b8e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Jan 2024 19:08:46 +0100 Subject: [PATCH 116/137] Temporary ranges support for timsort This notably allows timsort to accept an std::span. --- include/gfx/timsort.hpp | 2 +- tests/CMakeLists.txt | 8 ++++++++ tests/cxx_20_tests.cpp | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/cxx_20_tests.cpp diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 34f19ee..cb3c79b 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -731,7 +731,7 @@ template < typename Projection = std::identity > requires std::sortable, Compare, Projection> -void timsort(Range &range, Compare comp={}, Projection proj={}) { +void timsort(Range &&range, Compare comp={}, Projection proj={}) { gfx::timsort(std::begin(range), std::end(range), comp, proj); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d99b51f..e31d85d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -100,6 +100,13 @@ add_executable(cxx_17_tests configure_tests(cxx_17_tests) target_compile_features(cxx_17_tests PRIVATE cxx_std_17) +# Tests requiring C++20 support +add_executable(cxx_20_tests + cxx_20_tests.cpp +) +configure_tests(cxx_20_tests) +target_compile_features(cxx_20_tests PRIVATE cxx_std_20) + # Windows-specific tests if (WIN32) add_executable(windows_tests @@ -116,6 +123,7 @@ string(RANDOM LENGTH 5 ALPHABET 123456789 RNG_SEED) catch_discover_tests(cxx_98_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) +catch_discover_tests(cxx_20_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) if (WIN32) catch_discover_tests(windows_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) endif() diff --git a/tests/cxx_20_tests.cpp b/tests/cxx_20_tests.cpp new file mode 100644 index 0000000..c159726 --- /dev/null +++ b/tests/cxx_20_tests.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Morwenn. + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include "test_helpers.hpp" + +TEST_CASE( "support for temporary types" ) { + SECTION( "timsort over std::span" ) { + std::vector vec(50); + std::iota(vec.begin(), vec.end(), -25); + test_helpers::shuffle(vec); + + gfx::timsort(std::span(vec)); + CHECK(std::ranges::is_sorted(vec)); + } +} From a141e03953bd4000b02e5ed30cd5d82847eaa99a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Jan 2024 20:06:48 +0100 Subject: [PATCH 117/137] CMake: use CMAKE_CXX_COMPILER_FRONTEND_VARIANT to control warnings --- tests/CMakeLists.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e31d85d..867c2ad 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,7 +42,7 @@ macro(configure_tests target) ) # Add warnings - if (NOT MSVC) + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU") target_compile_options(${target} PRIVATE -Wall -Wextra -Wcast-align -Wmissing-declarations -Wmissing-include-dirs -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code @@ -51,10 +51,12 @@ macro(configure_tests target) endif() # Configure optimization options - target_compile_options(${target} PRIVATE - $<$,$>:-O0> - $<$,$>:-Og> - ) + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU") + target_compile_options(${target} PRIVATE + $<$,$>:-O0> + $<$,$>:-Og> + ) + endif() # Use lld or the gold linker if possible if (UNIX AND NOT APPLE) From d3ab5e191b8278e70104feacedfe96a5ae26dec7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 18 Jan 2024 00:44:56 +0100 Subject: [PATCH 118/137] Add range + iterator overload for timmerge Range overload supports temporaries. Also change a missed std::less<> into std::ranges::less. --- README.md | 9 +++++++++ include/gfx/timsort.hpp | 18 +++++++++++++++++- tests/cxx_20_tests.cpp | 14 ++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d0f26b..843014e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,15 @@ template < requires std::sortable void timmerge(Iterator first, Iterator middle, Iterator last, Compare compare={}, Projection projection={}); + +template < + std::ranges::random_access_range Range, + typename Compare = std::ranges::less, + typename Projection = std::identity +> + requires std::sortable, Compare, Projection> +void timmerge(Range &&range, std::ranges::iterator_t middle, + Compare compare={}, Projection projection={}) ``` ## EXAMPLE diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index cb3c79b..eb421d2 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -695,7 +695,7 @@ template */ template < std::random_access_iterator Iterator, - typename Compare = std::less<>, + typename Compare = std::ranges::less, typename Projection = std::identity > requires std::sortable @@ -707,6 +707,22 @@ void timmerge(Iterator first, Iterator middle, Iterator last, GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp, proj) && "Postcondition"); } +/** + * Stably merges two sorted halves [first, middle) and [middle, last) of a range into one + * sorted range [first, last) with a comparison function and a projection function. + */ +template < + std::ranges::random_access_range Range, + typename Compare = std::ranges::less, + typename Projection = std::identity +> + requires std::sortable, Compare, Projection> +void timmerge(Range &&range, std::ranges::iterator_t middle, + Compare comp={}, Projection proj={}) +{ + gfx::timmerge(std::begin(range), middle, std::end(range), comp, proj); +} + /** * Stably sorts a range with a comparison function and a projection function. */ diff --git a/tests/cxx_20_tests.cpp b/tests/cxx_20_tests.cpp index c159726..d9642e4 100644 --- a/tests/cxx_20_tests.cpp +++ b/tests/cxx_20_tests.cpp @@ -12,6 +12,20 @@ #include "test_helpers.hpp" TEST_CASE( "support for temporary types" ) { + SECTION( "timsmerge over std::span" ) { + std::vector vec(100); + std::iota(vec.begin(), vec.end(), -25); + test_helpers::shuffle(vec); + + auto middle = vec.begin() + 38; + gfx::timsort(vec.begin(), middle); + gfx::timsort(middle, vec.end()); + + auto view = std::span(vec); + gfx::timmerge(std::span(vec), view.begin() + 38); + CHECK(std::ranges::is_sorted(vec)); + } + SECTION( "timsort over std::span" ) { std::vector vec(50); std::iota(vec.begin(), vec.end(), -25); From 2d48ebffa59aec85fa25eaa73de987a2f5000b83 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 18 Jan 2024 22:40:22 +0100 Subject: [PATCH 119/137] Sentinel support for timsort and timmerge --- README.md | 6 ++++-- include/gfx/timsort.hpp | 18 +++++++++++------- tests/cxx_20_tests.cpp | 31 ++++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 843014e..e96bee2 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,12 @@ The list of available signatures is as follows (in namespace `gfx`): template < std::random_access_iterator Iterator, + std::sentinel_for Sentinel, typename Compare = std::ranges::less, typename Projection = std::identity > requires std::sortable -void timsort(Iterator first, Iterator last, +void timsort(Iterator first, Sentinel last, Compare compare={}, Projection projection={}); template < @@ -49,11 +50,12 @@ void timsort(Range &range, Compare compare={}, Projection projection={}); template < std::random_access_iterator Iterator, + std::sentinel_for Sentinel, typename Compare = std::ranges::less, typename Projection = std::identity > requires std::sortable -void timmerge(Iterator first, Iterator middle, Iterator last, +void timmerge(Iterator first, Iterator middle, Sentinel last, Compare compare={}, Projection projection={}); template < diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index eb421d2..7f79c3a 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -695,16 +695,18 @@ template */ template < std::random_access_iterator Iterator, + std::sentinel_for Sentinel, typename Compare = std::ranges::less, typename Projection = std::identity > requires std::sortable -void timmerge(Iterator first, Iterator middle, Iterator last, +void timmerge(Iterator first, Iterator middle, Sentinel last, Compare comp={}, Projection proj={}) { + auto last_it = std::ranges::next(first, last); GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp, proj) && "Precondition"); - GFX_TIMSORT_AUDIT(std::is_sorted(middle, last, comp, proj) && "Precondition"); - detail::TimSort::merge(first, middle, last, comp, proj); - GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp, proj) && "Postcondition"); + GFX_TIMSORT_AUDIT(std::is_sorted(middle, last_it, comp, proj) && "Precondition"); + detail::TimSort::merge(first, middle, last_it, comp, proj); + GFX_TIMSORT_AUDIT(std::is_sorted(first, last_it, comp, proj) && "Postcondition"); } /** @@ -728,14 +730,16 @@ void timmerge(Range &&range, std::ranges::iterator_t middle, */ template < std::random_access_iterator Iterator, + std::sentinel_for Sentinel, typename Compare = std::ranges::less, typename Projection = std::identity > requires std::sortable -void timsort(Iterator first, Iterator last, +void timsort(Iterator first, Sentinel last, Compare comp={}, Projection proj={}) { - detail::TimSort::sort(first, last, comp, proj); - GFX_TIMSORT_AUDIT(std::is_sorted(first, last, comp, proj) && "Postcondition"); + auto last_it = std::ranges::next(first, last); + detail::TimSort::sort(first, last_it, comp, proj); + GFX_TIMSORT_AUDIT(std::is_sorted(first, last_it, comp, proj) && "Postcondition"); } /** diff --git a/tests/cxx_20_tests.cpp b/tests/cxx_20_tests.cpp index d9642e4..d5dde39 100644 --- a/tests/cxx_20_tests.cpp +++ b/tests/cxx_20_tests.cpp @@ -3,6 +3,7 @@ * SPDX-License-Identifier: MIT */ #include +#include #include #include #include @@ -12,7 +13,7 @@ #include "test_helpers.hpp" TEST_CASE( "support for temporary types" ) { - SECTION( "timsmerge over std::span" ) { + SECTION( "timmerge over std::span" ) { std::vector vec(100); std::iota(vec.begin(), vec.end(), -25); test_helpers::shuffle(vec); @@ -35,3 +36,31 @@ TEST_CASE( "support for temporary types" ) { CHECK(std::ranges::is_sorted(vec)); } } + +TEST_CASE( "support for sentinels" ) +{ + SECTION( "timmerge with sentinel" ) { + std::vector vec(100); + std::iota(vec.begin(), vec.end(), -25); + test_helpers::shuffle(vec); + + auto middle = vec.begin() + 38; + gfx::timsort(vec.begin(), middle); + gfx::timsort(middle, vec.end()); + + gfx::timmerge(std::counted_iterator(vec.begin(), 85), + std::counted_iterator(middle, 85 - 38), + std::default_sentinel); + CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); + } + + SECTION( "timsort with sentinel" ) { + std::vector vec(100); + std::iota(vec.begin(), vec.end(), -25); + test_helpers::shuffle(vec); + + gfx::timsort(std::counted_iterator(vec.begin(), 85), + std::default_sentinel); + CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); + } +} From a23c08ca867e1a47bb0aa8ca2a0e1771e45cc73b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 20 Jan 2024 23:39:42 +0100 Subject: [PATCH 120/137] Let timmerge and timsort return the end iterator --- README.md | 16 ++++++++++------ include/gfx/timsort.hpp | 25 +++++++++++++++++-------- tests/cxx_20_tests.cpp | 35 ++++++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e96bee2..5853b81 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,9 @@ template < typename Projection = std::identity > requires std::sortable -void timsort(Iterator first, Sentinel last, - Compare compare={}, Projection projection={}); +auto timsort(Iterator first, Sentinel last, + Compare compare={}, Projection projection={}) + -> Iterator; template < std::ranges::random_access_range Range, @@ -44,7 +45,8 @@ template < typename Projection = std::identity > requires std::sortable, Compare, Projection> -void timsort(Range &range, Compare compare={}, Projection projection={}); +auto timsort(Range &range, Compare compare={}, Projection projection={}) + -> std::ranges::borrowed_iterator_t; // timmerge @@ -55,8 +57,9 @@ template < typename Projection = std::identity > requires std::sortable -void timmerge(Iterator first, Iterator middle, Sentinel last, - Compare compare={}, Projection projection={}); +auto timmerge(Iterator first, Iterator middle, Sentinel last, + Compare compare={}, Projection projection={}) + -> Iterator; template < std::ranges::random_access_range Range, @@ -64,8 +67,9 @@ template < typename Projection = std::identity > requires std::sortable, Compare, Projection> -void timmerge(Range &&range, std::ranges::iterator_t middle, +auto timmerge(Range &&range, std::ranges::iterator_t middle, Compare compare={}, Projection projection={}) + -> std::ranges::borrowed_iterator_t; ``` ## EXAMPLE diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 7f79c3a..89062f6 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -700,13 +700,16 @@ template < typename Projection = std::identity > requires std::sortable -void timmerge(Iterator first, Iterator middle, Sentinel last, - Compare comp={}, Projection proj={}) { +auto timmerge(Iterator first, Iterator middle, Sentinel last, + Compare comp={}, Projection proj={}) + -> Iterator +{ auto last_it = std::ranges::next(first, last); GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp, proj) && "Precondition"); GFX_TIMSORT_AUDIT(std::is_sorted(middle, last_it, comp, proj) && "Precondition"); detail::TimSort::merge(first, middle, last_it, comp, proj); GFX_TIMSORT_AUDIT(std::is_sorted(first, last_it, comp, proj) && "Postcondition"); + return last_it; } /** @@ -719,10 +722,11 @@ template < typename Projection = std::identity > requires std::sortable, Compare, Projection> -void timmerge(Range &&range, std::ranges::iterator_t middle, +auto timmerge(Range &&range, std::ranges::iterator_t middle, Compare comp={}, Projection proj={}) + -> std::ranges::borrowed_iterator_t { - gfx::timmerge(std::begin(range), middle, std::end(range), comp, proj); + return gfx::timmerge(std::begin(range), middle, std::end(range), comp, proj); } /** @@ -735,11 +739,14 @@ template < typename Projection = std::identity > requires std::sortable -void timsort(Iterator first, Sentinel last, - Compare comp={}, Projection proj={}) { +auto timsort(Iterator first, Sentinel last, + Compare comp={}, Projection proj={}) + -> Iterator +{ auto last_it = std::ranges::next(first, last); detail::TimSort::sort(first, last_it, comp, proj); GFX_TIMSORT_AUDIT(std::is_sorted(first, last_it, comp, proj) && "Postcondition"); + return last_it; } /** @@ -751,8 +758,10 @@ template < typename Projection = std::identity > requires std::sortable, Compare, Projection> -void timsort(Range &&range, Compare comp={}, Projection proj={}) { - gfx::timsort(std::begin(range), std::end(range), comp, proj); +auto timsort(Range &&range, Compare comp={}, Projection proj={}) + -> std::ranges::borrowed_iterator_t +{ + return gfx::timsort(std::begin(range), std::end(range), comp, proj); } } // namespace gfx diff --git a/tests/cxx_20_tests.cpp b/tests/cxx_20_tests.cpp index d5dde39..40ecd91 100644 --- a/tests/cxx_20_tests.cpp +++ b/tests/cxx_20_tests.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include "test_helpers.hpp" @@ -23,8 +24,9 @@ TEST_CASE( "support for temporary types" ) { gfx::timsort(middle, vec.end()); auto view = std::span(vec); - gfx::timmerge(std::span(vec), view.begin() + 38); + auto last_it = gfx::timmerge(std::span(vec), view.begin() + 38); CHECK(std::ranges::is_sorted(vec)); + CHECK(last_it == view.end()); } SECTION( "timsort over std::span" ) { @@ -32,8 +34,23 @@ TEST_CASE( "support for temporary types" ) { std::iota(vec.begin(), vec.end(), -25); test_helpers::shuffle(vec); - gfx::timsort(std::span(vec)); + auto last_it = gfx::timsort(std::span(vec)); CHECK(std::ranges::is_sorted(vec)); + CHECK(last_it == std::span(vec).end()); + } +} + +TEST_CASE( "dangling return type" ) { + SECTION( "timmerge over temporary std::vector" ) { + std::vector vec(30, 5); + auto last_it = gfx::timmerge(std::move(vec), vec.begin() + 14); + STATIC_CHECK(std::is_same_v); + } + + SECTION( "timsort over temporary std::vector" ) { + std::vector vec(50, 8); + auto last_it = gfx::timsort(std::move(vec)); + STATIC_CHECK(std::is_same_v); } } @@ -48,10 +65,12 @@ TEST_CASE( "support for sentinels" ) gfx::timsort(vec.begin(), middle); gfx::timsort(middle, vec.end()); - gfx::timmerge(std::counted_iterator(vec.begin(), 85), - std::counted_iterator(middle, 85 - 38), - std::default_sentinel); + auto last_it = gfx::timmerge(std::counted_iterator(vec.begin(), 85), + std::counted_iterator(middle, 85 - 38), + std::default_sentinel); CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); + CHECK(last_it == std::counted_iterator(vec.begin() + 85, 0)); + CHECK(last_it == std::default_sentinel); } SECTION( "timsort with sentinel" ) { @@ -59,8 +78,10 @@ TEST_CASE( "support for sentinels" ) std::iota(vec.begin(), vec.end(), -25); test_helpers::shuffle(vec); - gfx::timsort(std::counted_iterator(vec.begin(), 85), - std::default_sentinel); + auto last_it = gfx::timsort(std::counted_iterator(vec.begin(), 85), + std::default_sentinel); CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); + CHECK(last_it == std::counted_iterator(vec.begin() + 85, 0)); + CHECK(last_it == std::default_sentinel); } } From e108d6f2f537d3408257aaa15145413736ac045e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 21 Jan 2024 01:05:36 +0100 Subject: [PATCH 121/137] Cosmetic changes --- .clang-format | 9 +++++++-- include/gfx/timsort.hpp | 32 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.clang-format b/.clang-format index 94c4b7c..94cfc10 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,11 @@ # http://clang.llvm.org/docs/ClangFormatStyleOptions.html BasedOnStyle: LLVM -ColumnLimit: 120 +ColumnLimit: 100 IndentWidth: 4 +IndentPPDirectives: AfterHash +IndentRequiresClause: true +PointerAlignment: Left +QualifierAlignment: Right +ReferenceAlignment: Left AllowShortFunctionsOnASingleLine: false -Standard: Cpp03 +Standard: Cpp20 diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 89062f6..4a09ab5 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -78,8 +78,9 @@ namespace gfx { namespace detail { -template struct run { - typedef typename std::iterator_traits::difference_type diff_t; +template +struct run { + using diff_t = typename std::iterator_traits::difference_type; Iterator base; diff_t len; @@ -88,22 +89,24 @@ template struct run { } }; -template class TimSort { - typedef RandomAccessIterator iter_t; - typedef typename std::iterator_traits::value_type value_t; - typedef typename std::iterator_traits::difference_type diff_t; +template +class TimSort { + using iter_t = RandomAccessIterator; + using value_t = typename std::iterator_traits::value_type; + using diff_t = typename std::iterator_traits::difference_type; - static const int MIN_MERGE = 32; - static const int MIN_GALLOP = 7; + static constexpr int MIN_MERGE = 32; + static constexpr int MIN_GALLOP = 7; int minGallop_; // default to MIN_GALLOP std::vector tmp_; // temp storage for merges - typedef typename std::vector::iterator tmp_iter_t; + using tmp_iter_t = typename std::vector::iterator; std::vector > pending_; - static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare comp, Projection proj) { + static void binarySort(iter_t const lo, iter_t const hi, iter_t start, + Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= start); GFX_TIMSORT_ASSERT(start <= hi); if (start == lo) { @@ -113,7 +116,7 @@ template GFX_TIMSORT_ASSERT(lo <= start); value_t pivot = std::move(*start); - iter_t const pos = std::ranges::upper_bound(lo, start, std::invoke(proj, pivot), comp, proj); + auto pos = std::ranges::upper_bound(lo, start, std::invoke(proj, pivot), comp, proj); for (iter_t p = start; p > pos; --p) { *p = std::move(*(p - 1)); } @@ -351,22 +354,19 @@ template return std::ranges::upper_bound(base + (lastOfs + 1), base + ofs, key, comp, proj) - base; } - static void rotateLeft(iter_t first, iter_t last) - { + static void rotateLeft(iter_t first, iter_t last) { value_t tmp = std::move(*first); iter_t last_1 = std::move(first + 1, last, first); *last_1 = std::move(tmp); } - static void rotateRight(iter_t first, iter_t last) - { + static void rotateRight(iter_t first, iter_t last) { iter_t last_1 = last - 1; value_t tmp = std::move(*last_1); std::move_backward(first, last_1, last); *first = std::move(tmp); } - void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len1 > 0); From f1268e6d1aef4de2f07a3cafe3c4b81707aa5405 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 21 Jan 2024 17:32:59 +0100 Subject: [PATCH 122/137] Support for proxy iterators --- include/gfx/timsort.hpp | 56 ++++++++++++++++++++-------------------- tests/cxx_20_tests.cpp | 57 ++++++++++++++++++++--------------------- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 4a09ab5..cc8bbc1 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -114,11 +114,11 @@ class TimSort { } for (; start < hi; ++start) { GFX_TIMSORT_ASSERT(lo <= start); - value_t pivot = std::move(*start); + auto pivot = std::ranges::iter_move(start); auto pos = std::ranges::upper_bound(lo, start, std::invoke(proj, pivot), comp, proj); for (iter_t p = start; p > pos; --p) { - *p = std::move(*(p - 1)); + *p = std::ranges::iter_move(p - 1); } *pos = std::move(pivot); } @@ -139,7 +139,7 @@ class TimSort { } while (runHi < hi && std::invoke(comp, std::invoke(proj, *runHi), std::invoke(proj, *(runHi - 1)))); - std::reverse(lo, runHi); + std::ranges::reverse(lo, runHi); } else { // non-decreasing do { ++runHi; @@ -355,15 +355,15 @@ class TimSort { } static void rotateLeft(iter_t first, iter_t last) { - value_t tmp = std::move(*first); - iter_t last_1 = std::move(first + 1, last, first); + auto tmp = std::ranges::iter_move(first); + auto [_, last_1] = std::ranges::move(first + 1, last, first); *last_1 = std::move(tmp); } static void rotateRight(iter_t first, iter_t last) { - iter_t last_1 = last - 1; - value_t tmp = std::move(*last_1); - std::move_backward(first, last_1, last); + auto last_1 = last - 1; + auto tmp = std::ranges::iter_move(last_1); + std::ranges::move_backward(first, last_1, last); *first = std::move(tmp); } @@ -386,7 +386,7 @@ class TimSort { iter_t cursor2 = base2; iter_t dest = base1; - *dest = std::move(*cursor2); + *dest = std::ranges::iter_move(cursor2); ++cursor2; ++dest; --len2; @@ -403,7 +403,7 @@ class TimSort { GFX_TIMSORT_ASSERT(len2 > 0); if (std::invoke(comp, std::invoke(proj, *cursor2), std::invoke(proj, *cursor1))) { - *dest = std::move(*cursor2); + *dest = std::ranges::iter_move(cursor2); ++cursor2; ++dest; ++count2; @@ -412,7 +412,7 @@ class TimSort { goto epilogue; } } else { - *dest = std::move(*cursor1); + *dest = std::ranges::iter_move(cursor1); ++cursor1; ++dest; ++count1; @@ -429,7 +429,7 @@ class TimSort { count1 = gallopRight(std::invoke(proj, *cursor2), cursor1, len1, 0, comp, proj); if (count1 != 0) { - std::move_backward(cursor1, cursor1 + count1, dest + count1); + std::ranges::move_backward(cursor1, cursor1 + count1, dest + count1); dest += count1; cursor1 += count1; len1 -= count1; @@ -438,7 +438,7 @@ class TimSort { goto epilogue; } } - *dest = std::move(*cursor2); + *dest = std::ranges::iter_move(cursor2); ++cursor2; ++dest; if (--len2 == 0) { @@ -447,7 +447,7 @@ class TimSort { count2 = gallopLeft(std::invoke(proj, *cursor1), cursor2, len2, 0, comp, proj); if (count2 != 0) { - std::move(cursor2, cursor2 + count2, dest); + std::ranges::move(cursor2, cursor2 + count2, dest); dest += count2; cursor2 += count2; len2 -= count2; @@ -455,7 +455,7 @@ class TimSort { goto epilogue; } } - *dest = std::move(*cursor1); + *dest = std::ranges::iter_move(cursor1); ++cursor1; ++dest; if (--len1 == 1) { @@ -477,13 +477,13 @@ class TimSort { if (len1 == 1) { GFX_TIMSORT_ASSERT(len2 > 0); - std::move(cursor2, cursor2 + len2, dest); - *(dest + len2) = std::move(*cursor1); + std::ranges::move(cursor2, cursor2 + len2, dest); + *(dest + len2) = std::ranges::iter_move(cursor1); } else { GFX_TIMSORT_ASSERT(len1 != 0 && "Comparison function violates its general contract"); GFX_TIMSORT_ASSERT(len2 == 0); GFX_TIMSORT_ASSERT(len1 > 1); - std::move(cursor1, cursor1 + len1, dest); + std::ranges::move(cursor1, cursor1 + len1, dest); } } @@ -506,7 +506,7 @@ class TimSort { tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); iter_t dest = base2 + (len2 - 1); - *dest = std::move(*(--cursor1)); + *dest = std::ranges::iter_move(--cursor1); --dest; --len1; @@ -528,7 +528,7 @@ class TimSort { GFX_TIMSORT_ASSERT(len2 > 1); if (std::invoke(comp, std::invoke(proj, *cursor2), std::invoke(proj, *cursor1))) { - *dest = std::move(*cursor1); + *dest = std::ranges::iter_move(cursor1); --dest; ++count1; count2 = 0; @@ -537,7 +537,7 @@ class TimSort { } --cursor1; } else { - *dest = std::move(*cursor2); + *dest = std::ranges::iter_move(cursor2); --cursor2; --dest; ++count2; @@ -560,13 +560,13 @@ class TimSort { dest -= count1; cursor1 -= count1; len1 -= count1; - std::move_backward(cursor1, cursor1 + count1, dest + (1 + count1)); + std::ranges::move_backward(cursor1, cursor1 + count1, dest + (1 + count1)); if (len1 == 0) { goto epilogue; } } - *dest = std::move(*cursor2); + *dest = std::ranges::iter_move(cursor2); --cursor2; --dest; if (--len2 == 1) { @@ -579,12 +579,12 @@ class TimSort { dest -= count2; cursor2 -= count2; len2 -= count2; - std::move(cursor2 + 1, cursor2 + (1 + count2), dest + 1); + std::ranges::move(cursor2 + 1, cursor2 + (1 + count2), dest + 1); if (len2 <= 1) { goto epilogue; } } - *dest = std::move(*(--cursor1)); + *dest = std::ranges::iter_move(--cursor1); --dest; if (--len1 == 0) { goto epilogue; @@ -606,13 +606,13 @@ class TimSort { if (len2 == 1) { GFX_TIMSORT_ASSERT(len1 > 0); dest -= len1; - std::move_backward(cursor1 - len1, cursor1, dest + (1 + len1)); - *dest = std::move(*cursor2); + std::ranges::move_backward(cursor1 - len1, cursor1, dest + (1 + len1)); + *dest = std::ranges::iter_move(cursor2); } else { GFX_TIMSORT_ASSERT(len2 != 0 && "Comparison function violates its general contract"); GFX_TIMSORT_ASSERT(len1 == 0); GFX_TIMSORT_ASSERT(len2 > 1); - std::move(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); + std::ranges::move(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); } } diff --git a/tests/cxx_20_tests.cpp b/tests/cxx_20_tests.cpp index 40ecd91..c7dea2a 100644 --- a/tests/cxx_20_tests.cpp +++ b/tests/cxx_20_tests.cpp @@ -14,6 +14,16 @@ #include "test_helpers.hpp" TEST_CASE( "support for temporary types" ) { + SECTION( "timsort over std::span" ) { + std::vector vec(50); + std::iota(vec.begin(), vec.end(), -25); + test_helpers::shuffle(vec); + + auto last_it = gfx::timsort(std::span(vec)); + CHECK(std::ranges::is_sorted(vec)); + CHECK(last_it == std::span(vec).end()); + } + SECTION( "timmerge over std::span" ) { std::vector vec(100); std::iota(vec.begin(), vec.end(), -25); @@ -28,58 +38,47 @@ TEST_CASE( "support for temporary types" ) { CHECK(std::ranges::is_sorted(vec)); CHECK(last_it == view.end()); } - - SECTION( "timsort over std::span" ) { - std::vector vec(50); - std::iota(vec.begin(), vec.end(), -25); - test_helpers::shuffle(vec); - - auto last_it = gfx::timsort(std::span(vec)); - CHECK(std::ranges::is_sorted(vec)); - CHECK(last_it == std::span(vec).end()); - } } TEST_CASE( "dangling return type" ) { - SECTION( "timmerge over temporary std::vector" ) { - std::vector vec(30, 5); - auto last_it = gfx::timmerge(std::move(vec), vec.begin() + 14); - STATIC_CHECK(std::is_same_v); - } - SECTION( "timsort over temporary std::vector" ) { std::vector vec(50, 8); auto last_it = gfx::timsort(std::move(vec)); STATIC_CHECK(std::is_same_v); } + + SECTION( "timmerge over temporary std::vector" ) { + std::vector vec(30, 5); + auto last_it = gfx::timmerge(std::move(vec), vec.begin() + 14); + STATIC_CHECK(std::is_same_v); + } } -TEST_CASE( "support for sentinels" ) -{ - SECTION( "timmerge with sentinel" ) { +TEST_CASE( "support for sentinels" ) { + SECTION( "timsort with sentinel" ) { std::vector vec(100); std::iota(vec.begin(), vec.end(), -25); test_helpers::shuffle(vec); - auto middle = vec.begin() + 38; - gfx::timsort(vec.begin(), middle); - gfx::timsort(middle, vec.end()); - - auto last_it = gfx::timmerge(std::counted_iterator(vec.begin(), 85), - std::counted_iterator(middle, 85 - 38), - std::default_sentinel); + auto last_it = gfx::timsort(std::counted_iterator(vec.begin(), 85), + std::default_sentinel); CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); CHECK(last_it == std::counted_iterator(vec.begin() + 85, 0)); CHECK(last_it == std::default_sentinel); } - SECTION( "timsort with sentinel" ) { + SECTION( "timmerge with sentinel" ) { std::vector vec(100); std::iota(vec.begin(), vec.end(), -25); test_helpers::shuffle(vec); - auto last_it = gfx::timsort(std::counted_iterator(vec.begin(), 85), - std::default_sentinel); + auto middle = vec.begin() + 38; + gfx::timsort(vec.begin(), middle); + gfx::timsort(middle, vec.end()); + + auto last_it = gfx::timmerge(std::counted_iterator(vec.begin(), 85), + std::counted_iterator(middle, 85 - 38), + std::default_sentinel); CHECK(std::is_sorted(vec.begin(), vec.begin() + 85)); CHECK(last_it == std::counted_iterator(vec.begin() + 85, 0)); CHECK(last_it == std::default_sentinel); From d6cf373821d818d3a91426de2a3a1b8c8e1a4e33 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 21 Jan 2024 17:58:38 +0100 Subject: [PATCH 123/137] Fix audits --- include/gfx/timsort.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index cc8bbc1..e351397 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -705,10 +705,10 @@ auto timmerge(Iterator first, Iterator middle, Sentinel last, -> Iterator { auto last_it = std::ranges::next(first, last); - GFX_TIMSORT_AUDIT(std::is_sorted(first, middle, comp, proj) && "Precondition"); - GFX_TIMSORT_AUDIT(std::is_sorted(middle, last_it, comp, proj) && "Precondition"); + GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, middle, comp, proj) && "Precondition"); + GFX_TIMSORT_AUDIT(std::ranges::is_sorted(middle, last_it, comp, proj) && "Precondition"); detail::TimSort::merge(first, middle, last_it, comp, proj); - GFX_TIMSORT_AUDIT(std::is_sorted(first, last_it, comp, proj) && "Postcondition"); + GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, last_it, comp, proj) && "Postcondition"); return last_it; } @@ -745,7 +745,7 @@ auto timsort(Iterator first, Sentinel last, { auto last_it = std::ranges::next(first, last); detail::TimSort::sort(first, last_it, comp, proj); - GFX_TIMSORT_AUDIT(std::is_sorted(first, last_it, comp, proj) && "Postcondition"); + GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, last_it, comp, proj) && "Postcondition"); return last_it; } From 0dedf8faee19d78b347cb5ef9f5c870062c3e06d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 21 Jan 2024 18:17:49 +0100 Subject: [PATCH 124/137] Rename copy_to_tmp to move_to_tmp --- include/gfx/timsort.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index e351397..3949464 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -380,7 +380,7 @@ class TimSort { return rotateRight(base1, base2 + len2); } - copy_to_tmp(base1, len1); + move_to_tmp(base1, len1); tmp_iter_t cursor1 = tmp_.begin(); iter_t cursor2 = base2; @@ -500,7 +500,7 @@ class TimSort { return rotateRight(base1, base2 + len2); } - copy_to_tmp(base2, len2); + move_to_tmp(base2, len2); iter_t cursor1 = base1 + len1; tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); @@ -616,7 +616,7 @@ class TimSort { } } - void copy_to_tmp(iter_t const begin, diff_t len) { + void move_to_tmp(iter_t const begin, diff_t len) { tmp_.assign(std::make_move_iterator(begin), std::make_move_iterator(begin + len)); } From 5756f2a77cee232bd16c06d313f9bcd732dcfa9c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 22 Jan 2024 00:36:51 +0100 Subject: [PATCH 125/137] Update README, prepare release 3.0.0 --- .github/workflows/build-macos.yml | 2 +- README.md | 81 +++++++++++++++++-------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 8e4f900..ba223d0 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -28,7 +28,7 @@ jobs: matrix: cxx: - g++-10 - - $(brew --prefix llvm)/bin/clang++ # Clang 13 + - $(brew --prefix llvm)/bin/clang++ # Clang 17 config: # Release build - build_type: Release diff --git a/README.md b/README.md index 5853b81..49090ce 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-2.1.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v2.1.0) -[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F2.1.0-blue.svg)](https://conan.io/center/timsort?version=2.1.0) +[![Latest Release](https://img.shields.io/badge/release-3.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v3.0.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F3.0.0-blue.svg)](https://conan.io/center/timsort?version=3.0.0) [![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork) ## TimSort @@ -10,20 +10,26 @@ See also the following links for a detailed description of TimSort: * http://svn.python.org/projects/python/trunk/Objects/listsort.txt * http://en.wikipedia.org/wiki/Timsort -This library requires at least C++11. If you need a C++98 version, you can check the 1.x.y branch of this repository. - -According to the benchmarks, `gfx::timsort` is slower than [`std::sort()`][std-sort] on randomized sequences, but -faster on partially-sorted ones. It can be used as a drop-in replacement for [`std::stable_sort`][std-stable-sort], -with the difference that it can't fallback to a O(n log² n) algorithm when there isn't enough extra heap memory +This version of the library requires at least C++20. Older versions of the library, available in different branches, +offer support for older standards though implement fewer features: +* Branch `2.x.y` is compatible with C++11, and is slightly more permissive in some reagard due to the lack of + concepts to constrain its interface (it notaby supports iterators without postfix operator++/--). +* Branch `1.x.y` is compatible with C++03. +Older versions are not actively maintained anymore. If you need extended support for those, please open specific +issues for the problems you want solved. + +According to the benchmarks, `gfx::timsort` is slower than [`std::ranges::sort`][std-sort] on randomized sequences, +but faster on partially-sorted ones. It can be used as a drop-in replacement for [`std::ranges::stable_sort`][std-stable-sort], +with the difference that it can't fall back to a O(n log² n) algorithm when there isn't enough extra heap memory available. Merging sorted ranges efficiently is an important part of the TimSort algorithm. This library exposes `gfx::timmerge` -in the public API, a drop-in replacement for [`std::inplace_merge`][std-inplace-merge] with the difference that it -can't fallback to a O(n log n) algorithm when there isn't enough extra heap memory available. According to the -benchmarks, `gfx::timmerge` is slower than `std::inplace_merge` on heavily/randomly overlapping subranges of simple -elements, but it is faster for complex elements such as `std::string` and on sparsely overlapping subranges. +in the public API, a drop-in replacement for [`std::ranges::inplace_merge`][std-inplace-merge] with the difference +that it can't fall back to a O(n log n) algorithm when there isn't enough extra heap memory available. According to +the benchmarks, `gfx::timmerge` is slower than `std::ranges::inplace_merge` on heavily/randomly overlapping subranges +of simple elements, but it is faster for complex elements such as `std::string`, and on sparsely overlapping subranges. -The list of available signatures is as follows (in namespace `gfx`): +The ibrary exposes the following functions in namespace `gfx`: ```cpp // timsort @@ -74,7 +80,8 @@ auto timmerge(Range &&range, std::ranges::iterator_t middle, ## EXAMPLE -Example of using timsort with a comparison function and a projection function to sort a vector of strings by length: +Example of using timsort with a defaulted comparison function and a projection function to sort a vector of strings +by length: ```cpp #include @@ -92,31 +99,28 @@ gfx::timsort(collection, {}, &len); ## INSTALLATION & COMPATIBILITY -![Ubuntu builds status](https://github.com/timsort/cpp-TimSort/workflows/Ubuntu%20Builds/badge.svg?branch=master) -![Windows builds status](https://github.com/timsort/cpp-TimSort/workflows/Windows%20Builds/badge.svg?branch=master) -![MacOS builds status](https://github.com/timsort/cpp-TimSort/workflows/MacOS%20Builds/badge.svg?branch=master) - -The library has been tested with the following compilers: -* GCC 5.5 -* Clang 6 -* MSVC 2017 +[![Ubuntu Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-ubuntu.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-ubuntu.yml) +[![MSVC Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-msvc.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-msvc.yml) +[![MinGW-w64 Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-mingw.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-mingw.yml) +[![MacOS Builds](https://github.com/timsort/cpp-TimSort/actions/workflows/build-macos.yml/badge.svg?branch=3.x.y)](https://github.com/timsort/cpp-TimSort/actions/workflows/build-macos.yml) -It should also work with more recent compilers, and most likely with some older compilers too. We used to guarantee -support as far back as Clang 3.8, but the new continuous integration environment doesn't go that far back. +The library is tested with the following compilers: +* Ubuntu: GCC 10, Clang 11 +* Windows: MSVC 19.37.32826.1, MinGW-w64 GCC 12 +* MacOS: GCC 10, Clang 17 -The library can be installed on the system via CMake with the following commands: +The library can be installed on the system via [CMake][cmake] (at least 3.14) with the following commands: ```sh -cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -cd build -make install +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --install build ``` -Alternatively the library is also available on conan-center-index and can be installed in your local Conan cache via -the following command: +Alternatively the library is also available [Conan Center][conan-center] and can be directly installed in your local +[Conan][conan] cache with the following command: ```sh -conan install timsort/2.1.0 +conan install --requires=timsort/3.0.0 ``` ## DIAGNOSTICS & INFORMATION @@ -142,16 +146,16 @@ GFX_TIMSORT_VERSION_PATCH The tests are written with Catch2 and can be compiled with CMake and run through CTest. -When using the project's main `CMakeLists.txt`, the CMake variable `BUILD_TESTING` is `ON` by default unless the -project is included as a subdirectory. The following CMake variables are available to change the way the tests are +When using the project's main `CMakeLists.txt`, the CMake option `BUILD_TESTING` is `ON` by default unless the +project is included as a subdirectory. The following CMake options are available to change the way the tests are built with CMake: * `GFX_TIMSORT_USE_VALGRIND`: if `ON`, the tests will be run through Valgrind (`OFF` by default) * `GFX_TIMSORT_SANITIZE`: this variable takes a comma-separated list of sanitizers options to run the tests (empty by default) ## BENCHMARKS -Benchmarks are available in the `benchmarks` subdirectory, and can be constructed directly by passing `BUILD_BENCHMARKS=ON` -variable to CMake during the configuration step. +Benchmarks are available in the `benchmarks` subdirectory, and can be constructed directly by passing the option +`-DBUILD_BENCHMARKS=ON` to CMake during the configuration step. Example bench_sort output (timing scale: sec.): @@ -222,6 +226,9 @@ Detailed bench_merge results for different middle iterator positions can be foun https://github.com/timsort/cpp-TimSort/wiki/Benchmark-results - [std-inplace-merge]: https://en.cppreference.com/w/cpp/algorithm/inplace_merge - [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort - [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/stable_sort + [cmake]: https://cmake.org/ + [conan]: https://conan.io/ + [conan-center]: https://conan.io/center + [std-inplace-merge]: https://en.cppreference.com/w/cpp/algorithm/ranges/inplace_merge + [std-sort]: https://en.cppreference.com/w/cpp/algorithm/ranges/sort + [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/ranges/stable_sort From 6c0436d2172d7ebafa591781fed91b66597ad4da Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 23 Jan 2024 00:46:40 +0100 Subject: [PATCH 126/137] README badages: fix link to Conan Center --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49090ce..2b49f0a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Latest Release](https://img.shields.io/badge/release-3.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v3.0.0) -[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F3.0.0-blue.svg)](https://conan.io/center/timsort?version=3.0.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F3.0.0-blue.svg)](https://conan.io/center/recipes/timsort?version=3.0.0) [![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork) ## TimSort From 0f1c9d320678e87df196f85e9f4c8b2777a96505 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 23 Jan 2024 23:59:53 +0100 Subject: [PATCH 127/137] Use single-parameter std::ranges::next and std::ranges::prev Those functions have a single-parameter overload that might produce better codegen for iterator types where operator+/operator- are non-trivial such as those of std::deque. Instead those functions should call the simpler operator++/operator--. --- include/gfx/timsort.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 3949464..d6a0a9b 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -118,7 +118,7 @@ class TimSort { auto pos = std::ranges::upper_bound(lo, start, std::invoke(proj, pivot), comp, proj); for (iter_t p = start; p > pos; --p) { - *p = std::ranges::iter_move(p - 1); + *p = std::ranges::iter_move(std::ranges::prev(p)); } *pos = std::move(pivot); } @@ -128,7 +128,7 @@ class TimSort { Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo < hi); - iter_t runHi = lo + 1; + iter_t runHi = std::ranges::next(lo); if (runHi == hi) { return 1; } @@ -138,14 +138,14 @@ class TimSort { ++runHi; } while (runHi < hi && std::invoke(comp, std::invoke(proj, *runHi), - std::invoke(proj, *(runHi - 1)))); + std::invoke(proj, *std::ranges::prev(runHi)))); std::ranges::reverse(lo, runHi); } else { // non-decreasing do { ++runHi; } while (runHi < hi && !std::invoke(comp, std::invoke(proj, *runHi), - std::invoke(proj, *(runHi - 1)))); + std::invoke(proj, *std::ranges::prev(runHi)))); } return runHi - lo; @@ -356,12 +356,12 @@ class TimSort { static void rotateLeft(iter_t first, iter_t last) { auto tmp = std::ranges::iter_move(first); - auto [_, last_1] = std::ranges::move(first + 1, last, first); + auto [_, last_1] = std::ranges::move(std::ranges::next(first), last, first); *last_1 = std::move(tmp); } static void rotateRight(iter_t first, iter_t last) { - auto last_1 = last - 1; + auto last_1 = std::ranges::prev(last); auto tmp = std::ranges::iter_move(last_1); std::ranges::move_backward(first, last_1, last); *first = std::move(tmp); @@ -573,13 +573,15 @@ class TimSort { goto epilogue; } - count2 = len2 - gallopLeft(std::invoke(proj, *(cursor1 - 1)), + count2 = len2 - gallopLeft(std::invoke(proj, *std::ranges::prev(cursor1)), tmp_.begin(), len2, len2 - 1, comp, proj); if (count2 != 0) { dest -= count2; cursor2 -= count2; len2 -= count2; - std::ranges::move(cursor2 + 1, cursor2 + (1 + count2), dest + 1); + std::ranges::move(std::ranges::next(cursor2), + cursor2 + (1 + count2), + std::ranges::next(dest)); if (len2 <= 1) { goto epilogue; } From 7ee9139c3d69535626e8fb8437923ee4716fcaf4 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 24 Jan 2024 00:43:16 +0100 Subject: [PATCH 128/137] Do not template TimSort class on Compare and Projection Several functions in the class do not depend on those template parameters, which leads to more function template instantiations than needed. This commit templates individual functions needing it on the Compare and Projection types instead of templating the whole classe, leading to small gains in binary size. --- include/gfx/timsort.hpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index d6a0a9b..a9fe75b 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -89,7 +89,7 @@ struct run { } }; -template +template class TimSort { using iter_t = RandomAccessIterator; using value_t = typename std::iterator_traits::value_type; @@ -105,6 +105,7 @@ class TimSort { std::vector > pending_; + template static void binarySort(iter_t const lo, iter_t const hi, iter_t start, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= start); @@ -124,6 +125,7 @@ class TimSort { } } + template static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo < hi); @@ -172,6 +174,7 @@ class TimSort { pending_.push_back(run(runBase, runLen)); } + template void mergeCollapse(Compare comp, Projection proj) { while (pending_.size() > 1) { diff_t n = pending_.size() - 2; @@ -190,6 +193,7 @@ class TimSort { } } + template void mergeForceCollapse(Compare comp, Projection proj) { while (pending_.size() > 1) { diff_t n = pending_.size() - 2; @@ -201,6 +205,7 @@ class TimSort { } } + template void mergeAt(diff_t const i, Compare comp, Projection proj) { diff_t const stackSize = pending_.size(); GFX_TIMSORT_ASSERT(stackSize >= 2); @@ -223,6 +228,7 @@ class TimSort { mergeConsecutiveRuns(base1, len1, base2, len2, std::move(comp), std::move(proj)); } + template void mergeConsecutiveRuns(iter_t base1, diff_t len1, iter_t base2, diff_t len2, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len1 > 0); @@ -252,7 +258,7 @@ class TimSort { } } - template + template static diff_t gallopLeft(T const& key, Iter const base, diff_t const len, diff_t const hint, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len > 0); @@ -303,7 +309,7 @@ class TimSort { return std::ranges::lower_bound(base + (lastOfs + 1), base + ofs, key, comp, proj) - base; } - template + template static diff_t gallopRight(T const& key, Iter const base, diff_t const len, diff_t const hint, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len > 0); @@ -367,6 +373,7 @@ class TimSort { *first = std::move(tmp); } + template void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len1 > 0); @@ -487,6 +494,7 @@ class TimSort { } } + template void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(len1 > 0); @@ -625,6 +633,7 @@ class TimSort { public: + template static void merge(iter_t const lo, iter_t const mid, iter_t const hi, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= mid); @@ -641,6 +650,7 @@ class TimSort { << "; tmp_.size(): " << ts.tmp_.size()); } + template static void sort(iter_t const lo, iter_t const hi, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= hi); @@ -709,7 +719,7 @@ auto timmerge(Iterator first, Iterator middle, Sentinel last, auto last_it = std::ranges::next(first, last); GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, middle, comp, proj) && "Precondition"); GFX_TIMSORT_AUDIT(std::ranges::is_sorted(middle, last_it, comp, proj) && "Precondition"); - detail::TimSort::merge(first, middle, last_it, comp, proj); + detail::TimSort::merge(first, middle, last_it, comp, proj); GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, last_it, comp, proj) && "Postcondition"); return last_it; } @@ -746,7 +756,7 @@ auto timsort(Iterator first, Sentinel last, -> Iterator { auto last_it = std::ranges::next(first, last); - detail::TimSort::sort(first, last_it, comp, proj); + detail::TimSort::sort(first, last_it, comp, proj); GFX_TIMSORT_AUDIT(std::ranges::is_sorted(first, last_it, comp, proj) && "Postcondition"); return last_it; } From 0be56ef771a121d6348392a001dd40cfa0ca9fc7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 27 Jan 2024 18:17:15 +0100 Subject: [PATCH 129/137] Implement binarySort with rotateRight --- include/gfx/timsort.hpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index a9fe75b..6e6d0ef 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -115,13 +115,8 @@ class TimSort { } for (; start < hi; ++start) { GFX_TIMSORT_ASSERT(lo <= start); - auto pivot = std::ranges::iter_move(start); - - auto pos = std::ranges::upper_bound(lo, start, std::invoke(proj, pivot), comp, proj); - for (iter_t p = start; p > pos; --p) { - *p = std::ranges::iter_move(std::ranges::prev(p)); - } - *pos = std::move(pivot); + auto pos = std::ranges::upper_bound(lo, start, std::invoke(proj, *start), comp, proj); + rotateRight(pos, std::ranges::next(start)); } } From e02eb816ed2aaa958897364b204b6e400f8276d6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 27 Jan 2024 18:42:27 +0100 Subject: [PATCH 130/137] GFX_TIMSORT_ENABLE_AUDIT now automatically enables asserts too --- README.md | 5 +++-- include/gfx/timsort.hpp | 3 --- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2b49f0a..ee52147 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,10 @@ conan install --requires=timsort/3.0.0 The following configuration macros allow `gfx::timsort` and `gfx::timmerge` to emit diagnostics, which can be helpful to diagnose issues: -* Defining `GFX_TIMSORT_ENABLE_ASSERT` inserts assertions in key locations in the algorithm to avoid logic errors. +* Defining `GFX_TIMSORT_ENABLE_ASSERT` light inserts assertions in key locations in the algorithm to avoid logic errors. * Defining `GFX_TIMSORT_ENABLE_AUDIT` inserts assertions that verify pre- and postconditions. These verifications can - slow the algorithm down significantly. Enable the audits only while testing or debugging. + slow the algorithm down significantly. Enable the audits only while testing or debugging. Enabling audits automatically + enables lighter assertions too. * Defining `GFX_TIMSORT_ENABLE_LOG` inserts logs in key locations, which allow to follow more closely the flow of the algorithm. diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 6e6d0ef..ffef986 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -48,9 +48,6 @@ #if defined(GFX_TIMSORT_ENABLE_ASSERT) || defined(GFX_TIMSORT_ENABLE_AUDIT) # include -#endif - -#ifdef GFX_TIMSORT_ENABLE_ASSERT # define GFX_TIMSORT_ASSERT(expr) assert(expr) #else # define GFX_TIMSORT_ASSERT(expr) ((void)0) From 6b9ff4ddb993ca0a634fdcb4a3cbb7ff3043b5e8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 28 Jan 2024 00:03:47 +0100 Subject: [PATCH 131/137] Modernize/simplify internals a bit --- include/gfx/timsort.hpp | 53 +++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index ffef986..9a51ebe 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -95,12 +95,9 @@ class TimSort { static constexpr int MIN_MERGE = 32; static constexpr int MIN_GALLOP = 7; - int minGallop_; // default to MIN_GALLOP - + int minGallop_ = MIN_GALLOP; std::vector tmp_; // temp storage for merges - using tmp_iter_t = typename std::vector::iterator; - - std::vector > pending_; + std::vector> pending_; template static void binarySort(iter_t const lo, iter_t const hi, iter_t start, @@ -122,7 +119,7 @@ class TimSort { Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo < hi); - iter_t runHi = std::ranges::next(lo); + auto runHi = std::ranges::next(lo); if (runHi == hi) { return 1; } @@ -156,14 +153,8 @@ class TimSort { return n + r; } - TimSort() : minGallop_(MIN_GALLOP) { - } - - // Silence GCC -Winline warning - ~TimSort() {} - void pushRun(iter_t const runBase, diff_t const runLen) { - pending_.push_back(run(runBase, runLen)); + pending_.emplace_back(runBase, runLen); } template @@ -204,10 +195,10 @@ class TimSort { GFX_TIMSORT_ASSERT(i >= 0); GFX_TIMSORT_ASSERT(i == stackSize - 2 || i == stackSize - 3); - iter_t base1 = pending_[i].base; - diff_t len1 = pending_[i].len; - iter_t base2 = pending_[i + 1].base; - diff_t len2 = pending_[i + 1].len; + auto base1 = pending_[i].base; + auto len1 = pending_[i].len; + auto base2 = pending_[i + 1].base; + auto len2 = pending_[i + 1].len; pending_[i].len = len1 + len2; @@ -227,7 +218,7 @@ class TimSort { GFX_TIMSORT_ASSERT(len2 > 0); GFX_TIMSORT_ASSERT(base1 + len1 == base2); - diff_t const k = gallopRight(std::invoke(proj, *base2), base1, len1, 0, comp, proj); + auto k = gallopRight(std::invoke(proj, *base2), base1, len1, 0, comp, proj); GFX_TIMSORT_ASSERT(k >= 0); base1 += k; @@ -261,7 +252,7 @@ class TimSort { diff_t ofs = 1; if (std::invoke(comp, std::invoke(proj, base[hint]), key)) { - diff_t const maxOfs = len - hint; + auto maxOfs = len - hint; while (ofs < maxOfs && std::invoke(comp, std::invoke(proj, base[hint + ofs]), key)) { lastOfs = ofs; ofs = (ofs << 1) + 1; @@ -381,9 +372,9 @@ class TimSort { move_to_tmp(base1, len1); - tmp_iter_t cursor1 = tmp_.begin(); - iter_t cursor2 = base2; - iter_t dest = base1; + auto cursor1 = tmp_.begin(); + auto cursor2 = base2; + auto dest = base1; *dest = std::ranges::iter_move(cursor2); ++cursor2; @@ -502,9 +493,9 @@ class TimSort { move_to_tmp(base2, len2); - iter_t cursor1 = base1 + len1; - tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); - iter_t dest = base2 + (len2 - 1); + auto cursor1 = base1 + len1; + auto cursor2 = tmp_.begin() + (len2 - 1); + auto dest = base2 + (len2 - 1); *dest = std::ranges::iter_move(--cursor1); --dest; @@ -646,26 +637,26 @@ class TimSort { static void sort(iter_t const lo, iter_t const hi, Compare comp, Projection proj) { GFX_TIMSORT_ASSERT(lo <= hi); - diff_t nRemaining = (hi - lo); + auto nRemaining = hi - lo; if (nRemaining < 2) { return; // nothing to do } if (nRemaining < MIN_MERGE) { - diff_t const initRunLen = countRunAndMakeAscending(lo, hi, comp, proj); + auto initRunLen = countRunAndMakeAscending(lo, hi, comp, proj); GFX_TIMSORT_LOG("initRunLen: " << initRunLen); binarySort(lo, hi, lo + initRunLen, comp, proj); return; } TimSort ts; - diff_t const minRun = minRunLength(nRemaining); - iter_t cur = lo; + auto minRun = minRunLength(nRemaining); + auto cur = lo; do { - diff_t runLen = countRunAndMakeAscending(cur, hi, comp, proj); + auto runLen = countRunAndMakeAscending(cur, hi, comp, proj); if (runLen < minRun) { - diff_t const force = (std::min)(nRemaining, minRun); + auto force = (std::min)(nRemaining, minRun); binarySort(cur, cur + force, cur + runLen, comp, proj); runLen = force; } From 25443c78b851cfe76f456d1051bc8773b74f54a6 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 28 Jan 2024 00:42:50 +0100 Subject: [PATCH 132/137] MSVC: compile test suite with /W4 --- tests/CMakeLists.txt | 6 ++++-- tests/cxx_17_tests.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 867c2ad..03daf7f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,16 +42,18 @@ macro(configure_tests target) ) # Add warnings - if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") target_compile_options(${target} PRIVATE -Wall -Wextra -Wcast-align -Wmissing-declarations -Wmissing-include-dirs -Wnon-virtual-dtor -Wodr -Wpedantic -Wredundant-decls -Wundef -Wunreachable-code $<$:-Wlogical-op -Wuseless-cast> ) + elseif (MSVC) + target_compile_options(${target} PRIVATE /W4) endif() # Configure optimization options - if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") target_compile_options(${target} PRIVATE $<$,$>:-O0> $<$,$>:-Og> diff --git a/tests/cxx_17_tests.cpp b/tests/cxx_17_tests.cpp index 735c0c2..679632f 100644 --- a/tests/cxx_17_tests.cpp +++ b/tests/cxx_17_tests.cpp @@ -60,7 +60,7 @@ TEST_CASE( "generalized callables" ) { CHECK(is_vec_sorted()); } - std::uniform_int_distribution random_middle(0, vec.size()); + std::uniform_int_distribution random_middle(0, static_cast(vec.size())); SECTION( "timmerge for comparisons" ) { const auto middle = vec.begin() + random_middle(gen); From 929f8e727025ed9e744e7b40c7257c05d07898bd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 28 Jan 2024 12:23:57 +0100 Subject: [PATCH 133/137] Fortity test suite Compile tests with _LIBCPP_ENABLE_ASSERTIONS and _GLIBCXX_ASSERTIONS, and _FORTITY_SOURCE allow standard libraries to perform additional checks. --- tests/CMakeLists.txt | 9 ++++++++- tests/verbose_abort.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/verbose_abort.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 03daf7f..6dab07c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,7 +37,10 @@ macro(configure_tests target) target_compile_definitions(${target} PRIVATE # Somewhat speed up Catch2 compile times CATCH_CONFIG_FAST_COMPILE - # Enable assertions for more thorough tests + # Fortify test suite for more thorough checks + _FORTIFY_SOURCE=3 + _GLIBCXX_ASSERTIONS + _LIBCPP_ENABLE_ASSERTIONS=1 GFX_TIMSORT_ENABLE_ASSERT ) @@ -85,6 +88,7 @@ endmacro() # Tests that can run with C++98 add_executable(cxx_98_tests cxx_98_tests.cpp + verbose_abort.cpp ) configure_tests(cxx_98_tests) target_compile_features(cxx_98_tests PRIVATE cxx_std_98) @@ -93,6 +97,7 @@ target_compile_features(cxx_98_tests PRIVATE cxx_std_98) add_executable(cxx_11_tests merge_cxx_11_tests.cpp cxx_11_tests.cpp + verbose_abort.cpp ) configure_tests(cxx_11_tests) target_compile_features(cxx_11_tests PRIVATE cxx_std_11) @@ -100,6 +105,7 @@ target_compile_features(cxx_11_tests PRIVATE cxx_std_11) # Tests requiring C++17 support add_executable(cxx_17_tests cxx_17_tests.cpp + verbose_abort.cpp ) configure_tests(cxx_17_tests) target_compile_features(cxx_17_tests PRIVATE cxx_std_17) @@ -107,6 +113,7 @@ target_compile_features(cxx_17_tests PRIVATE cxx_std_17) # Tests requiring C++20 support add_executable(cxx_20_tests cxx_20_tests.cpp + verbose_abort.cpp ) configure_tests(cxx_20_tests) target_compile_features(cxx_20_tests PRIVATE cxx_std_20) diff --git a/tests/verbose_abort.cpp b/tests/verbose_abort.cpp new file mode 100644 index 0000000..eec0271 --- /dev/null +++ b/tests/verbose_abort.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Morwenn. + * + * SPDX-License-Identifier: MIT + */ +#include + +#ifdef _LIBCPP_VERSION +#if defined(_LIBCPP_ENABLE_ASSERTIONS) && _LIBCPP_ENABLE_ASSERTIONS + +#include +#include +#include + +namespace std::inline __1 +{ + /* + * Required to avoid linking issues with AppleClang when + * compiling with _LIBCPP_ENABLE_ASSERTIONS. + * See https://releases.llvm.org/16.0.0/projects/libcxx/docs/UsingLibcxx.html#enabling-the-safe-libc-mode + */ + [[noreturn]] + void __libcpp_verbose_abort(char const* format, ...) { + std::va_list list; + va_start(list, format); + std::vfprintf(stderr, format, list); + va_end(list); + + std::abort(); + } + } + +#endif +#endif From 978afc12e37bf919651472f8e308303be470884e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 28 Nov 2024 22:42:39 +0100 Subject: [PATCH 134/137] Fix std::views::zip support (issue #40) --- include/gfx/timsort.hpp | 9 ++++---- tests/CMakeLists.txt | 13 ++++++++++++ tests/cxx_23_tests.cpp | 47 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 tests/cxx_23_tests.cpp diff --git a/include/gfx/timsort.hpp b/include/gfx/timsort.hpp index 9a51ebe..f7803a3 100644 --- a/include/gfx/timsort.hpp +++ b/include/gfx/timsort.hpp @@ -89,14 +89,13 @@ struct run { template class TimSort { using iter_t = RandomAccessIterator; - using value_t = typename std::iterator_traits::value_type; - using diff_t = typename std::iterator_traits::difference_type; + using diff_t = std::iter_difference_t; static constexpr int MIN_MERGE = 32; static constexpr int MIN_GALLOP = 7; int minGallop_ = MIN_GALLOP; - std::vector tmp_; // temp storage for merges + std::vector> tmp_; // temp storage for merges std::vector> pending_; template @@ -344,14 +343,14 @@ class TimSort { } static void rotateLeft(iter_t first, iter_t last) { - auto tmp = std::ranges::iter_move(first); + std::iter_value_t tmp = std::ranges::iter_move(first); auto [_, last_1] = std::ranges::move(std::ranges::next(first), last, first); *last_1 = std::move(tmp); } static void rotateRight(iter_t first, iter_t last) { auto last_1 = std::ranges::prev(last); - auto tmp = std::ranges::iter_move(last_1); + std::iter_value_t tmp = std::ranges::iter_move(last_1); std::ranges::move_backward(first, last_1, last); *first = std::move(tmp); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6dab07c..540c895 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -118,6 +118,16 @@ add_executable(cxx_20_tests configure_tests(cxx_20_tests) target_compile_features(cxx_20_tests PRIVATE cxx_std_20) +# Tests requiring C++23 support +if ("cxx_std_23" IN_LIST CMAKE_CXX_COMPILE_FEATURES) + add_executable(cxx_23_tests + cxx_23_tests.cpp + verbose_abort.cpp + ) + configure_tests(cxx_23_tests) + target_compile_features(cxx_23_tests PRIVATE cxx_std_23) +endif() + # Windows-specific tests if (WIN32) add_executable(windows_tests @@ -135,6 +145,9 @@ catch_discover_tests(cxx_98_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_11_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_17_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) catch_discover_tests(cxx_20_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) +if ("cxx_std_23" IN_LIST CMAKE_CXX_COMPILE_FEATURES) + catch_discover_tests(cxx_23_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) +endif() if (WIN32) catch_discover_tests(windows_tests EXTRA_ARGS --rng-seed ${RNG_SEED}) endif() diff --git a/tests/cxx_23_tests.cpp b/tests/cxx_23_tests.cpp new file mode 100644 index 0000000..b27562a --- /dev/null +++ b/tests/cxx_23_tests.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Morwenn. + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_helpers.hpp" + +TEST_CASE( "support for std::ranges::views::zip" ) +{ + SECTION( "zip two small collections" ) { + // issue #40 + std::vector vec = {4, 2, 3, 1}; + std::array arr = {'A', 'C', 'B', 'D'}; + auto zipped = std::views::zip(vec, arr); + + gfx::timsort( + zipped, {}, + [](std::tuple const& pair) { + return std::get<0>(pair); + } + ); + CHECK( std::ranges::is_sorted(vec) ); + CHECK( std::ranges::is_sorted(arr, std::ranges::greater{}) ); + } + + SECTION( "zip two big collections" ) { + std::vector vec(3000); + std::deque deq(3000); + std::iota(vec.begin(), vec.end(), -500); + std::ranges::reverse(vec); + std::iota(deq.begin(), deq.end(), -500); + + auto zipped = std::views::zip(vec, deq); + test_helpers::shuffle(zipped); + + gfx::timsort(zipped); + CHECK( std::ranges::is_sorted(vec) ); + CHECK( std::ranges::is_sorted(deq, std::ranges::greater{}) ); + } +} From 4597b6c882447d4b4619db3452dcd92d7d6f5c95 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 1 Dec 2024 15:53:23 +0100 Subject: [PATCH 135/137] Only compile zip_view tests when zip is available --- tests/cxx_23_tests.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/cxx_23_tests.cpp b/tests/cxx_23_tests.cpp index b27562a..fe8fb3f 100644 --- a/tests/cxx_23_tests.cpp +++ b/tests/cxx_23_tests.cpp @@ -12,6 +12,9 @@ #include #include "test_helpers.hpp" + +#if defined(__cpp_lib_ranges_zip) + TEST_CASE( "support for std::ranges::views::zip" ) { SECTION( "zip two small collections" ) { @@ -45,3 +48,5 @@ TEST_CASE( "support for std::ranges::views::zip" ) CHECK( std::ranges::is_sorted(deq, std::ranges::greater{}) ); } } + +#endif // defined(__cpp_lib_ranges_zip) From fa3f53576a8b4d22e80f37b7c75620c0797e6ca2 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 3 Dec 2024 20:47:35 +0100 Subject: [PATCH 136/137] Upgrade downloaded Catch2 version to v3.7.1 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 540c895..bb9f237 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,7 +13,7 @@ set(GFX_TIMSORT_SANITIZE "" CACHE STRING "Comma-separated list of options to pas FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v3.5.0 + GIT_TAG fa43b77429ba76c462b1898d6cd2f2d7a9416b14 # v3.7.1 SYSTEM FIND_PACKAGE_ARGS 3.1.0 ) From 3f5ebd06264297b8a754ac14b2ba9bad85b8c651 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 3 Dec 2024 20:54:31 +0100 Subject: [PATCH 137/137] Release v3.0.1 --- CMakeLists.txt | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19c6fbf..2e74b8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.14.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(timsort VERSION 3.0.0 LANGUAGES CXX) +project(timsort VERSION 3.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index ee52147..5002c1c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-3.0.0-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v3.0.0) -[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F3.0.0-blue.svg)](https://conan.io/center/recipes/timsort?version=3.0.0) +[![Latest Release](https://img.shields.io/badge/release-3.0.1-blue.svg)](https://github.com/timsort/cpp-TimSort/releases/tag/v3.0.1) +[![Conan Package](https://img.shields.io/badge/conan-cpp--TimSort%2F3.0.1-blue.svg)](https://conan.io/center/recipes/timsort?version=3.0.1) [![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork) ## TimSort @@ -120,7 +120,7 @@ Alternatively the library is also available [Conan Center][conan-center] and can [Conan][conan] cache with the following command: ```sh -conan install --requires=timsort/3.0.0 +conan install --requires=timsort/3.0.1 ``` ## DIAGNOSTICS & INFORMATION