|
| 1 | +// This is just a temporary fork of https://github.com/josharian/vitter (ISC License, https://github.com/josharian/vitter/blob/main/LICENSE) |
| 2 | +// |
| 3 | +// This file will be removed once https://github.com/josharian/vitter/issues/1 is resolved. |
| 4 | + |
| 5 | +package collections |
| 6 | + |
| 7 | +import ( |
| 8 | + "math" |
| 9 | + "math/rand/v2" |
| 10 | +) |
| 11 | + |
| 12 | +// https://getkerf.wordpress.com/2016/03/30/the-best-algorithm-no-one-knows-about/ |
| 13 | + |
| 14 | +// Copyright Kevin Lawler, released under ISC License |
| 15 | + |
| 16 | +// _d generates an in-order uniform random sample of size 'want' from the range [0, max) using the provided PRNG. |
| 17 | +// |
| 18 | +// Parameters: |
| 19 | +// - prng: random number generator |
| 20 | +// - want: number of samples to select |
| 21 | +// - max: upper bound of the range [0, max) from which to sample |
| 22 | +// - choose: callback function invoked with each selected index in ascending order |
| 23 | +// |
| 24 | +// If the parameters are invalid (want < 0 or want > max), no samples are selected. |
| 25 | +// |
| 26 | +// Vitter, J.S. - An Efficient Algorithm for Sequential Random Sampling - ACM Trans. Math. Software 11 (1985), 37-57. |
| 27 | +func _d(prng *rand.Rand, want, max int, choose func(n int)) { |
| 28 | + if want <= 0 || want > max { |
| 29 | + return |
| 30 | + } |
| 31 | + // POTENTIAL_OPTIMIZATION_POINT: Christian Neukirchen points out we can replace exp(log(x)*y) by pow(x,y) |
| 32 | + // POTENTIAL_OPTIMIZATION_POINT: Vitter paper points out an exponentially distributed random var can provide speed ups |
| 33 | + // 'a' is space allocated for the hand |
| 34 | + // 'n' is the size of the hand |
| 35 | + // 'N' is the upper bound on the random card values |
| 36 | + j := -1 |
| 37 | + qu1 := -want + 1 + max |
| 38 | + const negalphainv = -13 // threshold parameter from Vitter's paper for algorithm selection |
| 39 | + threshold := -negalphainv * want |
| 40 | + |
| 41 | + wantf := float64(want) |
| 42 | + maxf := float64(max) |
| 43 | + ninv := 1.0 / wantf |
| 44 | + var nmin1inv float64 |
| 45 | + Vprime := math.Exp(math.Log(prng.Float64()) * ninv) |
| 46 | + |
| 47 | + qu1real := -wantf + 1.0 + maxf |
| 48 | + var U, X, y1, y2, top, bottom, negSreal float64 |
| 49 | + |
| 50 | + for want > 1 && threshold < max { |
| 51 | + var S int |
| 52 | + |
| 53 | + nmin1inv = 1.0 / (-1.0 + wantf) |
| 54 | + |
| 55 | + for { |
| 56 | + for { |
| 57 | + X = maxf * (-Vprime + 1.0) |
| 58 | + S = int(math.Floor(X)) |
| 59 | + |
| 60 | + if S < qu1 { |
| 61 | + break |
| 62 | + } |
| 63 | + |
| 64 | + Vprime = math.Exp(math.Log(prng.Float64()) * ninv) |
| 65 | + } |
| 66 | + |
| 67 | + U = prng.Float64() |
| 68 | + negSreal = float64(-S) |
| 69 | + y1 = math.Exp(math.Log(U*maxf/qu1real) * nmin1inv) |
| 70 | + Vprime = y1 * (-X/maxf + 1.0) * (qu1real / (negSreal + qu1real)) |
| 71 | + |
| 72 | + if Vprime <= 1.0 { |
| 73 | + break |
| 74 | + } |
| 75 | + |
| 76 | + y2 = 1.0 |
| 77 | + top = -1.0 + maxf |
| 78 | + var limit int |
| 79 | + |
| 80 | + if -1+want > S { |
| 81 | + bottom = -wantf + maxf |
| 82 | + limit = -S + max |
| 83 | + } else { |
| 84 | + bottom = -1.0 + negSreal + maxf |
| 85 | + limit = qu1 |
| 86 | + } |
| 87 | + |
| 88 | + for t := max - 1; t >= limit; t-- { |
| 89 | + y2 = (y2 * top) / bottom |
| 90 | + top-- |
| 91 | + bottom-- |
| 92 | + } |
| 93 | + |
| 94 | + if maxf/(-X+maxf) >= y1*math.Exp(math.Log(y2)*nmin1inv) { |
| 95 | + Vprime = math.Exp(math.Log(prng.Float64()) * nmin1inv) |
| 96 | + break |
| 97 | + } |
| 98 | + |
| 99 | + Vprime = math.Exp(math.Log(prng.Float64()) * ninv) |
| 100 | + } |
| 101 | + |
| 102 | + j += S + 1 |
| 103 | + |
| 104 | + choose(j) |
| 105 | + |
| 106 | + max = -S + (-1 + max) |
| 107 | + maxf = negSreal + (-1.0 + maxf) |
| 108 | + want-- |
| 109 | + wantf-- |
| 110 | + ninv = nmin1inv |
| 111 | + |
| 112 | + qu1 = -S + qu1 |
| 113 | + qu1real = negSreal + qu1real |
| 114 | + |
| 115 | + threshold += negalphainv |
| 116 | + } |
| 117 | + |
| 118 | + if want > 1 { |
| 119 | + methodA(prng, want, max, j, choose) // if i>0 then n has been decremented |
| 120 | + } else { |
| 121 | + S := int(math.Floor(float64(max) * Vprime)) |
| 122 | + |
| 123 | + j += S + 1 |
| 124 | + |
| 125 | + choose(j) |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +// methodA is the simpler fallback algorithm used when Algorithm D's optimizations are not beneficial. |
| 130 | +func methodA(prng *rand.Rand, want, max int, j int, choose func(n int)) { |
| 131 | + for want >= 2 { |
| 132 | + j++ |
| 133 | + V := prng.Float64() |
| 134 | + quot := float64(max-want) / float64(max) |
| 135 | + for quot > V { |
| 136 | + j++ |
| 137 | + max-- |
| 138 | + quot *= float64(max - want) |
| 139 | + quot /= float64(max) |
| 140 | + } |
| 141 | + choose(j) |
| 142 | + max-- |
| 143 | + want-- |
| 144 | + } |
| 145 | + |
| 146 | + S := int(math.Floor(float64(max) * prng.Float64())) |
| 147 | + j += S + 1 |
| 148 | + choose(j) |
| 149 | +} |
0 commit comments