chore: import upstream snapshot with attribution
Awesome CI Workflow / Code Formatter (push) Has been cancelled
Awesome CI Workflow / Compile checks (macOS-latest) (push) Has been cancelled
Awesome CI Workflow / Compile checks (ubuntu-latest) (push) Has been cancelled
Awesome CI Workflow / Compile checks (windows-latest) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:28 +08:00
commit 29cfe479ab
432 changed files with 68491 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# If necessary, use the RELATIVE flag, otherwise each source file may be listed
# with full pathname. RELATIVE may makes it easier to extract an executable name
# automatically.
file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp )
# file( GLOB APP_SOURCES ${CMAKE_SOURCE_DIR}/*.c )
# AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} APP_SOURCES)
foreach( testsourcefile ${APP_SOURCES} )
# I used a simple string replace, to cut off .cpp.
string( REPLACE ".cpp" "" testname ${testsourcefile} )
add_executable( ${testname} ${testsourcefile} )
set_target_properties(${testname} PROPERTIES LINKER_LANGUAGE CXX)
if(OpenMP_CXX_FOUND)
target_link_libraries(${testname} OpenMP::OpenMP_CXX)
endif()
install(TARGETS ${testname} DESTINATION "bin/probability")
endforeach( testsourcefile ${APP_SOURCES} )
+42
View File
@@ -0,0 +1,42 @@
/**
* @file
* @brief Addition rule of probabilities
*/
#include <iostream>
/**
* calculates the probability of the independent events A or B for independent
* events
* \parama [in] A probability of event A
* \parama [in] B probability of event B
* \returns probability of A and B
*/
double addition_rule_independent(double A, double B) {
return (A + B) - (A * B);
}
/** Calculates the probability of the events A or B for dependent events
* note that if value of B_given_A is unknown, use chainrule to find it
* \parama [in] A probability of event A
* \parama [in] B probability of event B
* \parama [in] B_given_A probability of event B condition A
* \returns probability of A and B
*/
double addition_rule_dependent(double A, double B, double B_given_A) {
return (A + B) - (A * B_given_A);
}
/** Main function */
int main() {
double A = 0.5;
double B = 0.25;
double B_given_A = 0.05;
std::cout << "independent P(A or B) = " << addition_rule_independent(A, B)
<< std::endl;
std::cout << "dependent P(A or B) = "
<< addition_rule_dependent(A, B, B_given_A) << std::endl;
return 0;
}
+34
View File
@@ -0,0 +1,34 @@
/**
* @file
* @brief [Bayes' theorem](https://en.wikipedia.org/wiki/Bayes%27_theorem)
*
* Bayes' theorem allows one to find \f$P(A|B)\f$ given \f$P(B|A)\f$ or
* \f$P(B|A)\f$ given \f$P(A|B)\f$ and \f$P(A)\f$ and \f$P(B)\f$.\n
* Note that \f$P(A|B)\f$ is read 'The probability of A given that the event B
* has occured'.
*/
#include <iostream>
/** returns P(A|B)
*/
double bayes_AgivenB(double BgivenA, double A, double B) {
return (BgivenA * A) / B;
}
/** returns P(B|A)
*/
double bayes_BgivenA(double AgivenB, double A, double B) {
return (AgivenB * B) / A;
}
/** Main function
*/
int main() {
double A = 0.01;
double B = 0.1;
double BgivenA = 0.9;
double AgivenB = bayes_AgivenB(BgivenA, A, B);
std::cout << "A given B = " << AgivenB << std::endl;
std::cout << "B given A = " << bayes_BgivenA(AgivenB, A, B) << std::endl;
return 0;
}
+100
View File
@@ -0,0 +1,100 @@
/**
* @file
* @brief [Binomial
* distribution](https://en.wikipedia.org/wiki/Binomial_distribution) example
*
* The binomial distribution models the number of
* successes in a sequence of n independent events
*
* Summary of variables used:
* * n : number of trials
* * p : probability of success
* * x : desired successes
*/
#include <cmath>
#include <iostream>
/** finds the expected value of a binomial distribution
* \param [in] n
* \param [in] p
* \returns \f$\mu=np\f$
*/
double binomial_expected(double n, double p) { return n * p; }
/** finds the variance of the binomial distribution
* \param [in] n
* \param [in] p
* \returns \f$\sigma^2 = n\cdot p\cdot (1-p)\f$
*/
double binomial_variance(double n, double p) { return n * p * (1 - p); }
/** finds the standard deviation of the binomial distribution
* \param [in] n
* \param [in] p
* \returns \f$\sigma = \sqrt{\sigma^2} = \sqrt{n\cdot p\cdot (1-p)}\f$
*/
double binomial_standard_deviation(double n, double p) {
return std::sqrt(binomial_variance(n, p));
}
/** Computes n choose r
* \param [in] n
* \param [in] r
* \returns \f$\displaystyle {n\choose r} =
* \frac{n!}{r!(n-r)!} = \frac{n\times(n-1)\times(n-2)\times\cdots(n-r)}{r!}
* \f$
*/
double nCr(double n, double r) {
double numerator = n;
double denominator = r;
for (int i = n - 1; i >= ((n - r) + 1); i--) {
numerator *= i;
}
for (int i = 1; i < r; i++) {
denominator *= i;
}
return numerator / denominator;
}
/** calculates the probability of exactly x successes
* \returns \f$\displaystyle P(n,p,x) = {n\choose x} p^x (1-p)^{n-x}\f$
*/
double binomial_x_successes(double n, double p, double x) {
return nCr(n, x) * std::pow(p, x) * std::pow(1 - p, n - x);
}
/** calculates the probability of a result within a range (inclusive, inclusive)
* \returns \f$\displaystyle \left.P(n,p)\right|_{x_0}^{x_1} =
* \sum_{i=x_0}^{x_1} P(i)
* =\sum_{i=x_0}^{x_1} {n\choose i} p^i (1-p)^{n-i}\f$
*/
double binomial_range_successes(double n, double p, double lower_bound,
double upper_bound) {
double probability = 0;
for (int i = lower_bound; i <= upper_bound; i++) {
probability += nCr(n, i) * std::pow(p, i) * std::pow(1 - p, n - i);
}
return probability;
}
/** main function */
int main() {
std::cout << "expected value : " << binomial_expected(100, 0.5)
<< std::endl;
std::cout << "variance : " << binomial_variance(100, 0.5) << std::endl;
std::cout << "standard deviation : "
<< binomial_standard_deviation(100, 0.5) << std::endl;
std::cout << "exactly 30 successes : " << binomial_x_successes(100, 0.5, 30)
<< std::endl;
std::cout << "45 or more successes : "
<< binomial_range_successes(100, 0.5, 45, 100) << std::endl;
return 0;
}
+151
View File
@@ -0,0 +1,151 @@
/**
* @file
* @brief [Exponential
* Distribution](https://en.wikipedia.org/wiki/Exponential_distribution)
*
* The exponential distribution is used to model
* events occuring between a Poisson process like radioactive decay.
*
* \f[P(x, \lambda) = \lambda e^{-\lambda x}\f]
*
* Summary of variables used:
* \f$\lambda\f$ : rate parameter
*/
#include <cassert> // For assert
#include <cmath> // For std::pow
#include <iostream> // For I/O operation
#include <stdexcept> // For std::invalid_argument
#include <string> // For std::string
/**
* @namespace probability
* @brief Probability algorithms
*/
namespace probability {
/**
* @namespace exponential_dist
* @brief Functions for the [Exponential
* Distribution](https://en.wikipedia.org/wiki/Exponential_distribution)
* algorithm implementation
*/
namespace geometric_dist {
/**
* @brief the expected value of the exponential distribution
* @returns \f[\mu = \frac{1}{\lambda}\f]
*/
double exponential_expected(double lambda) {
if (lambda <= 0) {
throw std::invalid_argument("lambda must be greater than 0");
}
return 1 / lambda;
}
/**
* @brief the variance of the exponential distribution
* @returns \f[\sigma^2 = \frac{1}{\lambda^2}\f]
*/
double exponential_var(double lambda) {
if (lambda <= 0) {
throw std::invalid_argument("lambda must be greater than 0");
}
return 1 / pow(lambda, 2);
}
/**
* @brief the standard deviation of the exponential distribution
* @returns \f[\sigma = \frac{1}{\lambda}\f]
*/
double exponential_std(double lambda) {
if (lambda <= 0) {
throw std::invalid_argument("lambda must be greater than 0");
}
return 1 / lambda;
}
} // namespace geometric_dist
} // namespace probability
/**
* @brief Self-test implementations
* @returns void
*/
static void test() {
double lambda_1 = 1;
double expected_1 = 1;
double var_1 = 1;
double std_1 = 1;
double lambda_2 = 2;
double expected_2 = 0.5;
double var_2 = 0.25;
double std_2 = 0.5;
double lambda_3 = 3;
double expected_3 = 0.333333;
double var_3 = 0.111111;
double std_3 = 0.333333;
double lambda_4 = 0; // Test 0
double lambda_5 = -2.3; // Test negative value
const float threshold = 1e-3f;
std::cout << "Test for lambda = 1 \n";
assert(
std::abs(expected_1 - probability::geometric_dist::exponential_expected(
lambda_1)) < threshold);
assert(std::abs(var_1 - probability::geometric_dist::exponential_var(
lambda_1)) < threshold);
assert(std::abs(std_1 - probability::geometric_dist::exponential_std(
lambda_1)) < threshold);
std::cout << "ALL TEST PASSED\n\n";
std::cout << "Test for lambda = 2 \n";
assert(
std::abs(expected_2 - probability::geometric_dist::exponential_expected(
lambda_2)) < threshold);
assert(std::abs(var_2 - probability::geometric_dist::exponential_var(
lambda_2)) < threshold);
assert(std::abs(std_2 - probability::geometric_dist::exponential_std(
lambda_2)) < threshold);
std::cout << "ALL TEST PASSED\n\n";
std::cout << "Test for lambda = 3 \n";
assert(
std::abs(expected_3 - probability::geometric_dist::exponential_expected(
lambda_3)) < threshold);
assert(std::abs(var_3 - probability::geometric_dist::exponential_var(
lambda_3)) < threshold);
assert(std::abs(std_3 - probability::geometric_dist::exponential_std(
lambda_3)) < threshold);
std::cout << "ALL TEST PASSED\n\n";
std::cout << "Test for lambda = 0 \n";
try {
probability::geometric_dist::exponential_expected(lambda_4);
probability::geometric_dist::exponential_var(lambda_4);
probability::geometric_dist::exponential_std(lambda_4);
} catch (std::invalid_argument& err) {
assert(std::string(err.what()) == "lambda must be greater than 0");
}
std::cout << "ALL TEST PASSED\n\n";
std::cout << "Test for lambda = -2.3 \n";
try {
probability::geometric_dist::exponential_expected(lambda_5);
probability::geometric_dist::exponential_var(lambda_5);
probability::geometric_dist::exponential_std(lambda_5);
} catch (std::invalid_argument& err) {
assert(std::string(err.what()) == "lambda must be greater than 0");
}
std::cout << "ALL TEST PASSED\n\n";
}
/**
* @brief Main function
* @return 0 on exit
*/
int main() {
test(); // Self test implementation
return 0;
}
+260
View File
@@ -0,0 +1,260 @@
/**
* @file
* @brief [Geometric
* Distribution](https://en.wikipedia.org/wiki/Geometric_distribution)
*
* @details
* The geometric distribution models the experiment of doing Bernoulli trials
* until a sucess was observed. There are two formulations of the geometric
* distribution: 1) The probability distribution of the number X of Bernoulli
* trials needed to get one success, supported on the set { 1, 2, 3, ... } 2)
* The probability distribution of the number Y = X 1 of failures before the
* first success, supported on the set { 0, 1, 2, 3, ... } Here, the first one
* is implemented.
*
* Common variables used:
* p - The success probability
* k - The number of tries
*
* @author [Domenic Zingsheim](https://github.com/DerAndereDomenic)
*/
#include <cassert> /// for assert
#include <cmath> /// for math functions
#include <cstdint> /// for fixed size data types
#include <ctime> /// for time to initialize rng
#include <iostream> /// for std::cout
#include <limits> /// for std::numeric_limits
#include <random> /// for random numbers
#include <vector> /// for std::vector
/**
* @namespace probability
* @brief Probability algorithms
*/
namespace probability {
/**
* @namespace geometric_dist
* @brief Functions for the [Geometric
* Distribution](https://en.wikipedia.org/wiki/Geometric_distribution) algorithm
* implementation
*/
namespace geometric_dist {
/**
* @brief Returns a random number between [0,1]
* @returns A uniformly distributed random number between 0 (included) and 1
* (included)
*/
float generate_uniform() {
return static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
}
/**
* @brief A class to model the geometric distribution
*/
class geometric_distribution {
private:
float p; ///< The succes probability p
public:
/**
* @brief Constructor for the geometric distribution
* @param p The success probability
*/
explicit geometric_distribution(const float& p) : p(p) {}
/**
* @brief The expected value of a geometrically distributed random variable
* X
* @returns E[X] = 1/p
*/
float expected_value() const { return 1.0f / p; }
/**
* @brief The variance of a geometrically distributed random variable X
* @returns V[X] = (1 - p) / p^2
*/
float variance() const { return (1.0f - p) / (p * p); }
/**
* @brief The standard deviation of a geometrically distributed random
* variable X
* @returns \sigma = \sqrt{V[X]}
*/
float standard_deviation() const { return std::sqrt(variance()); }
/**
* @brief The probability density function
* @details As we use the first definition of the geometric series (1),
* we are doing k - 1 failed trials and the k-th trial is a success.
* @param k The number of trials to observe the first success in [1,\infty)
* @returns A number between [0,1] according to p * (1-p)^{k-1}
*/
float probability_density(const uint32_t& k) const {
return std::pow((1.0f - p), static_cast<float>(k - 1)) * p;
}
/**
* @brief The cumulative distribution function
* @details The sum of all probabilities up to (and including) k trials.
* Basically CDF(k) = P(x <= k)
* @param k The number of trials in [1,\infty)
* @returns The probability to have success within k trials
*/
float cumulative_distribution(const uint32_t& k) const {
return 1.0f - std::pow((1.0f - p), static_cast<float>(k));
}
/**
* @brief The inverse cumulative distribution function
* @details This functions answers the question: Up to how many trials are
* needed to have success with a probability of cdf? The exact floating
* point value is reported.
* @param cdf The probability in [0,1]
* @returns The number of (exact) trials.
*/
float inverse_cumulative_distribution(const float& cdf) const {
return std::log(1.0f - cdf) / std::log(1.0f - p);
}
/**
* @brief Generates a (discrete) sample according to the geometrical
* distribution
* @returns A geometrically distributed number in [1,\infty)
*/
uint32_t draw_sample() const {
float uniform_sample = generate_uniform();
return static_cast<uint32_t>(
inverse_cumulative_distribution(uniform_sample)) +
1;
}
/**
* @brief This function computes the probability to have success in a given
* range of tries
* @details Computes P(min_tries <= x <= max_tries).
* Can be used to calculate P(x >= min_tries) by not passing a second
* argument. Can be used to calculate P(x <= max_tries) by passing 1 as the
* first argument
* @param min_tries The minimum number of tries in [1,\infty) (inclusive)
* @param max_tries The maximum number of tries in [min_tries, \infty)
* (inclusive)
* @returns The probability of having success within a range of tries
* [min_tries, max_tries]
*/
float range_tries(const uint32_t& min_tries = 1,
const uint32_t& max_tries =
std::numeric_limits<uint32_t>::max()) const {
float cdf_lower = cumulative_distribution(min_tries - 1);
float cdf_upper = max_tries == std::numeric_limits<uint32_t>::max()
? 1.0f
: cumulative_distribution(max_tries);
return cdf_upper - cdf_lower;
}
};
} // namespace geometric_dist
} // namespace probability
/**
* @brief Tests the sampling method of the geometric distribution
* @details Draws 1000000 random samples and estimates mean and variance
* These should be close to the expected value and variance of the given
* distribution to pass.
* @param dist The distribution to test
*/
void sample_test(
const probability::geometric_dist::geometric_distribution& dist) {
uint32_t n_tries = 1000000;
std::vector<float> tries;
tries.resize(n_tries);
float mean = 0.0f;
for (uint32_t i = 0; i < n_tries; ++i) {
tries[i] = static_cast<float>(dist.draw_sample());
mean += tries[i];
}
mean /= static_cast<float>(n_tries);
float var = 0.0f;
for (uint32_t i = 0; i < n_tries; ++i) {
var += (tries[i] - mean) * (tries[i] - mean);
}
// Unbiased estimate of variance
var /= static_cast<float>(n_tries - 1);
std::cout << "This value should be near " << dist.expected_value() << ": "
<< mean << std::endl;
std::cout << "This value should be near " << dist.variance() << ": " << var
<< std::endl;
}
/**
* @brief Self-test implementations
* @returns void
*/
static void test() {
probability::geometric_dist::geometric_distribution dist(0.3);
const float threshold = 1e-3f;
std::cout << "Starting tests for p = 0.3..." << std::endl;
assert(std::abs(dist.expected_value() - 3.33333333f) < threshold);
assert(std::abs(dist.variance() - 7.77777777f) < threshold);
assert(std::abs(dist.standard_deviation() - 2.788866755) < threshold);
assert(std::abs(dist.probability_density(5) - 0.07203) < threshold);
assert(std::abs(dist.cumulative_distribution(6) - 0.882351) < threshold);
assert(std::abs(dist.inverse_cumulative_distribution(
dist.cumulative_distribution(8)) -
8) < threshold);
assert(std::abs(dist.range_tries() - 1.0f) < threshold);
assert(std::abs(dist.range_tries(3) - 0.49f) < threshold);
assert(std::abs(dist.range_tries(5, 11) - 0.2203267f) < threshold);
std::cout << "All tests passed" << std::endl;
sample_test(dist);
dist = probability::geometric_dist::geometric_distribution(0.5f);
std::cout << "Starting tests for p = 0.5..." << std::endl;
assert(std::abs(dist.expected_value() - 2.0f) < threshold);
assert(std::abs(dist.variance() - 2.0f) < threshold);
assert(std::abs(dist.standard_deviation() - 1.4142135f) < threshold);
assert(std::abs(dist.probability_density(5) - 0.03125) < threshold);
assert(std::abs(dist.cumulative_distribution(6) - 0.984375) < threshold);
assert(std::abs(dist.inverse_cumulative_distribution(
dist.cumulative_distribution(8)) -
8) < threshold);
assert(std::abs(dist.range_tries() - 1.0f) < threshold);
assert(std::abs(dist.range_tries(3) - 0.25f) < threshold);
assert(std::abs(dist.range_tries(5, 11) - 0.062011f) < threshold);
std::cout << "All tests passed" << std::endl;
sample_test(dist);
dist = probability::geometric_dist::geometric_distribution(0.8f);
std::cout << "Starting tests for p = 0.8..." << std::endl;
assert(std::abs(dist.expected_value() - 1.25f) < threshold);
assert(std::abs(dist.variance() - 0.3125f) < threshold);
assert(std::abs(dist.standard_deviation() - 0.559016f) < threshold);
assert(std::abs(dist.probability_density(5) - 0.00128) < threshold);
assert(std::abs(dist.cumulative_distribution(6) - 0.999936) < threshold);
assert(std::abs(dist.inverse_cumulative_distribution(
dist.cumulative_distribution(8)) -
8) < threshold);
assert(std::abs(dist.range_tries() - 1.0f) < threshold);
assert(std::abs(dist.range_tries(3) - 0.04f) < threshold);
assert(std::abs(dist.range_tries(5, 11) - 0.00159997f) < threshold);
std::cout << "All tests have successfully passed!" << std::endl;
sample_test(dist);
}
/**
* @brief Main function
* @return 0 on exit
*/
int main() {
srand(time(nullptr));
test(); // run self-test implementations
return 0;
}
+79
View File
@@ -0,0 +1,79 @@
/**
* @file
* @brief [Poisson
* statistics](https://en.wikipedia.org/wiki/Poisson_distribution)
*
* The Poisson distribution counts how many
* events occur over a set time interval.
*/
#include <cmath>
#include <iostream>
/**
* poisson rate:\n
* calculate the events per unit time\n
* e.g 5 dollars every 2 mins = 5 / 2 = 2.5
*/
double poisson_rate(double events, double timeframe) {
return events / timeframe;
}
/**
* calculate the expected value over a time
* e.g rate of 2.5 over 10 mins = 2.5 x 10 = 25
*/
double poisson_expected(double rate, double time) { return rate * time; }
/**
* Compute factorial of a given number
*/
double fact(double x) {
double x_fact = x;
for (int i = x - 1; i > 0; i--) {
x_fact *= i;
}
if (x_fact <= 0) {
x_fact = 1;
}
return x_fact;
}
/**
* Find the probability of x successes in a Poisson dist.
* \f[p(\mu,x) = \frac{\mu^x e^{-\mu}}{x!}\f]
*/
double poisson_x_successes(double expected, double x) {
return (std::pow(expected, x) * std::exp(-expected)) / fact(x);
}
/**
* probability of a success in range for Poisson dist (inclusive, inclusive)
* \f[P = \sum_i p(\mu,i)\f]
*/
double poisson_range_successes(double expected, double lower, double upper) {
double probability = 0;
for (int i = lower; i <= upper; i++) {
probability += poisson_x_successes(expected, i);
}
return probability;
}
/**
* main function
*/
int main() {
double rate, expected;
rate = poisson_rate(3, 1);
std::cout << "Poisson rate : " << rate << std::endl;
expected = poisson_expected(rate, 2);
std::cout << "Poisson expected : " << expected << std::endl;
std::cout << "Poisson 0 successes : " << poisson_x_successes(expected, 0)
<< std::endl;
std::cout << "Poisson 0-8 successes : "
<< poisson_range_successes(expected, 0, 8) << std::endl;
return 0;
}
+234
View File
@@ -0,0 +1,234 @@
/**
* @file
* @brief An implementation of a median calculation of a sliding window along a
* data stream
*
* @details
* Given a stream of integers, the algorithm calculates the median of a fixed
* size window at the back of the stream. The leading time complexity of this
* algorithm is O(log(N), and it is inspired by the known algorithm to [find
* median from (infinite) data
* stream](https://www.tutorialcup.com/interview/algorithm/find-median-from-data-stream.htm),
* with the proper modifications to account for the finite window size for which
* the median is requested
*
* ### Algorithm
* The sliding window is managed by a list, which guarantees O(1) for both
* pushing and popping. Each new value is pushed to the window back, while a
* value from the front of the window is popped. In addition, the algorithm
* manages a multi-value binary search tree (BST), implemented by std::multiset.
* For each new value that is inserted into the window, it is also inserted to
* the BST. When a value is popped from the window, it is also erased from the
* BST. Both insertion and erasion to/from the BST are O(logN) in time, with N
* the size of the window. Finally, the algorithm keeps a pointer to the root of
* the BST, and updates its position whenever values are inserted or erased
* to/from BST. The root of the tree is the median! Hence, median retrieval is
* always O(1)
*
* Time complexity: O(logN). Space complexity: O(N). N - size of window
* @author [Yaniv Hollander](https://github.com/YanivHollander)
*/
#include <cassert> /// for assert
#include <cstdlib> /// for std::rand - needed in testing
#include <ctime> /// for std::time - needed in testing
#include <list> /// for std::list - used to manage sliding window
#include <set> /// for std::multiset - used to manage multi-value sorted sliding window values
#include <vector> /// for std::vector - needed in testing
/**
* @namespace probability
* @brief Probability algorithms
*/
namespace probability {
/**
* @namespace windowed_median
* @brief Functions for the Windowed Median algorithm implementation
*/
namespace windowed_median {
using Window = std::list<int>;
using size_type = Window::size_type;
/**
* @class WindowedMedian
* @brief A class to calculate the median of a leading sliding window at the
* back of a stream of integer values.
*/
class WindowedMedian {
const size_type _windowSize; ///< sliding window size
Window _window; ///< a sliding window of values along the stream
std::multiset<int> _sortedValues; ///< a DS to represent a balanced
/// multi-value binary search tree (BST)
std::multiset<int>::const_iterator
_itMedian; ///< an iterator that points to the root of the multi-value
/// BST
/**
* @brief Inserts a value to a sorted multi-value BST
* @param value Value to insert
*/
void insertToSorted(int value) {
_sortedValues.insert(value); /// Insert value to BST - O(logN)
const auto sz = _sortedValues.size();
if (sz == 1) { /// For the first value, set median iterator to BST root
_itMedian = _sortedValues.begin();
return;
}
/// If new value goes to left tree branch, and number of elements is
/// even, the new median in the balanced tree is the left child of the
/// median before the insertion
if (value < *_itMedian && sz % 2 == 0) {
--_itMedian; // O(1) - traversing one step to the left child
}
/// However, if the new value goes to the right branch, the previous
/// median's right child is the new median in the balanced tree
else if (value >= *_itMedian && sz % 2 != 0) {
++_itMedian; /// O(1) - traversing one step to the right child
}
}
/**
* @brief Erases a value from a sorted multi-value BST
* @param value Value to insert
*/
void eraseFromSorted(int value) {
const auto sz = _sortedValues.size();
/// If the erased value is on the left branch or the median itself and
/// the number of elements is even, the new median will be the right
/// child of the current one
if (value <= *_itMedian && sz % 2 == 0) {
++_itMedian; /// O(1) - traversing one step to the right child
}
/// However, if the erased value is on the right branch or the median
/// itself, and the number of elements is odd, the new median will be
/// the left child of the current one
else if (value >= *_itMedian && sz % 2 != 0) {
--_itMedian; // O(1) - traversing one step to the left child
}
/// Find the (first) position of the value we want to erase, and erase
/// it
const auto it = _sortedValues.find(value); // O(logN)
_sortedValues.erase(it); // O(logN)
}
public:
/**
* @brief Constructs a WindowedMedian object
* @param windowSize Sliding window size
*/
explicit WindowedMedian(size_type windowSize) : _windowSize(windowSize){};
/**
* @brief Insert a new value to the stream
* @param value New value to insert
*/
void insert(int value) {
/// Push new value to the back of the sliding window - O(1)
_window.push_back(value);
insertToSorted(value); // Insert value to the multi-value BST - O(logN)
if (_window.size() > _windowSize) { /// If exceeding size of window,
/// pop from its left side
eraseFromSorted(
_window.front()); /// Erase from the multi-value BST
/// the window left side value
_window.pop_front(); /// Pop the left side value from the window -
/// O(1)
}
}
/**
* @brief Gets the median of the values in the sliding window
* @return Median of sliding window. For even window size return the average
* between the two values in the middle
*/
float getMedian() const {
if (_sortedValues.size() % 2 != 0) {
return *_itMedian; // O(1)
}
return 0.5f * *_itMedian + 0.5f * *next(_itMedian); /// O(1)
}
/**
* @brief A naive and inefficient method to obtain the median of the sliding
* window. Used for testing!
* @return Median of sliding window. For even window size return the average
* between the two values in the middle
*/
float getMedianNaive() const {
auto window = _window;
window.sort(); /// Sort window - O(NlogN)
auto median =
*next(window.begin(),
window.size() / 2); /// Find value in the middle - O(N)
if (window.size() % 2 != 0) {
return median;
}
return 0.5f * median +
0.5f * *next(window.begin(), window.size() / 2 - 1); /// O(N)
}
};
} // namespace windowed_median
} // namespace probability
/**
* @brief Self-test implementations
* @param vals Stream of values
* @param windowSize Size of sliding window
*/
static void test(const std::vector<int> &vals, int windowSize) {
probability::windowed_median::WindowedMedian windowedMedian(windowSize);
for (const auto val : vals) {
windowedMedian.insert(val);
/// Comparing medians: efficient function vs. Naive one
assert(windowedMedian.getMedian() == windowedMedian.getMedianNaive());
}
}
/**
* @brief Main function
* @returns 0 on exit
*/
int main() {
/// A few fixed test cases
test({1, 2, 3, 4, 5, 6, 7, 8, 9},
3); /// Array of sorted values; odd window size
test({9, 8, 7, 6, 5, 4, 3, 2, 1},
3); /// Array of sorted values - decreasing; odd window size
test({9, 8, 7, 6, 5, 4, 5, 6}, 4); /// Even window size
test({3, 3, 3, 3, 3, 3, 3, 3, 3}, 3); /// Array with repeating values
test({3, 3, 3, 3, 7, 3, 3, 3, 3}, 3); /// Array with same values except one
test({4, 3, 3, -5, -5, 1, 3, 4, 5},
5); /// Array that includes repeating values including negatives
/// Array with large values - sum of few pairs exceeds MAX_INT. Window size
/// is even - testing calculation of average median between two middle
/// values
test({470211272, 101027544, 1457850878, 1458777923, 2007237709, 823564440,
1115438165, 1784484492, 74243042, 114807987},
6);
/// Random test cases
std::srand(static_cast<unsigned int>(std::time(nullptr)));
std::vector<int> vals;
for (int i = 8; i < 100; i++) {
const auto n =
1 + std::rand() /
((RAND_MAX + 5u) / 20); /// Array size in the range [5, 20]
auto windowSize =
1 + std::rand() / ((RAND_MAX + 3u) /
10); /// Window size in the range [3, 10]
vals.clear();
vals.reserve(n);
for (int i = 0; i < n; i++) {
vals.push_back(
rand() - RAND_MAX); /// Random array values (positive/negative)
}
test(vals, windowSize); /// Testing randomized test
}
return 0;
}