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

Share on:  
                 

alt

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

  1. http://www.pcg-random.org/
  2. https://golang.org/pkg/math/rand/

Courtesy András Belicza(https://github.com/icza)

Tags:   GOGO LANG