/// \file // Range v3 library // // Copyright Eric Niebler 2014-present // // Use, modification and distribution is subject to 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) // // Project home: https://github.com/ericniebler/range-v3 // //===----------------------------------------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is dual licensed under the MIT and the University of Illinois Open // Source Licenses. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #ifndef RANGES_V3_ALGORITHM_NTH_ELEMENT_HPP #define RANGES_V3_ALGORITHM_NTH_ELEMENT_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ranges { /// \cond namespace detail { // stable, 2-3 compares, 0-2 swaps template auto sort3(I x, I y, I z, C & pred, P & proj) -> CPP_ret(unsigned)( // requires forward_iterator && indirect_relation>) { unsigned r = 0; if(!invoke(pred, invoke(proj, *y), invoke(proj, *x))) // if x <= y { if(!invoke(pred, invoke(proj, *z), invoke(proj, *y))) // if y <= z return r; // x <= y && y <= z // x <= y && y > z ranges::iter_swap(y, z); // x <= z && y < z r = 1; if(invoke(pred, invoke(proj, *y), invoke(proj, *x))) // if x > y { ranges::iter_swap(x, y); // x < y && y <= z r = 2; } return r; // x <= y && y < z } if(invoke(pred, invoke(proj, *z), invoke(proj, *y))) // x > y, if y > z { ranges::iter_swap(x, z); // x < y && y < z r = 1; return r; } ranges::iter_swap(x, y); // x > y && y <= z r = 1; // x < y && x <= z if(invoke(pred, invoke(proj, *z), invoke(proj, *y))) // if y > z { ranges::iter_swap(y, z); // x <= y && y < z r = 2; } return r; } // x <= y && y <= z template auto selection_sort(I first, I last, C & pred, P & proj) -> CPP_ret(void)( // requires bidirectional_iterator && indirect_relation>) { RANGES_EXPECT(first != last); for(I lm1 = ranges::prev(last); first != lm1; ++first) { I i = ranges::min_element(first, last, std::ref(pred), std::ref(proj)); if(i != first) ranges::iter_swap(first, i); } } } // namespace detail /// \endcond /// \addtogroup group-algorithms /// @{ RANGES_BEGIN_NIEBLOID(nth_element) /// \brief function template \c nth_element template auto RANGES_FUN_NIEBLOID(nth_element)( I first, I nth, S end_, C pred = C{}, P proj = P{}) // ->CPP_ret(I)( // requires random_access_iterator && sortable) { I last = ranges::next(nth, end_), end_orig = last; // C is known to be a reference type using difference_type = iter_difference_t; difference_type const limit = 7; while(true) { restart: if(nth == last) return end_orig; difference_type len = last - first; switch(len) { case 0: case 1: return end_orig; case 2: if(invoke(pred, invoke(proj, *--last), invoke(proj, *first))) ranges::iter_swap(first, last); return end_orig; case 3: { I m = first; detail::sort3(first, ++m, --last, pred, proj); return end_orig; } } if(len <= limit) { detail::selection_sort(first, last, pred, proj); return end_orig; } // len > limit >= 3 I m = first + len / 2; I lm1 = last; unsigned n_swaps = detail::sort3(first, m, --lm1, pred, proj); // *m is median // partition [first, m) < *m and *m <= [m, last) //(this inhibits tossing elements equivalent to m around unnecessarily) I i = first; I j = lm1; // j points beyond range to be tested, *lm1 is known to be <= *m // The search going up is known to be guarded but the search coming down // isn't. Prime the downward search with a guard. if(!invoke(pred, invoke(proj, *i), invoke(proj, *m))) // if *first == *m { // *first == *m, *first doesn't go in first part // manually guard downward moving j against i while(true) { if(i == --j) { // *first == *m, *m <= all other elements // Parition instead into [first, i) == *first and *first < [i, // last) ++i; // first + 1 j = last; if(!invoke( pred, invoke(proj, *first), invoke( proj, *--j))) // we need a guard if *first == *(last-1) { while(true) { if(i == j) return end_orig; // [first, last) all equivalent // elements if(invoke( pred, invoke(proj, *first), invoke(proj, *i))) { ranges::iter_swap(i, j); ++n_swaps; ++i; break; } ++i; } } // [first, i) == *first and *first < [j, last) and j == last - // 1 if(i == j) return end_orig; while(true) { while( !invoke(pred, invoke(proj, *first), invoke(proj, *i))) ++i; while(invoke( pred, invoke(proj, *first), invoke(proj, *--j))) ; if(i >= j) break; ranges::iter_swap(i, j); ++n_swaps; ++i; } // [first, i) == *first and *first < [i, last) // The first part is sorted, if(nth < i) return end_orig; // nth_element the second part // nth_element(i, nth, last, pred); first = i; goto restart; } if(invoke(pred, invoke(proj, *j), invoke(proj, *m))) { ranges::iter_swap(i, j); ++n_swaps; break; // found guard for downward moving j, now use unguarded // partition } } } ++i; // j points beyond range to be tested, *lm1 is known to be <= *m // if not yet partitioned... if(i < j) { // known that *(i - 1) < *m while(true) { // m still guards upward moving i while(invoke(pred, invoke(proj, *i), invoke(proj, *m))) ++i; // It is now known that a guard exists for downward moving j while(!invoke(pred, invoke(proj, *--j), invoke(proj, *m))) ; if(i >= j) break; ranges::iter_swap(i, j); ++n_swaps; // It is known that m != j // If m just moved, follow it if(m == i) m = j; ++i; } } // [first, i) < *m and *m <= [i, last) if(i != m && invoke(pred, invoke(proj, *m), invoke(proj, *i))) { ranges::iter_swap(i, m); ++n_swaps; } // [first, i) < *i and *i <= [i+1, last) if(nth == i) return end_orig; if(n_swaps == 0) { // We were given a perfectly partitioned sequence. Coincidence? if(nth < i) { // Check for [first, i) already sorted j = m = first; while(++j != i) { if(invoke(pred, invoke(proj, *j), invoke(proj, *m))) // not yet sorted, so sort goto not_sorted; m = j; } // [first, i) sorted return end_orig; } else { // Check for [i, last) already sorted j = m = i; while(++j != last) { if(invoke(pred, invoke(proj, *j), invoke(proj, *m))) // not yet sorted, so sort goto not_sorted; m = j; } // [i, last) sorted return end_orig; } } not_sorted: // nth_element on range containing nth if(nth < i) { // nth_element(first, nth, i, pred); last = i; } else { // nth_element(i+1, nth, last, pred); first = ++i; } } return end_orig; } /// \overload template auto RANGES_FUN_NIEBLOID(nth_element)( Rng && rng, iterator_t nth, C pred = C{}, P proj = P{}) // ->CPP_ret(safe_iterator_t)( // requires random_access_range && sortable, C, P>) { return (*this)( begin(rng), std::move(nth), end(rng), std::move(pred), std::move(proj)); } RANGES_END_NIEBLOID(nth_element) namespace cpp20 { using ranges::nth_element; } /// @} } // namespace ranges #endif // include guard