Skip to content

Commit

Permalink
Reduce use of cppsort::[stable_]sort even more
Browse files Browse the repository at this point in the history
This also fixes an issue in the "writing a sorter" tutorial that used
the pre-1.0.0 arguments order for cppsort::sort.
  • Loading branch information
Morwenn committed Nov 1, 2020
1 parent b445ec4 commit 65d43bc
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 60 deletions.
4 changes: 2 additions & 2 deletions docs/Library-nomenclature.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

* *Comparison function*: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke). The default comparison function used by the sorting algorithms is [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void). Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order.

cppsort::sort(collection, std::greater<>{});
cppsort::heap_sort(collection, std::greater<>{});

Some algorithms don't accept such an additional parameter. It may be because they implement a non-comparison sort instead, a sorting algorithm that uses other properties of the elements to perform the sort rather than a comparison function (for example a [radix sort](https://en.wikipedia.org/wiki/Radix_sort)).

Expand All @@ -30,7 +30,7 @@

struct wrapper { int value; };
std::vector<wrapper> collection = { /* ... */ };
cppsort::sort(collection, &wrapper::value);
cppsort::heap_sort(collection, &wrapper::value);

Every *comparison sorter* is also a *projection sorter*, but there are also projection-only sorters, such as [`spread_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters).

Expand Down
2 changes: 1 addition & 1 deletion docs/Writing-a-bubble_sorter.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ struct bubble_sorter_impl
We can see several improvements compared to the previous version: first of all, we added an optional projection parameter which defauts to [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). This is a function object that returns a value as is so that the default behaviour of the algorithm is to run *as if* projections didn't exist. It is very likely to be optimized aways by the compiler anyway.
The second modification is one I wish we could do without (yet another thing that concepts would make more expressive): [`is_projection_iterator_v`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_projection-and-is_projection_iterator) is a trait that checks whether a projection function can be used on a dereferenced iterator. It also optionally checks that a given comparison function can be called with the result of two such projections. This trait exists to ensure that `cppsort::sort` won't call the functor when these conditions are not satisfied, which may be crucial when aggregating sorters with [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter).
The second modification is one I wish we could do without (yet another thing that concepts would make more expressive): [`is_projection_iterator_v`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_projection-and-is_projection_iterator) is a trait that checks whether a projection function can be used on a dereferenced iterator. It also optionally checks that a given comparison function can be called with the result of two such projections. This trait exists to ensure that a sorter's `operator()` won't be called when these conditions are not satisfied, which may be crucial when aggregating sorters with [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter).
Now that you know how to handle projections in your algorithm, here is the interesting part: you generally don't need to manually handle projections. The class template `sorter_facade` generates overloads of `operator()` taking projection functions that bake the projection into the comparison and forward that mix to the sorter implementation. In our implementation of `bubble_sort`, we always use the projection inside the comparison, so handling the projections by hand isn't giving us any optimization opportunity; we might as well implement just the comparison and add the small required SFINAE check:
Expand Down
4 changes: 2 additions & 2 deletions docs/Writing-a-sorter.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ This kind of comparison sorters help to compare things that don't have an overlo
```cpp
// Sort collection in reverse order with std::sort
cppsort::sort(collection, std_sorter{}, std::greater<>{});
cppsort::std_sort(collection, std::greater<>{});
```

It is worth noting that every *comparison sorter* provided by the library transforms the comparison parameter with [`utility::as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function) before actually using it. It allows to use pointers to member functions of the `lhs.compare_to(rhs)` kind out-of-the-box.
Expand Down Expand Up @@ -207,7 +207,7 @@ Note that most of the algorithms (actually, every *projection sorter* provided b
```cpp
struct wrapper { int value; }
std::vector<wrapper> vec = { {5}, {9}, {6}, {1}, {2}, {8}, {3}, {0}, {7}, {4} };
cppsort::sort(vec, selection_sorter{}, &wrapper::value);
cppsort::selection_sort(vec, &wrapper::value);
```

Thanks to that small trick, the `selection_sorter` will sort `vec`, using the member data `wrapper::value` instead of a full `wrapper` instance (which cannot be compared) to perform the comparisons on.
Expand Down
2 changes: 1 addition & 1 deletion include/cpp-sort/adapters/container_aware_adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace cppsort
{
namespace detail
{
// Hide the generic cppsort::sort
// Hide potential out-of-scope sort()
struct nope_type {};
template<typename... Args>
auto sort(Args&&...)
Expand Down
56 changes: 29 additions & 27 deletions testsuite/utility/as_projection.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2018 Morwenn
* Copyright (c) 2016-2020 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <algorithm>
Expand All @@ -10,9 +10,9 @@
#include <utility>
#include <vector>
#include <catch2/catch.hpp>
#include <cpp-sort/sort.h>
#include <cpp-sort/adapters/stable_adapter.h>
#include <cpp-sort/sorters/default_sorter.h>
#include <cpp-sort/sorters/pdq_sorter.h>
#include <cpp-sort/stable_sort.h>
#include <cpp-sort/utility/functional.h>

namespace
Expand Down Expand Up @@ -46,113 +46,115 @@ TEST_CASE( "try mixed comparison/projection function object",
std::shuffle(std::begin(collection), std::end(collection), engine);

tricky_function func;
cppsort::default_sorter sorter;
cppsort::stable_adapter<cppsort::default_sorter> stable_sorter;

SECTION( "without an untransformed function" )
{
auto vec = collection;
cppsort::sort(vec, func);
sorter(vec, func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::sort(std::begin(vec), std::end(vec), func);
sorter(std::begin(vec), std::end(vec), func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::pdq_sort(vec, func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::sort(cppsort::pdq_sort, vec, func);
cppsort::pdq_sort(vec, func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), func);
cppsort::pdq_sort(std::begin(vec), std::end(vec), func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(vec, func);
stable_sorter(vec, func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(std::begin(vec), std::end(vec), func);
stable_sorter(std::begin(vec), std::end(vec), func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(cppsort::pdq_sort, vec, func);
cppsort::stable_adapter<cppsort::pdq_sorter>{}(vec, func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), func);
cppsort::stable_adapter<cppsort::pdq_sorter>{}(std::begin(vec), std::end(vec), func);
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );
}

SECTION( "with a function wrapped in as_projection" )
{
auto vec = collection;
cppsort::sort(vec, cppsort::utility::as_projection(func));
sorter(vec, cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
cppsort::sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
sorter(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
cppsort::sort(cppsort::pdq_sort, vec, cppsort::utility::as_projection(func));
cppsort::pdq_sort(vec, cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
cppsort::pdq_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
cppsort::stable_sort(vec, cppsort::utility::as_projection(func));
stable_sorter(vec, cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
cppsort::stable_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
stable_sorter(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
cppsort::stable_sort(cppsort::pdq_sort, vec, cppsort::utility::as_projection(func));
cppsort::stable_adapter<cppsort::pdq_sorter>{}(vec, cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
cppsort::stable_adapter<cppsort::pdq_sorter>{}(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );
}

SECTION( "with a function wrapped in as_comparison" )
{
auto vec = collection;
cppsort::sort(vec, cppsort::utility::as_comparison(func));
sorter(vec, cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
sorter(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::sort(cppsort::pdq_sort, vec, cppsort::utility::as_comparison(func));
cppsort::pdq_sort(vec, cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
cppsort::pdq_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(vec, cppsort::utility::as_comparison(func));
stable_sorter(vec, cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
stable_sorter(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(cppsort::pdq_sort, vec, cppsort::utility::as_comparison(func));
cppsort::stable_adapter<cppsort::pdq_sorter>{}(vec, cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
cppsort::stable_adapter<cppsort::pdq_sorter>{}(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );
}
}
50 changes: 23 additions & 27 deletions testsuite/utility/as_projection_iterable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
#include <utility>
#include <vector>
#include <catch2/catch.hpp>
#include <cpp-sort/sort.h>
#include <cpp-sort/sorter_facade.h>
#include <cpp-sort/sorter_traits.h>
#include <cpp-sort/sorters/selection_sorter.h>
#include <cpp-sort/utility/functional.h>

namespace
Expand Down Expand Up @@ -55,7 +55,7 @@ namespace
auto operator()(Iterator first, Iterator last, Compare compare={}) const
-> call
{
cppsort::sort(first, last, compare);
cppsort::selection_sort(first, last, compare);
return call::iterator;
}

Expand All @@ -69,7 +69,7 @@ namespace
auto operator()(Iterable& iterable, Compare compare={}) const
-> call
{
cppsort::sort(iterable, compare);
cppsort::selection_sort(iterable, compare);
return call::iterable;
}
};
Expand All @@ -87,7 +87,7 @@ namespace
-> call
{
// Use as_projection to make an actual projection-only sorter
cppsort::sort(first, last, cppsort::utility::as_projection(projection));
cppsort::selection_sort(first, last, cppsort::utility::as_projection(projection));
return call::iterator;
}

Expand All @@ -102,7 +102,7 @@ namespace
-> call
{
// Use as_projection to make an actual projection-only sorter
cppsort::sort(iterable, cppsort::utility::as_projection(projection));
cppsort::selection_sort(iterable, cppsort::utility::as_projection(projection));
return call::iterable;
}
};
Expand Down Expand Up @@ -136,82 +136,78 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables and mixed compar

SECTION( "comparison_sorter" )
{
auto res1 = cppsort::sort(comp_sort, vec, func);
auto res1 = comp_sort(vec, func);
CHECK( res1 == call::iterable );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
auto res2 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), func);
auto res2 = comp_sort(std::begin(vec), std::end(vec), func);
CHECK( res2 == call::iterator );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
auto res3 = cppsort::sort(comp_sort, vec, cppsort::utility::as_comparison(func));
auto res3 = comp_sort(vec, cppsort::utility::as_comparison(func));
CHECK( res3 == call::iterable );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
auto res4 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec),
cppsort::utility::as_comparison(func));
auto res4 = comp_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func));
CHECK( res4 == call::iterator );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
auto res5 = cppsort::sort(comp_sort, vec, cppsort::utility::as_projection(func));
auto res5 = comp_sort(vec, cppsort::utility::as_projection(func));
CHECK( res5 == call::iterable );
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
auto res6 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec),
cppsort::utility::as_projection(func));
auto res6 = comp_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
CHECK( res6 == call::iterator );
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
auto res7 = cppsort::sort(comp_sort, vec, func,
cppsort::utility::as_projection(func));
auto res7 = comp_sort(vec, func, cppsort::utility::as_projection(func));
CHECK( res7 == call::iterable );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
auto res8 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), func,
cppsort::utility::as_projection(func));
auto res8 = comp_sort(std::begin(vec), std::end(vec), func,
cppsort::utility::as_projection(func));
CHECK( res8 == call::iterator );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
auto res9 = cppsort::sort(comp_sort, vec, cppsort::utility::as_comparison(func),
cppsort::utility::as_projection(func));
auto res9 = comp_sort(vec, cppsort::utility::as_comparison(func),
cppsort::utility::as_projection(func));
CHECK( res9 == call::iterable );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );

vec = collection;
auto res10 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec),
cppsort::utility::as_comparison(func),
cppsort::utility::as_projection(func));
auto res10 = comp_sort(std::begin(vec), std::end(vec),
cppsort::utility::as_comparison(func),
cppsort::utility::as_projection(func));
CHECK( res10 == call::iterator );
CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) );
}

SECTION( "projection_sorter" )
{
auto res1 = cppsort::sort(proj_sort, vec, cppsort::utility::as_projection(func));
auto res1 = proj_sort(vec, cppsort::utility::as_projection(func));
CHECK( res1 == call::iterable );
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
auto res2 = cppsort::sort(proj_sort, std::begin(vec), std::end(vec),
cppsort::utility::as_projection(func));
auto res2 = proj_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func));
CHECK( res2 == call::iterator );
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
auto res3 = cppsort::sort(proj_sort, vec, func);
auto res3 = proj_sort(vec, func);
CHECK( res3 == call::iterable );
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );

vec = collection;
auto res4 = cppsort::sort(proj_sort, std::begin(vec), std::end(vec), func);
auto res4 = proj_sort(std::begin(vec), std::end(vec), func);
CHECK( res4 == call::iterator );
CHECK( std::is_sorted(std::begin(vec), std::end(vec)) );
}
Expand Down

0 comments on commit 65d43bc

Please sign in to comment.