mirror of https://github.com/adamdruppe/arsd.git
more basic functionality
This commit is contained in:
parent
aa838cd2aa
commit
6234e64d7f
306
random.d
306
random.d
|
|
@ -1,6 +1,8 @@
|
||||||
/++
|
/++
|
||||||
A random number generator that can work with [std.random] but does not have to.
|
A random number generator that can work with [std.random] but does not have to.
|
||||||
|
|
||||||
|
It is designed to be reasonably good and fast, more for fun than for security.
|
||||||
|
|
||||||
|
|
||||||
Authors:
|
Authors:
|
||||||
Forked from Herringway's pcg.d file:
|
Forked from Herringway's pcg.d file:
|
||||||
|
|
@ -44,21 +46,305 @@ Herringway: like seeding with ranges instead of single values (mersenne twister
|
||||||
Herringway: as well as providing more sources of data to seed with, ike OS APIs n such
|
Herringway: as well as providing more sources of data to seed with, ike OS APIs n such
|
||||||
+/
|
+/
|
||||||
|
|
||||||
|
|
||||||
alias reasonableDefault = PCG!(uint, ulong, xslrr);
|
|
||||||
|
|
||||||
// desired functions:
|
// desired functions:
|
||||||
// https://phobos.dpldocs.info/source/std.random.d.html#L2119
|
// https://phobos.dpldocs.info/source/std.random.d.html#L2119
|
||||||
int uniform(int min, int max) { return 0; }
|
|
||||||
// might do a long uniform and maybe float? but idk divide that mebbe
|
/++
|
||||||
void shuffle(T)(T[] array) {} // fisher-yates algorithm
|
Gets a random number from a uniform distribution including min and up to (but not including) max from the reasonable default generator.
|
||||||
int weightedChoice(scope const int[] weights...) { return 0; } // std.random.dice
|
|
||||||
// the normal / gaussian distribution
|
History:
|
||||||
int bellCurve(int median, int standardDeviation) { return 0; }
|
Added April 17, 2025
|
||||||
|
+/
|
||||||
|
int uniform(int min, int max) {
|
||||||
|
return uniform(getReasonableDefaultGenerator(), min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
int uniform(Rng gen, int min, int max) {
|
||||||
|
auto f = cast(uint) gen.next;
|
||||||
|
|
||||||
|
// FIXME i think this is biased but also meh
|
||||||
|
return f % (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
alias randomInteger = uniform;
|
||||||
|
|
||||||
|
/+
|
||||||
|
unittest {
|
||||||
|
import arsd.core;
|
||||||
|
writeln(uniform(-10, 0));
|
||||||
|
}
|
||||||
|
+/
|
||||||
|
|
||||||
|
/++
|
||||||
|
Gets a random number between 0.0 and 1.0, including 0.0, but not including 1.0.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added April 18, 2025
|
||||||
|
+/
|
||||||
|
float randomFloat() {
|
||||||
|
return randomFloat(getReasonableDefaultGenerator());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
float randomFloat(Rng gen) {
|
||||||
|
auto max = (1 << float.mant_dig) - 1;
|
||||||
|
float n = uniform(gen, 0, max);
|
||||||
|
return n / max;
|
||||||
|
}
|
||||||
|
|
||||||
|
// might do a long uniform and maybe double too? idk
|
||||||
|
|
||||||
|
/++
|
||||||
|
Shuffles the contents of the array, in place. Assumes elements can be easily swapped.
|
||||||
|
(the current implementation is an in-place Fisher-Yates algorithm)
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added April 19, 2025
|
||||||
|
+/
|
||||||
|
void shuffle(T)(T[] array) {
|
||||||
|
shuffle(getReasonableDefaultGenerator(), array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void shuffle(T)(Rng gen, T[] array) {
|
||||||
|
assert(array.length < int.max);
|
||||||
|
|
||||||
|
foreach(index, item; array) {
|
||||||
|
auto ridx = uniform(gen, cast(int) index, cast(int) array.length);
|
||||||
|
if(ridx == index)
|
||||||
|
continue;
|
||||||
|
array[index] = array[ridx];
|
||||||
|
array[ridx] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version(arsd_random_unittest)
|
||||||
|
unittest {
|
||||||
|
auto array = [1,2,3,4,5,6,7,8,9,0];
|
||||||
|
auto results = new int[](array.length);
|
||||||
|
|
||||||
|
foreach(i; 0 .. 1_000_000) {
|
||||||
|
shuffle(array);
|
||||||
|
auto searchingFor = 9;
|
||||||
|
foreach(where, item; array)
|
||||||
|
if(searchingFor == item)
|
||||||
|
results[where]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
import arsd.core; writeln(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Returns an index of the weights, with the proportional odds given by the weights.
|
||||||
|
|
||||||
|
So weightedChoice([1, 2, 1]) is twice as likely to return 1 as it is 0 or 2.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added April 19, 2025
|
||||||
|
+/
|
||||||
|
int weightedChoice(scope const int[] weights...) {
|
||||||
|
return weightedChoice(getReasonableDefaultGenerator(), weights);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
int weightedChoice(Rng gen, scope const int[] weights...) {
|
||||||
|
int sum = 0;
|
||||||
|
foreach(weight; weights)
|
||||||
|
sum += weight;
|
||||||
|
|
||||||
|
int val = uniform(gen, 0, sum);
|
||||||
|
|
||||||
|
sum = 0;
|
||||||
|
foreach(idx, weight; weights) {
|
||||||
|
sum += weight;
|
||||||
|
|
||||||
|
if(val < sum)
|
||||||
|
return cast(int) idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Pick a random number off the normal (aka gaussian) distribution bell curve.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
median = median
|
||||||
|
standardDeviation = standard deviation
|
||||||
|
min = minimum value to ever return
|
||||||
|
max = one above the highest value to ever return; an exclusive endpoint
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added April 18, 2025
|
||||||
|
+/
|
||||||
|
int bellCurve(int median, int standardDeviation, int min = int.min, int max = int.max) {
|
||||||
|
return bellCurve(getReasonableDefaultGenerator(), median, standardDeviation, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
int bellCurve(Rng gen, int median, int standardDeviation, int min = int.min, int max = int.max) {
|
||||||
|
import core.stdc.math;
|
||||||
|
|
||||||
|
auto mag = standardDeviation * sqrt(-2.0 * log(randomFloat(gen)));
|
||||||
|
int value = cast(int) (mag * cos(2 * 3.14159268f * randomFloat(gen)) + median);
|
||||||
|
if(value < min)
|
||||||
|
value = min;
|
||||||
|
if(value >= max)
|
||||||
|
value = max - 1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
// bimodal distribution?
|
// bimodal distribution?
|
||||||
// maybe a pareto distribution too idk tho
|
// maybe a pareto distribution too idk tho
|
||||||
|
|
||||||
|
|
||||||
|
version(arsd_random_unittest)
|
||||||
|
unittest {
|
||||||
|
int[21] results;
|
||||||
|
foreach(i; 0 .. 1_000_00) {
|
||||||
|
//results[uniform(0, cast(int) results.length)] += 1;
|
||||||
|
//results[bellCurve(10, 3, 0, cast(int) results.length)] += 1;
|
||||||
|
|
||||||
|
results[weightedChoice([0, 2, 1, 0, 2, 6, 0, 6, 6])] += 1;
|
||||||
|
}
|
||||||
|
import std.stdio; writeln(results);
|
||||||
|
|
||||||
|
// foreach(i; 0 .. 10) writeln(bellCurve(100, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
A simple generic interface to a random number generator.
|
||||||
|
+/
|
||||||
|
interface Rng {
|
||||||
|
/++
|
||||||
|
Seeds the generator, calling the delegate zero (if it is a true rng), one, or more times to get all the state it needs.
|
||||||
|
+/
|
||||||
|
void seed(scope ulong delegate() getEntropy);
|
||||||
|
/++
|
||||||
|
Get the next number in the sequence. Some may not actually use all 64 bits of the return type.
|
||||||
|
+/
|
||||||
|
ulong next();
|
||||||
|
|
||||||
|
/+
|
||||||
|
/++
|
||||||
|
Saves a copy of the current generator state to a fresh object.
|
||||||
|
|
||||||
|
See_Also:
|
||||||
|
[saveState], which returns an array of bytes you can save to a file (or whatever)
|
||||||
|
+/
|
||||||
|
Rng save() const;
|
||||||
|
+/
|
||||||
|
}
|
||||||
|
|
||||||
|
/+
|
||||||
|
interface RestorableRng {
|
||||||
|
/++
|
||||||
|
Saves the rng state to an array.
|
||||||
|
|
||||||
|
To restore state, you must first construct an object of the same type, then call `restoreState`
|
||||||
|
on that fresh object. If you get the wrong type, it won't work right (and may or may not throw an exception).
|
||||||
|
+/
|
||||||
|
ubyte[] saveState() const;
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void restoreState(in ubyte[]);
|
||||||
|
}
|
||||||
|
+/
|
||||||
|
|
||||||
|
class RngFromRange(R) : Rng {
|
||||||
|
private R r;
|
||||||
|
|
||||||
|
void seed(scope ulong delegate() getEntropy) {
|
||||||
|
r = R(getEntropy());
|
||||||
|
}
|
||||||
|
ulong next() {
|
||||||
|
auto f = r.front;
|
||||||
|
r.popFront;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
Rng save() const {
|
||||||
|
auto t = new RngFromRange();
|
||||||
|
t.r = this.r.save;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias reasonableDefault = PCG!(uint, ulong, xslrr);
|
||||||
|
|
||||||
|
/++
|
||||||
|
Gets a "reasonable default" random number generator, one good enough
|
||||||
|
for my casual use. This is the object used by the other functions when
|
||||||
|
you don't explicitly use your own generator.
|
||||||
|
|
||||||
|
It will be automatically seeded from the operating system random number
|
||||||
|
pool if you don't pass one of your own.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added April 17, 2025
|
||||||
|
+/
|
||||||
|
Rng getReasonableDefaultGenerator(lazy ulong seed) @trusted {
|
||||||
|
static Rng generator;
|
||||||
|
if(generator is null) {
|
||||||
|
generator = new RngFromRange!reasonableDefault();
|
||||||
|
generator.seed(&seed);
|
||||||
|
}
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
Rng getReasonableDefaultGenerator() {
|
||||||
|
return getReasonableDefaultGenerator(unpredictableSeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong unpredictableSeed() {
|
||||||
|
ulong r;
|
||||||
|
osRandom(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
version (none) {
|
||||||
|
} else version (linux) {
|
||||||
|
private bool osRandom(out ulong result) @trusted {
|
||||||
|
import core.sys.posix.unistd;
|
||||||
|
import core.sys.posix.fcntl;
|
||||||
|
int fd = open("/dev/urandom", O_RDONLY);
|
||||||
|
if(fd == -1)
|
||||||
|
return false;
|
||||||
|
auto ret = read(fd, &result, typeof(result).sizeof);
|
||||||
|
if(ret != typeof(result).sizeof) {
|
||||||
|
close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else version (Windows) {
|
||||||
|
pragma(lib, "Bcrypt.lib");
|
||||||
|
|
||||||
|
private bool osRandom(out ulong result) @trusted {
|
||||||
|
import core.sys.windows.windef : PUCHAR, ULONG;
|
||||||
|
import core.sys.windows.ntdef : NT_SUCCESS;
|
||||||
|
import core.sys.windows.bcrypt : BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG;
|
||||||
|
|
||||||
|
const gotRandom = BCryptGenRandom(
|
||||||
|
null,
|
||||||
|
cast(PUCHAR) &result,
|
||||||
|
ULONG(typeof(result).sizeof),
|
||||||
|
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
|
||||||
|
);
|
||||||
|
|
||||||
|
return NT_SUCCESS(gotRandom);
|
||||||
|
}
|
||||||
|
} else version (all) {
|
||||||
|
private bool osRandom(out ulong result) @trusted {
|
||||||
|
import std.random;
|
||||||
|
result = std.random.unpredictableSeed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private V rotr(V)(V value, uint r) {
|
private V rotr(V)(V value, uint r) {
|
||||||
return cast(V)(value >> r | value << (-r & (V.sizeof * 8 - 1)));
|
return cast(V)(value >> r | value << (-r & (V.sizeof * 8 - 1)));
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +562,7 @@ struct PCG(T, S, alias func, S multiplier = DefaultPCGMultiplier!S, S increment
|
||||||
T front() const @safe pure nothrow @nogc @property {
|
T front() const @safe pure nothrow @nogc @property {
|
||||||
return func!T(state);
|
return func!T(state);
|
||||||
}
|
}
|
||||||
typeof(this) save() @safe pure nothrow @nogc {
|
typeof(this) save() @safe pure nothrow @nogc const {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
enum bool empty = false;
|
enum bool empty = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue