202

我正在尝试在 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)
}

我的实施非常缓慢。播种 usingtime会在一定时间内带来相同的随机数,因此循环一次又一次地迭代。如何改进我的代码?

4

10 回答 10

275

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

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

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

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

rand.Seed(time.Now().UTC().UnixNano())行从 randInt 函数移动到 main 的开头,一切都会更快。并失去了.UTC()电话,因为:

UnixNano 返回 t 作为 Unix 时间,即自 1970 年 1 月 1 日 UTC 以来经过的纳秒数。

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

package main

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

func main() {
    rand.Seed(time.Now().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)
}
于 2012-09-07T15:33:05.050 回答
98

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

这个程序不应该在 Go 操场上运行,但如果你在你的机器上运行它,你会粗略估计你可以期望的精度类型。我看到增量约为 1000000 ns,因此增量为 1 ms。那是未使用的 20 位熵。一直以来,高位大多是恒定的!?一天内大约有 24 位熵,这是非常暴力的(可能会产生漏洞)。

crypto/rand.Read这对您的重要性会有所不同,但您可以通过简单地使用作为种子的源来避免基于时钟的种子值的陷阱。它将为您提供您可能在随机数中寻找的非确定性质量(即使实际实现本身仅限于一组不同且确定性的随机序列)。

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并以非并发方式使用它来避免它。无论如何,您不应该在迭代之间重新播种随机数生成器,它从未被设计为以这种方式使用。


编辑:我曾经在 ITAM/SAM 工作,我们构建的客户端(然后)使用基于时钟的种子。在 Windows 更新后,公司机队中的许多机器几乎同时重新启动。这导致对上游服务器基础设施的恶意 DoS 攻击,因为客户端正在使用系统运行时间来播种随机性,而这些机器最终或多或少地随机选择相同的时间段进行报告。它们的目的是在一段时间内涂抹负载一个小时左右,但那没有发生。负责任地播种!

于 2019-02-02T09:38:16.503 回答
18

只是为了后代把它扔掉:有时最好使用初始字符集字符串生成随机字符串。如果字符串应该由人手动输入,这很有用;排除 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

于 2012-09-07T17:09:31.983 回答
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)
}

上面的函数是完全一样的。

我希望这些信息有用。

于 2012-10-20T11:23:27.020 回答
1

我尝试了下面的程序,每次都看到不同的字符串

package main

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

func RandomString(count int){
  rand.Seed(time.Now().UTC().UnixNano()) 
  for(count > 0 ){
    x := Random(65,91)
    fmt.Printf("%c",x)
    count--;
  }
}

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

func main() {
 RandomString(12)
}

我的控制台上的输出是

D:\james\work\gox>go run rand.go
JFBYKAPEBCRC
D:\james\work\gox>go run rand.go
VDUEBIIDFQIB
D:\james\work\gox>go run rand.go
VJYDQPVGRPXM
于 2020-08-08T02:19:20.073 回答
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))
}
于 2012-09-11T09:53:43.063 回答
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以 int 形式返回 [0,7) 中的非负伪随机数。

于 2018-12-16T01:51:51.500 回答
0

@[Denys Sé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)
}

来源

于 2020-02-04T15:19:56.310 回答
0

每次在 for 循环中调用 randint() 方法时,都会设置不同的种子,并根据时间生成一个序列。但是,由于 for 循环在您的计算机中快速运行,种子几乎是相同的,并且由于时间原因,生成的序列与过去的序列非常相似。所以在 randint() 方法之外设置种子就足够了。

package main

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

var r = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
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 {
    return min + r.Intn(max-min)
}
于 2021-02-10T09:37:48.240 回答
-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)
}
于 2019-06-27T08:09:06.047 回答