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

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


当前回答

非确定性地为math/rand生成器播种的最好方法是使用hash/ mapaphash (playground):

package main

import (
    "fmt"
    "hash/maphash"
    "math/rand"
)

func main() {
    r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))
    fmt.Println(r.Int())
}

与time.Now()相比,maphash保证不同的种子(即使在不同的机器上)。与crypto/rand相比,它要快得多,并且是一行程序。

其他回答

随着Go 1.20 (Q4 2022),种子随机数生成器的正确方法也可以是…什么都不做。

如果没有调用Seed,生成器将在程序启动时随机播种。

“math/rand:随机种子全局生成器”提案已被接受(2022年10月),并已开始实施:

CL 443058: math/rand:自动播种全局源代码

实现提案#54880,以自动播种全局源代码。

The justification for this not being a breaking change is that any use of the global source in a package's init function or exported API clearly must be valid - that is, if a package changes how much randomness it consumes at init time or in an exported API, that clearly isn't the kind of breaking change that requires issuing a v2 of that package. That kind of per-package change in the position of the global source is indistinguishable from seeding the global source differently. So if the per-package change is valid, so is auto-seeding. And then, of course, auto-seeding means that packages will be far less likely to depend on the specific results of the global source and therefore not break when those kinds of per-package changes happen in the future. Seed(1) can be called in programs that need the old sequence from the global source and want to restore the old behavior. Of course, those programs will still be broken by the per-package changes just described, and it would be better for them to allocate local sources rather than continue to use the global one.


在issue 20661和CL 436955中,还要注意math/rand。Read已弃用:对于几乎所有用例,crypto/rand。读更合适。

如下所示:

我们可以像这样用gosec linter和golanglint-ci 并注意G404代码: goangci -lint run——disable-all——启用gosec

非确定性地为math/rand生成器播种的最好方法是使用hash/ mapaphash (playground):

package main

import (
    "fmt"
    "hash/maphash"
    "math/rand"
)

func main() {
    r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))
    fmt.Println(r.Int())
}

与time.Now()相比,maphash保证不同的种子(即使在不同的机器上)。与crypto/rand相比,它要快得多,并且是一行程序。

只是为了子孙后代:有时使用初始字符集字符串生成随机字符串更可取。如果字符串应该由人类手动输入,这是有用的;排除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

这是纳秒,两次得到相同种子的概率是多少。 无论如何,谢谢你的帮助,这是我的最终解决方案基于所有的输入。

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))
}

每次在for循环中调用randint()方法时,都会设置一个不同的种子,并根据时间生成一个序列。但是由于循环在您的计算机中运行得很快,在很短的时间内,种子几乎是相同的,并且由于时间的原因,生成的序列与过去的序列非常相似。因此,将种子设置在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)
}