Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
Math/Statistics.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include <algorithm>
5#include <ranges>
6
7#include "Common.h"
10
11namespace Stroika::Foundation::Math {
12
13 /*
14 ********************************************************************************
15 ************************************ Mean **************************************
16 ********************************************************************************
17 */
18 template <typename RESULT_TYPE, input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
19 auto Mean (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end) -> RESULT_TYPE
20 {
21 // 'end' is only ever compared against here (and the loop below re-reads it every iteration), so it is
22 // never forwarded - there is nothing to move it into.
23 Require (start != end); // the mean of 0 items would be undefined
24 unsigned int cnt{};
25 RESULT_TYPE result{};
26 for (ITERATOR_OF_T i = start; i != end; ++i) {
27 result += *i;
28 ++cnt;
29 }
30 return result / cnt;
31 }
32 template <input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
33 inline auto Mean (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end) -> typename iterator_traits<ITERATOR_OF_T>::value_type
34 {
35 Require (start != end); // the mean of 0 items would be undefined - compare only, so no forward here
36 return Mean<typename iterator_traits<ITERATOR_OF_T>::value_type> (start, forward<ITERATOR_OF_T2> (end)); // last use
37 }
38 template <ranges::range CONTAINER_OF_T>
39 inline auto Mean (const CONTAINER_OF_T& container) -> typename CONTAINER_OF_T::value_type
40 {
41 Require (not container.empty ());
42 return Mean<typename CONTAINER_OF_T::value_type> (ranges::begin (container), ranges::end (container));
43 }
44
45 /*
46 ********************************************************************************
47 ********************************** Median **************************************
48 ********************************************************************************
49 */
50 template <typename RESULT_TYPE, input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2, Common::IInOrderComparer<RESULT_TYPE> INORDER_COMPARE_FUNCTION>
51 RESULT_TYPE Median (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end, INORDER_COMPARE_FUNCTION&& compare)
52 {
53 // @todo only do the COPY conditionally - if ITERATOR_OF_T isn't already a random-access iterator...
54 // NB: 'end' and 'compare' are each forwarded at most ONCE, at their last use, and passed as plain
55 // lvalues before that. nth_element () takes its comparer BY VALUE, so forwarding into the first call
56 // would move out of 'compare' and leave the second call (below, for the even case) reading a
57 // moved-from object. Harmless for a stateless comparer, silently wrong for a stateful one.
58 Require (start != end); // the median of no values would be undefined
59 Memory::StackBuffer<RESULT_TYPE> tmp{start, forward<ITERATOR_OF_T2> (end)}; // copy cuz data modified
60 size_t size = tmp.size ();
61 nth_element (tmp.begin (), tmp.begin () + size / 2, tmp.end (), compare);
62 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wmaybe-uninitialized\""); // warning with gcc cross-compile to raspberrypi - no idea why --LGP 2018-09-13
63 RESULT_TYPE result{tmp[size / 2]};
64 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"");
65 if ((size % 2) == 0) {
66 Assert (size >= 2); // cuz require at start >=1 and since even
67 // NB: Could use sort instead of nth_element, and some on the web suggest faster, but sort is O(n*log(n)), and nth_element is O(n) (even
68 // when you do it twice.
69 nth_element (tmp.begin (), tmp.begin () + size / 2 - 1, tmp.end (), forward<INORDER_COMPARE_FUNCTION> (compare));
70 result += tmp[size / 2 - 1];
71 result /= 2;
72 }
73 return result;
74 }
75 template <input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2, Common::IInOrderComparer<typename iterator_traits<ITERATOR_OF_T>::value_type> INORDER_COMPARE_FUNCTION>
76 inline auto Median (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end, INORDER_COMPARE_FUNCTION&& compare) ->
77 typename iterator_traits<ITERATOR_OF_T>::value_type
78 {
79 return Median<typename iterator_traits<ITERATOR_OF_T>::value_type> (start, forward<ITERATOR_OF_T2> (end),
80 forward<INORDER_COMPARE_FUNCTION> (compare));
81 }
82 template <ranges::range CONTAINER_OF_T, Common::IInOrderComparer<typename CONTAINER_OF_T::value_type> INORDER_COMPARE_FUNCTION>
83 inline auto Median (const CONTAINER_OF_T& container, INORDER_COMPARE_FUNCTION&& compare) -> typename CONTAINER_OF_T::value_type
84 {
85 Require (not container.empty ());
86 return Median<typename CONTAINER_OF_T::value_type> (ranges::begin (container), ranges::end (container),
87 forward<INORDER_COMPARE_FUNCTION> (compare));
88 }
89
90 /*
91 ********************************************************************************
92 **************************** StandardDeviation *********************************
93 ********************************************************************************
94 */
95 template <typename RESULT_TYPE, input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
96 RESULT_TYPE StandardDeviation (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end)
97 {
98 // 'end' is used again by the loop below, so it is NOT forwarded into Mean (). It cannot be passed as a
99 // plain lvalue either - Mean () takes ITERATOR_OF_T2&& constrained by sentinel_for<>, and an lvalue would
100 // deduce that to a reference type, which is not semiregular<> and so fails the constraint. Hand it a copy.
101 Require (start != end); // the std-deviation of no values would be undefined
102 RESULT_TYPE mean = Mean<RESULT_TYPE> (start, remove_cvref_t<ITERATOR_OF_T2>{end});
103 RESULT_TYPE accum{};
104 size_t n{};
105 for (auto i = start; i != end; ++i) {
106 ++n;
107 accum += (*i - mean) * (*i - mean);
108 }
109 Require (n >= 1); // the std-deviation of no values would be undefined
110 return sqrt (accum / (n - 1));
111 }
112 template <input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
113 inline auto StandardDeviation (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end) ->
114 typename iterator_traits<remove_cvref_t<ITERATOR_OF_T>>::value_type
115 {
116 Require (start != end); // the std-deviation of no values would be undefined - compare only, so no forward
117 return StandardDeviation<typename iterator_traits<ITERATOR_OF_T>::value_type> (start, forward<ITERATOR_OF_T2> (end)); // last use
118 }
119 template <ranges::range CONTAINER_OF_T>
120 inline auto StandardDeviation (const CONTAINER_OF_T& container) -> typename CONTAINER_OF_T::value_type
121 {
122 Require (not container.empty ()); // the std-deviation of no values would be undefined
123 return StandardDeviation<typename CONTAINER_OF_T::value_type> (ranges::begin (container), ranges::end (container));
124 }
125
126 /*
127 ********************************************************************************
128 **************************** ComputeCommonStatistics ***************************
129 ********************************************************************************
130 */
131 template <typename T, input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
132 CommonStatistics<T> ComputeCommonStatistics (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end)
133 {
134 CommonStatistics<T> results;
135 if (start != end) {
136 /*
137 * NB: 'end' is consumed up to five times below, ALL ON THE SAME PATH, so it must not be forwarded -
138 * that would move out of it and leave the later calls reading a moved-from sentinel.
139 *
140 * It cannot simply be passed as an lvalue either: Mean ()/Median ()/StandardDeviation () take
141 * ITERATOR_OF_T2&& constrained by sentinel_for<>, so an lvalue would deduce ITERATOR_OF_T2 to a
142 * REFERENCE type, and sentinel_for<> requires semiregular<> (hence default_initializable<>), which
143 * no reference type satisfies - the constraint would fail and it would not compile.
144 *
145 * So hand each call its own copy, as a prvalue.
146 */
147 using EndType_ = remove_cvref_t<ITERATOR_OF_T2>;
148 if constexpr (Common::IBuiltinArithmetic<T>) {
149 results.fMin = *min_element (start, EndType_{end});
150 results.fMax = *max_element (start, EndType_{end});
151 }
152 results.fMean = Mean (start, EndType_{end});
153 results.fMedian = Median (start, EndType_{end});
154 if constexpr (Common::IBuiltinArithmetic<T>) {
155 results.fStandardDeviation = StandardDeviation (start, EndType_{end});
156 }
157 }
158 return results;
159 }
160 template <ranges::range CONTAINER_OF_T>
161 inline auto ComputeCommonStatistics (const CONTAINER_OF_T& container) -> CommonStatistics<typename CONTAINER_OF_T::value_type>
162 {
163 return ComputeCommonStatistics<typename CONTAINER_OF_T::value_type> (ranges::begin (container), ranges::end (container));
164 }
165
166}
RESULT_TYPE StandardDeviation(const ITERATOR_OF_T &start, ITERATOR_OF_T2 &&end)
Alias: sd, standard-deviation, stddev.
CommonStatistics< T > ComputeCommonStatistics(const ITERATOR_OF_T &start, ITERATOR_OF_T2 &&end)
handy aggregation of several common random-variable statistics/measurements.
RESULT_TYPE Mean(const ITERATOR_OF_T &start, ITERATOR_OF_T2 &&end)
Mean (average) of a collection of numbers computed.
RESULT_TYPE Median(const ITERATOR_OF_T &start, ITERATOR_OF_T2 &&end, INORDER_COMPARE_FUNCTION &&compare={})
Median of a collection of numbers computed.
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual size_t size() const noexcept
concept true if integral or floating-point type 'T'. Not sure why not provided by std c++
Definition Concepts.h:71