Skip to content

Commit

Permalink
Merge pull request #113 from tcbrindle/pr/find_min_max
Browse files Browse the repository at this point in the history
Add find_min/max/minmax algorithms
  • Loading branch information
tcbrindle authored Aug 7, 2023
2 parents 051dce9 + e83bdcb commit d579bfa
Show file tree
Hide file tree
Showing 12 changed files with 478 additions and 2 deletions.
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

0 comments on commit d579bfa

Please sign in to comment.