Created
December 1, 2023 16:12
-
-
Save lordofscripts/85b68515638594a71bda715c8d260b17 to your computer and use it in GitHub Desktop.
Generates a list of unique random numbers but gives the freedom to use a reproduceable list (using math/rand) or a truly random list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ----------------------------------------------------------------- | |
* L o r d O f S c r i p t s (tm) | |
* Copyright (C)2023 Lord of Scripts | |
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
* Creates Unique Random Numbers | |
*-----------------------------------------------------------------*/ | |
import ( | |
crand "crypto/rand" // Not reproduceable. For sensitive apps | |
"fmt" | |
"math/big" | |
mrand "math/rand" // Can be reproduced. For simulation or reproduceability | |
) | |
const ( | |
TRY_LIMIT = 50 | |
) | |
/* ---------------------------------------------------------------- | |
* T y p e s | |
*-----------------------------------------------------------------*/ | |
type IUniqueRandNumber interface { | |
Next() int | |
Clear() // clear uniqueness tracker | |
Reset() // clear tracker & reseed (non-secure generator) | |
IsSecure() bool // whether using math.rand or crypto.rand | |
String() string | |
} | |
type UniqueRandNumber struct { | |
lower int | |
upper int | |
used map[int]bool | |
seed int64 | |
nonsecure *mrand.Rand // only when reproduceability is desired! | |
} | |
/* ---------------------------------------------------------------- | |
* C o n s t r u c t o r s | |
*-----------------------------------------------------------------*/ | |
// (ctor) unique random number [0..limit] | |
func newUniqueRandomNumber(limit int, seed int64) *UniqueRandNumber { | |
return newUniqueRandomNumberRange(0, limit, seed) | |
} | |
// (ctor) unique random number in the chosen range [from..limit] | |
func newUniqueRandomNumberRange(from, limit int, seed int64) *UniqueRandNumber { | |
xrnd := &UniqueRandNumber{lower: from, upper: limit, used: make(map[int]bool)} | |
if seed > -1 { | |
xrnd.nonsecure = recoverySeed(seed) | |
xrnd.seed = seed | |
} else { | |
xrnd.nonsecure = nil | |
} | |
return xrnd | |
} | |
/* ---------------------------------------------------------------- | |
* M e t h o d s | |
*-----------------------------------------------------------------*/ | |
func (r *UniqueRandNumber) String() string { | |
return fmt.Sprintf("[%d,%d] secure:%t", r.lower, r.upper, r.IsSecure()) | |
} | |
func (r *UniqueRandNumber) Next() int { | |
// generate next random nr. selecting either the secure or non-secure form | |
// range [lower,limit) i.e. lower included, limit excluded | |
var randomize = func(lower, limit int) int { | |
var number int = 0 | |
if r.IsSecure() { | |
nBig, err := crand.Int(crand.Reader, big.NewInt(int64(limit-lower))) | |
if err != nil { | |
panic(err) | |
} | |
number = int(nBig.Int64()) | |
} else { | |
number = r.nonsecure.Intn(limit - lower) | |
} | |
return number + lower | |
} | |
try := 0 | |
for { | |
num := randomize(r.lower, r.upper+1) | |
if !r.used[num] { | |
r.used[num] = true | |
return num | |
} else { | |
try += 1 | |
if try == TRY_LIMIT { | |
err := fmt.Errorf("Having trouble finding unique number :( %s done %d", r, len(r.used)) | |
println(err.Error()) | |
return -1 | |
} | |
} | |
} | |
} | |
func (r *UniqueRandNumber) IsSecure() bool { | |
return r.nonsecure == nil | |
} | |
// clears the list of outputed numbers so that they can be repeated. | |
// NOTE. For non-secure versions this does NOT reseed the random generator! | |
func (r *UniqueRandNumber) Clear() { | |
clear(r.generated) | |
} | |
// Same as Clear() but in the case of non-secure (seeded or math/rand) instances, | |
// the seed is planted again so that the series can be repeated. For secure | |
// instances (not-seeded or crypto/rand) this behaves exactly as Clear() | |
func (r *UniqueRandNumber) Reset() { | |
clear(r.generated) | |
if r.nonsecure != nil { | |
r.nonsecure.Seed(r.seed) | |
} | |
} | |
/* ---------------------------------------------------------------- | |
* F u n c t i o n s | |
*-----------------------------------------------------------------*/ | |
// As of Go 1.20 math/rand automatically seeds the random generator; therefore, | |
// rand.Seed() is deprecated. Programs which require a series that is | |
// reproduceable should use their own source like this. | |
func recoverySeed(seed int64) *mrand.Rand { | |
var random *mrand.Rand | |
source := mrand.NewSource(seed) | |
random = mrand.New(source) | |
return random | |
} | |
func main() { | |
var rndM, rndC IUniqueRandNumber | |
rndM = newUniqueRandomNumberRange(1, 10, 57892498812552) | |
rndC = newUniqueRandomNumberRange(1, 10, -1) | |
var vM, vC []int | |
for i := 1; i <= 10; i++ { | |
vM = append(vM, rndM.Next()) | |
vC = append(vC, rndC.Next()) | |
} | |
println(rndM.String()) | |
fmt.Printf("%v\n", vM) | |
println(rndC.String()) | |
fmt.Printf("%v\n", vC) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment