diff --git a/doc/indirect_sort.qbk b/doc/indirect_sort.qbk index 3c78936..6ac6de5 100644 --- a/doc/indirect_sort.qbk +++ b/doc/indirect_sort.qbk @@ -34,7 +34,7 @@ Or maybe you don't need the elements to actually be sorted - you just want to tr ``` -Assume that instead of an "array of structures", you have a "struct of arrays" +Assume that instead of an "array of structures", you have a "struct of arrays". ``` struct AType { Type0 key; @@ -70,12 +70,20 @@ Sorting the first one is easy, because each set of fields (`key`, `value1`, `val The function `indirect_sort` returns a `vector` containing the permutation necessary to put the input sequence into a sorted order. One version uses `std::less` to do the comparisons; the other lets the caller pass predicate to do the comparisons. +There is also a variant called `indirect_stable_sort`; it bears the same relation to `indirect_sort` that `std::stable_sort` does to `std::sort`. + ``` template std::vector indirect_sort (RAIterator first, RAIterator last); template std::vector indirect_sort (RAIterator first, RAIterator last, BinaryPredicate pred); + +template +std::vector indirect_stable_sort (RAIterator first, RAIterator last); + +template +std::vector indirect_stable_sort (RAIterator first, RAIterator last, BinaryPredicate pred); ``` [heading Examples] @@ -86,12 +94,14 @@ std::vector indirect_sort (RAIterator first, RAIterator last, BinaryPred [heading Complexity] -Both of the variants of `indirect_sort` run in ['O(N lg N)] time; they are not more (or less) efficient than `std::sort`. There is an extra layer of indirection on each comparison, but all off the swaps are done on values of type `size_t` +Both of the variants of `indirect_sort` run in ['O(N lg N)] time; they are not more (or less) efficient than `std::sort`. There is an extra layer of indirection on each comparison, but all of the swaps are done on values of type `size_t` [heading Exception Safety] [heading Notes] +In numpy, this algorithm is known as `argsort`. + [endsect] [/ File indirect_sort.qbk diff --git a/test/indirect_sort_test.cpp b/test/indirect_sort_test.cpp index 21bd3bd..dfef087 100644 --- a/test/indirect_sort_test.cpp +++ b/test/indirect_sort_test.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #define BOOST_TEST_MAIN #include @@ -20,7 +21,7 @@ #include #include -typedef std::vector Permutation; +using boost::algorithm::Permutation; // A permutation of size N is a sequence of values in the range [0..N) // such that no value appears more than once in the permutation. @@ -46,6 +47,9 @@ struct indirect_comp { Comp comp_; }; + //// ======================= + //// ==== indirect_sort ==== + //// ======================= template void test_one_sort(Iter first, Iter last) { Permutation perm = boost::algorithm::indirect_sort(first, last); @@ -53,7 +57,8 @@ void test_one_sort(Iter first, Iter last) { BOOST_CHECK (boost::algorithm::is_sorted(perm.begin(), perm.end(), indirect_comp(first))); // Make a copy of the data, apply the permutation, and ensure that it is sorted. - std::vector::value_type> v(first, last); + typedef std::vector::value_type> Vector; + Vector v(first, last); boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); BOOST_CHECK (boost::algorithm::is_sorted(v.begin(), v.end())); } @@ -66,7 +71,8 @@ void test_one_sort(Iter first, Iter last, Comp comp) { indirect_comp(first, comp))); // Make a copy of the data, apply the permutation, and ensure that it is sorted. - std::vector::value_type> v(first, last); + typedef std::vector::value_type> Vector; + Vector v(first, last); boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); BOOST_CHECK (boost::algorithm::is_sorted(v.begin(), v.end(), comp)); } @@ -93,8 +99,259 @@ void test_sort () { test_one_sort(v.begin(), v.end()); test_one_sort(v.begin(), v.end(), std::greater()); } + + + //// ============================== + //// ==== indirect_stable_sort ==== + //// ============================== + +template +struct MyPair { + MyPair () {} + + MyPair (const T1 &t1, const T2 &t2) + : first(t1), second(t2) {} + + T1 first; + T2 second; +}; + +template +bool operator < (const MyPair& lhs, const MyPair& rhs) { + return lhs.first < rhs.first; // compare only the first elements +} + +template +bool MyGreater (const MyPair& lhs, const MyPair& rhs) { + return lhs.first > rhs.first; // compare only the first elements +} + +template +void test_one_stable_sort(Iter first, Iter last) { + Permutation perm = boost::algorithm::indirect_stable_sort(first, last); + BOOST_CHECK (is_a_permutation(perm, std::distance(first, last))); + BOOST_CHECK (boost::algorithm::is_sorted(perm.begin(), perm.end(), indirect_comp(first))); + + if (first != last) { + Iter iFirst = first; + Iter iSecond = first; ++iSecond; + + while (iSecond != last) { + if (iFirst->first == iSecond->first) + BOOST_CHECK(iFirst->second < iSecond->second); + ++iFirst; + ++iSecond; + } + } + +// Make a copy of the data, apply the permutation, and ensure that it is sorted. + typedef std::vector::value_type> Vector; + Vector v(first, last); + boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); + BOOST_CHECK (boost::algorithm::is_sorted(v.begin(), v.end())); +} + +template +void test_one_stable_sort(Iter first, Iter last, Comp comp) { + Permutation perm = boost::algorithm::indirect_stable_sort(first, last, comp); + BOOST_CHECK (is_a_permutation(perm, std::distance(first, last))); + BOOST_CHECK (boost::algorithm::is_sorted(perm.begin(), perm.end(), indirect_comp(first, comp))); + + if (first != last) { + Iter iFirst = first; + Iter iSecond = first; ++iSecond; + + while (iSecond != last) { + if (iFirst->first == iSecond->first) + BOOST_CHECK(iFirst->second < iSecond->second); + ++iFirst; + ++iSecond; + } + } + +// Make a copy of the data, apply the permutation, and ensure that it is sorted. + typedef std::vector::value_type> Vector; + Vector v(first, last); + boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); + BOOST_CHECK (boost::algorithm::is_sorted(v.begin(), v.end(), comp)); +} + +void test_stable_sort () { + typedef MyPair Pair; + const int sz = 10; + Pair vals[sz]; + + for (int i = 0; i < sz; ++i) { + vals[i].first = 100 - (i >> 1); + vals[i].second = i; + } + + Pair *first = &vals[0]; + Pair const *cFirst = &vals[0]; + +// Test subsets + for (size_t i = 0; i <= sz; ++i) { + test_one_stable_sort(first, first + i); + test_one_stable_sort(first, first + i, MyGreater); + + // test with constant inputs + test_one_sort(cFirst, cFirst + i); + test_one_sort(cFirst, cFirst + i, MyGreater); + } +} + + //// =============================== + //// ==== indirect_partial_sort ==== + //// =============================== + +template +void test_one_partial_sort(Iter first, Iter middle, Iter last) { + const size_t middleIdx = std::distance(first, middle); + Permutation perm = boost::algorithm::indirect_partial_sort(first, middle, last); + BOOST_CHECK (is_a_permutation(perm, std::distance(first, last))); + BOOST_CHECK (boost::algorithm::is_sorted(perm.begin(), perm.begin() + middleIdx, indirect_comp(first))); + +// Make a copy of the data, apply the permutation, and ensure that it is sorted. + typedef std::vector::value_type> Vector; + Vector v(first, last); + boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); + BOOST_CHECK (boost::algorithm::is_sorted(v.begin(), v.begin() + middleIdx)); + +// Make sure that [middle, end) are all "greater" than the sorted part + if (middleIdx > 0) { + typename Vector::iterator lastSorted = v.begin() + middleIdx - 1; + for (typename Vector::iterator it = v.begin () + middleIdx; it != v.end(); ++it) + BOOST_CHECK(*lastSorted < *it); + } +} + +template +void test_one_partial_sort(Iter first, Iter middle, Iter last, Comp comp) { + const size_t middleIdx = std::distance(first, middle); + Permutation perm = boost::algorithm::indirect_partial_sort(first, middle, last, comp); + BOOST_CHECK (is_a_permutation(perm, std::distance(first, last))); + BOOST_CHECK (boost::algorithm::is_sorted(perm.begin(), perm.begin() + middleIdx, + indirect_comp(first, comp))); + +// Make a copy of the data, apply the permutation, and ensure that it is sorted. + typedef std::vector::value_type> Vector; + Vector v(first, last); + boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); + BOOST_CHECK (boost::algorithm::is_sorted(v.begin(), v.begin() + middleIdx, comp)); + +// Make sure that [middle, end) are all "greater" than the sorted part + if (middleIdx > 0) { + typename Vector::iterator lastSorted = v.begin() + middleIdx - 1; + for (typename Vector::iterator it = v.begin () + middleIdx; it != v.end(); ++it) + BOOST_CHECK(comp(*lastSorted, *it)); + } +} + + +void test_partial_sort () { + int num[] = { 1,3,5,7,9, 2, 4, 6, 8, 10 }; + const int sz = sizeof (num)/sizeof(num[0]); + int *first = &num[0]; + int const *cFirst = &num[0]; +// Test subsets + for (size_t i = 0; i <= sz; ++i) { + for (size_t j = 0; j < i; ++j) { + test_one_partial_sort(first, first + j, first + i); + test_one_partial_sort(first, first + j, first + i, std::greater()); + + // test with constant inputs + test_one_partial_sort(cFirst, cFirst + j, cFirst + i); + test_one_partial_sort(cFirst, cFirst + j, cFirst + i, std::greater()); + } + } + +// make sure we work with iterators as well as pointers + std::vector v(first, first + sz); + test_one_partial_sort(v.begin(), v.begin() + (sz / 2), v.end()); + test_one_partial_sort(v.begin(), v.begin() + (sz / 2), v.end(), std::greater()); + } + + + //// =================================== + //// ==== indirect_nth_element_sort ==== + //// =================================== + +template +void test_one_nth_element(Iter first, Iter nth, Iter last) { + const size_t nthIdx = std::distance(first, nth); + Permutation perm = boost::algorithm::indirect_nth_element(first, nth, last); + BOOST_CHECK (is_a_permutation(perm, std::distance(first, last))); + + for (size_t i = 0; i < nthIdx; ++i) + BOOST_CHECK(!(first[perm[nthIdx]] < first[perm[i]])); // all items before the nth element are <= the nth element + for (size_t i = nthIdx; i < std::distance(first, last); ++i) + BOOST_CHECK(!(first[perm[i]] < first[perm[nthIdx]])); // all items before the nth element are >= the nth element + +// Make a copy of the data, apply the permutation, and ensure that the result is correct. + typedef std::vector::value_type> Vector; + Vector v(first, last); + boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); + + for (size_t i = 0; i < nthIdx; ++i) + BOOST_CHECK(!(v[nthIdx] < v[i])); // all items before the nth element are <= the nth element + for (size_t i = nthIdx; i < v.size(); ++i) + BOOST_CHECK(!(v[i] < v[nthIdx])); // all items before the nth element are >= the nth element +} + +template +void test_one_nth_element(Iter first, Iter nth, Iter last, Comp comp) { + const size_t nthIdx = std::distance(first, nth); + + Permutation perm = boost::algorithm::indirect_nth_element(first, nth, last, comp); + BOOST_CHECK (is_a_permutation(perm, std::distance(first, last))); + for (size_t i = 0; i < nthIdx; ++i) + BOOST_CHECK(!comp(first[perm[nthIdx]], first[perm[i]])); // all items before the nth element are <= the nth element + for (size_t i = nthIdx; i < std::distance(first, last); ++i) + BOOST_CHECK(!comp(first[perm[i]], first[perm[nthIdx]])); // all items before the nth element are >= the nth element + + +// Make a copy of the data, apply the permutation, and ensure that the result is correct. + typedef std::vector::value_type> Vector; + Vector v(first, last); + boost::algorithm::apply_permutation(v.begin(), v.end(), perm.begin(), perm.end()); + + for (size_t i = 0; i < nthIdx; ++i) + BOOST_CHECK(!comp(v[nthIdx], v[i])); // all items before the nth element are <= the nth element + for (size_t i = nthIdx; i < v.size(); ++i) + BOOST_CHECK(!comp(v[i], v[nthIdx])); // all items before the nth element are >= the nth element +} + + +void test_nth_element () { + int num[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 10, 1, 2, 3, 4, 5 }; + const int sz = sizeof (num)/sizeof(num[0]); + int *first = &num[0]; + int const *cFirst = &num[0]; + +// Test subsets + for (size_t i = 0; i <= sz; ++i) { + for (size_t j = 0; j < i; ++j) { + test_one_nth_element(first, first + j, first + i); + test_one_nth_element(first, first + j, first + i, std::greater()); + + // test with constant inputs + test_one_nth_element(cFirst, cFirst + j, cFirst + i); + test_one_nth_element(cFirst, cFirst + j, cFirst + i, std::greater()); + } + } + +// make sure we work with iterators as well as pointers + std::vector v(first, first + sz); + test_one_nth_element(v.begin(), v.begin() + (sz / 2), v.end()); + test_one_nth_element(v.begin(), v.begin() + (sz / 2), v.end(), std::greater()); + } + + BOOST_AUTO_TEST_CASE( test_main ) { test_sort (); + test_stable_sort (); + test_partial_sort (); + test_nth_element (); }