diff --git a/examples/math/randomExample/src/ofApp.cpp b/examples/math/randomExample/src/ofApp.cpp index 7e9872db10d..b52b04be2b4 100644 --- a/examples/math/randomExample/src/ofApp.cpp +++ b/examples/math/randomExample/src/ofApp.cpp @@ -168,8 +168,8 @@ void ofApp::perform() { ofLogNotice("ofRandomPoisson(2.5)") << ofRandomPoisson(5.5); ofLogNotice("ofRandomPoisson(2.5)") << ofRandomPoisson(5.5); ofLogNotice("ofRandomPoisson({2.5, 10})") << ofRandomPoisson({ 5.5, 10 }); - ofLogNotice("ofRandomPoisson(2.5, 10, 0.1") << ofRandomPoisson(5.5); - ofLogNotice("ofRandomPoisson({2.5, 10, 0.1}") << ofRandomPoisson({ 5.5, 10, 0.1 }); + ofLogNotice("ofRandomPoisson(2.5, 10, 3") << ofRandomPoisson(5.5); + ofLogNotice("ofRandomPoisson({2.5, 10, 3}") << ofRandomPoisson({ 5.5, 10, 0.1 }); // bernoulli requires 1 arg (no defaults), and return type is bool // again, glm usage case is cast to float @@ -187,17 +187,17 @@ void ofApp::perform() { ofLogNotice("ofRandomBernoulli(.5)") << ofRandomBernoulli(.5); ofLogNotice("ofRandomBernoulli(.5)") << ofRandomBernoulli(.5); - ofLogNotice("yes aka bernoulli(.33)") << yes(.33); - ofLogNotice("yes aka bernoulli(.5)") << yes(.5); - ofLogNotice("yes aka bernoulli(.5)") << yes(.5); - ofLogNotice("yes aka bernoulli(.5)") << yes(.5); - ofLogNotice("yes aka bernoulli(.5)") << yes(.5); - - ofLogNotice("ofRandomYes aka bernoulli(.33)") << ofRandomYes(.33); - ofLogNotice("ofRandomYes aka bernoulli(.5)") << ofRandomYes(.5); - ofLogNotice("ofRandomYes aka bernoulli(.5)") << ofRandomYes(.5); - ofLogNotice("ofRandomYes aka bernoulli(.5)") << ofRandomYes(.5); - ofLogNotice("ofRandomYes aka bernoulli(.5)") << ofRandomYes(.5); + ofLogNotice("sometimes aka bernoulli(.33)") << sometimes(.33); + ofLogNotice("sometimes aka bernoulli(.5)") << sometimes(.5); + ofLogNotice("sometimes aka bernoulli(.5)") << sometimes(.5); + ofLogNotice("sometimes aka bernoulli(.5)") << sometimes(.5); + ofLogNotice("sometimes aka bernoulli(.5)") << sometimes(.5); + + ofLogNotice("ofRandomSometimes aka bernoulli(.33)") << ofRandomSometimes(.33); + ofLogNotice("ofRandomSometimes aka bernoulli(.5)") << ofRandomSometimes(.5); + ofLogNotice("ofRandomSometimes aka bernoulli(.5)") << ofRandomSometimes(.5); + ofLogNotice("ofRandomSometimes aka bernoulli(.5)") << ofRandomSometimes(.5); + ofLogNotice("ofRandomSometimes aka bernoulli(.5)") << ofRandomSometimes(.5); // lognormal requires 2 args (no defaults) diff --git a/libs/openFrameworks/math/ofMath.h b/libs/openFrameworks/math/ofMath.h index 67adf291570..508d63d5389 100644 --- a/libs/openFrameworks/math/ofMath.h +++ b/libs/openFrameworks/math/ofMath.h @@ -26,7 +26,7 @@ /// ~~~~~ /// /// \param max The maximum value of the random number. -float ofRandom(float max); +float ofRandom(float max = 1.0f); /// \brief Get a uniform random number between two values. /// diff --git a/libs/openFrameworks/utils/ofRandomDistributions.h b/libs/openFrameworks/utils/ofRandomDistributions.h index 58730ff4b4f..db8d5db2736 100644 --- a/libs/openFrameworks/utils/ofRandomDistributions.h +++ b/libs/openFrameworks/utils/ofRandomDistributions.h @@ -606,7 +606,7 @@ constexpr auto gaussian(Args &&... args) -> decltype(normal(std::forward -constexpr auto yes(Args &&... args) -> decltype(bernoulli(std::forward(args)...)) { +constexpr auto sometimes(Args &&... args) -> decltype(bernoulli(std::forward(args)...)) { return bernoulli(std::forward(args)...); } @@ -763,10 +763,10 @@ template T ofRandomBernoulli(T prob) { return of::random::bernoulli(prob); } template -T ofRandomYes(Args &&... args) { return of::random::yes(std::forward(args)...); } +T ofRandomSometimes(Args &&... args) { return of::random::sometimes(std::forward(args)...); } template -T ofRandomYes(T prob) { return of::random::yes(prob); } +T ofRandomSometimes(T prob) { return of::random::sometimes(prob); } template T ofRandomPoisson(Args &&... args) { return of::random::poisson(std::forward(args)...); } diff --git a/libs/openFrameworks/utils/ofUtils.h b/libs/openFrameworks/utils/ofUtils.h index 227bf298afc..60f9ec2a350 100644 --- a/libs/openFrameworks/utils/ofUtils.h +++ b/libs/openFrameworks/utils/ofUtils.h @@ -12,7 +12,7 @@ #include #include // For ofToBinary. #include -#include //for setprecision +#include //for setprecision #include #include @@ -247,6 +247,220 @@ void ofShuffle(Args &&... args) { of::shuffle(std::forward(args)...); } +namespace of { + +/// \class ofUrn +/// +/// A vector-backed class that can be progressively "emptied" by randomly pulling values out of it. +/// Useful to get non-repeated patterns from a set, aka Sampling Without Replacement. +/// When the Urn is depleted, it is automatically refilled (with ways to know about such events; see below) +/// +/// By default: +/// if the Urn contains unique values, repetitions are not allowed across phases; +/// if the Urn contains duplicates, repetitions are allowed. +/// +/// this arbitrary behavioral choice is "practical" and can controlled with auto_configure_edge_repeat_ +/// +/// note that objects deposited herein get copied around when shuffling to refill; for large objects +/// TODO: an optimisation would be to shadow the objects with a vector of iterators and shuffle the iterators instead +/// (or maybe it's the user's responsibility to pass a containe of references/pointers? +/// in any case it's not clear where the threshold (qty x size) for such an optimisation lies) +/// +/// \tparam T the type of contained values +/// \tparam Container the type of the underlying Container (only tested with vector; in place for future expansion) +template > +class urn { + + Container values_; + typename Container::const_iterator phase_; + + auto prepare() { + if (valid()) { + if (auto_configure_edge_repeat_) { + std::sort(values_.begin(), values_.end()); // perhaps costly for large sets + allow_edge_repeat_ = std::adjacent_find(values_.begin(), values_.end()) != values_.end(); + } + return refill(); + } else { + // ofLogError("ofUrn::prepare()") << "called on unitialized Urn"; + return false; + } + } + +public: + /// \brief if true, repetitions are not allowed for vectors of unique values + bool allow_edge_repeat_ { false }; + + /// \brief if true, allow edge repeats if the vector contains duplicates + bool auto_configure_edge_repeat_ { true }; + + /// \brief Construct an unitialized urn + urn() = default; + + /// \brief Copy-Construct an urn with contents of other urn + /// \param other the other Urn + urn(urn & other) { + values_ = other.get_values(); + prepare(); + } + + urn(urn && other) noexcept + : urn(std::exchange(other.values_, nullptr)) { + prepare(); + } + + /// \brief move-assign an urn with another urn + /// \param other the other Urn + /// \return a new Urn + auto & operator=(urn && other) noexcept { + std::swap(values_, other.values_); + prepare(); + return *this; + } + + /// \brief Assign-Construct an urn with contents of other urn + /// \param other the other Urn + /// \return a new Urn + auto & operator=(urn & other) { + if (this == &other) return *this; + values_ = other.get_values(); + prepare(); + return *this; + } + + /// \brief Assigns with values from compatibe container and resets the phase + /// \param Container the container of values + /// \return void + auto & operator=(Container & values) { + values_ = values; + prepare(); + return *this; + } + + /// \brief Construct an urn initialized with contents + /// \param Args the values + template + urn(Args &&... args) { + set(std::forward(args)...); + } + + ~urn() = default; + + /// \brief Sets values by assignement and resets the phase + /// \param Args the values + /// \return void + template + auto operator=(Args &&... args) { + set(std::forward(args)...); + } + + /// \brief Assigns with values from another container and resets the phase + /// \param Container the container of values + /// \return void + auto set(Container & values) { + values_ = values; + prepare(); + } + + /// \brief Sets the values of the container from another Urn and resets the phase + /// \param urn the other urn + /// \return void + auto set(urn & urn) { + values_ = urn.get_values(); + prepare(); + } + + /// \brief Sets the values of the container and resets the phase + /// \param Args the values + /// \return void + template + auto set(Args &&... args) { + values_.clear(); + values_.reserve(sizeof...(Args)); + std::cout << ("preparing for") << sizeof...(Args) << std::endl; + (values_.emplace_back(std::forward(args)), ...); + prepare(); + } + + /// \brief Check if urn contains potential values + /// (not using the name empty to distinguish from depleted()) + /// \return True if not empty + auto valid() const { return !values_.empty(); } + + /// \brief Check if urn is depleted + /// (not using the name empty to distinguish from valid()) + /// \return true if the phase is at the end of the container + auto depleted() const { return phase_ == values_.cend(); } + + /// \brief Get the total number of elements given a full urn + /// (not using the name size to distinguish from remain()) + /// \return number of elements + auto capacity() const { return values_.size(); } + + /// \brief Get the remaining number of elements in phase + /// (not using the name size to distinguish from capacity()) + /// \return number of remaining elements in phase + auto remain() const { return std::distance(phase_, values_.cend()); } + + /// \brief Refills the urn with the original elements and resets the phase + /// \return void + auto refill() { + if (valid()) { + auto last = *values_.end(); + ofShuffle(values_); + if (!allow_edge_repeat_) { + while (last == *values_.begin()) + ofShuffle(values_); + } + phase_ = values_.begin(); + return true; + } else { + // ofLogError("of::urn::refill()") << "called on unitialized Urn"; + return false; + } + } + + /// \brief Get a random element from the urn, refilling as needed + /// \return a T from the urn (could be garbage if Urn is invalid) + auto pull() { + if (valid()) { + if (depleted()) refill(); + return *phase_++; + } else { + // ofLogError("of::urn::pull()") << "called on unitialized Urn -- returning unitialized T"; + T v; + return v; + } + } + + /// \brief Get a random element from the urn + /// \return an optional from the urn, nullopt if empty or invalid + auto pull_or_empty() { + if (valid()) { + if (depleted()) return std::optional {}; + return std::optional(*phase_++); + } else { + // ofLogError("of::urn::pull_or_empty()") << "called on unitialized Urn -- returning nullopt"; + return std::optional {}; + } + } + + /// \brief Access the internals (advanced introspection) + /// \return a reference to the underlying stuff + auto & get_values() const { return values_; } + auto & get_phase() const { return phase_; } +}; + +/// \brief CTAD (namely to help deduce references across the perfect forwarding) +template +urn(T) -> urn; +} + +/// \brief alias to ofUrn +/// note that not all deductions carry across the aliasing, so ofUrn is slightly disavantaged vs of::urn +template > +using ofUrn = typename of::urn; + /// \section Vectors /// \brief Randomly reorder the values in a vector. /// \tparam T the type contained by the vector.