169
math/random/DrawList.h
Normal file
169
math/random/DrawList.h
Normal file
@@ -0,0 +1,169 @@
|
||||
#ifndef K_MATH_RANDOM_DRAWLIST_H
|
||||
#define K_MATH_RANDOM_DRAWLIST_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* add entries to a list and be able to draw from them depending oh their probability
|
||||
*/
|
||||
namespace Random {
|
||||
|
||||
/**
|
||||
* this class represents one entry within a DrawList.
|
||||
* such an entry consists of userData denoted by the template argument
|
||||
* and a probability.
|
||||
*/
|
||||
template <typename Entry> struct DrawListEntry {
|
||||
|
||||
template <typename> friend class DrawList;
|
||||
friend class DrawList_Cumulative_Test;
|
||||
|
||||
public:
|
||||
|
||||
/** the user data behind this entry */
|
||||
Entry entry;
|
||||
|
||||
/** this entry's probability */
|
||||
double probability;
|
||||
|
||||
private:
|
||||
|
||||
/** the cumulative probability, tracked among all entries within the DrawList */
|
||||
double cumulativeProbability;
|
||||
|
||||
public:
|
||||
|
||||
/** empty ctor */
|
||||
DrawListEntry() :
|
||||
entry(), probability(0), cumulativeProbability(0) {;}
|
||||
|
||||
/** ctor */
|
||||
DrawListEntry(const Entry& entry, double probability) :
|
||||
entry(entry), probability(probability), cumulativeProbability(0) {;}
|
||||
|
||||
/** compare this entrie's summed probability with the given probability */
|
||||
bool operator < (double probability) const {return cumulativeProbability < probability;}
|
||||
|
||||
private:
|
||||
|
||||
/** ctor */
|
||||
DrawListEntry(Entry& e, double probability, double summedProbability) :
|
||||
entry(entry), probability(probability), cumulativeProbability(summedProbability) {;}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* a DrawList is a data-structure containing entries that have a
|
||||
* probability assigned to them.
|
||||
* using the draw() function one may draw from these entries according
|
||||
* to their assigned probability in O(log(n))
|
||||
*/
|
||||
template <typename Entry> class DrawList {
|
||||
|
||||
friend class DrawList_Cumulative_Test;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
DrawList() : sumValid(false) {;}
|
||||
|
||||
|
||||
/** append a new entry to the end of the list */
|
||||
void push_back(const Entry& entry, const double probability) {
|
||||
const DrawListEntry<Entry> dle(entry, probability);
|
||||
entries.push_back(dle);
|
||||
sumValid = false;
|
||||
}
|
||||
|
||||
/** change the entry at the given position. ensure the vector is resized()! */
|
||||
void set(const uint32_t idx, const Entry& entry, const double probability) {
|
||||
entries[idx].entry = entry;
|
||||
entries[idx].probability = probability;
|
||||
sumValid = false;
|
||||
}
|
||||
|
||||
/** resize the underlying vector to hold the given number of entries */
|
||||
void resize(const uint32_t numEntries) {entries.resize(numEntries);}
|
||||
|
||||
/** clear all currently inserted entries */
|
||||
void clear() {entries.clear();}
|
||||
|
||||
/** does the underlying vector contain any entries? */
|
||||
bool empty() const {return entries.empty();}
|
||||
|
||||
/** the number of entries */
|
||||
uint32_t size() const {return entries.size();}
|
||||
|
||||
/** draw a random entry from the draw list */
|
||||
Entry& draw() {
|
||||
|
||||
// random value between [0, 1]
|
||||
double rand01 = double(rand()) / double(RAND_MAX);
|
||||
|
||||
return draw(rand01);
|
||||
|
||||
}
|
||||
|
||||
/** draw an entry according to the given probability [0,1] */
|
||||
Entry& draw(double rand01) {
|
||||
|
||||
// sanity check
|
||||
assert(!entries.empty());
|
||||
|
||||
ensureCumulativeProbability();
|
||||
|
||||
// random value between [0, summedProbability]
|
||||
// (this prevents us from norming the list to [0, 1])
|
||||
double rand = rand01 * entries[entries.size()-1].cumulativeProbability;
|
||||
|
||||
// binary search for the matching entry O(log(n))
|
||||
auto tmp = std::lower_bound(entries.begin(), entries.end(), rand);
|
||||
return (*tmp).entry;
|
||||
|
||||
// // O(n)
|
||||
// for (DrawListEntry<Entry>& dle : entries) {
|
||||
// if (dle.cumulativeProbability > rand) {return dle.entry;}
|
||||
// }
|
||||
// return entries[this->size()-1].entry;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/** ensure the cumulative probability is valid. if not -> calculate it */
|
||||
void ensureCumulativeProbability() {
|
||||
|
||||
// already valid?
|
||||
if (sumValid) {return;}
|
||||
|
||||
// calculate the cumulative probability
|
||||
double sum = 0;
|
||||
for (DrawListEntry<Entry>& dle : entries) {
|
||||
sum += dle.probability;
|
||||
dle.cumulativeProbability = sum;
|
||||
}
|
||||
|
||||
// the sum is now valid
|
||||
sumValid = true;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/** all entries within the DrawList */
|
||||
std::vector<DrawListEntry<Entry>> entries;
|
||||
|
||||
/** track wether the summedProbability is valid or not */
|
||||
bool sumValid;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // K_MATH_RANDOM_DRAWLIST_H
|
||||
174
math/random/DrawWheel.h
Normal file
174
math/random/DrawWheel.h
Normal file
@@ -0,0 +1,174 @@
|
||||
#ifndef K_MATH_RANDOM_DRAWLIST_H
|
||||
#define K_MATH_RANDOM_DRAWLIST_H
|
||||
|
||||
#include <vector>
|
||||
#include "../distribution/Uniform.h"
|
||||
|
||||
/**
|
||||
* add entries to a list and be able to draw from them depending oh their probability
|
||||
*
|
||||
* souces:
|
||||
* https://www.udacity.com/course/viewer#!/c-cs373/l-48704330/e-48748082/m-48740082
|
||||
* https://www.youtube.com/watch?list=PLpUPoM7Rgzi_7YWn14Va2FODh7LzADBSm&feature=player_detailpage&v=kZhOJgooMxI#t=567
|
||||
*/
|
||||
namespace Random {
|
||||
|
||||
/**
|
||||
* this class represents one entry within a DrawWheel.
|
||||
* such an entry consists of userData denoted by the template argument
|
||||
* and a probability.
|
||||
*/
|
||||
template <typename Entry> struct DrawWheelEntry {
|
||||
|
||||
template <typename> friend class DrawWheel;
|
||||
|
||||
public:
|
||||
|
||||
/** the user data behind this entry */
|
||||
Entry entry;
|
||||
|
||||
/** this entry's probability */
|
||||
double probability;
|
||||
|
||||
public:
|
||||
|
||||
/** empty ctor */
|
||||
DrawWheelEntry() : entry(), probability(0) {;}
|
||||
|
||||
/** ctor */
|
||||
DrawWheelEntry(const Entry& entry, double probability) : entry(entry), probability(probability) {;}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* a DrawWheel is a data-structure containing entries that have a
|
||||
* probability assigned to them.
|
||||
* using the draw() function one may draw from these entries according
|
||||
* to their assigned probability in ~O(n)
|
||||
*/
|
||||
template <typename Entry> class DrawWheel {
|
||||
|
||||
private:
|
||||
|
||||
/** all entries within the DrawWheel */
|
||||
std::vector<DrawWheelEntry<Entry>> entries;
|
||||
|
||||
/** is the current maximum valid? */
|
||||
bool maxValid = true;
|
||||
|
||||
/** the maximum weight among all entries */
|
||||
double curMax = 0;
|
||||
|
||||
/** the current index within the wheel */
|
||||
int curIdx = 0;
|
||||
|
||||
/** the current offset at the wheel's index */
|
||||
double curOffset = 0;
|
||||
|
||||
/** draw random numbers for the offset */
|
||||
K::UniformDistribution dist;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
DrawWheel() {;}
|
||||
|
||||
|
||||
/** append a new entry to the end of the list */
|
||||
void push_back(const Entry& entry, const double probability) {
|
||||
entries.push_back( DrawWheelEntry<Entry>(entry, probability) );
|
||||
if (curMax < probability) {curMax = probability;}
|
||||
}
|
||||
|
||||
/** change the entry at the given position. ensure the vector is resized()! */
|
||||
void set(const uint32_t idx, const Entry& entry, const double probability) {
|
||||
entries[idx].entry = entry;
|
||||
entries[idx].probability = probability;
|
||||
maxValid = false;
|
||||
}
|
||||
|
||||
/** resize the underlying vector to hold the given number of entries */
|
||||
void resize(const uint32_t numEntries) {entries.resize(numEntries);}
|
||||
|
||||
/** clear all currently inserted entries */
|
||||
void clear() {entries.clear();}
|
||||
|
||||
/** does the underlying vector contain any entries? */
|
||||
bool empty() const {return entries.empty();}
|
||||
|
||||
/** the number of entries */
|
||||
uint32_t size() const {return entries.size();}
|
||||
|
||||
|
||||
/** call this once before drawing anything */
|
||||
void init() {
|
||||
|
||||
// ensure the maximum number is correct
|
||||
ensureMaxProbability();
|
||||
|
||||
// setup the distribution to draw a new offset
|
||||
dist.reset(0, 2 * curMax);
|
||||
|
||||
// draw starting values
|
||||
curIdx = K::UniformDistribution::draw( (int)0, (int)entries.size() - 1);
|
||||
curOffset = dist.draw();
|
||||
|
||||
}
|
||||
|
||||
/** draw a random entry from the wheel */
|
||||
Entry& draw() {
|
||||
|
||||
while(true) {
|
||||
|
||||
// found a suitable particle? use it and draw the next random number
|
||||
if (entries[curIdx].probability >= curOffset) {
|
||||
|
||||
// next offset
|
||||
curOffset += dist.draw();
|
||||
|
||||
// return
|
||||
return entries[curIdx].entry;
|
||||
|
||||
// weight to small, subtract the elements weight and move on to the next element
|
||||
} else {
|
||||
|
||||
// remove the current entries probability
|
||||
curOffset -= entries[curIdx].probability;
|
||||
|
||||
// resume with the next one along the wheel
|
||||
curIdx = (curIdx + 1) % ((int)entries.size());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/** ensure the max probability is valid. if not -> calculate it */
|
||||
void ensureMaxProbability() {
|
||||
|
||||
// valid?
|
||||
if (maxValid) {return;}
|
||||
|
||||
// comparisen
|
||||
const auto lambda = [] (const DrawWheelEntry<Entry>& e1, const DrawWheelEntry<Entry>& e2) {return e1.probability < e2.probability;};
|
||||
|
||||
// find the largest entry
|
||||
const DrawWheelEntry<Entry> max = *std::max_element(entries.begin(), entries.end(), lambda);
|
||||
this->curMax = max.probability;
|
||||
|
||||
// the max is now valid
|
||||
maxValid = true;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // K_MATH_RANDOM_DRAWLIST_H
|
||||
27
math/random/Normal.h
Normal file
27
math/random/Normal.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef K_MATH_RND_NORMAL_H
|
||||
#define K_MATH_RND_NORMAL_H
|
||||
|
||||
namespace Random {
|
||||
|
||||
|
||||
/**
|
||||
* provides some common functions
|
||||
* for handling normal-distributed random numbers
|
||||
*/
|
||||
class Normal {
|
||||
|
||||
public:
|
||||
|
||||
/** get normal-distributed random number for given mu/sigma */
|
||||
static double get(double mu, double sigma) {
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
std::normal_distribution<> d(mu, sigma);
|
||||
return d(gen);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // K_MATH_RND_NORMAL_H
|
||||
29
math/random/RandomGenerator.h
Normal file
29
math/random/RandomGenerator.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef RANDOM_Random::RandomGenerator_H
|
||||
#define RANDOM_Random::RandomGenerator_H
|
||||
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
#ifdef USE_FIXED_SEED
|
||||
#define RANDOM_SEED 1234
|
||||
#else
|
||||
#define RANDOM_SEED ( std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1) )
|
||||
#endif
|
||||
|
||||
namespace Random {
|
||||
|
||||
class RandomGenerator : public std::minstd_rand {
|
||||
|
||||
public:
|
||||
|
||||
/** ctor with default seed */
|
||||
RandomGenerator() : std::minstd_rand(RANDOM_SEED) {;}
|
||||
|
||||
/** ctor with custom seed */
|
||||
RandomGenerator(result_type) : std::minstd_rand(RANDOM_SEED) {;}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // K_MATH_RANDOM_Random::RandomGenerator_H
|
||||
118
math/random/RandomIterator.h
Normal file
118
math/random/RandomIterator.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#ifndef RANDOMITERATOR_H
|
||||
#define RANDOMITERATOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include "../../Assertions.h"
|
||||
|
||||
namespace Random {
|
||||
|
||||
template <typename Element> class RandomIterator {
|
||||
|
||||
private:
|
||||
|
||||
/** the user's data-vector to randomly iterate */
|
||||
const std::vector<Element>& vec;
|
||||
|
||||
/** the number of random indices */
|
||||
int cnt;
|
||||
|
||||
/** the random number generator */
|
||||
std::minstd_rand gen;
|
||||
bool isRandomized = false;
|
||||
|
||||
/** X random indices */
|
||||
std::vector<int> indices;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
RandomIterator(const std::vector<Element>& vec, const int cnt) : vec(vec), cnt(cnt) {
|
||||
|
||||
//const uint64_t ts = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
static int seed = 0; ++seed;
|
||||
gen.seed(seed);
|
||||
|
||||
// sanity check
|
||||
if ((int)vec.size() < cnt) {throw Exception("number of elements in list is smaller than the requested number to draw");}
|
||||
if (cnt == 0) {throw Exception("number of elements in list must be at least 1");}
|
||||
if (vec.empty()) {throw Exception("empty input vector given");}
|
||||
|
||||
indices.resize(cnt);
|
||||
|
||||
}
|
||||
|
||||
/** create random samples (vector-indicies) that are hereafter available for iteration */
|
||||
void randomize() {
|
||||
|
||||
// random-number generator between [0:size-1]
|
||||
std::uniform_int_distribution<int> dist(0, (int) vec.size()-1);
|
||||
|
||||
// ensure we use each vector-index only ONCE
|
||||
bool used[vec.size()] = {false};
|
||||
|
||||
// draw X random samples
|
||||
for (int i = 0; i < cnt; ) {
|
||||
const int rnd = dist(gen);
|
||||
if (used[rnd]) {continue;} // already used? try again!
|
||||
used[rnd] = true; // mark as used
|
||||
indices[i] = rnd; // add to the index
|
||||
++i;
|
||||
}
|
||||
|
||||
// the vector is setup correctly
|
||||
isRandomized = true;
|
||||
|
||||
}
|
||||
|
||||
/** the iterator state */
|
||||
struct Iterator {
|
||||
|
||||
/** the current position within "indicies" */
|
||||
int pos;
|
||||
|
||||
/** the vector with the user-data to randomly iterate */
|
||||
const std::vector<Element>& vec;
|
||||
|
||||
/** the vector containing the random indices */
|
||||
const std::vector<int>& indices;
|
||||
|
||||
/** ctor */
|
||||
Iterator(const int pos, const std::vector<Element>& vec, const std::vector<int>& indices) : pos(pos), vec(vec), indices(indices) {;}
|
||||
|
||||
/** end-of-iteration? */
|
||||
bool operator != (const Iterator& o) const {return pos != o.pos;}
|
||||
|
||||
/** next value */
|
||||
Iterator& operator ++ () {++pos; return *this;}
|
||||
|
||||
/** get the user-data */
|
||||
Element operator * () {return vec[indices[pos]];}
|
||||
|
||||
};
|
||||
|
||||
//const Element& operator [] (const int idx) const {return vec[indices[idx]]; }
|
||||
|
||||
/** number of available random entries */
|
||||
size_t size() const {return cnt;}
|
||||
|
||||
|
||||
/** for-each access */
|
||||
Iterator begin() const { ensureRandomized(); return Iterator(0, vec, indices); }
|
||||
|
||||
/** for-each access */
|
||||
Iterator end() const { ensureRandomized(); return Iterator(cnt, vec, indices); }
|
||||
|
||||
private:
|
||||
|
||||
/** ensure the coder called randomize() before using the iterator */
|
||||
void ensureRandomized() const {
|
||||
Assert::isTrue(isRandomized, "call randomize() before using the iterator!");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // RANDOMITERATOR_H
|
||||
43
math/random/Uniform.h
Normal file
43
math/random/Uniform.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef K_MATH_RANDOM_UNIFORM_H
|
||||
#define K_MATH_RANDOM_UNIFORM_H
|
||||
|
||||
#include "RandomGenerator.h"
|
||||
|
||||
namespace Random {
|
||||
|
||||
/**
|
||||
* provides some common functions
|
||||
* for handling normal-distributed random numbers
|
||||
*/
|
||||
template <typename T> class Uniform {
|
||||
|
||||
private:
|
||||
|
||||
Random::RandomGenerator gen;
|
||||
|
||||
/** depending on T, Dist is either a uniform_real or uniform_int distribution */
|
||||
typedef typename std::conditional< std::is_floating_point<T>::value, std::uniform_real_distribution<T>, std::uniform_int_distribution<T> >::type Dist;
|
||||
Dist dist;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Uniform(const T min, const T max) : gen(RANDOM_SEED), dist(min, max) {
|
||||
|
||||
}
|
||||
|
||||
/** get a uniformaly distributed random number */
|
||||
T draw() {
|
||||
return dist(gen);
|
||||
}
|
||||
|
||||
/** set the seed to use */
|
||||
void setSeed(const long seed) {
|
||||
gen.seed(seed);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // K_MATH_RANDOM_UNIFORM_H
|
||||
26
math/random/Unique.h
Normal file
26
math/random/Unique.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef K_MATH_RND_UNIQUE_H
|
||||
#define K_MATH_RND_UNIQUE_H
|
||||
|
||||
namespace Random {
|
||||
|
||||
/**
|
||||
* provides some common functions
|
||||
* for handling uniquely distributed random numbers
|
||||
*/
|
||||
class Unique {
|
||||
|
||||
public:
|
||||
|
||||
/** get uniquely distributed random number between min and max */
|
||||
static double getBetween(double min, double max) {
|
||||
double rnd = (double) rand() / (double) RAND_MAX;
|
||||
rnd *= (max-min);
|
||||
rnd += min;
|
||||
return rnd;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // K_MATH_RND_UNIQUE_H
|
||||
Reference in New Issue
Block a user