Skip to content

Commit

Permalink
Implement ranges::minmax* (#67)
Browse files Browse the repository at this point in the history
* Implement ranges::minmax*

* Fix
  • Loading branch information
lackhole authored Dec 1, 2024
1 parent 82d1195 commit 380bc05
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 4 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ Implementation available in C++14 ~ :

| Header | Total | | C++17 | C++20 | C++23 | C++26 |
|-------------------------------------|---------------------------------------------------------|---|--------------------------------------------------------|---------------------------------------------------------|--------------------------------------------------------|--------------------------------------------------------|
| | ![](https://img.shields.io/badge/312/694-grey)![][p045] | | ![](https://img.shields.io/badge/44/113-grey)![][p039] | ![](https://img.shields.io/badge/231/429-grey)![][p054] | ![](https://img.shields.io/badge/59/144-grey)![][p041] | ![](https://img.shields.io/badge/18/135-grey)![][p013] |
| | ![](https://img.shields.io/badge/314/694-grey)![][p045] | | ![](https://img.shields.io/badge/44/113-grey)![][p039] | ![](https://img.shields.io/badge/233/429-grey)![][p054] | ![](https://img.shields.io/badge/59/144-grey)![][p041] | ![](https://img.shields.io/badge/18/135-grey)![][p013] |
| | | | | | | |
| [algorithm](#algorithm) | ![](https://img.shields.io/badge/53/115-grey)![][p046] | | ![](https://img.shields.io/badge/2/4-grey)![][p050] | ![](https://img.shields.io/badge/45/96-grey)![][p047] | ![](https://img.shields.io/badge/8/18-grey)![][p044] | ![](https://img.shields.io/badge/7/23-grey)![][p030] |
| [algorithm](#algorithm) | ![](https://img.shields.io/badge/55/115-grey)![][p048] | | ![](https://img.shields.io/badge/2/4-grey)![][p050] | ![](https://img.shields.io/badge/47/96-grey)![][p049] | ![](https://img.shields.io/badge/8/18-grey)![][p044] | ![](https://img.shields.io/badge/7/23-grey)![][p030] |
| [any](#any) | ![](https://img.shields.io/badge/5/5-grey)![][p100] | | ![](https://img.shields.io/badge/5/5-grey)![][p100] | | | |
| [array](#array) | ![](https://img.shields.io/badge/1/1-grey)![][p100] | | | ![](https://img.shields.io/badge/1/1-grey)![][p100] | | |
| [bit](#bit) | ![](https://img.shields.io/badge/2/14-grey)![][p014] | | | ![](https://img.shields.io/badge/1/13-grey)![][p008] | ![](https://img.shields.io/badge/1/1-grey)![][p100] | |
Expand Down Expand Up @@ -294,8 +294,8 @@ Description
| `ranges::max_element` | ![][c20ok] | |
| `ranges::min` | ![][c20ok] | |
| `ranges::min_element` | ![][c20ok] | |
| `ranges::minmax` | ![][c20no] | |
| `ranges::minmax_element` | ![][c20no] | |
| `ranges::minmax` | ![][c20ok] | |
| `ranges::minmax_element` | ![][c20ok] | |
| `ranges::clamp` | ![][c20no] | |
| `ranges::equal` | ![][c20ok] | |
| `ranges::lexicographical_compare` | ![][c20ok] | |
Expand Down
80 changes: 80 additions & 0 deletions include/preview/__algorithm/ranges/minmax.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Created by yonggyulee on 2024.12.01
//

#ifndef PREVIEW_ALGORITHM_RANGES_MINMAX_H_
#define PREVIEW_ALGORITHM_RANGES_MINMAX_H_

#include <initializer_list>
#include <type_traits>

#include "preview/__algorithm/ranges/min_max_result.h"
#include "preview/__algorithm/ranges/minmax_element.h"
#include "preview/__concepts/copyable.h"
#include "preview/__core/inline_variable.h"
#include "preview/__functional/identity.h"
#include "preview/__functional/invoke.h"
#include "preview/__functional/less.h"
#include "preview/__functional/wrap_functor.h"
#include "preview/__iterator/indirectly_copyable_storable.h"
#include "preview/__iterator/indirect_strict_weak_order.h"
#include "preview/__iterator/projected.h"
#include "preview/__ranges/input_range.h"
#include "preview/__ranges/iterator_t.h"
#include "preview/__ranges/range_value_t.h"
#include "preview/__type_traits/conjunction.h"

namespace preview {
namespace ranges {

template<typename T>
using minmax_result = min_max_result<T>;

namespace detail {

struct minmax_niebloid {
private:
template<typename I, typename Proj, typename Comp, bool = /* false */ projectable<I, Proj>::value>
struct check_range : std::false_type {};
template<typename I, typename Proj, typename Comp>
struct check_range<I, Proj, Comp, true> : indirect_strict_weak_order<Comp, projected<I, Proj>> {};

public:
template<typename T, typename Proj = identity, typename Comp = ranges::less, std::enable_if_t<
indirect_strict_weak_order<Comp, projected<const T*, Proj>>::value, int> = 0>
constexpr ranges::minmax_result<const T&>
operator()(const T& a, const T& b, Comp comp = {}, Proj proj = {}) const {
return preview::invoke(comp, preview::invoke(proj, b), preview::invoke(proj, a))
? ranges::minmax_result<const T&>{b, a}
: ranges::minmax_result<const T&>{a, b};
}

template<typename T, typename Proj = identity, typename Comp = ranges::less, std::enable_if_t<conjunction_v<
copyable<T>,
check_range<const T*, Proj, Comp>>, int> = 0>
constexpr ranges::minmax_result<const T&>
operator()(std::initializer_list<T> il, Comp comp = {}, Proj proj = {}) const {
auto result = ranges::minmax_element(il, preview::wrap_functor(comp), preview::wrap_functor(proj));
return {*result.min, *result.max};
}

template<typename R, typename Proj = identity, typename Comp = ranges::less, std::enable_if_t<conjunction_v<
input_range<R>,
check_range<ranges::iterator_t<R>, Proj, Comp>,
indirectly_copyable_storable<iterator_t<R>, range_value_t<R>*>
>, int> = 0>
constexpr ranges::minmax_result<range_value_t<R>>
operator()(R&& r, Comp comp = {}, Proj proj = {}) const {
auto result = ranges::minmax_element(r, preview::wrap_functor(comp), preview::wrap_functor(proj));
return {std::move(*result.min), std::move(*result.max)};
}
};

} // namespace detail

PREVIEW_INLINE_VARIABLE constexpr detail::minmax_niebloid minmax{};

} // namespace ranges
} // namespace preview

#endif // PREVIEW_ALGORITHM_RANGES_MINMAX_H_
118 changes: 118 additions & 0 deletions include/preview/__algorithm/ranges/minmax_element.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//
// Created by yonggyulee on 2024.12.01
//

#ifndef PREVIEW_ALGORITHM_RANGES_MINMAX_ELEMENT_H_
#define PREVIEW_ALGORITHM_RANGES_MINMAX_ELEMENT_H_

#include <type_traits>

#include "preview/__algorithm/ranges/min_max_result.h"
#include "preview/__core/inline_variable.h"
#include "preview/__functional/identity.h"
#include "preview/__functional/invoke.h"
#include "preview/__functional/less.h"
#include "preview/__functional/wrap_functor.h"
#include "preview/__iterator/forward_iterator.h"
#include "preview/__iterator/indirect_strict_weak_order.h"
#include "preview/__iterator/projected.h"
#include "preview/__iterator/sentinel_for.h"
#include "preview/__ranges/begin.h"
#include "preview/__ranges/borrowed_iterator_t.h"
#include "preview/__ranges/end.h"
#include "preview/__ranges/iterator_t.h"
#include "preview/__type_traits/conjunction.h"
#include "preview/__utility/cxx20_rel_ops.h"

namespace preview {
namespace ranges {

template<typename I>
using minmax_element_result = min_max_result<I>;

namespace detail {

struct minmax_element_niebloid {
private:
template<typename I, typename Proj, typename Comp, bool = /* false */ projectable<I, Proj>::value>
struct check_range : std::false_type {};
template<typename I, typename Proj, typename Comp>
struct check_range<I, Proj, Comp, true> : indirect_strict_weak_order<Comp, projected<I, Proj>> {};

// lambda is not constexpr until C++17
template<typename Proj, typename Comp>
struct minmax_element_less {
Proj& proj;
Comp& comp;

template<typename I>
constexpr auto operator()(const I& x, const I& y) const {
return preview::invoke(comp, preview::invoke(proj, *x), preview::invoke(proj, *y));
}
};

public:
template<typename I, typename S, typename Proj = identity, typename Comp = ranges::less, std::enable_if_t<conjunction<
forward_iterator<I>,
sentinel_for<S, I>,
check_range<I, Proj, Comp>
>::value, int> = 0>
constexpr minmax_element_result<I> operator()(I first, S last, Comp comp = {}, Proj proj = {}) const {
using namespace preview::rel_ops;

I min_ = first;
I max_ = first;
if (first == last || ++first == last) {
return {min_, max_};
}

minmax_element_less<Proj, Comp> less{proj, comp};

if (less(first, min_))
min_ = first;
else
max_ = first;

while (++first != last) {
I i = first;
if (++first == last) {
if (less(i, min_))
min_ = i;
else if (!less(i, max_))
max_ = i;
break;
}

if (less(first, i)) {
if (less(first, min_))
min_ = first;
if (!less(i, max_))
max_ = i;
} else {
if (less(i, min_))
min_ = i;
if (!less(first, max_))
max_ = first;
}
}

return {min_, max_};
}

template<typename R, typename Proj = identity, typename Comp = ranges::less, std::enable_if_t<conjunction<
forward_range<R>,
check_range<iterator_t<R>, Proj, Comp>
>::value, int> = 0>
constexpr minmax_element_result<borrowed_iterator_t<R>> operator()(R&& r, Comp comp = {}, Proj proj = {}) const {
return (*this)(ranges::begin(r), ranges::end(r), preview::wrap_functor(comp), preview::wrap_functor(proj));
}
};

} // namespace detail

PREVIEW_INLINE_VARIABLE constexpr detail::minmax_element_niebloid minmax_element{};

} // namespace ranges
} // namespace preview

#endif // PREVIEW_ALGORITHM_RANGES_MINMAX_ELEMENT_H_
2 changes: 2 additions & 0 deletions include/preview/algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
#include "preview/__algorithm/ranges/max_element.h"
#include "preview/__algorithm/ranges/min.h"
#include "preview/__algorithm/ranges/min_element.h"
#include "preview/__algorithm/ranges/minmax.h"
#include "preview/__algorithm/ranges/minmax_element.h"
#include "preview/__algorithm/ranges/min_max_result.h"
#include "preview/__algorithm/ranges/mismatch.h"
#include "preview/__algorithm/ranges/none_of.h"
Expand Down
83 changes: 83 additions & 0 deletions test/algorithm_ranges.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
#include <map>
#include <numeric>
#include <string>
#include <random>
#include <unordered_map>
#include <vector>

#include "preview/array.h"
#include "preview/core.h"
#include "preview/functional.h"
#include "preview/ranges.h"
Expand Down Expand Up @@ -559,3 +561,84 @@ TEST(VERSIONED(AlgorithmRanges), search_n) {
auto it = ranges::search_n(nums2, 2, {4, 2});
EXPECT_EQ(it.size(), 2);
}


#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif

TEST(VERSIONED(AlgorithmRanges), minmax_element) {
{
// leftmost minimum v
const auto v = {3, 9, 1, 4, 1, 2, 5, 9};
// rightmost maximum ^

#if PREVIEW_CXX_VERSION >= 17
const auto [min, max] = ranges::minmax_element(v);
#else
const auto p = ranges::minmax_element(v);
const auto min = p.min;
const auto max = p.max;
#endif
EXPECT_EQ(min, v.begin() + 2);
EXPECT_EQ(max, v.begin() + 7);
}
{
static constexpr int v[] = {3, 9, 1, 4, 1, 2, 5, 9};
constexpr auto minmax = ranges::minmax_element(v);
static_assert(minmax.min == preview::ranges::begin(v) + 2, "");
static_assert(minmax.max == preview::ranges::begin(v) + 7, "");
}
{
const int v[] = {3, 9, 1, 4, 1, 2, 5, 9};
const auto r = v | views::take(5);
#if PREVIEW_CXX_VERSION >= 17
const auto [min, max] = ranges::minmax_element(r);
#else
const auto p = ranges::minmax_element(r);
const auto min = p.min;
const auto max = p.max;
#endif
EXPECT_EQ(min, r.begin() + 2);
EXPECT_EQ(max, r.begin() + 1);
}
}

TEST(VERSIONED(AlgorithmRanges), minmax) {
{
constexpr auto v = preview::to_array({3, 1, 4, 1, 5, 9, 2, 6, 5});
#if PREVIEW_CXX_VERSION >= 17
auto [min, max] = ranges::minmax(v);
#else
auto p = ranges::minmax(v);
auto min = p.min;
auto max = p.max;
#endif
EXPECT_EQ(min, 1);
EXPECT_EQ(max, 9);
}

{
std::random_device rd;
std::mt19937_64 generator(rd());
std::uniform_int_distribution<> distribution(0, 9);

for (int i = 0; i < 100; ++i) {
const int x1 = distribution(generator);
const int x2 = distribution(generator);
#if PREVIEW_CXX_VERSION >= 17
auto [min, max] = ranges::minmax(x1, x2);
#else
auto p = ranges::minmax(x1, x2);
auto min = p.min;
auto max = p.max;
#endif
ASSERT_EQ(min, (std::min)(x1, x2));
ASSERT_EQ(max, (std::max)(x1, x2));
}
}
}

0 comments on commit 380bc05

Please sign in to comment.