Skip to main content
added 2472 characters in body
Source Link
JDługosz
  • 11.3k
  • 18
  • 40
template <typename T>
T average(std::vector<T> distributionVector)

First of all, why is it limited to/specific to vector?
Second, why are you passing it by value? Do you understand that this copies the entire vector?

Classically, such functions should take a pair of iterators to specify the input. As of C++20, you could use a to be continuedRange. By taking any parameter that satisfies the Range concept, that includes std::vector and any other sequential collection as well.

If you change the template declaration to accept any container, that doesn't affect the code! Except... it would work for std::deque, boost::small_vector, etc. but would have trouble when you pass it a raw array, or certain types of range views. For this reason, use the non-member begin, end, size, etc. to better abstract the collection/view.

Classically, this would be done with the "std:: two-step". With C++20, just use the newer std::ranges::begin etc.

if (distributionVector.size() == 0)

Use empty instead of comparing the size with 0. And again, use the non-member.

When testing, include a plain C array as one of the tests; e.g.

constexpr int test_val_1[] = { 1,2,3,4,6,8,9,34,45,78,89 };
   ⋮
const auto result = variance<double>(test_val_1);

Notice that in this example I also specified that I want a floating-point calculation of the variance, even though the input is integers.

the API

In many calculators, the stats are computed incrementally. You submit each value (or value pair) as you compute them or enter them, often with a key labeled "Σ+". You can model this with a class that keeps intermediate accumulators but does not need the entire list of input at once. It can be used incrementally.

The class would use template arguments for the type to use for the accumulators, which IIRC are things like the count of items (n), the sum, the sum of the squares, and more for two-variable input.

The class would have functions to submit a value, or submit a range of values at once. These member functions can themselves be templates, to be flexible as to the parameter type.

It would have functions like average and variance that operate on the stored data. This way you can call for multiple stats without having to re-compute things or make multiple passes over the same data.

to be continued

template <typename T>
T average(std::vector<T> distributionVector)

First of all, why is it limited to/specific to vector?
Second, why are you passing it by value? Do you understand that this copies the entire vector?

Classically, such functions should take a pair of iterators to specify the input. As of C++20, you could use a Range. By taking any parameter that satisfies the Range concept, that includes std::vector and any other sequential collection as well.

If you change the template declaration to accept any container, that doesn't affect the code! Except... it would work for std::deque, boost::small_vector, etc. but would have trouble when you pass it a raw array, or certain types of range views. For this reason, use the non-member begin, end, size, etc. to better abstract the collection/view.

Classically, this would be done with the "std:: two-step". With C++20, just use the newer std::ranges::begin etc.

if (distributionVector.size() == 0)

Use empty instead of comparing the size with 0. And again, use the non-member.

When testing, include a plain C array as one of the tests; e.g.

constexpr int test_val_1[] = { 1,2,3,4,6,8,9,34,45,78,89 };
   ⋮
const auto result = variance<double>(test_val_1);

Notice that in this example I also specified that I want a floating-point calculation of the variance, even though the input is integers.

the API

In many calculators, the stats are computed incrementally. You submit each value (or value pair) as you compute them or enter them, often with a key labeled "Σ+". You can model this with a class that keeps intermediate accumulators but does not need the entire list of input at once. It can be used incrementally.

The class would use template arguments for the type to use for the accumulators, which IIRC are things like the count of items (n), the sum, the sum of the squares, and more for two-variable input.

The class would have functions to submit a value, or submit a range of values at once. These member functions can themselves be templates, to be flexible as to the parameter type.

It would have functions like average and variance that operate on the stored data. This way you can call for multiple stats without having to re-compute things or make multiple passes over the same data.

Source Link
JDługosz
  • 11.3k
  • 18
  • 40

overflow checking

Generally, all the code in the standard library and elsewhere is written without inserting any overflow checking. Unless you have a special need, don't worry about it in your functions.

Now that it's a template, the caller could specify an extended-precision integer class, or a "safe" integer class, to get such checking. The checking is part of the type used, and need not be explicitly addressed in your code. It will be built into the operator+ etc. for that type. For example, see the videos from the 2021 C++now conference — I recall a presentation on Simplest Safe Integers, and there have been others in the past.

parameter, return, and other types

Another point of concern is for templatized argument, I am forced to use the same type as the return type, which means the mean of integers shall also result in an integer, clearly not a good choice. At the same time if I enforce double as the return type, then extending into multi-dimensional types like vectors or complex numbers will be problematic.

No, you are not forced to use the same type for the argument as the return.

You can take the input parameter as another template argument. Using classic syntax:

template <typename R, typename P>
R factorial (P x)
{ ⋯ }

With C++20, you can use Concepts to specify that the template parameters must be integral types.

Note that you put R first, since you will deduce P from the arguments but must give R. Example use:

const auto y = factorial<bignum_t>(432);

Likewise, you can take additional template arguments, perhaps with defaults, to use for internal computations if that becomes necessary.

It's probably much more efficient to use built-in integers for inputs and where you can inside the body of the function, and the extended-precision (or "safe") class for the accumulation of the result.

comparison for floating-point

That should be built-into the unit testing framework. (example)

the new functions

to be continued