Skip to content

Commit

Permalink
metrics::moves: mimick passed range iterator category
Browse files Browse the repository at this point in the history
  • Loading branch information
Morwenn committed Jun 27, 2024
1 parent 7282492 commit bb06b56
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 3 deletions.
4 changes: 3 additions & 1 deletion docs/Metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Returns an instance of `utility::metric<CountType, comparisons_tag>`.
Computes the number of moves performed by the *adapted sorter*: it takes both the number of calls to the move constructor and to the move assignment operator of an object into account. A swap operation is considered equivalent to three moves.

The tool currently works by creating a vector of a wrapper type which counts its moves, and moving the sorted contents back to the original collection, with the following implications:
* Sorters that behave differently depending on the iterator type always return the number of moves they perform when sorting random-access iterators.
* Said vector's iterators mimic the iterator category of the range passed to the *resulting sorter*: as such, behaviours that depend on the iterator _category_ are respected, but behaviours that depends on a specific iterator _type_ are not.
* Sorters that call operations specific to some types might return a result that is not representative of how they actually perform: this is due to the wrapper not benefiting from the specializations.
* Projection support is mandatory: `metrics::moves` passes a projection to the sorter in order to convert the wrapper type to its underlying type.

Expand All @@ -68,6 +68,8 @@ Returns an instance of `utility::metric<CountType, moves_tag>`.

*New in version 1.16.0*

*Changed in version 1.17.0:* `metrics::moves` now mimicks the iterator category of the passed range passed to the *resulting sorter*, leading to more accurate results.

### `projections`

```cpp
Expand Down
249 changes: 249 additions & 0 deletions include/cpp-sort/detail/fake_category_iterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
* Copyright (c) 2024 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_DETAIL_FAKE_CATEGORY_ITERATOR_H_
#define CPPSORT_DETAIL_FAKE_CATEGORY_ITERATOR_H_

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <iterator>
#include <type_traits>
#include <utility>
#include <cpp-sort/utility/iter_move.h>
#include "attributes.h"
#include "iterator_traits.h"
#include "type_traits.h"

namespace cppsort
{
namespace detail
{
////////////////////////////////////////////////////////////
// Iterator with fàake iterator category: it wrap a given
// iterator and pretends to have a different iterator
// category as specified by users

template<typename Iterator, typename IteratorTag>
class fake_category_iterator
{
using real_category_tag = iterator_category_t<Iterator>;
static_assert(
std::is_base_of<IteratorTag, real_category_tag>::value,
"The iterator tag of fake_iterator_category must be less "
"specific than that of the wrapped one"
);

public:

////////////////////////////////////////////////////////////
// Public types

using iterator_category = IteratorTag;
using iterator_type = Iterator;
using value_type = value_type_t<Iterator>;
using difference_type = difference_type_t<Iterator>;
using pointer = pointer_t<Iterator>;
using reference = reference_t<Iterator>;

////////////////////////////////////////////////////////////
// Constructors

fake_category_iterator() = default;

explicit fake_category_iterator(Iterator it):
_it(std::move(it))
{}

////////////////////////////////////////////////////////////
// Members access

CPPSORT_ATTRIBUTE_NODISCARD
auto base() const
-> iterator_type
{
return _it;
}

////////////////////////////////////////////////////////////
// Element access

CPPSORT_ATTRIBUTE_NODISCARD
auto operator*() const
-> decltype(*base())
{
return *_it;
}

CPPSORT_ATTRIBUTE_NODISCARD
auto operator->() const
-> pointer
{
return &(operator*());
}

////////////////////////////////////////////////////////////
// Increment/decrement operators

auto operator++()
-> fake_category_iterator&
{
++_it;
return *this;
}

auto operator++(int)
-> fake_category_iterator
{
auto res = *this;
++_it;
return res;
}

auto operator--()
-> fake_category_iterator&
{
--_it;
return *this;
}

auto operator--(int)
-> fake_category_iterator
{
auto res = *this;
--_it;
return res;
}

auto operator+=(difference_type increment)
-> fake_category_iterator&
{
_it += increment;
return *this;
}

auto operator-=(difference_type increment)
-> fake_category_iterator&
{
_it -= increment;
return *this;
}

////////////////////////////////////////////////////////////
// Elements access operators

CPPSORT_ATTRIBUTE_NODISCARD
auto operator[](difference_type pos) const
-> decltype(base()[pos])
{
return _it[pos];
}

////////////////////////////////////////////////////////////
// Comparison operators

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator==(const fake_category_iterator& lhs, const fake_category_iterator& rhs)
-> bool
{
return lhs.base() == rhs.base();
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator!=(const fake_category_iterator& lhs, const fake_category_iterator& rhs)
-> bool
{
return lhs.base() != rhs.base();
}

////////////////////////////////////////////////////////////
// Relational operators

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator<(const fake_category_iterator& lhs, const fake_category_iterator& rhs)
-> bool
{
return lhs.base() < rhs.base();
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator<=(const fake_category_iterator& lhs, const fake_category_iterator& rhs)
-> bool
{
return lhs.base() <= rhs.base();
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator>(const fake_category_iterator& lhs, const fake_category_iterator& rhs)
-> bool
{
return lhs.base() > rhs.base();
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator>=(const fake_category_iterator& lhs, const fake_category_iterator& rhs)
-> bool
{
return lhs.base() >= rhs.base();
}

////////////////////////////////////////////////////////////
// Arithmetic operators

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator+(fake_category_iterator it, difference_type size)
-> fake_category_iterator
{
it += size;
return it;
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator+(difference_type size, fake_category_iterator it)
-> fake_category_iterator
{
it += size;
return it;
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator-(fake_category_iterator it, difference_type size)
-> fake_category_iterator
{
it -= size;
return it;
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto operator-(const fake_category_iterator& lhs, const fake_category_iterator& rhs)
-> difference_type
{
return lhs.base() - rhs.base();
}

////////////////////////////////////////////////////////////
// iter_move/iter_swap

friend auto iter_swap(fake_category_iterator lhs, fake_category_iterator rhs)
-> void
{
using utility::iter_swap;
iter_swap(lhs.base(), rhs.base());
}

CPPSORT_ATTRIBUTE_NODISCARD
friend auto iter_move(fake_category_iterator it)
-> decltype(auto)
{
using utility::iter_move;
return iter_move(it.base());
}

private:

Iterator _it;
};
}}

#endif // CPPSORT_DETAIL_FAKE_CATEGORY_ITERATOR_H_
13 changes: 12 additions & 1 deletion include/cpp-sort/metrics/moves.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cpp-sort/utility/metrics_tools.h>
#include <cpp-sort/utility/size.h>
#include "../detail/checkers.h"
#include "../detail/fake_category_iterator.h"
#include "../detail/immovable_vector.h"
#include "../detail/iterator_traits.h"
#include "../detail/type_traits.h"
Expand Down Expand Up @@ -75,6 +76,14 @@ namespace metrics
cppsort::detail::rvalue_type_t<ForwardIterator>,
CountType
>;
// Use a special iterator that wraps wrapper_t* and pretends
// it is of a different iterator category to better force the
// sorter to use strategies matching the category of the
// passed iterator type
using iterator_t = cppsort::detail::fake_category_iterator<
wrapper_t*,
cppsort::detail::iterator_category_t<ForwardIterator>
>;

// Use an immovable_vector to ensure that no move is performed
// hile filling the vector
Expand All @@ -85,10 +94,12 @@ namespace metrics
}

std::forward<Sorter>(sorter)(
vec.begin(), vec.end(), std::move(compare),
iterator_t(vec.begin()), iterator_t(vec.end()), std::move(compare),
utility::as_projection(&wrapper_t::value) | std::move(projection)
);

// Move back the wrapped value only, not the wrapper itself,
// this ensures that those moves are not counted
auto vec_it = vec.begin();
for (auto it = first; it != last; ++it, ++vec_it) {
*it = std::move(vec_it->value);
Expand Down
32 changes: 31 additions & 1 deletion tests/metrics/moves.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
/*
* Copyright (c) 2023 Morwenn
* Copyright (c) 2023-2024 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <algorithm>
#include <cstddef>
#include <functional>
#include <iterator>
#include <list>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <cpp-sort/adapters/hybrid_adapter.h>
#include <cpp-sort/metrics/moves.h>
#include <cpp-sort/sorters/heap_sorter.h>
#include <cpp-sort/sorters/insertion_sorter.h>
#include <testing-tools/algorithm.h>
#include <testing-tools/distributions.h>
#include <testing-tools/span.h>
Expand Down Expand Up @@ -74,3 +77,30 @@ TEST_CASE( "metrics::moves with span",
std::less<>{}, &wrapper::value) );
}
}

TEST_CASE( "metrics::moves over iterator-category-sensitive sorters",
"[metrics][hybriad_adapter][heap_sorter][insertion_sorter]" )
{
cppsort::metrics::moves<
cppsort::hybrid_adapter<
cppsort::insertion_sorter,
cppsort::heap_sorter
>
> sorter;
auto distribution = dist::descending_plateau{};

std::vector<int> vec;
distribution(std::back_inserter(vec), 65);
std::list<int> li(vec.begin(), vec.end());

{
auto res = sorter(vec);
CHECK( res == 463 );
CHECK( std::is_sorted(vec.begin(), vec.end()) );
}
{
auto res = sorter(li);
CHECK( res == 1977 );
CHECK( std::is_sorted(li.begin(), li.end()) );
}
}

0 comments on commit bb06b56

Please sign in to comment.