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