Skip to content

Instantly share code, notes, and snippets.

@lordofscripts
Created December 1, 2023 16:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lordofscripts/85b68515638594a71bda715c8d260b17 to your computer and use it in GitHub Desktop.
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.
/* -----------------------------------------------------------------
* 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