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