如何正确播种随机数生成器


160

我正在尝试在Go中生成一个随机字符串,这是我到目前为止编写的代码:

package main

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

func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}

我的执行速度很慢。使用进行播种time会在一定时间内带来相同的随机数,因此循环会一次又一次地迭代。如何改善我的代码?


2
“ if string(randInt(65,90))!= temp {”看起来像是您试图增加额外的安全性,但是,事情之间偶然发生了同样的事情。通过这样做,您实际上可能在降低熵。
yaccz 2013年

2
附带说明,无需在“ time.Now()。UTC()。UnixNano()”中转换为UTC。Unix时间是从Epoch开始计算的,Epoch始终是UTC。
2015年

2
您应该将种子设置一次,只能设置一次,并且绝不能超过一次。好吧,如果您的应用程序可以运行数天,则可以每天设置一次。
卡斯珀拉

您应该播种一次。我想“ Z”可能永远不会出现,我猜呢?因此,我更喜欢使用开始索引包含和结束索引排除。
Jaehyun Yeom,

Answers:


232

每次设置相同的种子,您将获得相同的序列。因此,当然,如果您将种子设置为快速循环中的时间,则可能会多次调用相同的种子。

在您的情况下,当您调用randInt函数直到拥有不同的值时,您正在等待时间(由Nano返回)更改。

对于所有伪随机库,您仅需设置一次种子,例如在初始化程序时,除非您特别需要再现给定的序列(通常仅用于调试和单元测试)。

之后,您只需调用Intn即可获取下一个随机整数。

rand.Seed(time.Now().UTC().UnixNano())行从randInt函数移至main的开头,一切将会更快。

另请注意,我认为您可以简化字符串的构建:

package main

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

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    bytes := make([]byte, l)
    for i := 0; i < l; i++ {
        bytes[i] = byte(randInt(65, 90))
    }
    return string(bytes)
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}

感谢您的解释,我认为这需要每次播种。
CopperMan 2012年

13
您也可以添加rand.Seed(...)到功能init()init()在之前自动被调用main()。请注意,您不需要调用init()main()
贾巴2014年

2
@Jabba对。我的回答尽可能简单,离问题不太远,但是您的观察是正确的。
DenysSéguret2014年

7
请注意,到目前为止,所有发布的答案都没有以加密安全的方式初始化种子。根据您的应用程序,这可能根本不重要,或者可能导致灾难性故障。
Ingo Blechschmidt's

3
无论如何,@ IngoBlechschmidt都不math/rand是加密安全的。如果有此要求,crypto/rand则应使用。
邓肯·琼斯,

39

我不明白为什么人们在播种时间价值。以我的经验,这从来不是一个好主意。例如,虽然系统时钟可能以纳秒为单位表示,但系统的时钟精度不是纳秒。

该程序不应在Go操场上运行,但如果在计算机上运行它,则可以粗略估算出可以期望的精度类型。我看到大约1000000 ns的增量,所以增加了1 ms。那是20位未使用的熵。一直以来,高位大多是恒定的。

对您而言重要的程度会有所不同,但您只需使用种子的crypto/rand.Readas来源,就可以避免基于时钟的种子值的陷阱。它将为您提供您可能会在随机数中寻找的不确定性质量(即使实际实现本身仅限于一组不同且确定性的随机序列)。

import (
    crypto_rand "crypto/rand"
    "encoding/binary"
    math_rand "math/rand"
)

func init() {
    var b [8]byte
    _, err := crypto_rand.Read(b[:])
    if err != nil {
        panic("cannot seed math/rand package with cryptographically secure random number generator")
    }
    math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

作为附带说明,但与您的问题有关。您可以rand.Source使用此方法创建自己的方法,以避免使用锁来保护源代码的开销。所述rand包效用函数是便利的,但它们也使用锁罩下以防止源被同时使用。如果不需要,可以通过创建自己的方法Source并以非并行方式使用它来避免这种情况。无论如何,您不应该在两次迭代之间重新使用随机数生成器,它永远不会被设计为以这种方式使用。


5
这个答案很不为人知。特别是对于可能在一秒钟内运行多次的命令行工具,这是必须做的。谢谢
saeedgnu

1
您可以根据需要混合使用PID和主机名/ MAC,但是请注意,为RNG注入加密安全的源并不会使其具有加密安全性,因为有人可以重建PRNG内部状态。
尼克·T

PID并不是真正随机的。可以克隆MAC。您将如何以不引入不希望的偏斜/偏斜的方式混合它们?
约翰·莱德格伦

16

只是为了后代而已:有时最好使用初始字符集字符串来生成随机字符串。如果该字符串应该由人工手动输入,则很有用;排除0,O,1和l可以帮助减少用户错误。

var alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"

// generates a random string of fixed size
func srand(size int) string {
    buf := make([]byte, size)
    for i := 0; i < size; i++ {
        buf[i] = alpha[rand.Intn(len(alpha))]
    }
    return string(buf)
}

我通常将种子设置在一个init()块内。它们记录在这里:http : //golang.org/doc/effective_go.html#init


9
据我正确理解,没有必要使用-1in rand.Intn(len(alpha)-1)。这是因为rand.Intn(n)始终返回小于的数字n(换句话说:从零到n-1包含在内)。
快照

2
@snap是正确的;实际上,包括-1in len(alpha)-1可以确保序列中从不使用数字9。
carbocation 2015年

2
还应注意,排除0(零)是个好主意,因为您正在将字节片转换为字符串,这会导致0变为空字节。例如,尝试创建一个中间有一个“ 0”字节的文件,看看会发生什么。
埃里克·拉格伦2015年

14

好吧,为什么这么复杂!

package main

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

func main() {
    rand.Seed( time.Now().UnixNano())
    var bytes int

    for i:= 0 ; i < 10 ; i++{ 
        bytes = rand.Intn(6)+1
        fmt.Println(bytes)
        }
    //fmt.Println(time.Now().UnixNano())
}

这是基于dystroy的代码,但符合我的需要。

六岁了(兰特整数1 =< i =< 6

func randomInt (min int , max int  ) int {
    var bytes int
    bytes = min + rand.Intn(max)
    return int(bytes)
}

上面的功能是完全一样的。

我希望这些信息有用。


这将始终以相同的顺序(如果多次调用)以相同的顺序返回,这在我看来并不是很随机。检查现场示例:play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis '16

8
@ThomasModeneis:那是因为他们在操场上假时间
ofavre

1
谢谢@ofavre,那个假时间真的让我很头疼。
杰西·奇斯霍尔姆

1
在调用之前rand.Intn(),您仍然需要播种,否则,每次运行程序时,您总是会得到相同的数字。
Flavio Copes

var bytes int什么理由吗?有什么区别,以改变上述bytes = rand.Intn(6)+1bytes := rand.Intn(6)+1?他们俩似乎都为我工作,出于某种原因,其中之一不是最理想的吗?
pzkpfw

0

这是十亿分之一秒,两次获得相同种子的机会是多少。
无论如何,感谢您的帮助,这是我基于所有输入的最终解决方案。

package main

import (
    "math/rand"
    "time"
)

func init() {
    rand.Seed(time.Now().UTC().UnixNano())
}

// generates a random string
func srand(min, max int, readable bool) string {

    var length int
    var char string

    if min < max {
        length = min + rand.Intn(max-min)
    } else {
        length = min
    }

    if readable == false {
        char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    } else {
        char = "ABCDEFHJLMNQRTUVWXYZabcefghijkmnopqrtuvwxyz23479"
    }

    buf := make([]byte, length)
    for i := 0; i < length; i++ {
        buf[i] = char[rand.Intn(len(char)-1)]
    }
    return string(buf)
}

// For testing only
func main() {
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, false))
    println(srand(5, 7, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 4, true))
    println(srand(5, 400, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
}

1
回复:非常好what are the chances of getting the exact the exact same [nanosecond] twice?。这一切都依赖于内部的精度实现了golang运行时间的。即使单位是纳秒,最小的增量也可能是毫秒或什至秒。
杰西·奇斯霍尔姆

0

如果您的目的只是生成一个随机数的字符串,那么我认为没有必要将它与多个函数调用复杂化或每次都重置种子。

最重要的步骤是在实际运行之前调用一次种子函数rand.Init(x)Seed使用提供的种子值将默认Source初始化为确定性状态。因此,建议在实际函数调用伪随机数生成器之前先调用它一次。

这是创建随机数字符串的示例代码

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



func main(){
    rand.Seed(time.Now().UnixNano())

    var s string
    for i:=0;i<10;i++{
    s+=fmt.Sprintf("%d ",rand.Intn(7))
    }
    fmt.Printf(s)
}

我之所以使用Sprintf是因为它允许简单的字符串格式化。

另外,In rand.Intn(7) Intn将[0,7)中的非负伪随机数作为int返回。


0

@ [DenysSéguret]已正确发布。但是就我而言,我每次都需要新的种子,因此下面的代码;

如果您需要快速功能。我这样使用。


func RandInt(min, max int) int {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return r.Intn(max-min) + min
}

func RandFloat(min, max float64) float64 {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return min + r.Float64()*(max-min)
}

资源


-2

由于golang api的更改而导致的小更新,请省略.UTC():

现在的时间()。UTC(). UnixNano()-> time.Now()。UnixNano()

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

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(randomInt(100, 1000))
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.