diff --git a/include/cpp-sort/fwd.h b/include/cpp-sort/fwd.h index 0a10796c..79211325 100644 --- a/include/cpp-sort/fwd.h +++ b/include/cpp-sort/fwd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 Morwenn + * Copyright (c) 2016-2023 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_FWD_H_ @@ -12,14 +12,11 @@ namespace cppsort { - // - // This header contains forward declarations for - // every sorter and sorter adapter in the library, - // which helps to specialize some of the adapters - // or to provide information about some sorters - // without actually having to include the whole - // thing - // + // This header contains forward declarations for every + // sorter and sorter adapter in the library, which helps + // to specialize some of the adapters or to provide + // information about some sorters without actually having + // to include the whole thing //////////////////////////////////////////////////////////// // Sorters @@ -102,6 +99,17 @@ namespace cppsort struct stable_adapter; template struct verge_adapter; + + //////////////////////////////////////////////////////////// + // Metrics + + namespace metrics + { + template + struct comparisons; + template + struct projections; + } } #endif // CPPSORT_FWD_H_ diff --git a/include/cpp-sort/metrics.h b/include/cpp-sort/metrics.h new file mode 100644 index 00000000..b3fecd67 --- /dev/null +++ b/include/cpp-sort/metrics.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_METRICS_H_ +#define CPPSORT_METRICS_H_ + +#include + +#include +#include +#include + +#endif // CPPSORT_METRICS_H_ diff --git a/include/cpp-sort/metrics/comparisons.h b/include/cpp-sort/metrics/comparisons.h new file mode 100644 index 00000000..fab1c5b4 --- /dev/null +++ b/include/cpp-sort/metrics/comparisons.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2023 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_METRICS_COMPARISONS_H_ +#define CPPSORT_METRICS_COMPARISONS_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include "../detail/checkers.h" +#include "../detail/comparison_counter.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ +namespace metrics +{ + //////////////////////////////////////////////////////////// + // Tag + + struct comparisons_tag {}; + + //////////////////////////////////////////////////////////// + // Metric + + namespace detail + { + template + struct comparisons_impl: + utility::adapter_storage, + cppsort::detail::check_iterator_category, + cppsort::detail::check_is_always_stable + { + using tag_t = comparisons_tag; + using metric_t = utility::metric; + + comparisons_impl() = default; + + constexpr explicit comparisons_impl(Sorter&& sorter): + utility::adapter_storage(std::move(sorter)) + {} + + template< + typename Iterable, + typename Compare = std::less<>, + typename = cppsort::detail::enable_if_t< + not is_projection_v + > + > + auto operator()(Iterable&& iterable, Compare compare={}) const + -> metric_t + { + CountType count(0); + using cppsort::detail::comparison_counter; + comparison_counter cmp(std::move(compare), count); + this->get()(std::forward(iterable), std::move(cmp)); + return metric_t(count); + } + + template< + typename Iterator, + typename Compare = std::less<>, + typename = cppsort::detail::enable_if_t< + not is_projection_iterator_v + > + > + auto operator()(Iterator first, Iterator last, Compare compare={}) const + -> metric_t + { + CountType count(0); + using cppsort::detail::comparison_counter; + comparison_counter cmp(std::move(compare), count); + this->get()(std::move(first), std::move(last), std::move(cmp)); + return metric_t(count); + } + + template< + typename Iterable, + typename Compare, + typename Projection, + typename = cppsort::detail::enable_if_t< + is_projection_v + > + > + auto operator()(Iterable&& iterable, Compare compare, Projection projection) const + -> metric_t + { + CountType count(0); + using cppsort::detail::comparison_counter; + comparison_counter cmp(std::move(compare), count); + this->get()(std::forward(iterable), std::move(cmp), std::move(projection)); + return metric_t(count); + } + + template< + typename Iterator, + typename Compare, + typename Projection, + typename = cppsort::detail::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(Iterator first, Iterator last, + Compare compare, Projection projection) const + -> metric_t + { + CountType count(0); + using cppsort::detail::comparison_counter; + comparison_counter cmp(std::move(compare), count); + this->get()(std::move(first), std::move(last), std::move(cmp), std::move(projection)); + return metric_t(count); + } + }; + } + + template + struct comparisons: + sorter_facade> + { + comparisons() = default; + + constexpr explicit comparisons(Sorter sorter): + sorter_facade>(std::move(sorter)) + {} + }; +}} + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // is_stable specialization + + template + struct is_stable(Args...)>: + is_stable + {}; +} + +#endif // CPPSORT_METRICS_COMPARISONS_H_ diff --git a/include/cpp-sort/metrics/projections.h b/include/cpp-sort/metrics/projections.h new file mode 100644 index 00000000..3f03deb3 --- /dev/null +++ b/include/cpp-sort/metrics/projections.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2023 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_METRICS_PROJECTIONS_H_ +#define CPPSORT_METRICS_PROJECTIONS_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include "../detail/checkers.h" +#include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" + +namespace cppsort +{ +namespace metrics +{ + namespace detail + { + template + class projection_counter + { + public: + + projection_counter(Projection projection, CountType& count): + projection(std::move(projection)), + count(count) + {} + + template + auto operator()(T&& value) + -> decltype(auto) + { + ++count; + auto&& proj = utility::as_function(projection); + return proj(std::forward(value)); + } + + // Accessible member data + Projection projection; + + private: + + // Projection functions are generally passed by value, + // therefore we need to know where is the original counter + // in order to increment it + CountType& count; + }; + } + + //////////////////////////////////////////////////////////// + // Tag + + struct projections_tag {}; + + //////////////////////////////////////////////////////////// + // Metric + + namespace detail + { + template + struct projections_impl: + utility::adapter_storage, + cppsort::detail::check_iterator_category, + cppsort::detail::check_is_always_stable + { + using tag_t = projections_tag; + using metric_t = utility::metric; + + projections_impl() = default; + + constexpr explicit projections_impl(Sorter&& sorter): + utility::adapter_storage(std::move(sorter)) + {} + + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = cppsort::detail::enable_if_t> + > + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> metric_t + { + CountType count(0); + projection_counter counter(std::move(projection), count); + + this->get()(first, last, std::move(compare), std::move(counter)); + return metric_t(count); + } + }; + } + + template + struct projections: + sorter_facade> + { + projections() = default; + + constexpr explicit projections(Sorter sorter): + sorter_facade>(std::move(sorter)) + {} + }; +}} + +namespace cppsort +{ + //////////////////////////////////////////////////////////// + // is_stable specialization + + template + struct is_stable(Args...)>: + is_stable + {}; +} + +#endif // CPPSORT_METRICS_PROJECTIONS_H_ diff --git a/include/cpp-sort/utility/metrics_tools.h b/include/cpp-sort/utility/metrics_tools.h new file mode 100644 index 00000000..3ea61ef6 --- /dev/null +++ b/include/cpp-sort/utility/metrics_tools.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2023 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_UTILITY_METRICS_TOOLS_H_ +#define CPPSORT_UTILITY_METRICS_TOOLS_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include "../detail/type_traits.h" + +namespace cppsort +{ +namespace utility +{ + //////////////////////////////////////////////////////////// + // metric + // + // metric is a simple class holding a value of a given type + // and associating it to a tag. + + template + class metric + { + private: + + T _value; + + public: + + //////////////////////////////////////////////////////////// + // Construction + + metric() = default; + metric(const metric&) = default; + metric(metric&&) = default; + + constexpr explicit metric(const T& value) + noexcept(std::is_nothrow_copy_constructible::value): + _value(value) + {} + + constexpr explicit metric(T&& value) + noexcept(std::is_nothrow_move_constructible::value): + _value(std::move(value)) + {} + + //////////////////////////////////////////////////////////// + // Accessors + + constexpr explicit operator T() const + { + return _value; + } + + constexpr auto value() const + -> T + { + return _value; + } + + //////////////////////////////////////////////////////////// + // Assignment + + metric& operator=(const metric&) = default; + metric& operator=(metric&&) = default; + + constexpr auto operator=(const T& other) + noexcept(std::is_nothrow_copy_assignable::value) + -> metric& + { + _value = other; + return *this; + } + + constexpr auto operator=(T&& other) + noexcept(std::is_nothrow_move_assignable::value) + -> metric& + { + _value = std::move(other); + return *this; + } + + //////////////////////////////////////////////////////////// + // Comparison operators + + template + friend constexpr auto operator==(const metric& lhs, const metric& rhs) + -> bool + { + return lhs._value == rhs._value; + } + + friend constexpr auto operator==(const metric& lhs, const T& rhs) + -> bool + { + return lhs._value == rhs; + } + + friend constexpr auto operator==(const T& lhs, const metric& rhs) + -> bool + { + return lhs == rhs._value; + } + + template + friend constexpr auto operator!=(const metric& lhs, const metric& rhs) + -> bool + { + return lhs._value != rhs._value; + } + + friend constexpr auto operator!=(const metric& lhs, const T& rhs) + -> bool + { + return lhs._value != rhs; + } + + friend constexpr auto operator!=(const T& lhs, const metric& rhs) + -> bool + { + return lhs != rhs._value; + } + + //////////////////////////////////////////////////////////// + // Relational operators + + template + friend constexpr auto operator<(const metric& lhs, const metric& rhs) + -> bool + { + return lhs._value < rhs._value; + } + + friend constexpr auto operator<(const metric& lhs, const T& rhs) + -> bool + { + return lhs._value < rhs; + } + + friend constexpr auto operator<(const T& lhs, const metric& rhs) + -> bool + { + return lhs < rhs._value; + } + + template + friend constexpr auto operator<=(const metric& lhs, const metric& rhs) + -> bool + { + return lhs._value <= rhs._value; + } + + friend constexpr auto operator<=(const metric& lhs, const T& rhs) + -> bool + { + return lhs._value <= rhs; + } + + friend constexpr auto operator<=(const T& lhs, const metric& rhs) + -> bool + { + return lhs <= rhs._value; + } + + template + friend constexpr auto operator>(const metric& lhs, const metric& rhs) + -> bool + { + return lhs._value > rhs._value; + } + + friend constexpr auto operator>(const metric& lhs, const T& rhs) + -> bool + { + return lhs._value > rhs; + } + + friend constexpr auto operator>(const T& lhs, const metric& rhs) + -> bool + { + return lhs > rhs._value; + } + + template + friend constexpr auto operator>=(const metric& lhs, const metric& rhs) + -> bool + { + return lhs._value >= rhs._value; + } + + friend constexpr auto operator>=(const metric& lhs, const T& rhs) + -> bool + { + return lhs._value >= rhs; + } + + friend constexpr auto operator>=(const T& lhs, const metric& rhs) + -> bool + { + return lhs >= rhs._value; + } + + //////////////////////////////////////////////////////////// + // Stream operators + + friend auto operator<<(std::ostream& stream, const metric& met) + -> std::ostream& + { + stream << met.value(); + return stream; + } + }; +}} + +#endif // CPPSORT_UTILITY_METRICS_TOOLS_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 269b09c0..4521dc9a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -152,6 +152,10 @@ add_executable(main-tests adapters/stable_adapter_every_sorter.cpp adapters/verge_adapter_every_sorter.cpp + # Metrics tests + metrics/comparisons.cpp + metrics/projections.cpp + # Comparators tests comparators/case_insensitive_less.cpp comparators/flip_not.cpp diff --git a/tests/metrics/comparisons.cpp b/tests/metrics/comparisons.cpp new file mode 100644 index 00000000..7e54a09f --- /dev/null +++ b/tests/metrics/comparisons.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using wrapper = generic_wrapper; + +TEST_CASE( "basic metrics::comparisons tests", + "[metrics][selection_sorter]" ) +{ + // Selection sort always makes the same number of comparisons + // for a given size of arrays, allowing to deterministically + // check that number of comparisons + cppsort::metrics::comparisons< + cppsort::selection_sorter + > sorter; + + SECTION( "without projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 65); + + // Sort and check it is sorted + auto res = sorter(collection); + CHECK( res == 2080 ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "with projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80); + + // Sort and check it is sorted + auto res = sorter(collection, &wrapper::value); + CHECK( res == 3160 ); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper::value) ); + } +} + +TEST_CASE( "metrics::comparisons tests with std_sorter", + "[metrics][std_sorter]" ) +{ + cppsort::metrics::comparisons< + cppsort::std_sorter + > sorter; + + SECTION( "without projections" ) + { + // Fill the collection + std::vector collection; collection.reserve(65); + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 65, 0); + + // Sort and check it is sorted + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } +} + +TEST_CASE( "metrics::comparisons with span", + "[metrics][span][selection_sorter]" ) +{ + cppsort::metrics::comparisons< + cppsort::selection_sorter + > sorter; + + SECTION( "without projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 65, 0); + + // Sort and check it's sorted + auto res = sorter(make_span(collection)); + CHECK( res == 2080 ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "with projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80, 0); + + // Sort and check it's sorted + auto res = sorter(make_span(collection), &wrapper::value); + CHECK( res == 3160 ); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper::value) ); + } +} diff --git a/tests/metrics/projections.cpp b/tests/metrics/projections.cpp new file mode 100644 index 00000000..9e385d4f --- /dev/null +++ b/tests/metrics/projections.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using wrapper = generic_wrapper; + +TEST_CASE( "basic metrics::projections tests", + "[metrics][selection_sorter]" ) +{ + // Selection sort always makes the same number of projections + // for a given size of arrays, allowing to deterministically + // check that number + cppsort::metrics::projections< + cppsort::selection_sorter + > sorter; + + SECTION( "without projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 65); + + // Sort and check it's sorted + auto res = sorter(collection); + CHECK( res == 4160 ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "with projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80); + + // Sort and check it's sorted + auto res = sorter(collection, &wrapper::value); + CHECK( res == 6320 ); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper::value) ); + } +} + +TEST_CASE( "metrics::projections tests with std_sorter", + "[metrics][std_sorter]" ) +{ + cppsort::metrics::projections< + cppsort::std_sorter + > sorter; + + SECTION( "without projections" ) + { + // Fill the collection + std::vector collection; collection.reserve(65); + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 65, 0); + + // Sort and check it's sorted + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } +} + +TEST_CASE( "metrics::projections with span", + "[metrics][span][selection_sorter]" ) +{ + cppsort::metrics::projections< + cppsort::selection_sorter + > sorter; + + SECTION( "without projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 65, 0); + + // Sort and check it's sorted + auto res = sorter(make_span(collection)); + CHECK( res == 2080 ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "with projections" ) + { + // Fill the collection + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80, 0); + + // Sort and check it's sorted + auto res = sorter(make_span(collection), &wrapper::value); + CHECK( res == 3160 ); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper::value) ); + } +}