Golang: Generate fixed size random string
There are many ways you can generate random string, In this article I will explore fastest method to generate fixed size random string
Golang: Generate fixed size random string
There are many ways you can generate random string, In this article I will explore fastest method to generate fixed size random string
Generate Random String using rand.Intn()
This is most simplest way to generate random string but not slowest (ASCII table http://www.asciitable.com/)
//RandomString - Generate a random string of A-Z chars with len = l
func RandomString(len int) string {
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(65 + rand.Intn(25)) //A=65 and Z = 65+25
}
return string(bytes)
}
Generate Random String using Runes
Simple solution with [rand.Intn]. rand is part of import “math/rand” package. It implement pseudo-random number generators.
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
Generate Random String using Bytes
We could achieve it to be a const As an extra gain, the expression len(letters) will also be a const! (The expression len(s) is constant if s is a string constant.)
strings can be indexed which indexes its bytes, perfect, exactly what we want.
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
Generate Random String using Remainder
rand.Intn() much slower compared to rand.Int63() which produces a random number with 63 random bits. So we could simply call rand.Int63() and use the remainder after dividing by len(letterBytes):
func RandStringBytesRmndr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
}
return string(b)
}
Generate Random String using Masking
Building on the previous solution, we can maintain the equal
distribution of letters by using only as many of the lowest bits of the
random number as many is required to represent the number of letters. So
for example if we have 52 letters, it requires 6 bits to represent it:
52 = 110100b
. So we will only use the lowest 6 bits of the number returned by rand.Int63()
. And to maintain equal distribution of letters, we only
"accept" the number if it falls in the range
0..len(letterBytes)-1
. If the lowest
bits are greater, we discard it and query a new random number.
Note that the chance of the lowest bits to be greater than or equal to
len(letterBytes)
is less than
0.5
in general (0.25
on average), which means that even if this would be the
case, repeating this "rare" case decreases the chance of not finding a good number. After n
repetition, the chance that we sill don't have a good index is much less than pow(0.5, n)
, and this is just an upper estimation. In case of 52 letters the chance that the 6 lowest bits are
not good is only (64-52)/64 = 0.19
; which means for example that chances to not have a good number after 10
repetition is 1e-8
.
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)
func RandStringBytesMask(n int) string {
b := make([]byte, n)
for i := 0; i < n; {
if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i++
}
}
return string(b)
}
Generate Random String using Masking with rand.
There is a crypto/rand
package which provides a Read(b []byte)
function, so
we could use that to get as many bytes with a single call as many we need. This wouldn't help in terms of performance as crypto/rand
implements a cryptographically secure pseudorandom number generator so it's much
slower.
So let’s stick to the math/rand
package. The rand.Rand
uses a rand.Source
as the source of random
bits. rand.Source
is an interface which specifies a Int63() int64
method: exactly and the only thing we needed and used in our latest solution.
So we don’t really need a rand.Rand
(either explicit or the global, shared one of the rand
{.markup–code
.markup–p-code}package), a rand.Source
is perfectly enough for us:
var src = rand.NewSource(time.Now().UnixNano())
func RandStringBytesMaskImprSrc(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
Generate Random String using Remainder Improved
This is improved version of Reminder method. This is as fast as Mask with rand.Source
func RandASCIIBytes(n int) []byte {
output := make([]byte, n)
// We will take n bytes, one byte for each character of output.
randomness := make([]byte, n)
// read all random
_, err := rand.Read(randomness)
if err != nil {
panic(err)
}
l := len(letterBytes)
// fill output
for pos := range output {
// get random item
random := uint8(randomness[pos])
// random % 64
randomPos := random % uint8(l)
// put into output
output[pos] = letterBytes[randomPos]
}
return output
}
Benchmark Result
Fastest way to generate random number is using Mask with rand.Source (BenchmarkBytesMaskImprSrc) and Reminder (BenchmarkRandASCIIBytes)
BenchmarkRandomString-4 5000000 335 ns/op
BenchmarkRunes-4 3000000 446 ns/op
BenchmarkBytes-4 5000000 346 ns/op
BenchmarkBytesRmndr-4 5000000 279 ns/op
BenchmarkBytesMask-4 5000000 333 ns/op
BenchmarkBytesMaskImpr-4 20000000 108 ns/op
BenchmarkBytesMaskImprSrc-4 20000000 86.2 ns/op
BenchmarkRandASCIIBytes-4 20000000 96.2 ns/op
Source code
https://github.com/kpbird/golang_random_string
Reading
Courtesy András Belicza(https://github.com/icza)