From 61fb62a913cebf046594a096d376a01d5a5f6b6c Mon Sep 17 00:00:00 2001 From: sss <sss@karma> Date: Fri, 14 Feb 2025 09:18:07 -0500 Subject: [PATCH] CxxUtils: Add minmax_transformed_element.h Add [min,max]_transformed_element to return the minimum or maximum transformed element from a range along with its iterator. In principle this can be implemented using C++20 ranges, but then its difficult to avoid having the transformation evaluated multiple times per element. --- Control/CxxUtils/CMakeLists.txt | 3 +- .../CxxUtils/minmax_transformed_element.h | 102 ++++++++++++++++++ .../share/minmax_transformed_element_test.ref | 2 + .../test/minmax_transformed_element_test.cxx | 50 +++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 Control/CxxUtils/CxxUtils/minmax_transformed_element.h create mode 100644 Control/CxxUtils/share/minmax_transformed_element_test.ref create mode 100644 Control/CxxUtils/test/minmax_transformed_element_test.cxx diff --git a/Control/CxxUtils/CMakeLists.txt b/Control/CxxUtils/CMakeLists.txt index e6f9f78ea192..b67ed0f8f636 100644 --- a/Control/CxxUtils/CMakeLists.txt +++ b/Control/CxxUtils/CMakeLists.txt @@ -176,7 +176,8 @@ foreach( test sincos_test ArrayScanner_test Arrayrep_test reverse_wrapper_test atomic_bounded_decrement_test releasing_iterator_test SizedUInt_test UIntConv_test normalizeFunctionName_test pputils_test StringParse_test - range_with_at_test range_with_conv_test ) + range_with_at_test range_with_conv_test + minmax_transformed_element_test ) atlas_add_test( ${test} SOURCES test/${test}.cxx LINK_LIBRARIES CxxUtils TestTools ${Boost_LIBRARIES}) diff --git a/Control/CxxUtils/CxxUtils/minmax_transformed_element.h b/Control/CxxUtils/CxxUtils/minmax_transformed_element.h new file mode 100644 index 000000000000..b666838a8c17 --- /dev/null +++ b/Control/CxxUtils/CxxUtils/minmax_transformed_element.h @@ -0,0 +1,102 @@ +// This file's extension implies that it's C, but it's really -*- C++ -*-. +/* + * Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file CxxUtils/minmax_transformed_element.h + * @author scott snyder <snyder@bnl.gov> + * @date Feb, 2025 + * @brief Return the minimum or maximum element from a transformed range. + * + * These are like std::ranges::min_element and std::ranges::max_element, + * except that we apply a transform to the input range first. + * An example would be to take a range of vectors and find the one + * closest to some other vector. + * Why not just use std::ranges::min_element along with std::ranges::transform? + * First, it's a bit awkward without the enumerate/zip views from C++23. + * But more importantly, it's hard to avoid having the transform + * evaluated multiple times per element. This way, the transform is + * evaluated exactly once per element. + */ + + +#ifndef CXXUTILS_MINMAX_TRANSFORMED_ELEMENT_H +#define CXXUTILS_MINMAX_TRANSFORMED_ELEMENT_H + + +#include <ranges> +#include <utility> +#include <cstdlib> + + +namespace CxxUtils { + + +/** + * @brief Find the minimum transformed element in a range. + * @param r Input range. + * @param f Transform function. + * + * Evaluates f(x) for each x in r and finds the minimum. + * Returns a pair (fmin, itmin), where fmin is this minimum value, + * and itmin is an iterator pointing at the corresponding element in r. + * If more than one element has the same minimum value, the iterator + * of the first will be returned. + * + * Precondition: Range r is not empty. + */ +template <class RANGE, class FUNC> +inline +auto min_transformed_element (RANGE&& r, FUNC&& f) +{ + if (std::ranges::empty (r)) std::abort(); + auto it = std::ranges::begin(r); + auto itmin = it; + auto val = f(*it); + for (++it; it != std::ranges::end(r); ++it) { + auto val2 = f(*it); + if (val2 < val) { + val = val2; + itmin = it; + } + } + return std::make_pair (val, itmin); +} + + +/** + * @brief Find the maximum transformed element in a range. + * @param r Input range. + * @param f Transform function. + * + * Evaluates f(x) for each x in r and finds the maximum. + * Returns a pair (fmax, itmax), where fmax is this maximum value, + * and itmax is an iterator pointing at the corresponding element in r. + * If more than one element has the same maximum value, the iterator + * of the first will be returned. + * + * Precondition: Range r is not empty. + */ +template <class RANGE, class FUNC> +inline +auto max_transformed_element (RANGE&& r, FUNC&& f) +{ + if (std::ranges::empty (r)) std::abort(); + auto it = std::ranges::begin(r); + auto itmax = it; + auto val = f(*it); + for (++it; it != std::ranges::end(r); ++it) { + auto val2 = f(*it); + if (val2 > val) { + val = val2; + itmax = it; + } + } + return std::make_pair (val, itmax); +} + + +} // namespace CxxUtils + + +#endif // not CXXUTILS_MINMAX_TRANSFORMED_ELEMENT_H diff --git a/Control/CxxUtils/share/minmax_transformed_element_test.ref b/Control/CxxUtils/share/minmax_transformed_element_test.ref new file mode 100644 index 000000000000..49bb6a0007f1 --- /dev/null +++ b/Control/CxxUtils/share/minmax_transformed_element_test.ref @@ -0,0 +1,2 @@ +minmax_transformed_element_test +test1 diff --git a/Control/CxxUtils/test/minmax_transformed_element_test.cxx b/Control/CxxUtils/test/minmax_transformed_element_test.cxx new file mode 100644 index 000000000000..778851544e0c --- /dev/null +++ b/Control/CxxUtils/test/minmax_transformed_element_test.cxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file CxxUtils/test/minmax_transformed_element_test.cxx + * @author scott snyder <snyder@bnl.gov> + * @date Feb, 2025 + * @brief Unit tests for min/max_transformed_element. + */ + +#undef NDEBUG +#include "CxxUtils/minmax_transformed_element.h" +#include <vector> +#include <iostream> +#include <cmath> +#include <cassert> + + +struct distfrom +{ + distfrom (double x) : m_x(x) {} + double operator() (double y) const { return std::abs(y - m_x); } + double m_x; +}; + +void test1() +{ + std::cout << "test1\n"; + std::vector<double> v { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + { + auto [fmin, itmin] = CxxUtils::min_transformed_element (v, distfrom(4.25)); + assert (fmin == 0.25); + assert (*itmin == 4); + } + + { + auto [fmax, itmax] = CxxUtils::max_transformed_element (v, distfrom(2.5)); + assert (fmax == 6.5); + assert (*itmax == 9); + } +} + + +int main() +{ + std::cout << "minmax_transformed_element_test\n"; + test1(); + return 0; +} -- GitLab