Skip to content

Commit

Permalink
Add find_min/max/minmax algorithms
Browse files Browse the repository at this point in the history
These do the same as `min()`/`max()`/`minmax()`, but take multipass sequences and return a cursor to the requisite elements rather than an optional value, returning a past-the-end cursor if the input sequence is empty.

Fixes #112
  • Loading branch information
tcbrindle committed Aug 4, 2023
1 parent 051dce9 commit a949ab5
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 1 deletion.
1 change: 1 addition & 0 deletions include/flux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <flux/op/fill.hpp>
#include <flux/op/filter.hpp>
#include <flux/op/find.hpp>
#include <flux/op/find_min_max.hpp>
#include <flux/op/flatten.hpp>
#include <flux/op/fold.hpp>
#include <flux/op/for_each.hpp>
Expand Down
15 changes: 15 additions & 0 deletions include/flux/core/inline_sequence_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,21 @@ struct inline_sequence_base {
[[nodiscard]]
constexpr auto find_if_not(Pred pred);

template <typename Cmp = std::ranges::less>
requires strict_weak_order_for<Cmp, Derived>
[[nodiscard]]
constexpr auto find_max(Cmp cmp = Cmp{});

template <typename Cmp = std::ranges::less>
requires strict_weak_order_for<Cmp, Derived>
[[nodiscard]]
constexpr auto find_min(Cmp cmp = Cmp{});

template <typename Cmp = std::ranges::less>
requires strict_weak_order_for<Cmp, Derived>
[[nodiscard]]
constexpr auto find_minmax(Cmp cmp = Cmp{});

template <typename D = Derived, typename Func, typename Init>
requires foldable<Derived, Func, Init>
[[nodiscard]]
Expand Down
112 changes: 112 additions & 0 deletions include/flux/op/find_min_max.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@

// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail dot com)
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef FLUX_OP_FIND_MIN_MAX_HPP_INCLUDED
#define FLUX_OP_FIND_MIN_MAX_HPP_INCLUDED

#include <flux/core.hpp>
#include <flux/op/minmax.hpp>

namespace flux {

namespace detail {

struct find_min_fn {
template <multipass_sequence Seq,
strict_weak_order_for<Seq> Cmp = std::ranges::less>
[[nodiscard]]
constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t<Seq>
{
auto min = first(seq);
if (!is_last(seq, min)) {
for (auto cur = next(seq, min); !is_last(seq, cur); inc(seq, cur)) {
if (std::invoke(cmp, read_at(seq, cur), read_at(seq, min))) {
min = cur;
}
}
}

return min;
}
};

struct find_max_fn {
template <multipass_sequence Seq,
strict_weak_order_for<Seq> Cmp = std::ranges::less>
[[nodiscard]]
constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t<Seq>
{
auto max = first(seq);
if (!is_last(seq, max)) {
for (auto cur = next(seq, max); !is_last(seq, cur); inc(seq, cur)) {
if (!std::invoke(cmp, read_at(seq, cur), read_at(seq, max))) {
max = cur;
}
}
}

return max;
}
};

struct find_minmax_fn {
template <multipass_sequence Seq,
strict_weak_order_for<Seq> Cmp = std::ranges::less>
[[nodiscard]]
constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const
-> minmax_result<cursor_t<Seq>>
{
auto min = first(seq);
auto max = min;
if (!is_last(seq, min)) {
for (auto cur = next(seq, min); !is_last(seq, cur); inc(seq, cur)) {
auto&& elem = read_at(seq, cur);

if (std::invoke(cmp, elem, read_at(seq, min))) {
min = cur;
}
if (!std::invoke(cmp, elem, read_at(seq, max))) {
max = cur;
}
}
}

return {std::move(min), std::move(max)};
}
};

} // namespace detail

FLUX_EXPORT inline constexpr auto find_min = detail::find_min_fn{};
FLUX_EXPORT inline constexpr auto find_max = detail::find_max_fn{};
FLUX_EXPORT inline constexpr auto find_minmax = detail::find_minmax_fn{};

template <typename D>
template <typename Cmp>
requires strict_weak_order_for<Cmp, D>
constexpr auto inline_sequence_base<D>::find_min(Cmp cmp)
{
return flux::find_min(derived(), std::move(cmp));
}

template <typename D>
template <typename Cmp>
requires strict_weak_order_for<Cmp, D>
constexpr auto inline_sequence_base<D>::find_max(Cmp cmp)
{
return flux::find_max(derived(), std::move(cmp));
}

template <typename D>
template <typename Cmp>
requires strict_weak_order_for<Cmp, D>
constexpr auto inline_sequence_base<D>::find_minmax(Cmp cmp)
{
return flux::find_minmax(derived(), std::move(cmp));
}

} // namespace flux

#endif // FLUX_OP_FIND_MIN_MAX_HPP_INCLUDED
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ add_executable(test-libflux
test_fill.cpp
test_filter.cpp
test_find.cpp
test_find_min_max.cpp
test_flatten.cpp
test_for_each.cpp
test_fold.cpp
Expand Down
149 changes: 149 additions & 0 deletions test/test_find_min_max.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@

// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail dot com)
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include "catch.hpp"

#include <flux.hpp>

#include <array>

#include "test_utils.hpp"

namespace {

struct IntPair {
int a, b;
friend bool operator==(IntPair const&, IntPair const&) = default;
};


constexpr bool test_find_min()
{
// Empty range -> no min value
{
auto seq = flux::empty<int>;
auto cur = flux::find_min(seq);
STATIC_CHECK(seq.is_last(cur));
}

// Basic min works as expected
{
auto arr = std::array{5, 4, 3, 2, 1};

STATIC_CHECK(flux::read_at(arr, flux::find_min(arr)) == 1);

auto ref = flux::ref(arr);
STATIC_CHECK(ref[ref.find_min()] == 1); // much better!
}

// Can use custom comparator and projection
{
IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}};

auto cur = flux::find_min(arr, flux::proj(std::greater{}, &IntPair::a));

STATIC_CHECK(not flux::is_last(arr, cur));
STATIC_CHECK(flux::read_at(arr, cur) == IntPair{5, 6});

}

// If several elements are equally minimal, returns the first
{
IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}};

auto cur = flux::find_min(arr, flux::proj(std::less{}, &IntPair::a));

STATIC_CHECK(not flux::is_last(arr, cur));
STATIC_CHECK(flux::read_at(arr, cur).b == 2);
}

return true;
}
static_assert(test_find_min());

constexpr bool test_find_max()
{
// Empty range -> no max value
{
auto seq = flux::filter(std::array{2, 4, 6, 8, 10}, flux::pred::odd);
auto max = seq.find_max();
STATIC_CHECK(seq.is_last(max));
}

// Basic max works as expected
{
auto seq = flux::from(std::array{5, 4, 3, 2, 1});
auto cur = seq.find_max();
STATIC_CHECK(cur == 0);
STATIC_CHECK(seq[cur] == 5);
}

// Can use custom comparator and projection
{
IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}};

auto cur = flux::find_max(arr, flux::proj(std::greater{}, &IntPair::a));

STATIC_CHECK(flux::read_at(arr, cur) == IntPair{1, 2});
}

// If several elements are equally maximal, returns the last
{
IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}};

auto cur = flux::find_max(arr, flux::proj(std::less{}, &IntPair::b));

STATIC_CHECK(flux::read_at(arr, cur).b == 4);
}

return true;
}
static_assert(test_find_max());

constexpr bool test_find_minmax()
{
// Empty range -> no minmax
{
auto seq = flux::filter(std::array{2, 4, 6, 8, 10}, flux::pred::odd);
auto [min, max] = seq.find_minmax();
STATIC_CHECK(seq.is_last(min));
STATIC_CHECK(seq.is_last(max));
}

// Basic minmax works as expected
{
auto seq = flux::from(std::array{5, 4, 3, 2, 1});

auto result = seq.find_minmax();

STATIC_CHECK(seq[result.min] == 1);
STATIC_CHECK(seq[result.max] == 5);
}

// Can use custom comparator and projection
{
IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}};

auto result = flux::find_minmax(arr, flux::proj(std::greater<>{}, &IntPair::a));


STATIC_CHECK(flux::read_at(arr, result.min) == IntPair{5, 6});
STATIC_CHECK(flux::read_at(arr, result.max) == IntPair{1, 2});
}

// If several elements are equally minimal/maximal, returns the first/last resp.
{
IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}};
auto [min, max] = flux::find_minmax(arr, flux::proj(std::ranges::less{}, &IntPair::a));

STATIC_CHECK(flux::read_at(arr, min) == IntPair{1, 2});
STATIC_CHECK(flux::read_at(arr, max) == IntPair{1, 4});
}

return true;
}
static_assert(test_find_minmax());

}
2 changes: 1 addition & 1 deletion test/test_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <flux/op/from.hpp>
#include <flux/op/ref.hpp>

#define STATIC_CHECK(...) if (!(__VA_ARGS__)) throw false
#define STATIC_CHECK(...) if (!(__VA_ARGS__)) throw std::runtime_error("Test assertion failed")

inline namespace test_utils {

Expand Down

0 comments on commit a949ab5

Please sign in to comment.