Skip to content

Commit

Permalink
New adapter: drop_merge_adapter (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
Morwenn committed Oct 15, 2022
1 parent 429344c commit c55ac30
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 56 deletions.
20 changes: 20 additions & 0 deletions docs/Sorter-adapters.md
Expand Up @@ -71,6 +71,25 @@ struct counting_adapter;

Note that this adapter only works with sorters that satisfy the `ComparisonSorter` concept since it needs to adapt a comparison function.

### `drop_merge_adapter`

```cpp
#include <cpp-sort/adapters/drop_merge_adapter.h>
```

[Drop-merge sort][drop-merge-sort] is a [*Rem*-adaptive][probe-rem] sorting algorithm that isolates some of the elements to sort in a buffer in O(n) time in order to leave a single sorted run in the original collection, then it uses another algorithm to sort the elements isolated in a buffer, and merges the two resulting runs back into the original collection. `drop_merge_adapter` uses the *adapted sorter* to sort the (contiguous) buffer of isolated elements.

The *resulting sorter* always requires at least bidirectional iterators, no matter the iterator category of the *adapted sorter*. The *resulting sorter* is always unstable, no matter the stability of the *adapted sorter*.

```cpp
template<typename Sorter>
struct drop_merge_adapter;
```

Adapting any *sorter* with `drop_merge_adapter` effectively makes it [*Rem*-adaptive][probe-rem], making it a valuable tool to add adaptiveness to existing sorters.

*New in version 1.14.0*

### `hybrid_adapter`

```cpp
Expand Down Expand Up @@ -322,6 +341,7 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen
[ctad]: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
[cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort
[default-sorter]: Sorters.md#default_sorter
[drop-merge-sort]: https://github.com/emilk/drop-merge-sort
[fixed-size-sorters]: Fixed-size-sorters.md
[fixed-sorter-traits]: Sorter-traits.md#fixed_sorter_traits
[hybrid-adapter]: Sorter-adapters.md#hybrid_adapter
Expand Down
1 change: 1 addition & 0 deletions include/cpp-sort/adapters.h
Expand Up @@ -10,6 +10,7 @@
////////////////////////////////////////////////////////////
#include <cpp-sort/adapters/container_aware_adapter.h>
#include <cpp-sort/adapters/counting_adapter.h>
#include <cpp-sort/adapters/drop_merge_adapter.h>
#include <cpp-sort/adapters/hybrid_adapter.h>
#include <cpp-sort/adapters/indirect_adapter.h>
#include <cpp-sort/adapters/out_of_place_adapter.h>
Expand Down
84 changes: 84 additions & 0 deletions include/cpp-sort/adapters/drop_merge_adapter.h
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_ADAPTERS_DROP_MERGE_ADAPTER_H_
#define CPPSORT_ADAPTERS_DROP_MERGE_ADAPTER_H_

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <functional>
#include <iterator>
#include <type_traits>
#include <utility>
#include <cpp-sort/sorter_facade.h>
#include <cpp-sort/sorter_traits.h>
#include <cpp-sort/utility/adapter_storage.h>
#include <cpp-sort/utility/functional.h>
#include "../detail/drop_merge_sort.h"
#include "../detail/type_traits.h"

namespace cppsort
{
////////////////////////////////////////////////////////////
// Adapter

namespace detail
{
template<typename Sorter>
struct drop_merge_adapter_impl:
utility::adapter_storage<Sorter>
{
drop_merge_adapter_impl() = default;

constexpr explicit drop_merge_adapter_impl(Sorter&& sorter):
utility::adapter_storage<Sorter>(std::move(sorter))
{}

template<
typename ForwardIterator,
typename Compare = std::less<>,
typename Projection = utility::identity,
typename = detail::enable_if_t<
is_projection_iterator_v<Projection, ForwardIterator, Compare>
>
>
auto operator()(ForwardIterator first, ForwardIterator last,
Compare compare={}, Projection projection={}) const
-> void
{
static_assert(
std::is_base_of<
iterator_category,
iterator_category_t<ForwardIterator>
>::value,
"drop_merge_adapter requires at least bidirectional iterators"
);

drop_merge_sort(std::move(first), std::move(last),
std::move(compare), std::move(projection),
this->get());
}

////////////////////////////////////////////////////////////
// Sorter traits

using iterator_category = std::bidirectional_iterator_tag;
using is_always_stable = std::false_type;
};
}

template<typename Sorter>
struct drop_merge_adapter:
sorter_facade<detail::drop_merge_adapter_impl<Sorter>>
{
drop_merge_adapter() = default;

constexpr explicit drop_merge_adapter(Sorter sorter):
sorter_facade<detail::drop_merge_adapter_impl<Sorter>>(std::move(sorter))
{}
};
}

#endif // CPPSORT_ADAPTERS_DROP_MERGE_ADAPTER_H_
17 changes: 10 additions & 7 deletions include/cpp-sort/detail/drop_merge_sort.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017-2021 Morwenn
* Copyright (c) 2017-2022 Morwenn
* SPDX-License-Identifier: MIT
*/

Expand Down Expand Up @@ -34,7 +34,6 @@
#include <cpp-sort/utility/as_function.h>
#include <cpp-sort/utility/iter_move.h>
#include "iterator_traits.h"
#include "pdqsort.h"
#include "type_traits.h"

namespace cppsort
Expand All @@ -43,10 +42,14 @@ namespace detail
{
constexpr static bool double_comparison = true;

// move-only version
template<typename BidirectionalIterator, typename Compare, typename Projection>
template<
typename BidirectionalIterator,
typename Compare,
typename Projection,
typename Sorter
>
auto drop_merge_sort(BidirectionalIterator begin, BidirectionalIterator end,
Compare compare, Projection projection)
Compare compare, Projection projection, Sorter&& sorter)
-> void
{
using utility::iter_move;
Expand Down Expand Up @@ -122,10 +125,10 @@ namespace detail
}

// Sort the dropped elements
pdqsort(dropped.begin(), dropped.end(), compare, projection);
std::forward<Sorter>(sorter)(dropped.begin(), dropped.end(),
compare, projection);

auto back = end;

do {
auto& last_dropped = dropped.back();

Expand Down
2 changes: 2 additions & 0 deletions include/cpp-sort/fwd.h
Expand Up @@ -79,6 +79,8 @@ namespace cppsort
struct container_aware_adapter;
template<typename Sorter, typename CountType=std::size_t>
struct counting_adapter;
template<typename Sorter>
struct drop_merge_adapter;
template<typename... Sorters>
struct hybrid_adapter;
template<typename Sorter>
Expand Down
56 changes: 7 additions & 49 deletions include/cpp-sort/sorters/drop_merge_sorter.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017-2021 Morwenn
* Copyright (c) 2017-2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_SORTERS_DROP_MERGE_SORTER_H_
Expand All @@ -8,62 +8,20 @@
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <functional>
#include <iterator>
#include <type_traits>
#include <utility>
#include <cpp-sort/sorter_facade.h>
#include <cpp-sort/sorter_traits.h>
#include <cpp-sort/utility/functional.h>
#include <cpp-sort/adapters/drop_merge_adapter.h>
#include <cpp-sort/sorters/pdq_sorter.h>
#include <cpp-sort/utility/static_const.h>
#include "../detail/drop_merge_sort.h"
#include "../detail/iterator_traits.h"
#include "../detail/type_traits.h"

namespace cppsort
{
////////////////////////////////////////////////////////////
// Sorter

namespace detail
{
struct drop_merge_sorter_impl
{
template<
typename BidirectionalIterator,
typename Compare = std::less<>,
typename Projection = utility::identity,
typename = detail::enable_if_t<
is_projection_iterator_v<Projection, BidirectionalIterator, Compare>
>
>
auto operator()(BidirectionalIterator first, BidirectionalIterator last,
Compare compare={}, Projection projection={}) const
-> void
{
static_assert(
std::is_base_of<
iterator_category,
iterator_category_t<BidirectionalIterator>
>::value,
"drop_merge_sorter requires at least bidirectional iterators"
);

drop_merge_sort(std::move(first), std::move(last),
std::move(compare), std::move(projection));
}

////////////////////////////////////////////////////////////
// Sorter traits

using iterator_category = std::bidirectional_iterator_tag;
using is_always_stable = std::false_type;
};
}

struct drop_merge_sorter:
sorter_facade<detail::drop_merge_sorter_impl>
{};
drop_merge_adapter<pdq_sorter>
{
drop_merge_sorter() = default;
};

////////////////////////////////////////////////////////////
// Sort function
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Expand Up @@ -124,6 +124,7 @@ add_executable(main-tests
adapters/container_aware_adapter_forward_list.cpp
adapters/container_aware_adapter_list.cpp
adapters/counting_adapter.cpp
adapters/drop_merge_adapter_every_sorter.cpp
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:adapters/every_adapter_fptr.cpp>
adapters/every_adapter_internal_compare.cpp
adapters/every_adapter_non_const_compare.cpp
Expand Down
84 changes: 84 additions & 0 deletions tests/adapters/drop_merge_adapter_every_sorter.cpp
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <algorithm>
#include <iterator>
#include <list>
#include <vector>
#include <catch2/catch_template_test_macros.hpp>
#include <cpp-sort/adapters/drop_merge_adapter.h>
#include <cpp-sort/sorters.h>
#include <cpp-sort/utility/buffer.h>
#include <testing-tools/distributions.h>

TEMPLATE_TEST_CASE( "every random-access sorter with drop_merge_adapter", "[drop_merge_adapter]",
cppsort::adaptive_shivers_sorter,
cppsort::cartesian_tree_sorter,
cppsort::default_sorter,
cppsort::drop_merge_sorter,
cppsort::grail_sorter<>,
cppsort::heap_sorter,
cppsort::insertion_sorter,
cppsort::mel_sorter,
cppsort::merge_insertion_sorter,
cppsort::merge_sorter,
cppsort::pdq_sorter,
cppsort::poplar_sorter,
cppsort::quick_merge_sorter,
cppsort::quick_sorter,
cppsort::selection_sorter,
cppsort::slab_sorter,
cppsort::ska_sorter,
cppsort::smooth_sorter,
cppsort::spin_sorter,
cppsort::split_sorter,
cppsort::spread_sorter,
cppsort::std_sorter,
cppsort::tim_sorter,
cppsort::wiki_sorter<cppsort::utility::fixed_buffer<0>> )
{
std::vector<double> collection;
collection.reserve(1'000);
auto distribution = dist::inversions(0.2); // Big enough merges
distribution.call<double>(std::back_inserter(collection), 1'000);

cppsort::drop_merge_adapter<TestType> sorter;
sorter(collection);
CHECK( std::is_sorted(collection.begin(), collection.end()) );
}

TEMPLATE_TEST_CASE( "every bidirectional sorter with drop_merge_adapter", "[drop_merge_adapter]",
cppsort::adaptive_shivers_sorter,
cppsort::cartesian_tree_sorter,
cppsort::default_sorter,
cppsort::drop_merge_sorter,
cppsort::grail_sorter<>,
cppsort::heap_sorter,
cppsort::insertion_sorter,
cppsort::mel_sorter,
cppsort::merge_insertion_sorter,
cppsort::merge_sorter,
cppsort::pdq_sorter,
cppsort::poplar_sorter,
cppsort::quick_merge_sorter,
cppsort::quick_sorter,
cppsort::selection_sorter,
cppsort::slab_sorter,
cppsort::ska_sorter,
cppsort::smooth_sorter,
cppsort::spin_sorter,
cppsort::split_sorter,
cppsort::spread_sorter,
cppsort::std_sorter,
cppsort::tim_sorter,
cppsort::wiki_sorter<cppsort::utility::fixed_buffer<0>> )
{
std::list<double> collection;
auto distribution = dist::inversions(0.2); // Big enough merges
distribution.call<double>(std::back_inserter(collection), 1'000);

cppsort::drop_merge_adapter<TestType> sorter;
sorter(collection);
CHECK( std::is_sorted(collection.begin(), collection.end()) );
}

0 comments on commit c55ac30

Please sign in to comment.