82

Situation:

I've a slice of values and need to pick up a randomly chosen value from it. Then I want to concatenate it with a fixed string. This is my code so far:

func main() {
//create the reasons slice and append reasons to it
reasons := make([]string, 0)
reasons = append(reasons,
    "Locked out",
    "Pipes broke",
    "Food poisoning",
    "Not feeling well")

message := fmt.Sprint("Gonna work from home...", pick a random reason )
}

Question:

Is there a built-in function, which can help me by doing the "pick a random reason" part?

Rene Knop
  • 1,788
  • 3
  • 15
  • 27
Amistad
  • 7,100
  • 13
  • 48
  • 75
  • 1
    Related, you might find this question and answer useful: [How to generate a random string of a fixed length in golang?](http://stackoverflow.com/a/31832326/1705598) It contains your question as a sub-task, that is, choosing random letters from a slice of runes or characters. It might give you some good tips about the "random" world of Go, what's going on in the background and efficiency of different solutions. – icza Nov 30 '15 at 09:14
  • 3
    The answer to your question is in math/rand package documentation: https://golang.org/pkg/math/rand/ - check the first code example ;) – kostya Nov 30 '15 at 10:20
  • 2
    Tips:gofmt your code – holys Nov 30 '15 at 10:45

3 Answers3

149

Use function Intn from rand package to select a random index.

import (
  "math/rand"
  "time"
)

// ...

rand.Seed(time.Now().Unix()) // initialize global pseudo random generator
message := fmt.Sprint("Gonna work from home...", reasons[rand.Intn(len(reasons))])

Other solution is to use Rand object.

s := rand.NewSource(time.Now().Unix())
r := rand.New(s) // initialize local pseudorandom generator 
r.Intn(len(reasons))
jdlm
  • 6,327
  • 5
  • 29
  • 49
Grzegorz Żur
  • 47,257
  • 14
  • 109
  • 105
  • 10
    For the record, rand.Seed... is also needed. – simonmorley Dec 24 '16 at 12:18
  • 2
    @texnicii the documentation of `rand.Intn` specifies: `Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n) from the default Source.` The parenthesis after the `n` means the range does not contain n, therefore you should use `len(reasons)` if you do not want to ignore its last element – Aisteru Firë Oct 08 '22 at 09:34
  • 1
    `rand.Seed` has been deprecated since Go 1.20 – fedterzi May 05 '23 at 20:56
27

Just pick a random integer mod slice length:

rand.Seed(time.Now().Unix())
reasons := []string{
    "Locked out",
    "Pipes broke",
    "Food poisoning",
    "Not feeling well",
}
n := rand.Int() % len(reasons)
fmt.Print("Gonna work from home...", reasons[n])

Playground: http://play.golang.org/p/fEHElLJrEZ. (Note the commend about rand.Seed.)

Ainar-G
  • 34,563
  • 13
  • 93
  • 119
  • 16
    This is not good as `rand.Intn(len(reasons))`. Just use a `%` does not have equal probability on each value of [0, len(reasons)). – endless Jul 22 '19 at 02:15
  • True! Modular Bias! – cdnbcguy May 01 '20 at 19:38
  • 1
    Take a look at the rand.Intn code - it avoids the modular bias by masking to the number of bits needed to encompass n, then repeatedly generates random values until it gets one less then <= n. – user3642765 Aug 31 '22 at 19:41
  • I like this method. But for `n := r % l` to be equally random, I think it must satisfy `R % L == 0` (where `R` is the maximum value of `r` and `L` is the maximum value of `l`). Therefore, I feel it is safe to use the `rand.Intn(len(reasons))` approach as shown in the example in the official documentation. https://pkg.go.dev/math/rand#example-package – KEINOS Dec 13 '22 at 02:35
-2

Since this still shows up in Google's top results for Golang random string generation, I wanted to share what I have been working with.

Here is the solution I am using:

package main

import (
  "fmt"
  "strings"
  "time"
)

var (
  opts  = strings.Split("option1,option2,option3", ",")
  start = time.Now()
)

func main() {
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
}

func getRandomOpt() string {
  len := len(opts)
  n := uint32(0)
  if len > 0 {
    n = getRandomUint32() % uint32(len)
  }
  return opts[n]
}

func getRandomUint32() uint32 {
  x := time.Now().UnixNano()
  return uint32((x >> 32) ^ x)
}

And results:

option2 665ns
option1 41.406µs
option1 44.817µs
option3 47.329µs
option1 49.725µs
option3 52µs
option2 54.393µs
option2 56.798µs
option1 59.098µs

Source wise, I copied getRandomUint32() from fastrand: https://github.com/valyala/fastrand

And the solution proposed above. Performance isn't all that different, but I wanted to share results.

package main

import (
  "fmt"
  "math/rand"
  "strings"
  "time"
)

var (
  opts  = strings.Split("option1,option2,option3", ",")
  start = time.Now()
)

func main() {
  rand.Seed(start.Unix())
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
  fmt.Println(getRandomOpt(), time.Since(start))
}

func getRandomOpt() string {
  return opts[rand.Intn(len(opts))]
}

And results:

option3 11.865µs
option2 48.415µs
option3 52.809µs
option1 55.536µs
option3 58.191µs
option3 60.793µs
option1 63.391µs
option2 65.982µs
option2 68.601µs

These results were only run a few times locally and I grabbed what appeared to be the median result. There is certainly more work to be done in terms of iterations and whatnot, but I just wanted to share what I have.

  • "This [calling `Seed`] was taking … to execute on its own". You only seed a random number generator **once**. As a one time start-up call the time it takes should be irrelevant. – Dave C Aug 28 '19 at 11:29
  • What do you mean? If you're not seeding a random number on each randomOpt() call, you're going to get the same result from randomOpt(). The number needs to be randomly generated on each call. My solution's results: ```{option3} 697ns {option2} 50.316µs {option2} 54.371µs {option3} 57.262µs {option2} 60.501µs {option3} 63.108µs {option3} 66.74µs {option1} 69.45µs {option1} 73.442µs``` – Tyler Tyssedal Aug 28 '19 at 14:44
  • The other solution: ```{option1} 9.982µs {option1} 62.222µs {option1} 75µs {option1} 86.793µs {option1} 98.493µs {option1} 110.601µs {option1} 122.225µs {option1} 133.76µs {option1} 145.352µs``` which was ```func randomOpt() string { rand.Seed(start.Unix()) return opts[rand.Intn(len(opts))] } ``` – Tyler Tyssedal Aug 28 '19 at 14:45
  • "If you're not seeding a random number on each randomOpt() call" ... That's not how seeding works. You seed a random number generator **once** and then you call that same generator repeatedly getting different values. – Dave C Aug 28 '19 at 15:03
  • Ah got it. Putting the seed inside the Main() and not calling seed on each request was incorrect of me to label as the cause of the slowness. Fixing the seed, that solution is still slower than the one I provided: ```{option2} 10.01µs {option1} 68.63µs {option3} 72.933µs {option3} 75.834µs {option1} 97.274µs {option3} 100.264µs {option2} 102.983µs {option3} 105.97µs {option2} 108.537µs``` – Tyler Tyssedal Aug 28 '19 at 15:57
  • I am going to update my post to reflect this, offering both full options. After start-up, the performance isn't all that different, but I want my post to reflect it accurately. – Tyler Tyssedal Aug 28 '19 at 16:10