1 /// Pseudo random range
2 module tagion.utils.Random;
3 @safe:
4 
5 import std.format;
6 import std.range;
7 import std.traits : isNumeric;
8 
9 /// Generates a pseudo random sequence
10 @nogc
11 struct Random(T = uint) {
12     @nogc {
13         private T m_z;
14         private T m_w;
15         this(const T seed_value) pure nothrow {
16             seed(seed_value);
17         }
18 
19         private this(T m_z, T m_w) pure nothrow {
20             this.m_z = m_z;
21             this.m_w = m_w;
22         }
23 
24         /**
25      * 
26      * Params:
27      *   seed_value = is the seend of the pseudo random
28      */
29         void seed(const T seed_value) pure nothrow {
30             m_z = 13 * seed_value;
31             m_w = 7 * seed_value;
32         }
33 
34         /**
35          *  
36          * Returns: next random value 
37          */
38         T value() {
39             popFront;
40             return front;
41         }
42 
43         /**
44          * 
45          * Params:
46          *   max = sets the max value
47          * Returns: 
48          *   next random value less than max
49          */
50         T value(const(T) max) {
51             return value % max;
52         }
53 
54         /**
55          * 
56          * Params:
57          *   from = start value
58          *   to = until value
59          * Returns: random value in the range from..to
60          */
61         T value(const(T) from, const(T) to)
62         in {
63             assert(to > from);
64         }
65         do {
66             immutable range = to - from;
67             return (value % range) + from;
68         }
69 
70         /**
71          * pops the next random value
72          */
73         void popFront() pure nothrow {
74             m_z = 36_969 * (m_z & T.max) + (m_z >> 16);
75             m_w = 18_000 * (m_w & T.max) + (m_w >> 16);
76         }
77 
78         /**
79          * 
80          * Returns: current random value 
81          */
82         T front() const pure nothrow {
83             return (m_z << 16) + m_w;
84         }
85 
86         enum bool empty = false;
87 
88         /**
89          * Saves the forward range
90          * Returns: new random 
91          */
92         Random save() const pure nothrow {
93             return Random(m_z, m_w);
94         }
95 
96         import std.range.primitives : isForwardRange, isInfinite, isInputRange;
97 
98         static assert(isInputRange!(Random));
99         static assert(isForwardRange!(Random));
100         static assert(isInfinite!(Random));
101     }
102     /**
103      * 
104      * Returns: inner parameter for the random sequnece 
105      */
106     string toString() const pure {
107         return format("m_z %s, m_w %s, value %s", m_z, m_w, front);
108     }
109 
110 }
111 
112 ///
113 unittest {
114     import std.algorithm.comparison : equal;
115     import std.range : drop, take;
116 
117     auto r = Random!uint(1234);
118     auto r_forward = r.save;
119 
120     assert(equal(r.take(5), r_forward.take(5)));
121 
122     r = r.drop(7);
123     assert(r != r_forward);
124     r_forward = r.save;
125     assert(r == r_forward);
126     assert(equal(r.take(4), r_forward.take(4)));
127 }
128 
129 /// This data type can be used to group a sequence of pseudo random sequency
130 @nogc
131 struct Sequence(T = uint) {
132     import std.range;
133 
134     Random!T rand;
135     T size;
136     /** 
137      * Returns: sequence of random numbers
138      */
139     auto list() const pure nothrow {
140         return rand.save.take(size);
141     }
142 
143     /**
144      * Progress the random sequency next_size creates a new sequency
145      * Params:
146      *   next_size = the number of random to the next sequency 
147      * Returns: 
148      *   the net sequence after next_size randoms
149      */
150     Sequence progress(T next_size) const pure nothrow {
151         Sequence result;
152         result.rand = rand.save.drop(size);
153         result.size = next_size;
154         return result;
155     }
156 }
157 
158 ///
159 unittest {
160     import std.algorithm;
161     import std.range;
162     import std.stdio;
163 
164     alias RandomT = Random!uint;
165 
166     enum sample = 5;
167     { // Simple range of sequences with fixed size
168 
169         auto start = RandomT(1234);
170         auto rand_range = recurrence!(q{
171         a[n-1].drop(3)
172         })(start);
173         assert(equal(
174                 rand_range
175                 .take(sample)
176                 .map!q{a.take(3)}
177                 .joiner,
178                 start.save
179                 .take(sample * 3)));
180     }
181 
182     { // Range of sequences with random size
183         auto start = RandomT(1234);
184         // Generate sequency range with variable size in the range 4..8
185         auto rand_range = recurrence!(
186                 (a, n) =>
187                 a[n - 1].progress(start.value(4, 8))
188         )(Sequence!uint(start.save, 5));
189 
190         // Store the begen of the random start
191         auto begin = start;
192         const total_number = rand_range.take(sample).map!q{a.size}.sum;
193         // restore start random
194         auto expected = start = begin;
195         assert(equal(expected.take(total_number),
196                 rand_range.take(sample).map!q{a.list}.joiner));
197 
198     }
199 
200 }
201 
202 /**
203  * Generate a random id 
204  * Returns: random id
205 **/
206 const(T) generateId(T = uint)() nothrow if(isNumeric!T) {
207     T id = 0;
208     import stdrnd = std.random;
209 
210     auto rnd = Random!T(stdrnd.unpredictableSeed);
211     do {
212         id = rnd.value();
213     }
214     while (id is 0 || id is T.max);
215     return id;
216 }