header_utils
Loading...
Searching...
No Matches
random.h
1
4
5#pragma once
6
7#include "min-cpp-version/cpp20.h"
8#include <random>
9#include <numeric>
10#include "span.h"
11
13{
19 // TODO: Tests
20 // check out https://github.com/effolkronium/random/
21
24 struct good_random_engine
25 {
26 uint64_t u, v, w;
27 good_random_engine(uint64_t j) noexcept {
28 seed(j);
29 }
30
31 void seed(uint64_t j) noexcept
32 {
33 v = 4101842887655102017LL;
34 w = 1;
35 u = j ^ v; (void)this->operator()();
36 v = u; (void)this->operator()();
37 w = v; (void)this->operator()();
38 }
39
40 [[nodiscard]] static constexpr uint64_t max() noexcept { return UINT64_MAX; }
41 [[nodiscard]] static constexpr uint64_t min() noexcept { return 0; }
42
43 [[nodiscard]] uint64_t operator()() noexcept
44 {
45 u = u * 2862933555777941757LL + 7046029254386353087LL;
46 v ^= v >> 17; v ^= v << 31; v ^= v >> 8;
47 w = 4294957665U * (w & 0xffffffff) + (w >> 32);
48 uint64_t x = u ^ (u << 21); x ^= x >> 35; x ^= x << 4;
49 return (x + v) ^ w;
50 }
51 };
52
53#if GHPL_CPP20
54 static_assert(std::uniform_random_bit_generator<good_random_engine>);
55#endif
56
58 thread_local inline std::default_random_engine default_random_engine;
59
61 template <typename INTEGER = uint64_t, typename RANDOM = std::default_random_engine>
62 [[nodiscard]] constexpr INTEGER integer(RANDOM& rng = ::ghassanpl::random::default_random_engine)
63 {
64 std::uniform_int_distribution<INTEGER> dist;
65 return dist(rng);
66 }
67
69 template <typename REAL = double, typename RANDOM = std::default_random_engine>
71 {
72 std::uniform_real_distribution<REAL> dist;
73 return dist(rng);
74 }
75
76 /*
77 template <typename REAL = double, typename T, typename RANDOM = std::default_random_engine>
78 [[nodiscard]] auto between(T const& a, T const& b, RANDOM& rng = ::ghassanpl::random::default_random_engine)
79 {
80 using std::lerp;
81 if constexpr (std::is_integral_v<T>)
82 return lerp(a, b, ::ghassanpl::random::percentage<REAL>(rng));
83 else
84 return lerp(a, b, ::ghassanpl::random::percentage<REAL>(rng));
85 }
86 */
87
89 template <typename REAL = double, typename RANDOM = std::default_random_engine>
90 [[nodiscard]] REAL normal(RANDOM& rng = ::ghassanpl::random::default_random_engine)
91 {
92 static std::normal_distribution<REAL> dist;
93 return dist(rng);
94 }
95
97 template <typename RANDOM = std::default_random_engine>
98 [[nodiscard]] uint64_t dice(uint64_t n_sided, RANDOM& rng = ::ghassanpl::random::default_random_engine)
99 {
100 if (n_sided < 2) return 0;
101 std::uniform_int_distribution<uint64_t> dist{ 0, n_sided - 1 };
102 return dist(rng) + 1;
103 }
104
106 template <typename RANDOM = std::default_random_engine>
107 [[nodiscard]] uint64_t dice(uint64_t n_dice, uint64_t n_sided, RANDOM& rng = ::ghassanpl::random::default_random_engine)
108 {
109 if (n_sided < 2 || n_dice == 0) return 0;
110 std::uniform_int_distribution<uint64_t> dist{ 0, n_sided - 1 };
111 uint64_t sum = 0;
112 for (uint64_t i = 0; i < n_dice; ++i)
113 sum += dist(rng) + 1;
114 return sum;
115 }
116
118 template <uint64_t N_SIDED, typename RANDOM = std::default_random_engine>
119 [[nodiscard]] uint64_t dice(RANDOM& rng = ::ghassanpl::random::default_random_engine)
120 {
121 static_assert(N_SIDED >= 2, "you cannot roll a 0 or 1-sided die");
122 static std::uniform_int_distribution<uint64_t> dist{ 0, N_SIDED - 1 };
123 return dist(rng) + 1;
124 }
125
127 template <uint64_t N_DICE, uint64_t N_SIDED, typename RANDOM = std::default_random_engine>
128 [[nodiscard]] uint64_t dice(RANDOM& rng = ::ghassanpl::random::default_random_engine)
129 {
130 static_assert(N_DICE >= 1 , "you cannot roll fewer than one die");
131 static_assert(N_SIDED >= 2, "you cannot roll a 0 or 1-sided die");
132 static std::uniform_int_distribution<uint64_t> dist{ 0, N_SIDED - 1 };
133 uint64_t sum = 0;
134 for (uint64_t i = 0; i < N_DICE; ++i)
135 sum += dist(rng) + 1;
136 return sum;
137 }
138
140 template <typename RANDOM = std::default_random_engine>
141 [[nodiscard]] bool coin(RANDOM& rng = ::ghassanpl::random::default_random_engine)
142 {
143 std::uniform_int_distribution<uint64_t> dist{ 0, 1 };
144 return dist(rng) == 0;
145 }
146
147 namespace operators
148 {
153
154 [[nodiscard]] inline int operator""_d2(unsigned long long int n) { return (int)dice(n, 2, default_random_engine); }
155 [[nodiscard]] inline int operator""_d4(unsigned long long int n) { return (int)dice(n, 4, default_random_engine); }
156 [[nodiscard]] inline int operator""_d6(unsigned long long int n) { return (int)dice(n, 6, default_random_engine); }
157 [[nodiscard]] inline int operator""_d8(unsigned long long int n) { return (int)dice(n, 8, default_random_engine); }
158 [[nodiscard]] inline int operator""_d10(unsigned long long int n) { return (int)dice(n, 10, default_random_engine); }
159 [[nodiscard]] inline int operator""_d12(unsigned long long int n) { return (int)dice(n, 12, default_random_engine); }
160 [[nodiscard]] inline int operator""_d20(unsigned long long int n) { return (int)dice(n, 20, default_random_engine); }
161 [[nodiscard]] inline int operator""_d100(unsigned long long int n) { return (int)dice(n, 100, default_random_engine); }
162
164 }
165
166 template <typename RANDOM = std::default_random_engine, std::integral T>
167 [[nodiscard]] T in_integer_range(T from, T to, RANDOM& rng = ::ghassanpl::random::default_random_engine)
168 {
169 static_assert(is_any_of_v<T, short, int, long, long long, unsigned short, unsigned int, unsigned long, unsigned long long>, "in_integer_range only works on real integer types");
170 if (from >= to) return T{};
171 std::uniform_int_distribution<T> dist{ from, to };
172 return dist(rng);
173 }
174
175 template <typename RANDOM = std::default_random_engine, std::integral T>
176 [[nodiscard]] T in_integer_range(T to, RANDOM& rng = ::ghassanpl::random::default_random_engine)
177 {
178 return in_integer_range(T{}, to, rng);
179 }
180
181 template <typename RANDOM = std::default_random_engine, std::floating_point T>
182 [[nodiscard]] T in_real_range(T from, T to, RANDOM& rng = ::ghassanpl::random::default_random_engine)
183 {
184 static_assert(std::is_floating_point_v<T>, "in_real_range only works on floating point types");
185 if (from >= to) return T{};
186 std::uniform_real_distribution<T> dist{ from, to };
187 return dist(rng);
188 }
189
190 template <typename RANDOM = std::default_random_engine, std::floating_point T>
191 [[nodiscard]] T in_real_range(T to, RANDOM& rng = ::ghassanpl::random::default_random_engine)
192 {
193 return in_real_range(T{}, to, rng);
194 }
195
196 template <typename RANDOM = std::default_random_engine, typename T>
197 [[nodiscard]] auto in_range(T from, T to, RANDOM& rng = ::ghassanpl::random::default_random_engine)
198 {
199 if constexpr (std::is_enum_v<T>)
200 {
201 if (from >= to) return T{};
202
203 std::uniform_int_distribution<std::underlying_type_t<T>> dist(from, to);
204 return (T)dist(rng);
205 }
206 else if constexpr (std::is_floating_point_v<T>)
207 {
208 if (from >= to) return T{};
209 return in_real_range(from, to, rng);
210 }
211 else if constexpr (is_any_of_v<T, short, int, long, long long, unsigned short, unsigned int, unsigned long, unsigned long long>)
212 {
213 if (from >= to) return T{};
214 return in_integer_range(from, to, rng);
215 }
216 else if constexpr (
217 is_any_of_v<T, unsigned char, signed char, char, wchar_t, char16_t, char32_t>
218#ifdef __cpp_char8_t
219 || std::is_same_v<T, char8_t>
220#endif
221 )
222 {
223 if (from >= to) return T{};
226 using signed_as_T = std::conditional_t<std::is_signed_v<T>, int64_t, uint64_t>;
227 return static_cast<T>(in_integer_range(static_cast<signed_as_T>(from), static_cast<signed_as_T>(to), rng));
228 }
229 else if constexpr (requires{ lerp(from, to, ::ghassanpl::random::percentage<float>(rng)); })
230 {
231 return lerp(from, to, ::ghassanpl::random::percentage<float>(rng));
232 }
233 else if constexpr (requires{ lerp(from, to, ::ghassanpl::random::percentage<double>(rng)); })
234 {
235 return lerp(from, to, ::ghassanpl::random::percentage<double>(rng));
236 }
237 else if constexpr (requires{ mix(from, to, ::ghassanpl::random::percentage<float>(rng)); })
238 {
239 return mix(from, to, ::ghassanpl::random::percentage<float>(rng));
240 }
241 else if constexpr (requires{ mix(from, to, ::ghassanpl::random::percentage<double>(rng)); })
242 {
243 return mix(from, to, ::ghassanpl::random::percentage<double>(rng));
244 }
245 }
246
247 // TODO: Should we move the below to random_seq?
248
249 template <typename T = float>
250 [[nodiscard]] constexpr T halton_sequence(size_t index, size_t base = 2)
251 {
252 static_assert(std::is_floating_point_v<T>, "halton_sequence only works on floating point types");
253
254 auto result = T(0);
255 auto fraction = T(1);
256 while (index > 0)
257 {
258 fraction /= base;
259 result += fraction * (index % base);
260 index /= base;
261 }
262 return result;
263 }
264
266 template <typename RANDOM = std::default_random_engine>
267 [[nodiscard]] bool with_probability(double probability, RANDOM& rng = ::ghassanpl::random::default_random_engine)
268 {
269 return percentage(rng) < std::clamp(probability, 0.0, 1.0);
270 }
271
273 template <typename RANDOM = std::default_random_engine>
274 [[nodiscard]] bool with_probability(double probability, double& result, RANDOM& rng = ::ghassanpl::random::default_random_engine)
275 {
276 auto res = result = percentage(rng);
277 return res < std::clamp(probability, 0.0, 1.0);
278 }
279
281 template <typename RANDOM = std::default_random_engine>
282 [[nodiscard]] bool one_in(size_t n, RANDOM& rng = ::ghassanpl::random::default_random_engine)
283 {
284 if (n == 0) return false;
285 return with_probability(1.0 / double(n), rng);
286 }
287
289 template <typename RANDOM = std::default_random_engine, typename T>
291 {
292 using std::begin;
293 using std::end;
294 std::shuffle(begin(cont), end(cont), rng);
295 }
296
298 template <typename RANDOM = std::default_random_engine, typename T>
299 requires std::ranges::sized_range<T>
300 [[nodiscard]] auto iterator(T& cont, RANDOM& rng = ::ghassanpl::random::default_random_engine)
301 {
302 using std::size;
303 using std::begin;
304 return begin(cont) + in_integer_range(0LL, (int64_t)size(cont) - 1, rng);
305 }
306
308 template <typename RANDOM = std::default_random_engine, typename T, typename PRED>
309 requires std::ranges::sized_range<T>
310 [[nodiscard]] auto iterator_if(T& cont, PRED&& pred, RANDOM& rng = ::ghassanpl::random::default_random_engine)
311 {
312 using std::size;
313 using std::begin;
314 using std::end;
315 auto begin_it = begin(cont);
316 const auto end_it = end(cont);
317 const auto valid_count = std::count_if(begin_it, end_it, pred);
318 auto item_position = in_integer_range(0LL, (int64_t)valid_count - 1, rng);
319 for (; begin_it != end_it; ++begin_it)
320 {
321 if (pred(*begin_it) && (item_position--) == 0)
322 {
323 return begin_it;
324 }
325 }
326 return end_it;
327 }
328
330 template <typename RANDOM = std::default_random_engine, typename T>
331 [[nodiscard]] auto index(T& cont, RANDOM& rng = ::ghassanpl::random::default_random_engine)
332 {
333 return std::distance(begin(cont), iterator(cont, rng));
334 }
335
337 template <typename RANDOM = std::default_random_engine, typename T, typename PRED>
338 [[nodiscard]] auto index_if(T& cont, PRED&& pred, RANDOM& rng = ::ghassanpl::random::default_random_engine)
339 {
340 return std::distance(begin(cont), iterator_if(cont, std::forward<PRED>(pred), rng));
341 }
342
344 template <typename RANDOM = std::default_random_engine, typename T>
345 [[nodiscard]] auto* element(T& cont, RANDOM& rng = ::ghassanpl::random::default_random_engine)
346 {
347 using std::end;
348 auto result = iterator(cont, rng);
349 return (result != end(cont)) ? std::addressof(*result) : nullptr;
350 }
351
353 template <typename RANDOM = std::default_random_engine, typename T, typename PRED>
354 [[nodiscard]] auto* element_if(T& cont, PRED&& pred, RANDOM& rng = ::ghassanpl::random::default_random_engine)
355 {
356 using std::end;
357 auto result = iterator_if(cont, std::forward<PRED>(pred), rng);
358 return (result != end(cont)) ? std::addressof(*result) : nullptr;
359 }
360
362 template <typename... T>
363 [[nodiscard]] auto one_of(T&&... values)
364 {
366 static_assert(sizeof...(values) > 0, "at least one value must be provided");
367 auto v = std::array{ std::forward<T>(values)... };
369 }
370
372 template <typename RANDOM = std::default_random_engine, typename T>
373 [[nodiscard]] auto one_of(std::initializer_list<T> values, RANDOM& rng = ::ghassanpl::random::default_random_engine)
374 {
375 if (values.size() == 0) throw std::invalid_argument("values");
376 return *element(values, rng);
377 }
378
379 // TODO: random_range()
380 //
381 // TODO: template <typename T> T enum_value(enum_flags<T> set);
382
386 template <typename RANDOM = std::default_random_engine, typename T>
387 [[nodiscard]] auto make_bag_randomizer(T& container, RANDOM& rng = ::ghassanpl::random::default_random_engine)
388 {
389 using Iterator = decltype(std::begin(container));
390 struct randomizer_t
391 {
392 randomizer_t(RANDOM& rng, T& container)
393 : mRNG(rng)
394 {
395 for (auto it = std::begin(container); it != std::end(container); ++it)
396 mIterators.push_back(it);
397 mCurrent = mIterators.end();
398 }
399 decltype(auto) next() { if (mCurrent == mIterators.end()) { shuffle(); } return *mCurrent++; }
400 void shuffle() { std::shuffle(mIterators.begin(), mIterators.end(), mRNG); mCurrent = mIterators.begin(); }
401 private:
402 RANDOM& mRNG;
403 std::vector<Iterator> mIterators;
404 typename std::vector<Iterator>::iterator mCurrent;
405 };
406
407 return randomizer_t{ rng, container };
408 }
409
412 template <typename T, typename RANDOM>
413 [[nodiscard]] size_t option_with_probability(span<T const> option_probabilities, RANDOM& rng = ::ghassanpl::random::default_random_engine)
414 {
416 static_assert(std::is_convertible_v<T, double>, "option probabilities must be convertible to double");
418 std::discrete_distribution<size_t> dist(option_probabilities.begin(), option_probabilities.end());
419 return dist(rng);
420 }
421
426 template <typename RANGE, typename FUNC, typename RANDOM>
427 [[nodiscard]] auto iterator_with_probability(RANGE&& range, FUNC&& prob_func, RANDOM& rng = ::ghassanpl::random::default_random_engine)
428 {
429#ifdef __cpp_lib_ranges
430 static_assert(std::ranges::forward_range<RANGE>, "range must be forward range");
431 static_assert(std::is_invocable_v<FUNC, std::ranges::range_reference_t<RANGE>>);
432 static_assert(std::convertible_to<std::invoke_result_t<FUNC, std::ranges::range_reference_t<RANGE>>, double>);
433#endif
434
435 const auto start = std::begin(range);
436 const auto end = std::end(range);
437
438 const auto sum = std::accumulate(start, end, 0.0, [&](auto acc, auto&& item) { return acc + (double)prob_func(item); });
439 auto target = in_real_range(0.0, sum, rng);
440
441 for (auto it = start; it != end; ++it)
442 {
443 const auto weight = (double)prob_func(*it);
444 if (target < weight)
445 return it;
446 target = target - weight;
447 }
448
449 return end;
450 }
451
453}
std::default_random_engine default_random_engine
A thread-local random engine used by default by all random functions.
Definition random.h:58
auto * element_if(T &cont, PRED &&pred, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an pointer to a random element in cont that matches pred.
Definition random.h:354
auto * element(T &cont, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an pointer to a random element in cont.
Definition random.h:345
bool one_in(size_t n, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns true with a one-in-n chance.
Definition random.h:282
REAL percentage(RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns a random value between 0 and 1 of the given floating-point type.
Definition random.h:70
auto index_if(T &cont, PRED &&pred, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an index of a random element in cont that matches pred.
Definition random.h:338
auto one_of(T &&... values)
Returns one of its arguments at random.
Definition random.h:363
size_t option_with_probability(span< T const > option_probabilities, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an index of one of the options in option_probabilities, where its elements are the probabilit...
Definition random.h:413
bool coin(RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns true or false randomly.
Definition random.h:141
void shuffle(T &cont, RANDOM &rng=::ghassanpl::random::default_random_engine)
Shuffles the cont container (as if by std::shuffle).
Definition random.h:290
auto iterator(T &cont, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an iterator to a random element in cont.
Definition random.h:300
auto iterator_with_probability(RANGE &&range, FUNC &&prob_func, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an iterator to a random object in range, with the probability weights of an object being chos...
Definition random.h:427
uint64_t dice(uint64_t n_sided, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns the result of throwing an n_sided die (1-N).
Definition random.h:98
auto iterator_if(T &cont, PRED &&pred, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an iterator to a random element in cont that matches pred.
Definition random.h:310
auto index(T &cont, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an index of a random element in cont.
Definition random.h:331
auto make_bag_randomizer(T &container, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an object that represents a random "bag" of iterators to objects in container.
Definition random.h:387
REAL normal(RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns a random value of the given floating-point type in a normal distribution.
Definition random.h:90
constexpr INTEGER integer(RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns a random integer of the given type.
Definition random.h:62
bool with_probability(double probability, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns true with the given 0-1 probability, false otherwise.
Definition random.h:267
constexpr CONTAINER to(RANGE &&range, TYPES &&... args)
to<container>();
Definition ranges.h:386
Contains functions that return predicates; appropriate for functions like std::all_of.
Definition functional.h:82