diff --git a/Jamfile.v2 b/Jamfile.v2 index 9e5d38f..0db1a94 100644 --- a/Jamfile.v2 +++ b/Jamfile.v2 @@ -36,6 +36,7 @@ exe alrbreaker : alrbreaker.cpp ; exe binaryalrbreaker : binaryalrbreaker.cpp ; exe caseinsensitive : caseinsensitive.cpp ; exe generalizedstruct : generalizedstruct.cpp ; +exe timsort : timsortsample.cpp ; # benchmarks need to be built with linkflags="-lboost_system -lboost_thread" #exe parallelint : parallelint.cpp boost_system ; diff --git a/doc/Jamfile.v2 b/doc/Jamfile.v2 index df68f37..029d05c 100644 --- a/doc/Jamfile.v2 +++ b/doc/Jamfile.v2 @@ -158,3 +158,8 @@ explicit images ; # : inspect # test name # ; +############################################################################### +alias boostdoc ; +explicit boostdoc ; +alias boostrelease : standalone ; +explicit boostrelease ; diff --git a/example/timsortalreadysorted.cpp b/example/timsortalreadysorted.cpp new file mode 100644 index 0000000..967413b --- /dev/null +++ b/example/timsortalreadysorted.cpp @@ -0,0 +1,89 @@ +/* + Copyright (c) Alexander Zaitsev , 2016 + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + See http://www.boost.org/ for latest version. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace boost::sort; + +#define DATA_TYPE int + + +//Pass in an argument to test std::sort +int main(int argc, const char ** argv) { + size_t uCount,uSize=sizeof(DATA_TYPE); + bool stdSort = false; + unsigned loopCount = 1; + for (int u = 1; u < argc; ++u) { + if (std::string(argv[u]) == "-std") + stdSort = true; + else + loopCount = atoi(argv[u]); + } + //Sorts the data once, then times sorting of already-sorted data + loopCount += 1; + std::ifstream input("input.txt", std::ios_base::in | std::ios_base::binary); + if (input.fail()) { + printf("input.txt could not be opened\n"); + return 1; + } + double total = 0.0; + std::vector array; + input.seekg (0, std::ios_base::end); + size_t length = input.tellg(); + uCount = length/uSize; + input.seekg (0, std::ios_base::beg); + //Conversion to a vector + array.resize(uCount); + unsigned v = 0; + while (input.good() && v < uCount) // EOF or failure stops the reading + input.read(reinterpret_cast(&(array[v++])), uSize ); + //Run multiple loops, if requested + for (unsigned u = 0; u < loopCount; ++u) { + clock_t start, end; + double elapsed; + start = clock(); + if (stdSort) + //std::sort(&(array[0]), &(array[0]) + uCount); + std::sort(array.begin(), array.end()); + else { + printf("call\n"); + //integer_sort(&(array[0]), &(array[0]) + uCount); + timsort(array.begin(), array.end()); + } + end = clock(); + elapsed = static_cast(end - start) ; + std::ofstream ofile; + if (stdSort) + ofile.open("standard_sort_out.txt", std::ios_base::out | + std::ios_base::binary | std::ios_base::trunc); + else + ofile.open("boost_sort_out.txt", std::ios_base::out | + std::ios_base::binary | std::ios_base::trunc); + if (ofile.good()) { + for (unsigned v = 0; v < array.size(); ++v) { + ofile.write(reinterpret_cast(&(array[v])), sizeof(array[v]) ); + } + ofile.close(); + } + if (u) + total += elapsed; + } + if (stdSort) + printf("std::sort elapsed time %f\n", total / CLOCKS_PER_SEC); + else + printf("timsort elapsed time %f\n", total / CLOCKS_PER_SEC); + return 0; +} diff --git a/example/timsortmostlysorted.cpp b/example/timsortmostlysorted.cpp new file mode 100644 index 0000000..3d426ea --- /dev/null +++ b/example/timsortmostlysorted.cpp @@ -0,0 +1,97 @@ +/* + Copyright (c) Alexander Zaitsev , 2016 + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + See http://www.boost.org/ for latest version. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace boost::sort; + +#define DATA_TYPE int + +unsigned +get_index(unsigned count) +{ + unsigned result = unsigned((rand() % (1 << 16))*uint64_t(count)/(1 << 16)); + if (result >= count || result < 0) + result = count - 1; + return result; +} + +//Pass in an argument to test std::sort +int main(int argc, const char ** argv) { + srand(1); + size_t uCount,uSize=sizeof(DATA_TYPE); + bool stdSort = false; + unsigned loopCount = 1; + for (int u = 1; u < argc; ++u) { + if (std::string(argv[u]) == "-std") + stdSort = true; + else + loopCount = atoi(argv[u]); + } + //Sorts the data once, then times sorting of mostly-sorted data + loopCount += 1; + std::ifstream input("input.txt", std::ios_base::in | std::ios_base::binary); + if (input.fail()) { + printf("input.txt could not be opened\n"); + return 1; + } + double total = 0.0; + std::vector array; + input.seekg (0, std::ios_base::end); + size_t length = input.tellg(); + uCount = length/uSize; + input.seekg (0, std::ios_base::beg); + //Conversion to a vector + array.resize(uCount); + unsigned v = 0; + while (input.good() && v < uCount) // EOF or failure stops the reading + input.read(reinterpret_cast(&(array[v++])), uSize ); + //Run multiple loops, if requested + for (unsigned u = 0; u < loopCount; ++u) { + for (unsigned v = 0; v < uCount/10; ++v) + std::swap(array[get_index(uCount)], array[get_index(uCount)]); + clock_t start, end; + double elapsed; + start = clock(); + if (stdSort) + std::sort(array.begin(), array.end()); + else + timsort(array.begin(), array.end()); + end = clock(); + elapsed = static_cast(end - start) ; + std::ofstream ofile; + if (stdSort) + ofile.open("standard_sort_out.txt", std::ios_base::out | + std::ios_base::binary | std::ios_base::trunc); + else + ofile.open("boost_sort_out.txt", std::ios_base::out | + std::ios_base::binary | std::ios_base::trunc); + if (ofile.good()) { + for (unsigned v = 0; v < array.size(); ++v) { + ofile.write(reinterpret_cast(&(array[v])), sizeof(array[v]) ); + } + ofile.close(); + } + if (u) + total += elapsed; + } + if (stdSort) + printf("std::sort elapsed time %f\n", total / CLOCKS_PER_SEC); + else + printf("timsort elapsed time %f\n", total / CLOCKS_PER_SEC); + return 0; +} + diff --git a/example/timsortsample.cpp b/example/timsortsample.cpp new file mode 100644 index 0000000..ab32a51 --- /dev/null +++ b/example/timsortsample.cpp @@ -0,0 +1,92 @@ +/* + Copyright (c) Alexander Zaitsev , 2016 + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + See http://www.boost.org/ for latest version. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace boost::sort; + +#define DATA_TYPE int + + +//Pass in an argument to test std::sort +int main(int argc, const char ** argv) +{ + size_t uCount, uSize = sizeof(DATA_TYPE); + bool stdSort = false; + unsigned loopCount = 1; + for (int u = 1; u < argc; ++u) + { + if (std::string(argv[u]) == "-std") + stdSort = true; + else + loopCount = atoi(argv[u]); + } + std::ifstream input("input.txt", std::ios_base::in | std::ios_base::binary); + if (input.fail()) + { + printf("input.txt could not be opened\n"); + return 1; + } + double total = 0.0; + std::vector array; + input.seekg (0, std::ios_base::end); + size_t length = input.tellg(); + uCount = length / uSize; + //Run multiple loops, if requested + for (unsigned u = 0; u < loopCount; ++u) + { + input.seekg (0, std::ios_base::beg); + //Conversion to a vector + array.resize(uCount); + unsigned v = 0; + while (input.good() && v < uCount) + input.read(reinterpret_cast(&(array[v++])), uSize ); + if (v < uCount) + array.resize(v); + clock_t start, end; + double elapsed; + start = clock(); + if (stdSort) + std::sort(array.begin(), array.end()); + else + boost::sort::timsort(array.begin(), array.end()); + end = clock(); + elapsed = static_cast(end - start); + std::ofstream ofile; + if (stdSort) + ofile.open("standard_sort_out.txt", std::ios_base::out | + std::ios_base::binary | std::ios_base::trunc); + else + ofile.open("boost_sort_out.txt", std::ios_base::out | + std::ios_base::binary | std::ios_base::trunc); + if (ofile.good()) + { + for (unsigned v = 0; v < array.size(); ++v) + { + ofile.write(reinterpret_cast(&(array[v])), sizeof(array[v]) ); + } + ofile.close(); + } + total += elapsed; + array.clear(); + } + input.close(); + if (stdSort) + printf("std::sort elapsed time %f\n", total / CLOCKS_PER_SEC); + else + printf("timsort elapsed time %f\n", total / CLOCKS_PER_SEC); + return 0; +} diff --git a/include/boost/sort/spreadsort/float_sort.hpp b/include/boost/sort/spreadsort/float_sort.hpp index 37966c2..44de916 100644 --- a/include/boost/sort/spreadsort/float_sort.hpp +++ b/include/boost/sort/spreadsort/float_sort.hpp @@ -23,6 +23,8 @@ Scott McMurray #include #include #include +#include +#include namespace boost { namespace sort { @@ -90,6 +92,18 @@ Some performance plots of runtime vs. n and log(range) are provided:\n detail::float_sort(first, last); } + /*! + \brief Floating-point sort algorithm using range. + + \param[in] range Range [first, last) for sorting. + + */ + template + inline void float_sort(Range& range) + { + float_sort(boost::begin(range), boost::end(range)); + } + /*! \brief Floating-point sort algorithm using random access iterators with just right-shift functor. @@ -108,6 +122,19 @@ Some performance plots of runtime vs. n and log(range) are provided:\n detail::float_sort(first, last, rshift(*first, 0), rshift); } + /*! + \brief Floating-point sort algorithm using range with just right-shift functor. + + \param[in] range Range [first, last) for sorting. + \param[in] rshift Functor that returns the result of shifting the value_type right a specified number of bits. + + */ + template + inline void float_sort(Range& range, Right_shift rshift) + { + float_sort(boost::begin(range), boost::end(range), rshift); + } + /*! \brief Float sort algorithm using random access iterators with both right-shift and user-defined comparison operator. @@ -127,6 +154,21 @@ Some performance plots of runtime vs. n and log(range) are provided:\n else detail::float_sort(first, last, rshift(*first, 0), rshift, comp); } + + + /*! + \brief Float sort algorithm using range with both right-shift and user-defined comparison operator. + + \param[in] range Range [first, last) for sorting. + \param[in] rshift Functor that returns the result of shifting the value_type right a specified number of bits. + \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + */ + + template + inline void float_sort(Range& range, Right_shift rshift, Compare comp) + { + float_sort(boost::begin(range), boost::end(range), rshift, comp); + } } } } diff --git a/include/boost/sort/spreadsort/integer_sort.hpp b/include/boost/sort/spreadsort/integer_sort.hpp index 0727ccd..0b788a1 100644 --- a/include/boost/sort/spreadsort/integer_sort.hpp +++ b/include/boost/sort/spreadsort/integer_sort.hpp @@ -24,6 +24,8 @@ Doxygen comments by Paul A. Bristow Jan 2015 #include #include #include +#include +#include namespace boost { namespace sort { @@ -80,6 +82,46 @@ Some performance plots of runtime vs. n and log(range) are provided:\n detail::integer_sort(first, last, *first >> 0); } +/*! \brief Integer sort algorithm using range. + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c integer_sort is asymptotically faster +than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, +so its worst-case with default settings for 32-bit integers is + O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_integer_sort + \n + osx_integer_sort + + \param[in] range Range [first, last) for sorting. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). + +*/ +template +inline void integer_sort(Range& range) +{ + integer_sort(boost::begin(range), boost::end(range)); +} + /*! \brief Integer sort algorithm using random access iterators with both right-shift and user-defined comparison operator. (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). @@ -129,6 +171,50 @@ Some performance plots of runtime vs. n and log(range) are provided:\n detail::integer_sort(first, last, shift(*first, 0), shift, comp); } +/*! \brief Integer sort algorithm using range with both right-shift and user-defined comparison operator. + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c integer_sort is asymptotically faster +than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, +so its worst-case with default settings for 32-bit integers is + O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_integer_sort + \n + osx_integer_sort + + \param[in] range Range [first, last) for sorting. + \param[in] shift Functor that returns the result of shifting the value_type right a specified number of bits. + \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). +*/ +template +inline void integer_sort(Range& range, Right_shift shift, Compare comp) +{ + integer_sort(boost::begin(range), boost::end(range), shift, comp); +} + /*! \brief Integer sort algorithm using random access iterators with just right-shift functor. (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). @@ -177,6 +263,50 @@ Some performance plots of runtime vs. n and log(range) are provided:\n else detail::integer_sort(first, last, shift(*first, 0), shift); } + + +/*! \brief Integer sort algorithm using range with just right-shift functor. + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n + +\par Performance: +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c integer_sort is asymptotically faster +than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, +so its worst-case with default settings for 32-bit integers is + O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + * windows_integer_sort\n + * osx_integer_sort + + \param[in] range Range [first, last) for sorting. + \param[in] shift A functor that returns the result of shifting the value_type right a specified number of bits. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). + +*/ +template +inline void integer_sort(Range& range, Right_shift shift) +{ + integer_sort(boost::begin(range), boost::end(range), shift); +} } } } diff --git a/include/boost/sort/spreadsort/spreadsort.hpp b/include/boost/sort/spreadsort/spreadsort.hpp index 4837712..97abf3e 100644 --- a/include/boost/sort/spreadsort/spreadsort.hpp +++ b/include/boost/sort/spreadsort/spreadsort.hpp @@ -12,6 +12,8 @@ Some improvements suggested by: Phil Endecott and Frank Gennari float_mem_cast fix provided by: Scott McMurray + Range support provided by: + Alexander Zaitsev */ #ifndef BOOST_SORT_SPREADSORT_HPP @@ -25,6 +27,8 @@ Scott McMurray #include #include #include +#include +#include namespace boost { namespace sort { @@ -139,6 +143,25 @@ namespace spreadsort { boost::uint16_t unused = 0; string_sort(first, last, unused); } + +/*! +\brief Generic @c spreadsort variant detects value_type and calls required sort function. +\note Sorting other data types requires picking between @c integer_sort, @c float_sort and @c string_sort directly, +as @c spreadsort won't accept types that don't have the appropriate @c type_traits. + +\param[in] range Range [first, last) for sorting. + +\pre [@c first, @c last) is a valid range. +\post The elements in the range [@c first, @c last) are sorted in ascending order. +*/ + +template +void spreadsort(Range& range) +{ + spreadsort(boost::begin(range), boost::end(range)); +} + + } // namespace spreadsort } // namespace sort } // namespace boost diff --git a/include/boost/sort/spreadsort/string_sort.hpp b/include/boost/sort/spreadsort/string_sort.hpp index 4c3f1fb..e592304 100644 --- a/include/boost/sort/spreadsort/string_sort.hpp +++ b/include/boost/sort/spreadsort/string_sort.hpp @@ -21,6 +21,8 @@ Phil Endecott and Frank Gennari #include #include #include +#include +#include namespace boost { namespace sort { @@ -33,10 +35,8 @@ namespace spreadsort { which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n \par Worst-case performance is O(N * (lg(range)/s + s)) , -so @c integer_sort is asymptotically faster -than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, -so its worst-case with default settings for 32-bit integers is - O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n Some performance plots of runtime vs. n and log(range) are provided:\n windows_string_sort\n osx_string_sort @@ -81,6 +81,47 @@ Some performance plots of runtime vs. n and log(range) are provided:\n detail::string_sort(first, last, unused); } +/*! \brief String sort algorithm using range, allowing character-type overloads.\n + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_string_sort\n + osx_string_sort + + \tparam Unsigned_char_type Unsigned character type used for string. + \param[in] range Range [first, last) for sorting. + \param[in] unused value with the same type as the result of the [] operator, defining the Unsigned_char_type. The actual value is unused. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). + +*/ + +template +inline void string_sort(Range& range, Unsigned_char_type unused) +{ + string_sort(boost::begin(range), boost::end(range), unused); +} /*! \brief String sort algorithm using random access iterators, wraps using default of unsigned char. (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). @@ -88,10 +129,8 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n Worst-case performance is O(N * (lg(range)/s + s)) , -so @c integer_sort is asymptotically faster -than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, -so its worst-case with default settings for 32-bit integers is - O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n Some performance plots of runtime vs. n and log(range) are provided:\n windows_string_sort \n @@ -129,22 +168,58 @@ Some performance plots of runtime vs. n and log(range) are provided:\n string_sort(first, last, unused); } +/*! \brief String sort algorithm using range, wraps using default of unsigned char. + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_string_sort + \n + osx_string_sort + + \param[in] range Range [first, last) for sorting. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). + +*/ +template +inline void string_sort(Range& range) +{ + string_sort(boost::begin(range), boost::end(range)); +} /*! \brief String sort algorithm using random access iterators, allowing character-type overloads. (All variants fall back to @c std::sort if the data size is too small, < detail::min_sort_size). - \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par Worst-case performance is O(N * (lg(range)/s + s)) , -so @c integer_sort is asymptotically faster -than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, -so its worst-case with default settings for 32-bit integers is - O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n Some performance plots of runtime vs. n and log(range) are provided:\n - windows_integer_sort - \n - osx_integer_sort + windows_string_sort\n + osx_string_sort \tparam RandomAccessIter Random access iterator @@ -190,23 +265,67 @@ Some performance plots of runtime vs. n and log(range) are provided:\n detail::reverse_string_sort(first, last, unused); } +/*! \brief String sort algorithm using range, allowing character-type overloads. -/*! \brief String sort algorithm using random access iterators, wraps using default of @c unsigned char. - - (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + (All variants fall back to @c std::sort if the data size is too small, < detail::min_sort_size). - \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n Worst-case performance is O(N * (lg(range)/s + s)) , -so @c integer_sort is asymptotically faster -than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, -so its worst-case with default settings for 32-bit integers is - O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n Some performance plots of runtime vs. n and log(range) are provided:\n windows_integer_sort \n osx_integer_sort + + \tparam Comp Functor type to use for comparison. + \tparam Unsigned_char_type Unsigned character type used for string. + + \param[in] range Range [first, last) for sorting. + \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + \param[in] unused value with the same type as the result of the [] operator, defining the Unsigned_char_type. The actual value is unused. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). +*/ +template +inline void reverse_string_sort(Range& range, Compare comp, Unsigned_char_type unused) +{ + reverse_string_sort(boost::begin(range), boost::end(range), comp, unused); +} + +/*! \brief String sort algorithm using random access iterators, wraps using default of @c unsigned char. + + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c string_sort is asymptotically faster +than pure comparison-based algorithms.\n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_string_sort\n + osx_string_sort + \param[in] first Iterator pointer to first element. \param[in] last Iterator pointing to one beyond the end of data. \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. @@ -242,22 +361,61 @@ Some performance plots of runtime vs. n and log(range) are provided:\n reverse_string_sort(first, last, comp, unused); } +/*! \brief String sort algorithm using range, wraps using default of @c unsigned char. + + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_string_sort\n + osx_string_sort + + \param[in] range Range [first, last) for sorting. + \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). +*/ +template +inline void reverse_string_sort(Range& range, Compare comp) +{ + reverse_string_sort(boost::begin(range), boost::end(range), comp); +} /*! \brief String sort algorithm using random access iterators, wraps using default of @c unsigned char. (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). - \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par Worst-case performance is O(N * (lg(range)/s + s)) , -so @c integer_sort is asymptotically faster -than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, -so its worst-case with default settings for 32-bit integers is - O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n Some performance plots of runtime vs. n and log(range) are provided:\n - windows_integer_sort - \n - osx_integer_sort + windows_string_sort\n + osx_string_sort \param[in] first Iterator pointer to first element. \param[in] last Iterator pointing to one beyond the end of data. @@ -306,23 +464,64 @@ Some performance plots of runtime vs. n and log(range) are provided:\n } } +/*! \brief String sort algorithm using range, wraps using default of @c unsigned char. + + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_string_sort\n + osx_string_sort + + \param[in] range Range [first, last) for sorting. + \param[in] getchar Bracket functor equivalent to @c operator[], taking a number corresponding to the character offset. + \param[in] length Functor to get the length of the string in characters. + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). + +*/ +template +inline void string_sort(Range& range, Get_char getchar, Get_length length) +{ + string_sort(boost::begin(range), boost::end(range), getchar, length); +} /*! \brief String sort algorithm using random access iterators, wraps using default of @c unsigned char. (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). - \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par Worst-case performance is O(N * (lg(range)/s + s)) , -so @c integer_sort is asymptotically faster -than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, -so its worst-case with default settings for 32-bit integers is - O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n Some performance plots of runtime vs. n and log(range) are provided:\n - windows_integer_sort - \n - osx_integer_sort + windows_string_sort\n + osx_string_sort \param[in] first Iterator pointer to first element. @@ -374,22 +573,67 @@ Some performance plots of runtime vs. n and log(range) are provided:\n } } +/*! \brief String sort algorithm using range, wraps using default of @c unsigned char. + + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_string_sort\n + osx_string_sort + + + \param[in] range Range [first, last) for sorting. + \param[in] getchar Bracket functor equivalent to @c operator[], taking a number corresponding to the character offset. + \param[in] length Functor to get the length of the string in characters. + \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). + +*/ +template +inline void string_sort(Range& range, + Get_char getchar, Get_length length, Compare comp) +{ + string_sort(boost::begin(range), boost::end(range), getchar, length, comp); +} /*! \brief Reverse String sort algorithm using random access iterators. (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). - \details @c integer_sort is a fast templated in-place hybrid radix/comparison algorithm, + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par Worst-case performance is O(N * (lg(range)/s + s)) , -so @c integer_sort is asymptotically faster -than pure comparison-based algorithms. @c s is @c max_splits, which defaults to 11, -so its worst-case with default settings for 32-bit integers is - O(N * ((32/11) slow radix-based iterations fast comparison-based iterations).\n\n +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n Some performance plots of runtime vs. n and log(range) are provided:\n - windows_integer_sort - \n - osx_integer_sort + windows_string_sort\n + osx_string_sort \param[in] first Iterator pointer to first element. @@ -442,6 +686,54 @@ Some performance plots of runtime vs. n and log(range) are provided:\n getchar((*last), 0)); } } + +/*! \brief Reverse String sort algorithm using range. + + (All variants fall back to @c std::sort if the data size is too small, < @c detail::min_sort_size). + + \details @c string_sort is a fast templated in-place hybrid radix/comparison algorithm, +which in testing tends to be roughly 50% to 2X faster than @c std::sort for large tests (>=100kB).\n +\par +Worst-case performance is O(N * (lg(range)/s + s)) , +so @c string_sort is asymptotically faster +than pure comparison-based algorithms. \n\n +Some performance plots of runtime vs. n and log(range) are provided:\n + windows_string_sort\n + osx_string_sort + + + \param[in] range Range [first, last) for sorting. + \param[in] getchar Bracket functor equivalent to @c operator[], taking a number corresponding to the character offset. + \param[in] length Functor to get the length of the string in characters. + \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + + + \pre [@c first, @c last) is a valid range. + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + the right shift, subtraction of right-shifted elements, functors, + or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. + \warning Invalid arguments cause undefined behaviour. + \note @c spreadsort function provides a wrapper that calls the fastest sorting algorithm available for a data type, + enabling faster generic-programming. + + \remark The lesser of O(N*log(N)) comparisons and O(N*log(K/S + S)) operations worst-case, where: + \remark * N is @c last - @c first, + \remark * K is the log of the range in bits (32 for 32-bit integers using their full range), + \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). + +*/ +template +inline void reverse_string_sort(Range& range, Get_char getchar, Get_length length, Compare comp) +{ + reverse_string_sort(boost::begin(range), boost::end(range), getchar, length, comp); +} } } } diff --git a/include/boost/sort/timsort.hpp b/include/boost/sort/timsort.hpp new file mode 100644 index 0000000..f6cc449 --- /dev/null +++ b/include/boost/sort/timsort.hpp @@ -0,0 +1,848 @@ +/* + Copyright (c) Fuji, Goro (gfx) , 2011 + Copyright (c) Alexander Zaitsev , 2016 + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + See http://www.boost.org/ for latest version. + + Ported from: + https://github.com/gfx/cpp-TimSort/blob/master/timsort.hpp +*/ + +#ifndef BOOST_SORT_TIMSORT_HPP +#define BOOST_SORT_TIMSORT_HPP + +#include +#include +#include +#include + +#include +#include + +#include + +namespace boost { namespace sort +{ + +// --------------------------------------- +// Declaration +// --------------------------------------- + +/** + * Same as std::stable_sort(first, last). + */ +template +void timsort(const RandomAccessIterator first, const RandomAccessIterator last); + +/** + * Same as std::stable_sort(first, last, compare). + */ +template +void timsort(const RandomAccessIterator first, const RandomAccessIterator last, Compare compare); + + +template +class Comparator +{ +public: + typedef Value value_type; + typedef Compare func_type; + + Comparator(Compare f) : less_(f) + { + } + + Comparator(const Comparator &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_; +}; + + +//Implementation +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 Comparator compare_t; + + static const int MIN_MERGE = 32; + + compare_t comp_; + + static const 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; + + struct run + { + iter_t base; + diff_t len; + + run(const iter_t b, const diff_t l) : base(b), len(l) + { + } + }; + + std::vector pending_; + + static void sort(const iter_t lo, const iter_t hi, compare_t c) + { + diff_t nRemaining = (hi - lo); + if (nRemaining < 2) + { + return; // nothing to do + } + + if (nRemaining < MIN_MERGE) + { + const diff_t initRunLen = countRunAndMakeAscending(lo, hi, c); + binarySort(lo, hi, lo + initRunLen, c); + return; + } + + TimSort ts(c); + const diff_t minRun = minRunLength(nRemaining); + iter_t cur = lo; + do + { + diff_t runLen = countRunAndMakeAscending(cur, hi, c); + + if (runLen < minRun) + { + const diff_t force = std::min(nRemaining, minRun); + binarySort(cur, cur + force, cur + runLen, c); + runLen = force; + } + + ts.pushRun(cur, runLen); + ts.mergeCollapse(); + + cur += runLen; + nRemaining -= runLen; + } + while (nRemaining != 0); + + ts.mergeForceCollapse(); + } // sort() + + static void binarySort(const iter_t lo, const iter_t hi, iter_t start, compare_t compare) + { + if (start == lo) + { + ++start; + } + for (; start < hi; ++start) + { + /*const*/ value_t pivot = boost::move(*start); + + const iter_t pos = std::upper_bound(lo, start, pivot, compare.less_function()); + for (iter_t p = start; p > pos; --p) + { + *p = boost::move(*(p - 1)); + } + *pos = boost::move(pivot); + } + } + + static diff_t countRunAndMakeAscending(const iter_t lo, const iter_t hi, compare_t compare) + { + iter_t runHi = lo + 1; + if (runHi == hi) + { + return 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))) + { + ++runHi; + } + } + + return runHi - lo; + } + + static diff_t minRunLength(diff_t n) + { + diff_t r = 0; + while (n >= MIN_MERGE) + { + r |= (n & 1); + n >>= 1; + } + return n + r; + } + + TimSort(compare_t c) : comp_(c), minGallop_(MIN_GALLOP) + { + } + + void pushRun(const iter_t runBase, const diff_t runLen) + { + pending_.push_back(run(runBase, runLen)); + } + + void mergeCollapse() + { + 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) + { + --n; + } + mergeAt(n); + } + else if (pending_[n].len <= pending_[n + 1].len) + { + mergeAt(n); + } + else + { + break; + } + } + } + + void mergeForceCollapse() + { + while (pending_.size() > 1) + { + diff_t n = pending_.size() - 2; + + if (n > 0 && pending_[n - 1].len < pending_[n + 1].len) + { + --n; + } + mergeAt(n); + } + } + + void mergeAt(const diff_t i) + { + const diff_t stackSize = pending_.size(); + + 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; + + pending_[i].len = len1 + len2; + + if (i == stackSize - 3) + { + pending_[i + 1] = pending_[i + 2]; + } + + pending_.pop_back(); + + const diff_t k = gallopRight(*base2, base1, len1, 0); + + base1 += k; + len1 -= k; + + if (len1 == 0) + { + return; + } + + len2 = gallopLeft(*(base1 + (len1 - 1)), base2, len2, len2 - 1); + if (len2 == 0) + { + return; + } + + if (len1 <= len2) + { + mergeLo(base1, len1, base2, len2); + } + else + { + mergeHi(base1, len1, base2, len2); + } + } + + template + diff_t gallopLeft(ref_t key, const Iter base, const diff_t len, const diff_t hint) + { + diff_t lastOfs = 0; + diff_t ofs = 1; + + if (comp_.gt(key, *(base + hint))) + { + const diff_t maxOfs = len - hint; + while (ofs < maxOfs && comp_.gt(key, *(base + (hint + ofs)))) + { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + + if (ofs <= 0) + { // int overflow + ofs = maxOfs; + } + } + if (ofs > maxOfs) + { + ofs = maxOfs; + } + + lastOfs += hint; + ofs += hint; + } + else + { + const diff_t maxOfs = hint + 1; + while (ofs < maxOfs && comp_.le(key, *(base + (hint - ofs)))) + { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + + if (ofs <= 0) + { + ofs = maxOfs; + } + } + if (ofs > maxOfs) + { + ofs = maxOfs; + } + + const diff_t tmp = lastOfs; + lastOfs = hint - ofs; + ofs = hint - tmp; + } + + return std::lower_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; + } + + template + diff_t gallopRight(ref_t key, const Iter base, const diff_t len, const diff_t hint) + { + diff_t ofs = 1; + diff_t lastOfs = 0; + + if (comp_.lt(key, *(base + hint))) + { + const diff_t maxOfs = hint + 1; + while (ofs < maxOfs && comp_.lt(key, *(base + (hint - ofs)))) + { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + + if (ofs <= 0) + { + ofs = maxOfs; + } + } + if (ofs > maxOfs) + { + ofs = maxOfs; + } + + const diff_t tmp = lastOfs; + lastOfs = hint - ofs; + ofs = hint - tmp; + } + else + { + const diff_t maxOfs = len - hint; + while (ofs < maxOfs && comp_.ge(key, *(base + (hint + ofs)))) + { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + + if (ofs <= 0) + { // int overflow + ofs = maxOfs; + } + } + if (ofs > maxOfs) + { + ofs = maxOfs; + } + + lastOfs += hint; + ofs += hint; + } + + return std::upper_bound(base + (lastOfs + 1), base + ofs, key, comp_.less_function()) - base; + } + + void mergeLo(const iter_t base1, diff_t len1, const iter_t base2, diff_t len2) + { + copy_to_tmp(base1, len1); + + tmp_iter_t cursor1 = tmp_.begin(); + iter_t cursor2 = base2; + iter_t dest = base1; + + *(dest++) = boost::move(*(cursor2++)); + if (--len2 == 0) + { + boost::move(cursor1, cursor1 + len1, dest); + return; + } + if (len1 == 1) + { + boost::move(cursor2, cursor2 + len2, dest); + *(dest + len2) = boost::move(*cursor1); + return; + } + + int minGallop(minGallop_); + + // outer: + while (true) + { + diff_t count1 = 0; + diff_t count2 = 0; + + bool break_outer = false; + do + { + if (comp_.lt(*cursor2, *cursor1)) + { + *(dest++) = boost::move(*(cursor2++)); + ++count2; + count1 = 0; + if (--len2 == 0) + { + break_outer = true; + break; + } + } + else + { + *(dest++) = boost::move(*(cursor1++)); + ++count1; + count2 = 0; + if (--len1 == 1) + { + break_outer = true; + break; + } + } + } + while ((count1 | count2) < minGallop); + if (break_outer) + { + break; + } + + do + { + count1 = gallopRight(*cursor2, cursor1, len1, 0); + if (count1 != 0) + { + boost::move_backward(cursor1, cursor1 + count1, dest + count1); + dest += count1; + cursor1 += count1; + len1 -= count1; + + if (len1 <= 1) + { + break_outer = true; + break; + } + } + *(dest++) = boost::move(*(cursor2++)); + if (--len2 == 0) + { + break_outer = true; + break; + } + + count2 = gallopLeft(*cursor1, cursor2, len2, 0); + if (count2 != 0) + { + boost::move(cursor2, cursor2 + count2, dest); + dest += count2; + cursor2 += count2; + len2 -= count2; + if (len2 == 0) + { + break_outer = true; + break; + } + } + *(dest++) = boost::move(*(cursor1++)); + if (--len1 == 1) + { + break_outer = true; + break; + } + + --minGallop; + } + while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); + if (break_outer) + { + break; + } + + if (minGallop < 0) + { + minGallop = 0; + } + minGallop += 2; + } // end of "outer" loop + + minGallop_ = std::min(minGallop, 1); + + if (len1 == 1) + { + boost::move(cursor2, cursor2 + len2, dest); + *(dest + len2) = boost::move(*cursor1); + } + else + { + boost::move(cursor1, cursor1 + len1, dest); + } + } + + void mergeHi(const iter_t base1, diff_t len1, const iter_t base2, diff_t len2) + { + copy_to_tmp(base2, len2); + + iter_t cursor1 = base1 + (len1 - 1); + tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); + iter_t dest = base2 + (len2 - 1); + + *(dest--) = boost::move(*(cursor1--)); + if (--len1 == 0) + { + boost::move(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); + return; + } + if (len2 == 1) + { + dest -= len1; + cursor1 -= len1; + boost::move_backward(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); + *dest = boost::move(*cursor2); + return; + } + + int minGallop(minGallop_); + + // outer: + while (true) + { + diff_t count2 = 0; + diff_t count1 = 0; + + bool break_outer = false; + do + { + if (comp_.lt(*cursor2, *cursor1)) + { + *(dest--) = boost::move(*(cursor1--)); + ++count1; + count2 = 0; + if (--len1 == 0) + { + break_outer = true; + break; + } + } + else + { + *(dest--) = boost::move(*(cursor2--)); + ++count2; + count1 = 0; + if (--len2 == 1) + { + break_outer = true; + break; + } + } + } + while ((count1 | count2) < minGallop); + if (break_outer) + { + break; + } + + do + { + count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1); + if (count1 != 0) + { + dest -= count1; + cursor1 -= count1; + len1 -= count1; + boost::move_backward(cursor1 + 1, cursor1 + (1 + count1), dest + (1 + count1)); + + if (len1 == 0) + { + break_outer = true; + break; + } + } + *(dest--) = boost::move(*(cursor2--)); + if (--len2 == 1) + { + break_outer = true; + break; + } + + count2 = len2 - gallopLeft(*cursor1, tmp_.begin(), len2, len2 - 1); + if (count2 != 0) + { + dest -= count2; + cursor2 -= count2; + len2 -= count2; + boost::move(cursor2 + 1, cursor2 + (1 + count2), dest + 1); + if (len2 <= 1) + { + break_outer = true; + break; + } + } + *(dest--) = boost::move(*(cursor1--)); + if (--len1 == 0) + { + break_outer = true; + break; + } + + minGallop--; + } + while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP)); + if (break_outer) + { + break; + } + + if (minGallop < 0) + { + minGallop = 0; + } + minGallop += 2; + } // end of "outer" loop + + minGallop_ = std::min(minGallop, 1); + + if (len2 == 1) + { + dest -= len1; + boost::move_backward(cursor1 + (1 - len1), cursor1 + 1, dest + (1 + len1)); + *dest = boost::move(*cursor2); + } + else + { + boost::move(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); + } + } + + void copy_to_tmp(const iter_t begin, const diff_t len) + { + tmp_.clear(); + tmp_.reserve(len); + boost::move(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); +}; + + +/*! \brief Timsort algorithm using random access iterators. + + \details Timsort was designed to take advantage of partial orderings that already exist in most real-world data. + Timsort operates by finding runs, subsequences of at least two elements that are either non-descending + (each element is equal to or greater than its predecessor) or strictly descending + (each element is lower than its predecessor). If it is descending, it must be strictly descending, + since descending runs are later reversed by a simple swap of elements from both ends converging in the middle. + After obtaining such a run in the given array, Timsort processes it, and then searches for the next run. + + \param[in] first Iterator pointer to first element. + \param[in] last Iterator pointing to one beyond the end of data. + + \pre [@c first, @c last) is a valid range. + \pre @c RandomAccessIter @c value_type is mutable. + \pre @c RandomAccessIter @c value_type is LessThanComparable + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + functors, or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. + \warning Invalid arguments cause undefined behaviour. + + \remark O(N*log(N)) operations worst-case, where: + \remark * N is @c last - @c first, + +*/ + +template +void timsort(const RandomAccessIterator first, const RandomAccessIterator last) +{ + typedef typename std::iterator_traits::value_type value_type; + timsort(first, last, std::less()); +} + +/*! \brief Timsort algorithm using random access iterators and a comparison functor. + + \details Timsort was designed to take advantage of partial orderings that already exist in most real-world data. + Timsort operates by finding runs, subsequences of at least two elements that are either non-descending + (each element is equal to or greater than its predecessor) or strictly descending + (each element is lower than its predecessor). If it is descending, it must be strictly descending, + since descending runs are later reversed by a simple swap of elements from both ends converging in the middle. + After obtaining such a run in the given array, Timsort processes it, and then searches for the next run. + + \param[in] first Iterator pointer to first element. + \param[in] last Iterator pointing to one beyond the end of data. + \param[in] compare A binary functor that returns whether the first element passed to it should go before the second in order. + + \pre [@c first, @c last) is a valid range. + \pre @c RandomAccessIter @c value_type is mutable. + \pre @c RandomAccessIter @c value_type is LessThanComparable + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + functors, or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. + \warning Invalid arguments cause undefined behaviour. + + \remark O(N*log(N)) operations worst-case, where: + \remark * N is @c last - @c first, + +*/ + +template +void timsort(const RandomAccessIterator first, const RandomAccessIterator last, Compare compare) +{ + TimSort::sort(first, last, compare); +} + +/*! \brief Timsort algorithm using range. + + \details Timsort was designed to take advantage of partial orderings that already exist in most real-world data. + Timsort operates by finding runs, subsequences of at least two elements that are either non-descending + (each element is equal to or greater than its predecessor) or strictly descending + (each element is lower than its predecessor). If it is descending, it must be strictly descending, + since descending runs are later reversed by a simple swap of elements from both ends converging in the middle. + After obtaining such a run in the given array, Timsort processes it, and then searches for the next run. + + \param[in] range Range [first, last) for sorting. + + \pre [@c first, @c last) is a valid range. + \pre @c RandomAccessIter @c value_type is mutable. + \pre @c RandomAccessIter @c value_type is LessThanComparable + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + functors, or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. + \warning Invalid arguments cause undefined behaviour. + + \remark O(N*log(N)) operations worst-case, where: + \remark * N is @c last - @c first, + +*/ + +template +void timsort(Range& range) +{ + timsort(boost::begin(range), boost::end(range)); +} + +/*! \brief Timsort algorithm using range and a comparison functor. + + \details Timsort was designed to take advantage of partial orderings that already exist in most real-world data. + Timsort operates by finding runs, subsequences of at least two elements that are either non-descending + (each element is equal to or greater than its predecessor) or strictly descending + (each element is lower than its predecessor). If it is descending, it must be strictly descending, + since descending runs are later reversed by a simple swap of elements from both ends converging in the middle. + After obtaining such a run in the given array, Timsort processes it, and then searches for the next run. + + \param[in] range Range [first, last) for sorting. + \param[in] compare A binary functor that returns whether the first element passed to it should go before the second in order. + + \pre [@c first, @c last) is a valid range. + \pre @c RandomAccessIter @c value_type is mutable. + \pre @c RandomAccessIter @c value_type is LessThanComparable + \post The elements in the range [@c first, @c last) are sorted in ascending order. + + \return @c void. + + \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), + functors, or any operations on iterators throw. + + \warning Throwing an exception may cause data loss. + \warning Invalid arguments cause undefined behaviour. + + \remark O(N*log(N)) operations worst-case, where: + \remark * N is @c last - @c first, + +*/ + +template +void timsort(Range& range, Compare compare) +{ + timsort(boost::begin(range), boost::end(range), compare); +} + +} +} + +#endif // BOOST_SORT_TIMSORT_HPP \ No newline at end of file diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 32c1815..5d8a8d7 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -19,7 +19,8 @@ import testing ; : : : : string_sort ] [ run sort_detail_test.cpp : : : : sort_detail ] - + [ run timsort_test.cpp + : : : : timsort ] ; } diff --git a/test/timsort_test.cpp b/test/timsort_test.cpp new file mode 100644 index 0000000..231f070 --- /dev/null +++ b/test/timsort_test.cpp @@ -0,0 +1,126 @@ +/* + Copyright (c) Alexander Zaitsev , 2016 + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + See http://www.boost.org/ for latest version. +*/ + +#include +// Include unit test framework +#include +#include +#include +#include +#include + +using namespace std; +using namespace boost::sort; + +struct A +{ + int a; + int b; + + A(int val_a, int val_b) : a(val_a), b(val_b) {} + + bool operator<(const A& val) const + { + return a < val.a; + } + + bool operator==(const A& val) const + { + return a == val.a && b == val.b; + } +}; + + +boost::int32_t +rand_32(bool sign = true) { + boost::int32_t result = rand() | (rand()<< 16); + if (rand() % 2) + result |= 1 << 15; + //Adding the sign bit + if (sign && (rand() % 2)) + result *= -1; + return result; +} + +void stable_test() +{ + vector base_vec; + for(size_t i = 0; i < 1000; ++i) + { + base_vec.push_back(A(rand() % 6, rand() % 11)); + } + + vector test_vec = base_vec; + vector sorted_vec = base_vec; + stable_sort(sorted_vec.begin(), sorted_vec.end()); + timsort(test_vec); + BOOST_CHECK(test_vec == sorted_vec); +} + +void random_test() +{ + // Prepare inputs + vector base_vec; + size_t count = 100000; + //Generating semirandom numbers + for (size_t u = 0; u < count; ++u) + { + base_vec.push_back(rand_32()); + } + vector sorted_vec = base_vec; + vector test_vec = base_vec; + std::sort(sorted_vec.begin(), sorted_vec.end()); + //Testing basic call + timsort(test_vec.begin(), test_vec.end()); + BOOST_CHECK(test_vec == sorted_vec); + //Boost.Range variant + test_vec = base_vec; + timsort(test_vec); + BOOST_CHECK(test_vec == sorted_vec); + //Functor + test_vec = base_vec; + timsort(test_vec.begin(), test_vec.end(), less()); + BOOST_CHECK(test_vec == sorted_vec); + //reverse order + test_vec = base_vec; + sorted_vec = base_vec; + std::sort(test_vec.begin(), test_vec.end(), greater()); + std::sort(sorted_vec.begin(), sorted_vec.end()); + timsort(test_vec.begin(), test_vec.end()); + BOOST_CHECK(test_vec == sorted_vec); +} + + +template +void corner_test(T val) +{ + vector test_vec; + boost::sort::timsort(test_vec.begin(), test_vec.end()); + BOOST_CHECK(test_vec.size() == 0); + test_vec.push_back(val); + boost::sort::timsort(test_vec.begin(), test_vec.end()); + BOOST_CHECK(test_vec.size() == 1); + BOOST_CHECK(test_vec[0] == val); +} + + +// test main +int test_main( int, char*[] ) +{ + srand(1); + + random_test(); + stable_test(); + + //Corner tests + corner_test(42); + corner_test(42.0); + corner_test(0); + corner_test(A(42, 322)); + return 0; +} \ No newline at end of file