在Go中生成随机布尔值

18

在Go中生成随机布尔值的最快方法是什么?

目前我是这样做的:

package main

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

// random generator
var src = rand.NewSource(time.Now().UnixNano())
var r = rand.New(src)

func main() {
    for i := 0; i < 100; i++ {
        // generate a random boolean and print it 
        fmt.Printf("bool: %s\n", r.Intn(2) != 0)
    }
}

我该如何改善这个问题?


为什么src和r是全局变量?尽量避免使用全局变量。这会成为一种习惯,然后你就会继续使用它。 - sahaj
4个回答

15

我只是个新手,但这个方案对我来说比其他提供的解决方案更有意义:

package randbool

import (
    "math/rand"
    "time"
)

/*
RandBool
    This function returns a random boolean value based on the current time
*/
func RandBool() bool {
    rand.Seed(time.Now().UnixNano())
    return rand.Intn(2) == 1
}

我对golang不太熟悉,因此请原谅我的格式。


1
不错的解决方案,但你也可以只返回 rand.Intn(2) == 1 而不需要 else :D - Kashkashio
@Kashkashio 感谢你的修改。我经常会不自觉地犯这个错误。 - Robert J
rand.Intn(2)总是返回1,因为由Intn()函数生成的数字大于0且不包括(小于)传递给函数的参数。因此,return rand.Intn(3) == 1应该是正确的返回值。 - Ardi Bello
这是错误的 rand.Intn(2) 在0和1之间交替。这很容易测试。如果您使用 rand.Intn(3),则预计会获得一个严重加权的布尔值,true ~33%的时间和false ~66%的时间。 - Robert J

14

这里可以找到生成随机bool值的示例(不一定是最快的解决方案,因为那并不是要求):

如何在Go中让函数随机返回true或false

此类算法中最慢的部分始终是获取随机数据(随机信息)。例如,rand.Int31()调用返回31位随机比特,但如果我们仅将其用于“生成”随机bool值(这是1位信息),我们会浪费30位(这可能是30个额外的随机bool值!)。

使用 rand.Source 是一个不错的选择,因为我们不需要 rand.Rand 对随机数据进行所有的 "代码功夫"。我们只需要一个随机信息源。 rand.Source 定义了一种获取随机信息的方法:
Int63() int64

这个 Source.Int63() 方法返回 63 位随机数;为了更快,我们应该使用全部。当然,生成一个单独的 bool 值只需要其中的 1 位,但是我们应该存储剩余的部分,并在后续请求随机 bool 时使用它们。
下面是实现方法:
type boolgen struct {
    src       rand.Source
    cache     int64
    remaining int
}

func (b *boolgen) Bool() bool {
    if b.remaining == 0 {
        b.cache, b.remaining = b.src.Int63(), 63
    }

    result := b.cache&0x01 == 1
    b.cache >>= 1
    b.remaining--

    return result
}

创建这样一个 boolgen 的方法如下:
func New() *boolgen {
    return &boolgen{src: rand.NewSource(time.Now().UnixNano())}
}

使用示例:

r := New()
for i := 0; i < 100; i++ {
    if i%10 == 0 {
        fmt.Println()
    }
    fmt.Print(r.Bool(), " ")
}

示例输出(在Go Playground上尝试):

false false true true false false false false false false 
false false false true false false true false true true 
false false true false true false false true true true 
false false false false false false false true true false 
true true true true false false false false true false 
true true true false true true true true true true 
true true false true true false false true false true 
true true false false false true true true true false 
true false false true true true true false false true 
true false false false false false false false true false 

一些注意事项:

rand.NewSource() 返回的 Source 对于多个 goroutine 的并发使用是不安全的,因此我们的 boolgen 也不适合在多个 goroutine 中同时使用。这一方面是好的,因为它比使用 rand 包的默认源(该源在这方面是安全的,顺便说一下,这个源是未导出的,因此只能通过 rand 包的函数间接 "访问")更快(因为不涉及同步)。

如果需要从多个 goroutine 中使用这个函数,最快的方式(跟问题精神相符)就是所有 goroutine 创建自己的 boolgen,这样就不需要同步。

如果必须使 boolgen 本身支持并发使用,则仅需要保护其 Bool() 方法以使用 sync.Mutex


3
我使用了stdlib中的math/rand包和另一个go语言伪随机数生成器github.com/MichaelTJones/pcg,对不同方法进行了速度比较。
以下是我用于计时不同变体的代码:
package main

import (
    "fmt"
    "math/rand"
    "testing"

    "github.com/MichaelTJones/pcg"
)

func BenchmarkBool(b *testing.B) {

    pcg32 := pcg.NewPCG32()

    ff := []func() bool{
        func() bool { return rand.Intn(2) == 0 },          // 1
        func() bool { return rand.Int31n(2) == 0 },        // 2
        func() bool { return rand.Int63n(2) == 0 },        // 3
        func() bool { return rand.Float32() < .5 },        // 4
        func() bool { return rand.Float64() < .5 },        // 5
        func() bool { return rand.Int31()&(1<<30) == 0 },  // 6
        func() bool { return rand.Uint32()&(1<<31) == 0 }, // 7
        func() bool { return rand.Int63()&(1<<62) == 0 },  // 8
        func() bool { return rand.Uint64()&(1<<63) == 0 }, // 9
        func() bool { return pcg32.Random()&0x01 == 0 },   // 10
    }
    for i, f := range ff {
        b.Run(fmt.Sprintf("method%v", i+1), func(b *testing.B) {
            for n := 0; n < b.N; n++ {
                _ = f()
            }
        })
    }
}

在我的电脑上,此程序的输出结果为:
BenchmarkBool/method1-4             50000000            36.8 ns/op
BenchmarkBool/method2-4             50000000            34.7 ns/op
BenchmarkBool/method3-4             50000000            31.5 ns/op
BenchmarkBool/method4-4             50000000            33.3 ns/op
BenchmarkBool/method5-4             50000000            30.1 ns/op
BenchmarkBool/method6-4             50000000            29.4 ns/op
BenchmarkBool/method7-4             50000000            31.0 ns/op
BenchmarkBool/method8-4             50000000            28.7 ns/op
BenchmarkBool/method9-4             50000000            29.5 ns/op
BenchmarkBool/method10-4            300000000            4.86 ns/op

i.e.第10种方法最快,第1种方法最慢。


@felix 我必须说,在编辑之前,我更喜欢我的答案,因为它仅比较了标准库中可用的方法。如果要添加外部包,为什么只选择那个不太常见的 github.com/MichaelTJones/pcg 而不是其他的呢? - jochen
1
很抱歉听到这个消息!我包含了pcg包,因为它是在go2中可能会使用的解决方案(有关详细信息,请参见github.com/golang/go/issues/21835),也是我在项目中最终使用的解决方案。如果您无法回滚我的更改,请随时告诉我,我会为您完成。 - felix
@felix 啊,谢谢!如果pcg即将进入标准库,那么它在这里的包含看起来就不那么随意了。 - jochen

-1

嗨,我使用这种方法,它运行良好

random_bool.go

import (
      "math/rand"
      "time"
)

const (
    Agree = 1
    Disagree = 0
)

func AreYouAgree() bool {
    rand.Seed(time.Now().UnixNano())
    ran := rand.Intn(Agree + 1)
    if ran == Agree {
        return true
    }
    return false
}

感谢阅读答案。;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接