/* ***************************************************************************** * kblib is a general utility library for C++14 and C++17, intended to provide * performant high-level abstractions and more expressive ways to do simple * things. * * Copyright (c) 2021 killerbee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ /** * @file * @brief {WIP} Provides a fast and generic sorting interface. * * @author killerbee * @date 2019-2021 * @copyright GNU General Public Licence v3.0 */ #ifndef SORT_H #define SORT_H #include "algorithm.h" #include "bits.h" #include "fakestd.h" #include "simple.h" #include #include namespace KBLIB_NS { /** * @brief In-place insertion sort. As is usual, it is stable. * Provides as a guarantee that it will perform no moves on sorted input. * * @remark Complexity: * @remark Average case O(n^2) * @remark Best-case Θ(n) (for sorted input) * @remark worst-case O(n^2) (for reverse-sorted input) * * @param begin,end The range to sort * @param compare The comparison predicate */ template > constexpr auto insertion_sort( const RandomAccessIt begin, const RandomAccessIt end, Compare&& compare = {}) noexcept(noexcept(swap(*begin, *begin)) and noexcept(compare(*begin, *begin))) -> void { // Trivial inputs are trivially already sorted. if (end - begin <= 1) { return; } for (auto pos = begin + 1; pos != end; ++pos) { // This search is linear, not binary, because insertion_sort is meant // to never be used for arrays large enough for binary search. auto index = kblib::find_if(begin, pos, [&compare, pos](const auto& a) { return compare(*pos, a); }); kblib::rotate(index, pos, pos + 1); } return; } /** * @brief Out-of-place insertion sort, which does not modify the input. * Provides as a guarantee that it will perform no moves on sorted input. * * @remark Complexity: * @remark Average case O(n^2) * @remark Best-case Θ(n) (for sorted input) * @remark worst-case O(n^2) (for reverse-sorted input) * * @param begin,end The input range * @param d_begin,d_end The output range * @param compare The comparison predicate */ template > constexpr auto insertion_sort_copy( const RandomAccessIt begin, const RandomAccessIt end, const RandomAccessIt2 d_begin, const RandomAccessIt2 d_end, Compare&& compare = {}) noexcept(noexcept(detail_algorithm:: shift_backward(d_begin, d_begin, d_end)) and noexcept(*d_begin = *begin)) -> void { const auto dist = end - begin; assert(end - begin == d_end - d_begin); if (dist == 0) { return; } else if (dist == 1) { // ranges of 1 are trivial to sort *d_begin = *begin; return; } else if (dist == 2) { // Special case distance=2 to perform no swaps, and because the loop // for the general case assumes 3 or more elements. if (compare(*begin, *(begin + 1))) { *d_begin = *begin; *(d_begin + 1) = *(begin + 1); return; } else { *d_begin = *(begin + 1); *(d_begin + 1) = *begin; } } else { // This loop writes in reverse because shifting backwards can be done // in a forward pass, making it simpler than a forward shift, and is // sufficient for the algorithm, and reads in reverse to avoid worst- // case complexity for sorted inputs, given that it writes in reverse. auto read = std::prev(end); auto write = std::prev(d_end); // push first element to get the loop invariant *write = *read; do { --read, --write; // This search is linear, not binary, because insertion_sort_copy // is meant to never be used for arrays large enough for binary // search. #if 1 auto index = kblib::find_if( write + 1, d_end, [&compare, read](const auto& a) { return not compare(a, *read); }); #else auto index = kblib::find_if(write + 1, d_end, [&compare, read](const auto& a) { if (stable) { // find first element greater than // *read return compare(*read, a); } else { // find first element greater than // or equal to *read return ! compare(a, *read); } }); #endif detail_algorithm::shift_backward(write, write + 1, index); *(index - 1) = *read; } while (write != d_begin); return; } } /** * @brief An adaptive sort which, at the cost of some additional work (time * complexity Θ(sqrt(n))), avoids quadratic behavior for reverse-sorted * inputs (and, in fact, handles them in optimal time). It's still possible * to fool it, but it requires a staggered input, which is a highly unlikely * shape for random data. * * @remark Complexity: * @remark Average case O(n^2) * @remark Best-case Θ(n + sqrt(n)) (for sorted input) * @remark worst-case O(n^2) (for reverse-sorted input) * * @param begin,end The input range * @param d_begin,d_end The output range * @param compare The comparison predicate */ template > constexpr auto adaptive_insertion_sort_copy( const RandomAccessIt begin, const RandomAccessIt end, const RandomAccessIt2 d_begin, const RandomAccessIt2 d_end, Compare&& compare = {}) noexcept(noexcept(detail_algorithm:: shift_backward(d_begin, d_begin, d_end)) and noexcept(*d_begin = *begin)) -> void { const auto dist = end - begin; // For trivial inputs, don't bother doing anything if (dist < 3) { insertion_sort_copy(begin, end, d_begin, d_end, std::forward(compare)); } // A very rudimentary way of estimating the sortedness of the input, by // counting the relative number of ascending and descending adjacent pairs // within the first sqrt(n) elements. const auto scan_end = begin + static_cast(std::sqrt(static_cast(dist))); std::ptrdiff_t dir{}; for (auto pos = begin; pos != scan_end - 1; ++pos) { if (compare(*pos, *(pos + 1))) { ++dir; } else if (compare(*(pos + 1), *pos)) { --dir; } } if (dir >= 0) { insertion_sort_copy(begin, end, d_begin, d_end, std::forward(compare)); } else { // If the input is predominantly in reverse order, sort it in reverse // IMPORTANT: the reverse iterators go end, begin insertion_sort_copy(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), d_begin, d_end, std::forward(compare)); } } template struct is_radix_sortable : std::false_type {}; template struct is_radix_sortable::value>> : std::true_type {}; template struct is_radix_sortable::value>> : std::true_type {}; template struct is_radix_sortable, void> : std::true_type {}; template struct is_radix_sortable< T, void_if_t and std::is_integral::value>> : std::true_type {}; template constexpr bool is_radix_sortable_v = is_radix_sortable::value; #if __cpp_lib_byte template constexpr bool is_byte_v = std::is_same::type, std::byte>::value; #else template constexpr bool is_byte_v = false; #endif template KBLIB_NODISCARD constexpr auto byte_count(T) noexcept -> enable_if_t::value, std::size_t> { auto res = kblib::div(kblib::bits_of + std::is_signed::value, CHAR_BIT); return to_unsigned(res.quot + (res.rem != 0)); } template KBLIB_NODISCARD constexpr auto byte_count(T) noexcept -> enable_if_t::value, std::size_t> { using U = typename std::underlying_type::type; auto res = kblib::div(kblib::bits_of + std::is_signed::value, CHAR_BIT); return to_unsigned(res.quot + (res.rem != 0)); } template KBLIB_NODISCARD constexpr auto byte_count(T*) noexcept -> std::size_t { return byte_count(std::uintptr_t{}); } template KBLIB_NODISCARD constexpr auto byte_count(const std::unique_ptr&) noexcept -> std::size_t { return byte_count(std::uintptr_t{}); } template KBLIB_NODISCARD constexpr auto byte_count(const T& x) noexcept -> enable_if_t< is_linear_container_v< T> and (std::is_integral::value or is_byte_v) and sizeof(typename T::value_type) == 1, std::size_t> { return fakestd::size(x); } template constexpr auto byte_count(const T& x) noexcept -> enable_if_t< is_linear_container_v and std::is_integral::value and (sizeof(typename T::value_type) > 1), std::size_t> { using value_type = typename T::value_type; return fakestd::size(x) * byte_count(value_type{}); } // Can be used to detect unsupported types with overload resolution. // cert-dcl50-cpp: An exception to the rule for defining as deleted is not // implemented by clang-tidy. This definition does not pose a security risk. constexpr auto byte_count(...) -> void = delete; template KBLIB_NODISCARD constexpr auto get_byte_index(T x, std::size_t idx) noexcept -> enable_if_t::value, unsigned char> { return static_cast(x >> (idx * CHAR_BIT)); } template KBLIB_NODISCARD constexpr auto get_byte_index(const T& x, std::size_t idx) noexcept -> enable_if_t::value, unsigned char> { return static_cast(etoi(x) >> (idx * CHAR_BIT)); } // Note that the ordering created by casting to std::uintptr_t may not agree // with the ordering imposed by std::less. template KBLIB_NODISCARD auto get_byte_index(T* x, std::size_t idx) noexcept -> unsigned char { return get_byte_index(byte_cast(x), idx); } template KBLIB_NODISCARD auto get_byte_index(const std::unique_ptr& x, std::size_t idx) noexcept -> unsigned char { return get_byte_index(byte_cast(x.get()), idx); } template KBLIB_NODISCARD constexpr auto get_byte_index(const T& x, std::size_t idx) noexcept -> enable_if_t< is_linear_container_v< T> and (std::is_integral::value or is_byte_v) and sizeof(typename T::value_type) == 1, unsigned char> { return static_cast(x[idx]); } template KBLIB_NODISCARD constexpr auto get_byte_index(const T& x, std::size_t idx) noexcept -> enable_if_t and std::is_integral::value and (sizeof(typename T::value_type) > 1), unsigned char> { using value_type = typename T::value_type; auto bytes_per = byte_count(value_type{}); return static_cast(to_unsigned(x[idx / bytes_per]) >> (idx % bytes_per * CHAR_BIT)); } template KBLIB_NODISCARD constexpr auto get_byte_index(const T& x, std::size_t idx) noexcept -> enable_if_t and std::is_enum::value, unsigned char> { using U = typename std::underlying_type::type; auto bytes_per = byte_count(U{}); return static_cast(to_unsigned(etoi(x[idx / bytes_per])) >> (idx % bytes_per * CHAR_BIT)); } template struct is_trivial_transformation : bool_constant::value> {}; template <> struct is_trivial_transformation : std::true_type {}; namespace detail_sort { template constexpr auto sort(RandomAccessIt, const RandomAccessIt, Compare) -> void {} template constexpr auto stable_sort(RandomAccessIt, const RandomAccessIt, Compare) -> void {} template constexpr auto merge_sort(RandomAccessIt begin, const RandomAccessIt end, Compare cmp) -> void { if (end - begin <= small_size) { insertion_sort(begin, end, cmp); return; } else { auto middle = begin + (end - begin) / 2; merge_sort(begin, middle, cmp); merge_sort(middle, end, cmp); std::inplace_merge(begin, middle, end, cmp); return; } } template constexpr auto heap_sort(RandomAccessIt begin, const RandomAccessIt end, Compare cmp) -> void { std::make_heap(begin, end, cmp); while (begin != end) { std::pop_heap(begin, end--, cmp); } return; } enum class sort_direction { ascending, descending }; template constexpr auto radix_sort_i(RandomAccessIt begin, const RandomAccessIt end, Projection proj) -> void { using E = decay_t; const auto max_bytes = byte_count(E{}); for (const auto byte : range(max_bytes)) { for (auto it = begin; it != end; ++it) { } } } template constexpr auto radix_sort_s(RandomAccessIt begin, const RandomAccessIt end, Projection proj) -> void { if (begin != end) { const auto max_bytes = kblib::accumulate( begin, end, std::size_t{}, [&](auto m, const auto& el) { return max(m, byte_count(proj(*el))); }); } const auto max_bytes = [&] { std::size_t max_bytes{}; for (const auto v : indirect(begin, end)) { max_bytes = max(max_bytes, byte_count(v)); } return max_bytes; }(); } template constexpr auto make_array_for(std::false_type) -> kblib::containing_ptr> { return {}; } template auto make_array_for(std::true_type) -> kblib::heap_value> { return {in_place_agg}; } template constexpr auto counting_sort(RandomAccessIt1 begin, const RandomAccessIt1 end, RandomAccessIt2 d_begin, Projection proj) -> void { using E = decay_t; constexpr auto size = std::numeric_limits>::max(); auto count = make_array_for(bool_constant<(size > 256)>{}); for (auto cur = begin; cur != end; ++cur) { ++count->at(proj(*cur)); } kblib::transform_exclusive_scan(begin(*count), end(*count), begin(*count), std::size_t{}, std::plus<>{}, proj); for (auto cur = end; cur != begin; --cur) { auto el = std::prev(cur); d_begin[--count->at(proj(*el))] = *el; } return; } /** * @brief Sort data after applying an arbitrary transformation to it. The * primary template handles the general case of arbitrary transformation * and arbitrary compare predicate. */ template ::value, bool = std::is_fundamental::value, bool = is_radix_sortable_v, bool = std::is_integral::value> struct sort_transform_impl { static constexpr auto inplace(RandomAccessIt begin, const RandomAccessIt end, UnaryOperation&& transform, BinaryPredicate&& compare) -> void { auto comp = [&compare, &transform](RandomAccessIt a, RandomAccessIt b) { return kblib::invoke(compare, kblib::invoke(transform, *a), kblib::invoke(transform, *b)); }; if (end - begin < small_size) { insertion_sort(begin, end, comp); return; } else { /// TODO(killerbee13): write efficient inplace sort_transform } } static constexpr auto scratch(RandomAccessIt begin, const RandomAccessIt end, UnaryOperation&& transform, BinaryPredicate&& compare) -> void { auto comp = [&compare, &transform](RandomAccessIt a, RandomAccessIt b) { return kblib::invoke(compare, kblib::invoke(transform, *a), kblib::invoke(transform, *b)); }; if (end - begin < small_size) { insertion_sort(begin, end, comp); return; } else { /// TODO(killerbee13): write efficient sort_transform } } template static constexpr auto copy(RandomAccessIt begin, const RandomAccessIt end, RandomAccessIt2 d_begin, RandomAccessIt2 d_end, UnaryOperation&& transform, BinaryPredicate&& compare) -> void { auto comp = [&compare, &transform](RandomAccessIt a, RandomAccessIt b) { return kblib::invoke(compare, kblib::invoke(transform, *a), kblib::invoke(transform, *b)); }; if (end - begin < small_size) { insertion_sort_copy(begin, end, d_begin, d_end, comp); return; } else { /// TODO(killerbee13): write efficent sort_transform_copy } } }; #if 1 /** * @brief Sort implementation for pointer to member object of non-fundamental * type, so sort keys are constant time to extract (this is most similar to * a general sort()) */ template struct sort_transform_impl { static constexpr auto inplace(KBLIB_UNUSED RandomAccessIt begin, KBLIB_UNUSED const RandomAccessIt end, KBLIB_UNUSED UnaryOperation&& transform, KBLIB_UNUSED BinaryPredicate&& compare) -> void { auto comp = [&compare, &transform](RandomAccessIt a, RandomAccessIt b) { return kblib::invoke(compare, kblib::invoke(transform, *a), kblib::invoke(transform, *b)); }; /// TODO(killerbee13): write efficient sort_transform } }; #endif /** * @brief Sort implementation for pointer to member object of fundamental * non-integral type with default sorting, so sort keys are constant time * to extract and compare */ template struct sort_transform_impl, SortKey, small_size, true, true, false, false> { static constexpr auto inplace(KBLIB_UNUSED RandomAccessIt begin, KBLIB_UNUSED const RandomAccessIt end, KBLIB_UNUSED UnaryOperation&& transform, KBLIB_UNUSED std::less&& compare) -> void { /// TODO(killerbee13): write efficient inplace sort_transform } }; /** * @brief Sort implementation for pointer to member object of fundamental * non-integral type with reverse sorting, so sort keys are constant time * to extract and compare */ template struct sort_transform_impl, SortKey, small_size, true, true, false, false> { static constexpr auto inplace(KBLIB_UNUSED RandomAccessIt begin, KBLIB_UNUSED const RandomAccessIt end, KBLIB_UNUSED UnaryOperation&& transform, KBLIB_UNUSED std::greater&& compare) -> void { /// TODO(killerbee13): write efficient inplace sort_transform } }; /** * @brief Sort implementation for pointer to member object of integral * type with default sorting, so we can do radix sort */ template struct sort_transform_impl, SortKey, small_size, true, true, true, true> { static constexpr auto inplace(KBLIB_UNUSED RandomAccessIt begin, KBLIB_UNUSED const RandomAccessIt end, KBLIB_UNUSED UnaryOperation&& transform, KBLIB_UNUSED std::less&& compare) -> void { /// TODO(killerbee13): write efficient inplace radix sort_transform } }; /** * @brief Sort implementation for pointer to member object of integral * type with reverse sorting, so we can do radix sort */ template struct sort_transform_impl, SortKey, small_size, true, true, true, true> { static constexpr auto inplace(KBLIB_UNUSED RandomAccessIt begin, KBLIB_UNUSED const RandomAccessIt end, KBLIB_UNUSED UnaryOperation&& transform, KBLIB_UNUSED std::greater&& compare) -> void { /// TODO(killerbee13): write efficient inplace radix sort_transform } }; #if 1 // Do string radix sorting /** * @brief Sort implementation for key of radix sortable type * type with default sorting */ template struct sort_transform_impl, SortKey, small_size, M, false, true, false> { static constexpr auto inplace(KBLIB_UNUSED RandomAccessIt begin, KBLIB_UNUSED const RandomAccessIt end, KBLIB_UNUSED UnaryOperation&& transform, KBLIB_UNUSED std::less&& compare) -> void { /// TODO(killerbee13): write efficient inplace radix sort_transform } }; /** * @brief Sort implementation for key of radix sortable type * type with reverse sorting */ template struct sort_transform_impl, SortKey, small_size, M, false, true, false> { static constexpr auto inplace(KBLIB_UNUSED RandomAccessIt begin, KBLIB_UNUSED const RandomAccessIt end, KBLIB_UNUSED UnaryOperation&& transform, KBLIB_UNUSED std::greater&& compare) -> void { /// TODO(killerbee13): write efficient inplace radix sort_transform } }; #endif } // namespace detail_sort /** * @brief Sorts a range after applying a transformation. * * Complexity: worst-case O(N log(N)), where N = std::distance(begin, end) * comparisons and swaps. * * @param begin,end The range to sort * @param transform A transformer (such as unary function or pointer to * member) which will be applied to each object before comparing it. A * transformer may not modify the object it is called with. * * @param compare A comparison predicate which returns true if the first * argument shall be ordered before the second. BinaryPredicate must meet the * requirements of the Compare named requirement. */ template constexpr auto sort_transform(RandomAccessIt begin, RandomAccessIt end, UnaryOperation&& transform, BinaryPredicate&& compare) -> void { detail_sort::sort_transform_impl< RandomAccessIt, UnaryOperation, BinaryPredicate, decltype(kblib::invoke(transform, *begin))>:: inplace(begin, end, std::forward(transform), std::forward(compare)); } /** * @brief * * @param begin,end The range to sort * @param transform The transformation to apply */ template constexpr auto sort_transform(RandomAccessIt begin, RandomAccessIt end, UnaryOperation&& transform) -> void { detail_sort::sort_transform_impl< RandomAccessIt, UnaryOperation, std::less<>, decltype(kblib::invoke(transform, *begin))>::inplace(begin, end, std::forward( transform), std::less<>{}); } /** * @brief Sorts a range. * * Complexity: worst-case O(N log(N)), where N = std::distance(begin, end) * comparisons and swaps. * * @param begin,end The range to sort * * @param compare A comparison predicate which returns true if the first * argument shall be ordered before the second. BinaryPredicate must meet the * requirements of the Compare named requirement. */ template constexpr auto sort(RandomAccessIt begin, RandomAccessIt end, BinaryPredicate&& compare) -> void { detail_sort::sort_transform_impl< RandomAccessIt, identity, BinaryPredicate, decltype(kblib::invoke(transform, *begin))>::inplace(begin, end, identity{}, std::forward( compare)); } /** * @brief * * @param begin,end The range to sort */ template constexpr auto sort(RandomAccessIt begin, RandomAccessIt end) -> void { detail_sort::sort_transform_impl< RandomAccessIt, identity, std::less<>, decltype(kblib::invoke(transform, *begin))>::inplace(begin, end, identity{}, std::less<>{}); } } // namespace KBLIB_NS #endif // SORT_H