Stroika Library 3.0d25
 
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
8#include "Stroika/Foundation/Math/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 RESULT_TYPE result{tmp[size / 2]};
63 if ((size % 2) == 0) {
64 Assert (size >= 2); // cuz require at start >=1 and since even
65 // 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
66 // when you do it twice.
67 nth_element (tmp.begin (), tmp.begin () + size / 2 - 1, tmp.end (), forward<INORDER_COMPARE_FUNCTION> (compare));
68 result += tmp[size / 2 - 1];
69 result /= 2;
70 }
71 return result;
72 }
73 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>
74 inline auto Median (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end, INORDER_COMPARE_FUNCTION&& compare) ->
75 typename iterator_traits<ITERATOR_OF_T>::value_type
76 {
77 return Median<typename iterator_traits<ITERATOR_OF_T>::value_type> (start, forward<ITERATOR_OF_T2> (end),
78 forward<INORDER_COMPARE_FUNCTION> (compare));
79 }
80 template <ranges::range CONTAINER_OF_T, Common::IInOrderComparer<typename CONTAINER_OF_T::value_type> INORDER_COMPARE_FUNCTION>
81 inline auto Median (const CONTAINER_OF_T& container, INORDER_COMPARE_FUNCTION&& compare) -> typename CONTAINER_OF_T::value_type
82 {
83 Require (not container.empty ());
84 return Median<typename CONTAINER_OF_T::value_type> (ranges::begin (container), ranges::end (container),
85 forward<INORDER_COMPARE_FUNCTION> (compare));
86 }
87
88 /*
89 ********************************************************************************
90 **************************** StandardDeviation *********************************
91 ********************************************************************************
92 */
93 template <typename RESULT_TYPE, input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
94 RESULT_TYPE StandardDeviation (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end)
95 {
96 // 'end' is used again by the loop below, so it is NOT forwarded into Mean (). It cannot be passed as a
97 // plain lvalue either - Mean () takes ITERATOR_OF_T2&& constrained by sentinel_for<>, and an lvalue would
98 // deduce that to a reference type, which is not semiregular<> and so fails the constraint. Hand it a copy.
99 Require (start != end); // the std-deviation of no values would be undefined
100 RESULT_TYPE mean = Mean<RESULT_TYPE> (start, remove_cvref_t<ITERATOR_OF_T2>{end});
101 RESULT_TYPE accum{};
102 size_t n{};
103 for (auto i = start; i != end; ++i) {
104 ++n;
105 accum += (*i - mean) * (*i - mean);
106 }
107 Require (n >= 1); // the std-deviation of no values would be undefined
108 return sqrt (accum / (n - 1));
109 }
110 template <input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
111 inline auto StandardDeviation (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end) ->
112 typename iterator_traits<remove_cvref_t<ITERATOR_OF_T>>::value_type
113 {
114 Require (start != end); // the std-deviation of no values would be undefined - compare only, so no forward
115 return StandardDeviation<typename iterator_traits<ITERATOR_OF_T>::value_type> (start, forward<ITERATOR_OF_T2> (end)); // last use
116 }
117 template <ranges::range CONTAINER_OF_T>
118 inline auto StandardDeviation (const CONTAINER_OF_T& container) -> typename CONTAINER_OF_T::value_type
119 {
120 Require (not container.empty ()); // the std-deviation of no values would be undefined
121 return StandardDeviation<typename CONTAINER_OF_T::value_type> (ranges::begin (container), ranges::end (container));
122 }
123
124 /*
125 ********************************************************************************
126 **************************** ComputeCommonStatistics ***************************
127 ********************************************************************************
128 */
129 template <typename T, input_iterator ITERATOR_OF_T, sentinel_for<ITERATOR_OF_T> ITERATOR_OF_T2>
130 CommonStatistics<T> ComputeCommonStatistics (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end)
131 {
132 CommonStatistics<T> results;
133 if (start != end) {
134 /*
135 * NB: 'end' is consumed up to five times below, ALL ON THE SAME PATH, so it must not be forwarded -
136 * that would move out of it and leave the later calls reading a moved-from sentinel.
137 *
138 * It cannot simply be passed as an lvalue either: Mean ()/Median ()/StandardDeviation () take
139 * ITERATOR_OF_T2&& constrained by sentinel_for<>, so an lvalue would deduce ITERATOR_OF_T2 to a
140 * REFERENCE type, and sentinel_for<> requires semiregular<> (hence default_initializable<>), which
141 * no reference type satisfies - the constraint would fail and it would not compile.
142 *
143 * So hand each call its own copy, as a prvalue.
144 */
145 using EndType_ = remove_cvref_t<ITERATOR_OF_T2>;
146 if constexpr (Common::IBuiltinArithmetic<T>) {
147 results.fMin = *min_element (start, EndType_{end});
148 results.fMax = *max_element (start, EndType_{end});
149 }
150 results.fMean = Mean (start, EndType_{end});
151 results.fMedian = Median (start, EndType_{end});
152 if constexpr (Common::IBuiltinArithmetic<T>) {
153 results.fStandardDeviation = StandardDeviation (start, EndType_{end});
154 }
155 }
156 return results;
157 }
158 template <ranges::range CONTAINER_OF_T>
159 inline auto ComputeCommonStatistics (const CONTAINER_OF_T& container) -> CommonStatistics<typename CONTAINER_OF_T::value_type>
160 {
161 return ComputeCommonStatistics<typename CONTAINER_OF_T::value_type> (ranges::begin (container), ranges::end (container));
162 }
163
164}
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'.
Definition Concepts.h:73