// Package uniquerand provides a way for generating unique random numbers.
package uniquerand
import (
"math/rand"
)
// defRandSrc is the random generator used by default.
// it's a function takes an integer, r, and returns a random number in range [0, r).
var defRandSrc = rand.Intn
// defRange is the default range used for the zero value of the Int.
const defRange = 10
const blockSize = 32
const fullBlock = blockType((1 << blockSize) - 1)
type blockType = uint32
// Int allows returning unique random numbers within a predefined range.
// It depends on another source for randomness, keeps track of all generated
// numbers, and makes sure that the returned number is unique.
// The zero value produces unique numbers using math/rand in range [0, 10).
type Int struct {
r int // range
c int // count
m blockType // block num 0 (default memory).
em []blockType // block num 1+ (extra memory).
src func(r int) int
}
type Config struct {
// Range is the exclusive upper limit of the unique random number that could be
// generated, starting from 0.
// If not passed, the default range (10) is used.
Range int
// Src is the source of the random numbers.
// It's a function that takes the Range value, as r, and returns a non-negative
// pseudo-random number in the half-open interval [0,r).
// If not passed, math/rand.Intn is used.
Src func(r int) int
}
// NewInt creates a new [Int] from the provided [Config].
func NewInt(c Config) *Int {
uri := &Int{}
uri.Config(c)
return uri
}
// Config readies the random generator to be used, according to the Config provided.
// Each call discards any previous calls to either Config or Reset.
func (uri *Int) Config(c Config) {
uri.Reset(c.Range)
uri.src = c.Src
}
// Reset sets the range of the Int generator and resets all previous memory.
// If the given range is less than or equal to zero, the default range (10) is used.
// After calling Reset, the generator is ready to produce unique random numbers within
// the specified range.
// It doesn't change the randomness source.
func (uri *Int) Reset(r int) {
if r <= 0 {
r = defRange
}
// reset the default fields
uri.r = r
uri.c = 0
uri.m = 0
uri.em = nil
// return if we don't need the extra memory
if r <= blockSize {
return
}
// allocate the extra memory
l := r / blockSize
if int(r%blockSize) == 0 {
l = l - 1
}
if l != 0 {
uri.em = make([]blockType, l)
}
}
// Range returns the current range of the Int generator, which is the exclusive
// upper limit of the unique random number that could be generated, starting from 0.
// If the range has been set (via Reset or Config), Range returns it.
// If the range has not been set, or it has been set to zero or less, Range returns the
// default range (10).
//
// Example:
//
// uri := Int{}
// uri.Range() // returns 10 (default range)
// uri.Reset(100)
// uri.Range() // returns 100
func (uri *Int) Range() int {
if uri.r > 0 {
return uri.r
}
return defRange
}
// Count returns the number of used unique random numbers from the range so far.
// Unique random numbers are generated using Get, and returned using Put.
// At any time, Count() <= Range().
//
// Example:
//
// uri := Int{}
// uri.Count() // returns 0
// uri.Get() // generates a unique random number
// uri.Count() // returns 1
// uri.Put(1) // returns the unique random number
// uri.Count() // returns 0
func (uri *Int) Count() int {
return uri.c
}
// Used returns true if the unique number provided is currently consumed
// from the specified range, or false otherwise.
func (uri *Int) Used(num int) (ok bool) {
if num < 0 || num >= uri.Range() {
return false
}
// Block Number, Memory Block, Target Mask, Masked Memory
_, _, _, mm := uri.has(num)
// num is already available (not consumed by Get)
if mm == 0 {
return false
}
return true
}
func (uri *Int) has(n int) (bn int, mb, tm, mm blockType) {
// get the Block Number
bn = n / blockSize
// get the respective Memory Block
mb = uri.m
if bn > 0 {
mb = uri.em[bn-1]
}
sv := n % blockSize // Shift Value
tm = blockType(1 << sv) // Target Mask
mm = mb & tm // Masked Memory
return
}
// Get returns a unique random number within the specified range and true.
// It returns 0 and false if the specified range ran out of unique numbers.
// The range can be specified using either the Reset or the Config methods.
// If no range is specified, the default range (10) is used.
func (uri *Int) Get() (urn int, ok bool) {
randSrc := defRandSrc
if uri.src != nil {
randSrc = uri.src
}
grn := randSrc(uri.Range()) // Generated Random Number
// Block Number, Memory Block, Target Mask, Masked Memory
bn, mb, tm, mm := uri.has(grn)
// Generated Random Number was not generated before
if mm == 0 {
// update the respective Memory Block
if bn > 0 {
uri.em[bn-1] = mb | tm
} else {
uri.m = mb | tm
}
urn = grn // Unique Random Number
uri.c++ // update the counter
return urn, true
}
// Generated Random Number was generated before
return uri.getSlow()
}
// getSlow is responsible for finding a unique number based on the current
// state of the memory fields (m and em).
// the number returned depends on the history of the generated numbers so far.
func (uri *Int) getSlow() (urn int, ok bool) {
// quick check if the default memory block is already full.
if uri.m != fullBlock {
// loop over the default memory to find the first block that has a zero bit
for j := 0; j < blockSize; j++ {
tm := blockType(1 << j) // current block's Target Mask
mm := uri.m & tm // current block's Masked Memory
if mm != 0 {
continue // the current bit is not zero
}
uri.m = uri.m | tm // update the respective Memory Block
urn = j // calculate the Unique Random Number
if urn < uri.Range() {
uri.c++ // update the counter
return urn, true
}
return 0, false
}
}
// loop over the extra memory to find the first block that has a zero bit
for i, m := range uri.em {
// skip this memory block if it's already full.
if m == fullBlock {
continue
}
// if this block is all 0s, simply set it to 1 and return
if m == 0 {
uri.em[i] = 1 // update the respective Memory Block
urn = i * blockSize // calculate the Unique Random Number
urn += blockSize // add the base default memory size
uri.c++ // update the counter
return urn, true
}
// otherwise, search for the first 0 in this block
for j := 0; j < blockSize; j++ {
tm := blockType(1 << j) // current block's Target Mask
mm := m & tm // current block's Masked Memory
if mm != 0 {
continue // the current bit is not zero
}
uri.em[i] = m | tm // update the respective Memory Block
urn = i*blockSize + j // calculate the Unique Random Number
urn += blockSize // add the base default memory size
if urn < uri.Range() {
uri.c++ // update the counter
return urn, true
}
return 0, false
}
}
return 0, false
}
// Put marks the provided number as not used, allowing a previously generated number
// to be generated again later via Get.
// It returns true if the number was generated before, or false otherwise.
func (uri *Int) Put(num int) (ok bool) {
if num < 0 || num >= uri.Range() {
return false
}
// Block Number, Memory Block, Target Mask, Masked Memory
bn, mb, tm, mm := uri.has(num)
// num is already available (not consumed by Get)
if mm == 0 {
return false
}
// update the respective Memory Block
if bn > 0 {
uri.em[bn-1] = mb &^ tm
} else {
uri.m = mb &^ tm
}
uri.c-- // update the counter
return true
}