Skip to content

Commit

Permalink
New component: sorted_iterators (#200)
Browse files Browse the repository at this point in the history
  • Loading branch information
Morwenn committed Nov 6, 2022
1 parent 5e9c74b commit 8bd558c
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 2 deletions.
29 changes: 27 additions & 2 deletions docs/Miscellaneous-utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ using make_index_range = make_integer_range<std::size_t, Begin, End, Step>;
#include <cpp-sort/utility/sorted_indices.h>
```

`utility::sorted_indices` is a a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]).
`utility::sorted_indices` is a function object that takes a sorter and returns a new function object. This new function object accepts a random-access collection and returns an `std::vector` containing the indices that would sort that collection (similarly to [`numpy.argsort`][numpy-argsort]).

```cpp
std::vector<int> vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 };
Expand All @@ -274,7 +274,32 @@ auto indices = get_sorted_indices_for(vec);
Concretely `sorted_indices` is designed like a [sorter adapter][sorter-adapters] and therefore supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade]. The main reason it does not sit with sorter adapters is that the returned function object is not a sorter per se since it doesn't sort the passed collection directly.
When the collection contains several elements, the order of their indices in the results depend on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of elements that compare equal appear in a stable order in the result.
When the collection contains several elements that compare equivalent, the order of their indices in the result depends on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_indices` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the indices of elements that compare equivalent appear in a stable order in the result.
*New in version 1.14.0*
### `sorted_iterators`
```cpp
#include <cpp-sort/utility/sorted_iterators.h>
```

`utility::sorted_iterators` is a function object that takes a sorter and returns a new function object. This new function object accepts a collection and returns an `std::vector` containing iterators to the passed collection in a sorted order. It is designed like a [sorter adapter][sorter-adapters] and as such supports the whole gamut of parameters provided by [`sorter_facade`][sorter-facade].

```cpp
std::list<int> li = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 };
auto get_sorted_iterators_for = cppsort::utility::sorted_iterators<cppsort::heap_sorter>{};
const auto iterators = get_sorted_iterators_for(li);

// Displays 0 1 2 3 4 5 6 7 8 9
for (auto it: iterators) {
std::cout << *it << ' ';
}
```

It can be thought of as a kind of sorted view of the passed collection - as long as said collection does not change. It can be useful when the order of the original collection must be preserved, but operations have to be performed on the sorted collection.

When the collection contains several elements that compare equivalent, the order of the corresponding iterators in the result depends on the sorter being used. However that order should be consistent across all stabe sorters. `sorted_iterators` follows the [`is_stable` protocol][is-stable], so the trait can be used to check whether the iterators to elements that compare equivalent appear in a stable order in the result.

*New in version 1.14.0*

Expand Down
144 changes: 144 additions & 0 deletions include/cpp-sort/utility/sorted_iterators.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_UTILITY_SORTED_ITERATORS_H_
#define CPPSORT_UTILITY_SORTED_ITERATORS_H_

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <functional>
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>
#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 <cpp-sort/utility/size.h>
#include "../detail/checkers.h"
#include "../detail/functional.h"
#include "../detail/iterator_traits.h"
#include "../detail/type_traits.h"

namespace cppsort
{
namespace utility
{
namespace detail
{
template<
typename Sorter,
typename Iterator,
typename Compare,
typename Projection
>
auto compute_sorted_iterators(Sorter&& sorter, Iterator first, Iterator last,
cppsort::detail::difference_type_t<Iterator> size,
Compare compare, Projection projection)
-> std::vector<Iterator>
{
// Copy the iterators in a vector
std::vector<Iterator> iterators;
iterators.reserve(size);
for (auto it = first; it != last; ++it) {
iterators.emplace_back(it);
}

// Sort the iterators on pointed values
std::forward<Sorter>(sorter)(iterators.begin(), iterators.end(),
std::move(compare),
cppsort::detail::indirect(std::move(projection)));

return iterators;
}

template<typename Sorter>
struct sorted_iterators_impl:
utility::adapter_storage<Sorter>,
cppsort::detail::check_is_always_stable<Sorter>
{
sorted_iterators_impl() = default;

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

template<
typename ForwardIterable,
typename Compare = std::less<>,
typename Projection = utility::identity,
typename = cppsort::detail::enable_if_t<
is_projection_v<Projection, ForwardIterable, Compare>
>
>
auto operator()(ForwardIterable&& iterable, Compare compare={}, Projection projection={}) const
-> std::vector<cppsort::detail::remove_cvref_t<decltype(std::begin(iterable))>>
{
using category = cppsort::detail::iterator_category_t<decltype(std::begin(iterable))>;
static_assert(
std::is_base_of<iterator_category, category>::value,
"sorted_iterators requires at least forward iterators"
);

auto dist = cppsort::utility::size(iterable);
return compute_sorted_iterators(this->get(), std::begin(iterable), std::end(iterable),
dist, std::move(compare), std::move(projection));
}

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

auto dist = std::distance(first, last);
return compute_sorted_iterators(this->get(), std::move(first), std::move(last),
dist, std::move(compare), std::move(projection));
}

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

using iterator_category = std::forward_iterator_tag;
};
}

template<typename Sorter>
struct sorted_iterators:
sorter_facade<detail::sorted_iterators_impl<Sorter>>
{
sorted_iterators() = default;

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

namespace cppsort
{
////////////////////////////////////////////////////////////
// is_stable specialization

template<typename Sorter, typename... Args>
struct is_stable<cppsort::utility::sorted_iterators<Sorter>(Args...)>:
is_stable<Sorter(Args...)>
{};
}

#endif // CPPSORT_UTILITY_SORTED_ITERATORS_H_
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ add_executable(main-tests
utility/chainable_projections.cpp
utility/iter_swap.cpp
utility/sorted_indices.cpp
utility/sorted_iterators.cpp
utility/sorting_networks.cpp
)
configure_tests(main-tests)
Expand Down
1 change: 1 addition & 0 deletions tests/utility/sorted_indices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: MIT
*/
#include <cstddef>
#include <iterator>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <cpp-sort/sorters/heap_sorter.h>
Expand Down
92 changes: 92 additions & 0 deletions tests/utility/sorted_iterators.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <iterator>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <cpp-sort/sorters/heap_sorter.h>
#include <cpp-sort/sorters/insertion_sorter.h>
#include <cpp-sort/utility/sorted_iterators.h>
#include <testing-tools/distributions.h>

TEST_CASE( "basic sorted_iterators test", "[utility][sorted_iterators]" )
{
SECTION( "simple case" )
{
auto get_sorted_iterators_for = cppsort::utility::sorted_iterators<cppsort::heap_sorter>{};
const std::vector<int> vec = { 6, 4, 2, 1, 8, 7, 0, 9, 5, 3 };
auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size());

std::vector<const int*> expected = {
vec.data() + 6, vec.data() + 3, vec.data() + 2,
vec.data() + 9, vec.data() + 1, vec.data() + 8,
vec.data() + 0, vec.data() + 5, vec.data() + 4,
vec.data() + 7
};
CHECK( indices == expected );
}

SECTION( "empty collection" )
{
auto get_sorted_iterators_for = cppsort::utility::sorted_iterators<cppsort::heap_sorter>{};
const std::vector<int> vec = {};
auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size());

std::vector<const int*> expected = {};
CHECK( indices == expected );

}

SECTION( "all_equal" )
{
// Use a stable algorithm to get deterministic results for equivalent elements
auto get_sorted_iterators_for = cppsort::utility::sorted_iterators<cppsort::insertion_sorter>{};
std::vector<int> vec;
auto distribution = dist::all_equal{};
distribution(std::back_inserter(vec), 10);
auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size());

std::vector<int*> expected = {
vec.data() + 0, vec.data() + 1, vec.data() + 2,
vec.data() + 3, vec.data() + 4, vec.data() + 5,
vec.data() + 6, vec.data() + 7, vec.data() + 8,
vec.data() + 9
};
CHECK( indices == expected );
}

SECTION( "ascending" )
{
auto get_sorted_iterators_for = cppsort::utility::sorted_iterators<cppsort::heap_sorter>{};
std::vector<int> vec;
auto distribution = dist::ascending{};
distribution(std::back_inserter(vec), 10);
auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size());

std::vector<int*> expected = {
vec.data() + 0, vec.data() + 1, vec.data() + 2,
vec.data() + 3, vec.data() + 4, vec.data() + 5,
vec.data() + 6, vec.data() + 7, vec.data() + 8,
vec.data() + 9
};
CHECK( indices == expected );
}

SECTION( "descending" )
{
auto get_sorted_iterators_for = cppsort::utility::sorted_iterators<cppsort::heap_sorter>{};
std::vector<int> vec;
auto distribution = dist::descending{};
distribution(std::back_inserter(vec), 10);
auto indices = get_sorted_iterators_for(vec.data(), vec.data() + vec.size());

std::vector<int*> expected = {
vec.data() + 9, vec.data() + 8, vec.data() + 7,
vec.data() + 6, vec.data() + 5, vec.data() + 4,
vec.data() + 3, vec.data() + 2, vec.data() + 1,
vec.data() + 0
};
CHECK( indices == expected );
}
}

0 comments on commit 8bd558c

Please sign in to comment.