1 /** Randomize existing instances and generate randomized instances of a given type.
2 
3     Copyright: Per Nordlöw 2018-.
4     License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
5     Authors: $(WEB Per Nordlöw)
6 
7     See_Also: http://forum.dlang.org/thread/byonwfghdqgcirdjyboh@forum.dlang.org
8 
9     TODO Can these be tagged with @nogc? Currently std.random.uniform may allocate.
10     TODO Tags as nothrow when std.random gets there.
11     TODO How to handle possibly null reference (class, dynamic types) types?
12     Answer relates to how to randomize empty/null variable length structures
13     (arrays, strings, etc).
14     - Maybe some kind of length randomization?
15  */
16 module nxt.random_ex;
17 
18 import std.traits: isIntegral, isFloatingPoint, isNumeric, isIterable, isStaticArray, isArray, hasIndirections, isSomeString, isScalarType, isBoolean;
19 import std.range: ElementType, hasAssignableElements;
20 import std.random: uniform;
21 
22 version(unittest) private enum testLength = 64;
23 
24 /** Randomize value $(D x). */
25 ref E randInPlace(E)(return ref E x)
26 if (isBoolean!E)
27 {
28     return x = cast(bool)uniform(0, 2);
29 }
30 
31 /** Randomize value $(D x), optionally in range [$(D low), $(D high)]. */
32 ref E randInPlace(E)(return ref E x)
33 if (isIntegral!E)
34 {
35     return x = uniform(E.min, E.max);    // BUG: Never assigns the value E.max
36 }
37 
38 /** Randomize value $(D x), optional in range [$(D low), $(D high)]. */
39 ref E randInPlace(E)(return ref E x)
40 if (isFloatingPoint!E)
41 {
42     return x = uniform(cast(E)0, cast(E)1);
43 }
44 
45 /** Randomize value $(D x), optionally in range [$(D low), $(D high)]. */
46 ref E randInPlaceWithRange(E)(return ref E x,
47                               E low,
48                               E high)
49 if (isIntegral!E)
50 {
51     return x = uniform(low, high);    // BUG: Never assigns the value E.max
52 }
53 
54 /** Randomize value of $(D x), optional in range [$(D low), $(D high)]. */
55 ref E randInPlaceWithRange(E)(return ref E x,
56                               E low /* E.min_normal */,
57                               E high /* E.max */)
58 if (isFloatingPoint!E)
59 {
60     return x = uniform(low, high);
61 }
62 
63 version(unittest)
64 {
65     import nxt.rational: Rational, rational;
66 }
67 
68 /** Randomize value of $(D x). */
69 ref Rational!E randInPlace(Rational, E)(return ref Rational!E x) @trusted
70 if (isIntegral!E)
71 {
72     return x = rational(uniform(E.min, E.max),
73                         uniform(1, E.max));
74 }
75 
76 @safe unittest
77 {
78     Rational!int x;
79     x.randInPlace();
80 }
81 
82 /** Generate random value of $(D x).
83     See_Also: http://forum.dlang.org/thread/emlgflxpgecxsqweauhc@forum.dlang.org
84  */
85 ref dchar randInPlace(return ref dchar x) @trusted
86 {
87     auto ui = uniform(0,
88                       0xD800 +
89                       (0x110000 - 0xE000) - 2 // minus two for U+FFFE and U+FFFF
90         );
91     if (ui < 0xD800)
92     {
93         return x = ui;
94     }
95     else
96     {
97         ui -= 0xD800;
98         ui += 0xE000;
99 
100         // skip undefined
101         if (ui < 0xFFFE)
102             return x = ui;
103         else
104             ui += 2;
105 
106         assert(ui < 0x110000);
107         return x = ui;
108     }
109 }
110 
111 unittest
112 {
113     auto x = randomized!dchar;
114     dstring d = "alphaalphaalphaalphaalphaalphaalphaalphaalphaalpha";
115     auto r = d.randomize; // TODO Use Phobos function to check if string is legally coded.
116 }
117 
118 /** Randomize value of $(D x). */
119 dstring randInPlace(dstring x) @trusted
120 {
121     typeof(x) y;
122     foreach (ix; 0 .. x.length)
123         y ~= randomized!dchar; // TODO How to do this in a better way?
124     x = y;
125     return x;
126 }
127 
128 /** Randomize value of $(D x).
129  */
130 R randInPlace(R)(R x)
131 if (isIterable!R &&
132     hasAssignableElements!R)
133 {
134     import core.lifetime : move;
135     foreach (ref e; x)
136     {
137         e.randInPlace();
138     }
139     return move(x);             // TODO remove when compiler does this for us
140 }
141 
142 /** Randomize all elements of $(D x).
143     Each element is randomized within range `[elementLow, elementHigh]`.
144  */
145 R randInPlaceWithElementRange(R, E)(R x,
146                                     E elementLow,
147                                     E elementHigh)
148 if (isIterable!R &&
149     hasAssignableElements!R &&
150     is(ElementType!R == E))
151 {
152     import core.lifetime : move;
153     foreach (ref e; x)
154     {
155         e.randInPlaceWithRange(elementLow, elementHigh);
156     }
157     return move(x);
158 }
159 
160 @safe unittest
161 {
162     void testDynamic(T)()
163     {
164         auto x = new T[testLength];
165         auto y = x.dup;
166         x.randInPlace();
167         y.randInPlace();
168         assert(y != x);
169     }
170     testDynamic!int;
171     testDynamic!float;
172     testDynamic!bool;
173 }
174 
175 /** Randomize elements of $(D x).
176  */
177 ref T randInPlace(T)(return ref T x)
178 if (isStaticArray!T)
179 {
180     foreach (ref e; x)
181     {
182         e.randInPlace();
183     }
184     return x;
185 }
186 
187 @safe unittest
188 {
189     void testStatic(T)()
190     {
191         T[testLength] x;
192         auto y = x;
193         x.randInPlace();
194         y.randInPlace();
195         assert(y != x);
196     }
197     testStatic!bool;
198     testStatic!int;
199     testStatic!real;
200     enum E { a, b, c, d, e, f, g, h,
201              i, j, k, l, m, n, o, p }
202     testStatic!E;
203 }
204 
205 /** Blockwise-randomize elements of $(D x) of array type $(D A).
206     Randomizes in array blocks of type $(D B).
207  */
208 ref A randInPlaceBlockwise(B = size_t, A)(ref A x)
209 if (isArray!A &&
210     isIntegral!(ElementType!A))
211 {
212     alias E = ElementType!A;
213     static assert(E.sizeof < B.sizeof);
214     enum mult = B.sizeof / E.sizeof; // block multiplicity
215 
216     immutable n = x.length;
217 
218     // beginning unaligned bytes
219     auto p = cast(size_t)x.ptr;
220     immutable size_t mask = B.sizeof - 1;
221     immutable r = (p & mask) / E.sizeof; // element-offset from B-aligned address before x
222     size_t k = 0; // E-index to first B-block
223     if (r)
224     {
225         import std.algorithm.comparison : min;
226         k = min(n, mult - r); // at first aligned B-block
227         foreach (i, ref e; x[0 .. k])
228         {
229             e.randInPlace();
230         }
231     }
232 
233     // mid blocks of type B
234     auto bp = cast(B*)(x.ptr + k); // block pointer
235     immutable nB = (n - k) / mult; // number of B-blocks
236     foreach (ref b; 0 .. nB) // for each block index
237     {
238         bp[b].randInPlace();
239     }
240 
241     // ending unaligned bytes
242     foreach (i, ref e; x[k + nB*mult .. $])
243     {
244         e.randInPlace();
245     }
246 
247     return x;
248 }
249 
250 unittest
251 {
252     static void test(B = size_t, T)()
253     {
254         enum n = 1024;
255 
256         // dynamic array
257         for (size_t i = 0; i < n; i++)
258         {
259             T[] da = new T[i];
260             da.randInPlaceBlockwise!B;
261             size_t j = randomInstanceOf!(typeof(i))(0, n/2);
262             da.randInPlaceBlockwise!B;
263         }
264 
265         // static array
266         T[n] sa;
267         auto sa2 = sa[1 .. $];
268         sa2.randInPlaceBlockwise!B;
269     }
270 
271     import std.meta : AliasSeq;
272     foreach (T; AliasSeq!(byte, short, int, ubyte, ushort, uint))
273     {
274         test!(size_t, T);
275     }
276 }
277 
278 /** Randomize members of $(D x).
279  */
280 auto ref randInPlace(T)(return ref T x)
281 if (is(T == struct))
282 {
283     foreach (ref e; x.tupleof)
284     {
285         e.randInPlace();
286     }
287     return x;
288 }
289 
290 @safe unittest
291 {
292     struct T { ubyte a, b, c, d; }
293     T[testLength] x;
294     auto y = x;
295     x.randInPlace();
296     y.randInPlace();
297     assert(y != x);
298 }
299 
300 /** Randomize members of $(D x).
301  */
302 auto ref randInPlace(T)(T x)
303 if (is(T == class))
304 {
305     foreach (ref e; x.tupleof)
306     {
307         e.randInPlace();
308     }
309     return x;
310 }
311 
312 alias randomize = randInPlace;
313 
314 unittest
315 {
316     void testClass(E)()
317     {
318         class T { E a, b; }
319         auto x = new T;
320         auto y = new T;
321         x.randInPlace();
322         y.randInPlace();
323         assert(y != x);
324     }
325     testClass!bool;
326     testClass!int;
327     testClass!float;
328 }
329 
330 /** Returns: randomized instance of type $(D T).
331  */
332 T randomInstanceOf(T)()
333 {
334     /* TODO recursively only void-initialize parts of T that are POD, not
335      reference types */
336     static if (hasIndirections!T)
337         T x;
338     else
339         /* don't init - randInPlace below fills in everything safely */
340         T x = void;
341     x.randInPlace();
342     return x;
343 }
344 
345 /** Returns: randomized instance of type $(D T).
346  */
347 T randomInstanceOf(T)(T low = T.min,
348                       T high = T.max)
349 if (isNumeric!T)
350 {
351     /* TODO recursively only void-initialize parts of T that are POD, not
352        reference types */
353     static if (hasIndirections!T)
354         T x;
355     else
356         /* don't init - `randInPlace()` below fills in everything safely */
357         T x = void;
358     return x.randInPlaceWithRange(low, high);
359 }
360 
361 alias randomized = randomInstanceOf;
362 
363 /** Random number generator xoroshiro128+
364 
365    See_Also: http://xoroshiro.di.unimi.it/
366    See_Also: http://forum.dlang.org/post/kdobdorqztlsomweftmi@forum.dlang.org
367    See_Also: https://www.reddit.com/r/programming/comments/4gtlfz/xoroshiro128_the_fastest_fullperiod_pseudorandom/
368  */
369 struct Xoroshiro128plus
370 {
371     @safe pure nothrow @nogc:
372     public:
373 
374     enum ulong min = ulong.min;
375     enum ulong max = ulong.max;
376 
377     enum bool isUniformRandom = true;
378 
379     /// Range primitives
380     enum bool empty = false;
381 
382     /// ditto
383     ulong front() @property
384     {
385         return s[0] + s[1];
386     }
387 
388     /// ditto
389     void popFront()
390     {
391         immutable ulong s1 = s[1] ^ s[0];
392         s[0] = rotateLeft(s[0], 55) ^ s1 ^ (s1 << 14);
393         s[1] = rotateLeft(s1, 36);
394     }
395 
396     void seed(ulong s0, ulong s1)
397     in
398     {
399         // seeds are not both 0
400         assert(!(!s0 && !s1));
401     }
402     do
403     {
404         s[0] = s0;
405         s[1] = s1;
406     }
407 
408     void seed(ulong[2] s01)
409     in
410     {
411         // seeds are not both 0
412         assert(!(!s01[0] && !s01[1]));
413     }
414     do
415     {
416         s[] = s01[];
417     }
418 
419 private:
420     ulong[2] s;
421 
422     static ulong rotateLeft(ulong x, int k)
423     in
424     {
425         assert(k <= 64);
426     }
427     do
428     {
429         return (x << k) | (x >> (64 - k));
430     }
431 }
432 
433 @safe pure nothrow unittest
434 {
435     Xoroshiro128plus gen;
436     gen.seed(150078950, 1313143614);
437     import std.random : uniform;
438     import std.range : generate, take;
439     auto x = generate!(() => uniform!int(gen)).take(103);
440 }