Skip to content

Commit

Permalink
New adapter: split_adapter (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
Morwenn committed Oct 9, 2022
1 parent 87c544d commit 36b35e2
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 15 deletions.
23 changes: 23 additions & 0 deletions docs/Sorter-adapters.md
Expand Up @@ -219,6 +219,28 @@ using sorter = cppsort::hybrid_adapter<

*Warning: this adapter only supports default-constructible stateless sorters.*

### `split_adapter`

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

The adapter implements the "in-place" version of the *SplitSort* algorithm described in *Splitsort — an adaptive sorting algorithm* by C. Levcopoulos and O. Petersson. The algorithm works as follows:
1. It performs a O(n) pass on the collection to isolate an approximation of a longest non-decreasing subsequence in the left part of the collection, and places the removed elements in the right part of the collection.
2. It uses the *adapted sorter* to sort the right part of the collection.
3. It merges the two parts of the collection in O(n) time O(n) space if possible, otherwise it merges them in O(n log n) time O(1) space.

The core algorithm behind `split_adapter` requires at least bidirectional iterators to work, as such the *resulting sorter* requires bidirectional iterators if the *adapted sorter* supports them, otherwise it requires the same category of iterators at that accepted by the *adapter sorter*. The *resulting sorter* is always unstable, no matter the stability of the *adapted sorter*.

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

Adapting any *sorter* with `split_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*

### `stable_adapter`, `make_stable` and `stable_t`

```cpp
Expand Down Expand Up @@ -308,6 +330,7 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen
[issue-104]: https://github.com/Morwenn/cpp-sort/issues/104
[low-moves-sorter]: Fixed-size-sorters.md#low_moves_sorter
[mountain-sort]: https://github.com/Morwenn/mountain-sort
[probe-rem]: Measures-of-presortedness.md#rem
[schwartzian-transform]: https://en.wikipedia.org/wiki/Schwartzian_transform
[stable-adapter]: Sorter-adapters.md#stable_adapter-make_stable-and-stable_t
[self-sort-adapter]: Sorter-adapters.md#self_sort_adapter
Expand Down
3 changes: 2 additions & 1 deletion include/cpp-sort/adapters.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2018 Morwenn
* Copyright (c) 2015-2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_ADAPTERS_H_
Expand All @@ -16,6 +16,7 @@
#include <cpp-sort/adapters/schwartz_adapter.h>
#include <cpp-sort/adapters/self_sort_adapter.h>
#include <cpp-sort/adapters/small_array_adapter.h>
#include <cpp-sort/adapters/split_adapter.h>
#include <cpp-sort/adapters/stable_adapter.h>
#include <cpp-sort/adapters/verge_adapter.h>

Expand Down
107 changes: 107 additions & 0 deletions include/cpp-sort/adapters/split_adapter.h
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_ADAPTERS_SPLIT_ADAPTER_H_
#define CPPSORT_ADAPTERS_SPLIT_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/split_sort.h"
#include "../detail/type_traits.h"

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

namespace detail
{
template<typename Sorter, typename=void>
struct split_adapter_iterator_category
{
using type = std::bidirectional_iterator_tag;
};

template<typename Sorter>
struct split_adapter_iterator_category<
Sorter,
void_t<typename sorter_traits<Sorter>::iterator_category>
>
{
using type = detail::conditional_t<
std::is_base_of<
std::bidirectional_iterator_tag,
typename sorter_traits<Sorter>::iterator_category
>::value,
typename sorter_traits<Sorter>::iterator_category,
std::bidirectional_iterator_tag
>;
};

template<typename Sorter>
struct split_adapter_impl:
utility::adapter_storage<Sorter>
{
split_adapter_impl() = default;

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

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,
"split_adapter requires a stronger iterator category"
);

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

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

using iterator_category
= typename split_adapter_iterator_category<Sorter>::type;
using is_always_stable = std::false_type;
};
}

template<typename Sorter>
struct split_adapter:
sorter_facade<detail::split_adapter_impl<Sorter>>
{
split_adapter() = default;

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

#endif // CPPSORT_ADAPTERS_SPLIT_ADAPTER_H_
29 changes: 17 additions & 12 deletions include/cpp-sort/detail/split_sort.h
Expand Up @@ -13,32 +13,37 @@
#include <cpp-sort/utility/as_function.h>
#include <cpp-sort/utility/iter_move.h>
#include "inplace_merge.h"
#include "iterator_traits.h"
#include "pdqsort.h"

namespace cppsort
{
namespace detail
{
template<typename RandomAccessIterator, typename Compare, typename Projection>
auto split_sort(RandomAccessIterator first, RandomAccessIterator last,
Compare compare, Projection projection)
template<
typename BidirectionalIterator,
typename Compare,
typename Projection,
typename Sorter
>
auto split_sort(BidirectionalIterator first, BidirectionalIterator last,
Compare compare, Projection projection, Sorter&& sorter)
-> void
{
// This algorithm tries to isolate an approximate longest
// non-decreasing subsequence in the left portion of the
// collection, and the other elements in the right portion
// of the collection, then to sort the remaining elements
// and to merge both portions (LNDS)
// non-decreasing subsequence (LNDS) in the left portion
// of the collection, and the other elements in the right
// portion of the collection, then to sort the remaining
// elements and to merge both portions

if (last - first < 2) return;
if (first == last || std::next(first) == last) {
return;
}

auto&& comp = utility::as_function(compare);
auto&& proj = utility::as_function(projection);

// Read and reorganize elements until middle is found
auto middle = first; // Last element of the LNDS
for (auto reader_it = std::next(first) ; reader_it != last ; ++reader_it) {
for (auto reader_it = std::next(first); reader_it != last; ++reader_it) {
if (comp(proj(*reader_it), proj(*middle))) {
// We remove the top of the subsequence as well as the new element
if (middle != first) {
Expand All @@ -53,7 +58,7 @@ namespace detail
}

// Sort second part of the collection and merge
pdqsort(middle, last, compare, projection);
std::forward<Sorter>(sorter)(middle, last, compare, projection);
inplace_merge(first, middle, last, std::move(compare), std::move(projection));
}
}}
Expand Down
2 changes: 2 additions & 0 deletions include/cpp-sort/fwd.h
Expand Up @@ -92,6 +92,8 @@ namespace cppsort
template<template<std::size_t> class FixedSizeSorter, typename Indices>
struct small_array_adapter;
template<typename Sorter>
struct split_adapter;
template<typename Sorter>
struct stable_adapter;
template<typename Sorter>
struct verge_adapter;
Expand Down
6 changes: 4 additions & 2 deletions include/cpp-sort/sorters/split_sorter.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2021 Morwenn
* Copyright (c) 2019-2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_SORTERS_SPLIT_SORTER_H_
Expand All @@ -14,6 +14,7 @@
#include <utility>
#include <cpp-sort/sorter_facade.h>
#include <cpp-sort/sorter_traits.h>
#include <cpp-sort/sorters/pdq_sorter.h>
#include <cpp-sort/utility/functional.h>
#include <cpp-sort/utility/static_const.h>
#include "../detail/iterator_traits.h"
Expand Down Expand Up @@ -50,7 +51,8 @@ namespace cppsort
);

split_sort(std::move(first), std::move(last),
std::move(compare), std::move(projection));
std::move(compare), std::move(projection),
cppsort::pdq_sort);
}

////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Expand Up @@ -145,6 +145,7 @@ add_executable(main-tests
adapters/self_sort_adapter_no_compare.cpp
adapters/small_array_adapter.cpp
adapters/small_array_adapter_is_stable.cpp
adapters/split_adapter_every_sorter.cpp
adapters/stable_adapter_every_sorter.cpp
adapters/verge_adapter_every_sorter.cpp

Expand Down
70 changes: 70 additions & 0 deletions tests/adapters/split_adapter_every_sorter.cpp
@@ -0,0 +1,70 @@
/*
* 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/split_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 split_adapter", "[split_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::split_adapter<TestType> sorter;
sorter(collection);
CHECK( std::is_sorted(collection.begin(), collection.end()) );
}

TEMPLATE_TEST_CASE( "every bidirectional sorter with split_adapter", "[split_adapter]",
cppsort::cartesian_tree_sorter,
cppsort::default_sorter,
cppsort::drop_merge_sorter,
cppsort::insertion_sorter,
cppsort::mel_sorter,
cppsort::merge_sorter,
cppsort::quick_merge_sorter,
cppsort::quick_sorter,
cppsort::selection_sorter,
cppsort::slab_sorter )
{
std::list<double> collection;
auto distribution = dist::inversions(0.2); // Big enough merges
distribution.call<double>(std::back_inserter(collection), 1'000);

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

0 comments on commit 36b35e2

Please sign in to comment.