1 /**
2  * ae.utils.random
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.random;
15 
16 import std.algorithm.comparison;
17 import std.algorithm.mutation;
18 import std.array;
19 import std.random;
20 import std.range.primitives;
21 
22 /// Like `randomShuffle`, but returns results incrementally
23 /// (still copies the input, but calls `gen` only as needed).
24 /// Like `randomCover`, but much faster
25 /// (O(n) instead of O(n^2), though less space-efficient.
26 auto incrementalRandomShuffle(Range, RandomGen)(Range range, ref RandomGen gen)
27 if (isInputRange!Range && isUniformRNG!RandomGen)
28 {
29 	alias E = ElementType!Range;
30 	static struct IncrementalRandomShuffle
31 	{
32 	private:
33 		E[] arr;
34 		size_t i;
35 		RandomGen* gen;
36 
37 		this(Range range, RandomGen* gen)
38 		{
39 			this.arr = range.array;
40 			this.gen = gen;
41 			prime();
42 		}
43 
44 		void prime()
45 		{
46 			import std.algorithm.mutation : swapAt;
47 			arr.swapAt(i, i + uniform(0, arr.length - i, gen));
48 		}
49 
50 	public:
51 		@property bool empty() const { return i == arr.length; }
52 		ref E front() { return arr[i]; }
53 		void popFront()
54 		{
55 			i++;
56 			if (!empty)
57 				prime();
58 		}
59 	}
60 
61 	return IncrementalRandomShuffle(move(range), &gen);
62 }
63 
64 auto incrementalRandomShuffle(Range)(Range range)
65 if (isInputRange!Range)
66 { return incrementalRandomShuffle(range, rndGen); }
67 
68 unittest
69 {
70 	auto shuffled = [1, 2].incrementalRandomShuffle;
71 	assert(shuffled.equal([1, 2]) || shuffled.equal([2, 1]));
72 }