Skip to content

Commit 1173ef0

Browse files
Random tweaks + ofUrn [needed in 12.1 for final of::random interface] (#7736)
* ofRandomDistributions: change yes to sometimes * ofRandomDistributions: change yes to sometimes * ofRandom: empty call defaults to range 1.0 * Added an Urn (Sampling Without Replacement) companion to shuffle * clang-format * ofUrn: more initialization versatility * ofRandom: default max to explicit float 1.0f --------- Co-authored-by: alexandre burton <burton@artificiel.org>
1 parent 796a899 commit 1173ef0

File tree

4 files changed

+232
-18
lines changed

4 files changed

+232
-18
lines changed

examples/math/randomExample/src/ofApp.cpp

+13-13
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ void ofApp::perform() {
168168
ofLogNotice("ofRandomPoisson<int>(2.5)") << ofRandomPoisson<long long>(5.5);
169169
ofLogNotice("ofRandomPoisson<glm::vec2>(2.5)") << ofRandomPoisson<glm::vec2>(5.5);
170170
ofLogNotice("ofRandomPoisson<glm::vec2>({2.5, 10})") << ofRandomPoisson<glm::vec2>({ 5.5, 10 });
171-
ofLogNotice("ofRandomPoisson<glm::vec3>(2.5, 10, 0.1") << ofRandomPoisson<glm::vec3>(5.5);
172-
ofLogNotice("ofRandomPoisson<glm::vec3>({2.5, 10, 0.1}") << ofRandomPoisson<glm::vec3>({ 5.5, 10, 0.1 });
171+
ofLogNotice("ofRandomPoisson<glm::vec3>(2.5, 10, 3") << ofRandomPoisson<glm::vec3>(5.5);
172+
ofLogNotice("ofRandomPoisson<glm::vec3>({2.5, 10, 3}") << ofRandomPoisson<glm::vec3>({ 5.5, 10, 0.1 });
173173

174174
// bernoulli requires 1 arg (no defaults), and return type is bool
175175
// again, glm usage case is cast to float
@@ -187,17 +187,17 @@ void ofApp::perform() {
187187
ofLogNotice("ofRandomBernoulli<bool>(.5)") << ofRandomBernoulli<bool>(.5);
188188
ofLogNotice("ofRandomBernoulli(.5)") << ofRandomBernoulli(.5);
189189

190-
ofLogNotice("yes aka bernoulli<glm::vec3>(.33)") << yes<glm::vec3>(.33);
191-
ofLogNotice("yes aka bernoulli<glm::vec2>(.5)") << yes<glm::vec2>(.5);
192-
ofLogNotice("yes aka bernoulli<float>(.5)") << yes<float>(.5);
193-
ofLogNotice("yes aka bernoulli<bool>(.5)") << yes<bool>(.5);
194-
ofLogNotice("yes aka bernoulli(.5)") << yes(.5);
195-
196-
ofLogNotice("ofRandomYes aka bernoulli<glm::vec3>(.33)") << ofRandomYes<glm::vec3>(.33);
197-
ofLogNotice("ofRandomYes aka bernoulli<glm::vec2>(.5)") << ofRandomYes<glm::vec2>(.5);
198-
ofLogNotice("ofRandomYes aka bernoulli<float>(.5)") << ofRandomYes<float>(.5);
199-
ofLogNotice("ofRandomYes aka bernoulli<bool>(.5)") << ofRandomYes<bool>(.5);
200-
ofLogNotice("ofRandomYes aka bernoulli(.5)") << ofRandomYes(.5);
190+
ofLogNotice("sometimes aka bernoulli<glm::vec3>(.33)") << sometimes<glm::vec3>(.33);
191+
ofLogNotice("sometimes aka bernoulli<glm::vec2>(.5)") << sometimes<glm::vec2>(.5);
192+
ofLogNotice("sometimes aka bernoulli<float>(.5)") << sometimes<float>(.5);
193+
ofLogNotice("sometimes aka bernoulli<bool>(.5)") << sometimes<bool>(.5);
194+
ofLogNotice("sometimes aka bernoulli(.5)") << sometimes(.5);
195+
196+
ofLogNotice("ofRandomSometimes aka bernoulli<glm::vec3>(.33)") << ofRandomSometimes<glm::vec3>(.33);
197+
ofLogNotice("ofRandomSometimes aka bernoulli<glm::vec2>(.5)") << ofRandomSometimes<glm::vec2>(.5);
198+
ofLogNotice("ofRandomSometimes aka bernoulli<float>(.5)") << ofRandomSometimes<float>(.5);
199+
ofLogNotice("ofRandomSometimes aka bernoulli<bool>(.5)") << ofRandomSometimes<bool>(.5);
200+
ofLogNotice("ofRandomSometimes aka bernoulli(.5)") << ofRandomSometimes(.5);
201201

202202
// lognormal requires 2 args (no defaults)
203203

libs/openFrameworks/math/ofMath.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
/// ~~~~~
2727
///
2828
/// \param max The maximum value of the random number.
29-
float ofRandom(float max);
29+
float ofRandom(float max = 1.0f);
3030

3131
/// \brief Get a uniform random number between two values.
3232
///

libs/openFrameworks/utils/ofRandomDistributions.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ constexpr auto gaussian(Args &&... args) -> decltype(normal<T>(std::forward<Args
606606

607607
/// \brief alias for of::random::bernoulli
608608
template <class T = bool, typename... Args>
609-
constexpr auto yes(Args &&... args) -> decltype(bernoulli<T>(std::forward<Args>(args)...)) {
609+
constexpr auto sometimes(Args &&... args) -> decltype(bernoulli<T>(std::forward<Args>(args)...)) {
610610
return bernoulli<T>(std::forward<Args>(args)...);
611611
}
612612

@@ -763,10 +763,10 @@ template <class T>
763763
T ofRandomBernoulli(T prob) { return of::random::bernoulli<T>(prob); }
764764

765765
template <class T, typename... Args>
766-
T ofRandomYes(Args &&... args) { return of::random::yes<T>(std::forward<Args>(args)...); }
766+
T ofRandomSometimes(Args &&... args) { return of::random::sometimes<T>(std::forward<Args>(args)...); }
767767

768768
template <class T>
769-
T ofRandomYes(T prob) { return of::random::yes<T>(prob); }
769+
T ofRandomSometimes(T prob) { return of::random::sometimes<T>(prob); }
770770

771771
template <class T, typename... Args>
772772
T ofRandomPoisson(Args &&... args) { return of::random::poisson<T>(std::forward<Args>(args)...); }

libs/openFrameworks/utils/ofUtils.h

+215-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include <algorithm>
1313
#include <bitset> // For ofToBinary.
1414
#include <chrono>
15-
#include <iomanip> //for setprecision
15+
#include <iomanip> //for setprecision
1616
#include <optional>
1717
#include <sstream>
1818

@@ -247,6 +247,220 @@ void ofShuffle(Args &&... args) {
247247
of::shuffle(std::forward<Args>(args)...);
248248
}
249249

250+
namespace of {
251+
252+
/// \class ofUrn
253+
///
254+
/// A vector-backed class that can be progressively "emptied" by randomly pulling values out of it.
255+
/// Useful to get non-repeated patterns from a set, aka Sampling Without Replacement.
256+
/// When the Urn is depleted, it is automatically refilled (with ways to know about such events; see below)
257+
///
258+
/// By default:
259+
/// if the Urn contains unique values, repetitions are not allowed across phases;
260+
/// if the Urn contains duplicates, repetitions are allowed.
261+
///
262+
/// this arbitrary behavioral choice is "practical" and can controlled with auto_configure_edge_repeat_
263+
///
264+
/// note that objects deposited herein get copied around when shuffling to refill; for large objects
265+
/// TODO: an optimisation would be to shadow the objects with a vector of iterators and shuffle the iterators instead
266+
/// (or maybe it's the user's responsibility to pass a containe of references/pointers?
267+
/// in any case it's not clear where the threshold (qty x size) for such an optimisation lies)
268+
///
269+
/// \tparam T the type of contained values
270+
/// \tparam Container the type of the underlying Container (only tested with vector; in place for future expansion)
271+
template <class T, class Container = std::vector<T>>
272+
class urn {
273+
274+
Container values_;
275+
typename Container::const_iterator phase_;
276+
277+
auto prepare() {
278+
if (valid()) {
279+
if (auto_configure_edge_repeat_) {
280+
std::sort(values_.begin(), values_.end()); // perhaps costly for large sets
281+
allow_edge_repeat_ = std::adjacent_find(values_.begin(), values_.end()) != values_.end();
282+
}
283+
return refill();
284+
} else {
285+
// ofLogError("ofUrn::prepare()") << "called on unitialized Urn";
286+
return false;
287+
}
288+
}
289+
290+
public:
291+
/// \brief if true, repetitions are not allowed for vectors of unique values
292+
bool allow_edge_repeat_ { false };
293+
294+
/// \brief if true, allow edge repeats if the vector contains duplicates
295+
bool auto_configure_edge_repeat_ { true };
296+
297+
/// \brief Construct an unitialized urn
298+
urn() = default;
299+
300+
/// \brief Copy-Construct an urn with contents of other urn
301+
/// \param other the other Urn
302+
urn(urn<T> & other) {
303+
values_ = other.get_values();
304+
prepare();
305+
}
306+
307+
urn(urn<T> && other) noexcept
308+
: urn(std::exchange(other.values_, nullptr)) {
309+
prepare();
310+
}
311+
312+
/// \brief move-assign an urn with another urn
313+
/// \param other the other Urn
314+
/// \return a new Urn
315+
auto & operator=(urn<T> && other) noexcept {
316+
std::swap(values_, other.values_);
317+
prepare();
318+
return *this;
319+
}
320+
321+
/// \brief Assign-Construct an urn with contents of other urn
322+
/// \param other the other Urn
323+
/// \return a new Urn
324+
auto & operator=(urn<T> & other) {
325+
if (this == &other) return *this;
326+
values_ = other.get_values();
327+
prepare();
328+
return *this;
329+
}
330+
331+
/// \brief Assigns with values from compatibe container and resets the phase
332+
/// \param Container the container of values
333+
/// \return void
334+
auto & operator=(Container & values) {
335+
values_ = values;
336+
prepare();
337+
return *this;
338+
}
339+
340+
/// \brief Construct an urn initialized with contents
341+
/// \param Args the values
342+
template <typename... Args>
343+
urn(Args &&... args) {
344+
set(std::forward<Args>(args)...);
345+
}
346+
347+
~urn() = default;
348+
349+
/// \brief Sets values by assignement and resets the phase
350+
/// \param Args the values
351+
/// \return void
352+
template <typename... Args>
353+
auto operator=(Args &&... args) {
354+
set(std::forward<Args>(args)...);
355+
}
356+
357+
/// \brief Assigns with values from another container and resets the phase
358+
/// \param Container the container of values
359+
/// \return void
360+
auto set(Container & values) {
361+
values_ = values;
362+
prepare();
363+
}
364+
365+
/// \brief Sets the values of the container from another Urn and resets the phase
366+
/// \param urn the other urn
367+
/// \return void
368+
auto set(urn<T> & urn) {
369+
values_ = urn.get_values();
370+
prepare();
371+
}
372+
373+
/// \brief Sets the values of the container and resets the phase
374+
/// \param Args the values
375+
/// \return void
376+
template <typename... Args>
377+
auto set(Args &&... args) {
378+
values_.clear();
379+
values_.reserve(sizeof...(Args));
380+
std::cout << ("preparing for") << sizeof...(Args) << std::endl;
381+
(values_.emplace_back(std::forward<Args>(args)), ...);
382+
prepare();
383+
}
384+
385+
/// \brief Check if urn contains potential values
386+
/// (not using the name empty to distinguish from depleted())
387+
/// \return True if not empty
388+
auto valid() const { return !values_.empty(); }
389+
390+
/// \brief Check if urn is depleted
391+
/// (not using the name empty to distinguish from valid())
392+
/// \return true if the phase is at the end of the container
393+
auto depleted() const { return phase_ == values_.cend(); }
394+
395+
/// \brief Get the total number of elements given a full urn
396+
/// (not using the name size to distinguish from remain())
397+
/// \return number of elements
398+
auto capacity() const { return values_.size(); }
399+
400+
/// \brief Get the remaining number of elements in phase
401+
/// (not using the name size to distinguish from capacity())
402+
/// \return number of remaining elements in phase
403+
auto remain() const { return std::distance(phase_, values_.cend()); }
404+
405+
/// \brief Refills the urn with the original elements and resets the phase
406+
/// \return void
407+
auto refill() {
408+
if (valid()) {
409+
auto last = *values_.end();
410+
ofShuffle(values_);
411+
if (!allow_edge_repeat_) {
412+
while (last == *values_.begin())
413+
ofShuffle(values_);
414+
}
415+
phase_ = values_.begin();
416+
return true;
417+
} else {
418+
// ofLogError("of::urn::refill()") << "called on unitialized Urn";
419+
return false;
420+
}
421+
}
422+
423+
/// \brief Get a random element from the urn, refilling as needed
424+
/// \return a T from the urn (could be garbage if Urn is invalid)
425+
auto pull() {
426+
if (valid()) {
427+
if (depleted()) refill();
428+
return *phase_++;
429+
} else {
430+
// ofLogError("of::urn::pull()") << "called on unitialized Urn -- returning unitialized T";
431+
T v;
432+
return v;
433+
}
434+
}
435+
436+
/// \brief Get a random element from the urn
437+
/// \return an optional<T> from the urn, nullopt if empty or invalid
438+
auto pull_or_empty() {
439+
if (valid()) {
440+
if (depleted()) return std::optional<T> {};
441+
return std::optional<T>(*phase_++);
442+
} else {
443+
// ofLogError("of::urn::pull_or_empty()") << "called on unitialized Urn -- returning nullopt";
444+
return std::optional<T> {};
445+
}
446+
}
447+
448+
/// \brief Access the internals (advanced introspection)
449+
/// \return a reference to the underlying stuff
450+
auto & get_values() const { return values_; }
451+
auto & get_phase() const { return phase_; }
452+
};
453+
454+
/// \brief CTAD (namely to help deduce references across the perfect forwarding)
455+
template <class T>
456+
urn(T) -> urn<T>;
457+
}
458+
459+
/// \brief alias to ofUrn
460+
/// note that not all deductions carry across the aliasing, so ofUrn is slightly disavantaged vs of::urn
461+
template <typename T, typename Container = std::vector<T>>
462+
using ofUrn = typename of::urn<T, Container>;
463+
250464
/// \section Vectors
251465
/// \brief Randomly reorder the values in a vector.
252466
/// \tparam T the type contained by the vector.

0 commit comments

Comments
 (0)