1 /// Functions for "luck", or influenced results 2 module unexpected.influence; 3 4 import std.range : ElementType, isInfinite; 5 6 enum hasMinMax(T) = is(typeof(T.min) == typeof(T.max)) && is(typeof(T.min) == T); 7 8 auto influencedChoice(Range, Rand, Element = ElementType!Range)(double luck, ref Rand rng, Range range) if (hasMinMax!(ElementType!Range)) { 9 return influencedChoiceCommon!true(luck, rng, range, ElementType!Range.min, ElementType!Range.max); 10 } 11 auto influencedChoice(Range, Rand, Element = ElementType!Range)(double luck, ref Rand rng, Range range, Element worst, Element best) { 12 return influencedChoiceCommon!true(luck, rng, range, worst, best); 13 } 14 auto influencedChoice(Range, Rand, Element = ElementType!Range)(double luck, ref Rand rng, Range range) if (!isInfinite!Range && !hasMinMax!(ElementType!Range)) { 15 return influencedChoiceCommon!false(luck, rng, range, Element.init, Element.init); 16 } 17 auto influencedChoiceCommon(bool useWorstBest = true, Range, Rand, Element = ElementType!Range)(double luck, ref Rand rng, Range range, Element worst, Element best) 18 in(!range.empty, "Nothing to choose") 19 in(!useWorstBest || (best >= worst), "Worst value must be less than best") 20 out(result; !useWorstBest || (result >= worst), "Result is less than worst value") 21 out(result; !useWorstBest || (result <= best), "Result is greater than best value") 22 { 23 import std.algorithm.comparison : min; 24 import std.math : isInfinity; 25 import std.random : uniform01; 26 static if (useWorstBest) { 27 if (luck.isInfinity) { 28 if (luck > 0) { 29 return best; 30 } else { 31 return worst; 32 } 33 } 34 } 35 auto chosen = range.front; 36 range.popFront(); 37 bool negative = luck < 0.0; 38 if (luck < 0.0) { 39 luck = -luck; 40 } 41 while ((luck > 0.0) && !range.empty) { 42 static if (useWorstBest) { 43 if (chosen > best) { 44 return best; 45 } else if (chosen < worst) { 46 return worst; 47 } 48 } 49 const remaining = min(luck, 1.0); 50 luck -= remaining; 51 if (remaining >= uniform01(rng)) { 52 auto next = range.front; 53 range.popFront(); 54 if (negative ^ (next > chosen)) { 55 chosen = next; 56 } 57 } 58 } 59 return chosen; 60 } 61 62 @safe pure unittest { 63 import std.random : Random, uniform; 64 import std.range : generate; 65 static struct Result { 66 enum min = Result(0); 67 enum max = Result(100); 68 int x; 69 int opCmp(Result b) const pure @safe { 70 return x - b.x; 71 } 72 } 73 Random rand; 74 enum iterations = 100; 75 double test(double luck) { 76 long total; 77 foreach (i; 0 .. iterations) { 78 total += influencedChoice(luck, rand, generate!(() => Result(uniform(0, 100, rand)))).x; 79 } 80 return total / double(iterations); 81 } 82 assert(test(0.0) < test(1.0)); 83 assert(test(0.0) > test(-1.0)); 84 assert(test(double.infinity) == 100); 85 assert(test(-double.infinity) == 0); 86 87 double test2(double luck, int min = int.min, int max = int.max) { 88 long total; 89 foreach (i; 0 .. iterations) { 90 const value = influencedChoice(luck, rand, generate!(() => uniform(0, 100, rand)), min, max); 91 assert(value >= min); 92 assert(value <= max); 93 total += value; 94 } 95 return total / double(iterations); 96 } 97 assert(test2(0.0) < test2(1.0)); 98 assert(test2(0.0) > test2(-1.0)); 99 assert(test2(double.infinity) == int.max); 100 assert(test2(-double.infinity) == int.min); 101 assert(test2(double.infinity, 0, 100) == 100); 102 assert(test2(-double.infinity, 0, 100) == 0); 103 assert(test2(0.0, 0, 100) > test2(-1.0, 0, 100)); 104 } 105 106 @safe pure unittest { 107 import std.random : Random, uniform; 108 import std.range : generate, take; 109 static struct Result { 110 int x; 111 int opCmp(Result b) const pure @safe { 112 return x - b.x; 113 } 114 } 115 Random rand; 116 enum iterations = 100; 117 double test(double luck, int min = int.min, int max = int.max) { 118 long total; 119 foreach (i; 0 .. iterations) { 120 const value = influencedChoice(luck, rand, generate!(() => Result(uniform(0, 100, rand))).take(100)); 121 assert(value.x >= min); 122 assert(value.x <= max); 123 total += value.x; 124 } 125 return total / double(iterations); 126 } 127 assert(test(0.0) < test(1.0)); 128 assert(test(0.0) > test(-1.0)); 129 assert(test(double.infinity) > test(-double.infinity)); 130 assert(test(double.infinity, 0, 100) > test(-double.infinity, 0, 100)); 131 assert(test(0.0, 0, 100) > test(-1.0, 0, 100)); 132 } 133 134 auto influencedWeightedChoice(Range, Weights, Rand, Element = ElementType!Range)(double luck, ref Rand rng, Range range, Weights weights) if (hasMinMax!(ElementType!Range)) { 135 return influencedWeightedChoice(luck, rng, range, weights, Element.min, Element.max); 136 } 137 auto influencedWeightedChoice(Range, Weights, Rand, Element = ElementType!Range)(double luck, ref Rand rng, Range range, Weights weights) if (!hasMinMax!(ElementType!Range)) { 138 import std.random : dice; 139 import std.range : drop, front, generate; 140 return influencedChoiceCommon!false(luck, rng, generate!({ return range.drop(dice(rng, weights)).front; }), range.front, range.front); 141 } 142 auto influencedWeightedChoice(Range, Weights, Rand, Element = ElementType!Range)(double luck, ref Rand rng, Range range, Weights weights, Element worst, Element best) { 143 import std.random : dice; 144 import std.range : drop, front, generate; 145 return influencedChoiceCommon!true(luck, rng, generate!({ return range.drop(dice(rng, weights)).front; }), worst, best); 146 } 147 148 @safe pure unittest { 149 import std.random : Random; 150 import std.range : iota; 151 Random rand; 152 enum iterations = 100; 153 enum vals = [100, 50, 33, 0, 25]; 154 double test2(double luck, int min, int max) { 155 long total; 156 foreach (i; 0 .. iterations) { 157 const value = influencedWeightedChoice(luck, rand, vals, iota(5), min, max); 158 assert(value >= min); 159 assert(value <= max); 160 total += value; 161 } 162 return total / double(iterations); 163 } 164 assert(test2(0.0, 0, 100) < test2(1.0, 0, 100)); 165 assert(test2(0.0, 0, 100) > test2(-1.0, 0, 100)); 166 assert(test2(double.infinity, 0, 100) == 100); 167 assert(test2(-double.infinity, 0, 100) == 0); 168 assert(test2(0.0, 0, 100) > test2(-1.0, 0, 100)); 169 } 170 171 auto influencedUniform(Rand, T)(double luck, ref Rand rng, T worst, T best) { 172 import std.random : uniform; 173 static auto uniformRange(Rand rng, T worst, T best) { 174 struct Result { 175 T front; 176 enum bool empty = false; 177 void popFront() { 178 front = uniform(worst, best, rng); 179 } 180 } 181 Result result; 182 result.popFront(); 183 return result; 184 } 185 return influencedChoiceCommon!true(luck, rng, uniformRange(rng, worst, best), worst, best); 186 } 187 188 @safe pure unittest { 189 import std.random : Random; 190 Random rand; 191 enum iterations = 100; 192 double test(double luck, int min, int max) { 193 long total; 194 foreach (i; 0 .. iterations) { 195 const value = influencedUniform(luck, rand, min, max); 196 assert(value >= min); 197 assert(value <= max); 198 total += value; 199 } 200 return total / double(iterations); 201 } 202 assert(test(0.0, 0, 100) < test(1.0, 0, 100)); 203 assert(test(0.0, 0, 100) > test(-1.0, 0, 100)); 204 assert(test(double.infinity, 0, 100) == 100); 205 assert(test(-double.infinity, 0, 100) == 0); 206 assert(test(0.0, 0, 100) > test(-1.0, 0, 100)); 207 }