Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add find_min/max/minmax algorithms #113

Merged
merged 2 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 97 additions & 1 deletion docs/reference/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,102 @@ Algorithms
requires std::predicate<Pred&, element_t<Seq>> \
auto find_if_not(Seq&& seq, Pred pred) -> cursor_t<Seq>;

``find_max``
------------

.. function::
template <multipass_sequence Seq, strict_weak_order_for<Seq> Cmp = std::ranges::less> \
auto find_max(Seq&& seq, Cmp cmp = {}) -> cursor_t<Seq>;

Returns a cursor to the maximum element of :var:`seq`, compared using :var:`cmp`.

If several elements are equally maximal, :func:`find_max` returns a cursor to the **last** such element.

.. note:: This behaviour differs from :func:`std::max_element()`, which returns an iterator to the *first* maximal element.

:param seq: A multipass sequence
:param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::ranges::less`

:returns: A cursor pointing to the maximum element of :var:`seq`.

:example:

.. literalinclude:: ../../example/docs/find_max.cpp
:language: cpp
:linenos:
:dedent:
:lines: 10-31

:see also:
* `std::ranges::max_element() <https://en.cppreference.com/w/cpp/algorithm/ranges/max_element>`_
* :func:`flux::max`
* :func:`flux::find_minmax`

``find_min``
------------

.. function::
template <multipass_sequence Seq, strict_weak_order_for<Seq> Cmp = std::ranges::less> \
auto find_min(Seq&& seq, Cmp cmp = {}) -> cursor_t<Seq>;

Returns a cursor to the minimum element of :var:`seq`, compared using :var:`cmp`.

If several elements are equally minimal, :func:`find_min` returns a cursor to the **first** such element.

:param seq: A multipass sequence
:param cmp: A comparator to use to find the minimum element, defaulting to :type:`std::ranges::less`

:returns: A cursor pointing to the minimum element of :var:`seq`.

:example:

.. literalinclude:: ../../example/docs/find_min.cpp
:language: cpp
:linenos:
:dedent:
:lines: 10-31

:see also:
* `std::ranges::min_element() <https://en.cppreference.com/w/cpp/algorithm/ranges/min_element>`_
* :func:`flux::min`
* :func:`flux::minmax`

``find_minmax``
---------------

.. function::
template <multipass_sequence Seq, strict_weak_order_for<Seq> Cmp = std::ranges::less> \
auto find_minmax(Seq&& seq, Cmp cmp = {}) -> minmax_result<cursor_t<Seq>>;

Returns a pair of cursors to the minimum and maximum elements of :var:`seq`, compared using :var:`cmp`.

If several elements are equally minimal, :func:`find_minmax` returns a cursor to the first. If several elements are equally maximal, :func:`find_minmax` returns a cursor to the last.

Equivalent to::

minmax_element<cursor_t<Seq>>{.min = find_min(seq, cmp),
.max = find_max(seq, cmp)};

but only does a single pass over :var:`seq`.

:param seq: A multipass sequence
:param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::ranges::less`

:returns: A cursor pointing to the maximum element of :var:`seq`.

:example:

.. literalinclude:: ../../example/docs/find_minmax.cpp
:language: cpp
:linenos:
:dedent:
:lines: 10-33

:see also:
* `std::ranges::minmax_element() <https://en.cppreference.com/w/cpp/algorithm/ranges/minmax_element>`_
* :func:`flux::minmax`


``fold``
--------

Expand Down Expand Up @@ -408,7 +504,7 @@ Algorithms
``minmax``
----------

.. struct:: template <sequence Seq> minmax_result;
.. struct:: template <typename T> minmax_result;

.. function::
template <sequence Seq, typename Cmp = std::ranges::less> \
Expand Down
3 changes: 3 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ add_example(example-docs-cursors docs/cursors.cpp)
add_example(example-docs-cycle docs/cycle.cpp)
add_example(example-docs-drop docs/drop.cpp)
add_example(example-docs-ends-with docs/ends_with.cpp)
add_example(example-docs-find-max docs/find_max.cpp)
add_example(example-docs-find-min docs/find_min.cpp)
add_example(example-docs-find-minmax docs/find_minmax.cpp)
add_example(example-docs-mask docs/mask.cpp)
add_example(example-docs-prescan docs/prescan.cpp)
add_example(example-docs-read-only docs/read_only.cpp)
Expand Down
32 changes: 32 additions & 0 deletions example/docs/find_max.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

#include <flux.hpp>

#include <cassert>
#include <string>
#include <vector>

int main()
{
struct Person {
std::string name;
int age;
};

std::vector<Person> people{
{"Alice", 44},
{"Bob", 63},
{"Chris", 29},
{"Dani", 29},
{"Eddy", 63}
};

// Get a cursor to the maximum of the people vector, according to age
auto max_cur = flux::find_max(people, flux::proj(std::less{}, &Person::age));

// The oldest person is 63
assert(flux::read_at(people, max_cur).age == 63);

// Note that (unlike std::max_element) find_max() return a cursor to the
// *last* of several equally-maximum elements
assert(flux::read_at(people, max_cur).name == "Eddy");
}
32 changes: 32 additions & 0 deletions example/docs/find_min.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

#include <flux.hpp>

#include <cassert>
#include <string>
#include <vector>

int main()
{
struct Person {
std::string name;
int age;
};

std::vector<Person> people{
{"Alice", 44},
{"Bob", 63},
{"Chris", 29},
{"Dani", 29},
{"Eddy", 63}
};

// Get a cursor to the maximum of the people vector, according to age
auto min_cur = flux::find_min(people, flux::proj(std::less{}, &Person::age));

// The youngest person is 29
assert(flux::read_at(people, min_cur).age == 29);

// Note that find_min() return a cursor to the first of several
// equally-minimum elements
assert(flux::read_at(people, min_cur).name == "Chris");
}
34 changes: 34 additions & 0 deletions example/docs/find_minmax.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

#include <flux.hpp>

#include <cassert>
#include <string>
#include <vector>

int main()
{
struct Person {
std::string name;
int age;
};

std::vector<Person> people{
{"Alice", 44},
{"Bob", 63},
{"Chris", 29},
{"Dani", 29},
{"Eddy", 63}
};

// find_minmax() returns a pair of cursors which we can destructure
// Here we'll get the min and max of the people vector, according to age
auto [min, max] = flux::find_minmax(people, flux::proj(std::less{}, &Person::age));

// The "minimum" is Chris. Dani is the same age, but Chris appears earlier
// in the sequence
assert(flux::read_at(people, min).name == "Chris");

// The "maximum" is Eddy. Bob is the same age, but Eddy appears later in the
// sequence
assert(flux::read_at(people, max).name == "Eddy");
}
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 include/flux/op/minmax.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <flux/core.hpp>

#include <flux/op/fold.hpp>
#include <flux/op/slice.hpp>

namespace flux {
Expand Down
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
Loading