Làm thế nào để gieo hạt giống ngẫu nhiên


160

Tôi đang cố gắng tạo một chuỗi ngẫu nhiên trong Go và đây là mã tôi đã viết cho đến nay:

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

Việc thực hiện của tôi rất chậm. Việc sử dụng seeding timemang lại cùng một số ngẫu nhiên trong một thời gian nhất định, do đó vòng lặp lặp đi lặp lại. Làm thế nào tôi có thể cải thiện mã của tôi?


2
"Nếu chuỗi (randInt (65,90))! = Temp {" có vẻ như bạn đang cố gắng thêm bảo mật bổ sung, nhưng này, mọi thứ sẽ giống nhau một cách tình cờ. Bằng cách này, bạn có thể thực sự hạ thấp entropy.
yaccz

2
Là một lưu ý phụ, không cần phải chuyển đổi sang UTC trong "time.Now (). UTC (). UnixNano ()". Thời gian Unix được tính từ Epoch dù sao cũng là UTC.
Grzegorz Luczywo

2
Bạn nên đặt hạt giống một lần, chỉ một lần và không bao giờ nhiều hơn một lần. tốt, trong trường hợp ứng dụng của bạn chạy trong nhiều ngày, bạn có thể thiết lập nó một lần một ngày.
Casperah

Bạn nên gieo hạt một lần. Và tôi nghĩ "Z" có thể không bao giờ xuất hiện, tôi đoán vậy? Vì vậy, tôi thích sử dụng chỉ số bắt đầu bao gồm và chỉ số kết thúc độc quyền.
Jaehyun Yeom

Câu trả lời:


232

Mỗi lần bạn đặt cùng một hạt giống, bạn sẽ nhận được cùng một chuỗi. Vì vậy, tất nhiên nếu bạn đang đặt hạt giống theo thời gian trong một vòng lặp nhanh, có thể bạn sẽ gọi nó với cùng một hạt giống nhiều lần.

Trong trường hợp của bạn, khi bạn gọi randInthàm của mình cho đến khi bạn có một giá trị khác, bạn đang chờ thời gian (được trả về bởi Nano) để thay đổi.

Đối với tất cả các thư viện giả ngẫu nhiên , bạn chỉ phải đặt hạt giống một lần, ví dụ như khi khởi tạo chương trình của bạn trừ khi bạn đặc biệt cần sao chép một chuỗi đã cho (thường chỉ được thực hiện để gỡ lỗi và kiểm tra đơn vị).

Sau đó, bạn chỉ cần gọi Intnđể có được số nguyên ngẫu nhiên tiếp theo.

Di chuyển rand.Seed(time.Now().UTC().UnixNano())dòng từ chức năng randInt đến đầu của chính và mọi thứ sẽ nhanh hơn.

Cũng lưu ý rằng tôi nghĩ bạn có thể đơn giản hóa việc xây dựng chuỗi của mình:

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

Cảm ơn vì đã giải thích điều đó, tôi nghĩ rằng điều này cần phải được gieo hạt mỗi lần.
CopperMan

13
Bạn cũng có thể thêm rand.Seed(...)vào chức năng init(). init()được gọi tự động trước main(). Lưu ý rằng bạn không cần phải gọi init()từ main()!
Jabba

2
@Jabba Phải. Tôi đã giữ câu trả lời của mình đơn giản nhất có thể và không quá xa câu hỏi, nhưng quan sát của bạn là đúng.
Denys Séguret

7
Xin lưu ý rằng không ai trong số các anwers được đăng cho đến nay khởi tạo hạt giống theo cách bảo mật bằng mật mã. Tùy thuộc vào ứng dụng của bạn, điều này có thể không quan trọng hoặc nó có thể dẫn đến thất bại thảm hại.
Ingo Blechschmidt

3
@IngoBlechschmidt math/randdù sao cũng không an toàn về mặt mật mã. Nếu đó là một yêu cầu, crypto/randnên được sử dụng.
Duncan Jones

39

Tôi không hiểu tại sao mọi người lại gieo hạt giống với giá trị thời gian. Điều này có trong kinh nghiệm của tôi không bao giờ là một ý tưởng tốt. Ví dụ, trong khi đồng hồ hệ thống có thể được biểu thị bằng nano giây, độ chính xác của đồng hồ hệ thống không phải là nano giây.

Chương trình này không nên chạy trên sân chơi Go nhưng nếu bạn chạy nó trên máy của mình, bạn sẽ có được ước tính sơ bộ về loại độ chính xác mà bạn có thể mong đợi. Tôi thấy số gia tăng khoảng 1000000 ns, nên tăng 1 ms. Đó là 20 bit của entropy không được sử dụng. Tất cả trong khi các bit cao chủ yếu là không đổi.

Mức độ mà điều này quan trọng với bạn sẽ khác nhau nhưng bạn có thể tránh những cạm bẫy của các giá trị hạt giống dựa trên đồng hồ chỉ bằng cách sử dụng crypto/rand.Readnguồn làm hạt giống của bạn. Nó sẽ cung cấp cho bạn chất lượng không xác định mà bạn có thể đang tìm kiếm trong các số ngẫu nhiên của mình (ngay cả khi bản thân việc triển khai thực tế bị giới hạn trong một tập hợp các chuỗi ngẫu nhiên khác biệt và xác định).

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[:])))
}

Là một lưu ý phụ nhưng liên quan đến câu hỏi của bạn. Bạn có thể tự tạo rand.Sourcebằng phương pháp này để tránh chi phí có khóa bảo vệ nguồn. Các randchức năng tiện ích gói là thuận tiện nhưng họ cũng sử dụng khóa dưới mui xe để ngăn chặn nguồn được sử dụng đồng thời. Nếu bạn không cần điều đó, bạn có thể tránh nó bằng cách tạo riêng của bạn Sourcevà sử dụng nó theo cách không đồng thời. Bất kể, bạn KHÔNG nên định hướng lại trình tạo số ngẫu nhiên giữa các lần lặp, nó không bao giờ được thiết kế để được sử dụng theo cách đó.


5
Câu trả lời này rất ít được đánh giá cao. Đặc biệt đối với các công cụ dòng lệnh có thể chạy nhiều lần trong một giây, đây là việc phải làm. Cảm ơn bạn
saeedgnu

1
Bạn có thể kết hợp trong PID và tên máy chủ / MAC nếu cần, nhưng hãy cẩn thận khi gieo RNG với nguồn an toàn về mặt mật mã sẽ không làm cho nó an toàn về mặt mật mã vì ai đó có thể tái tạo trạng thái bên trong PRNG.
Nick T

PID không thực sự ngẫu nhiên. MAC có thể được nhân bản. Làm thế nào bạn có thể trộn chúng theo cách không giới thiệu một độ lệch / sai lệch không mong muốn?
John Leidegren

16

chỉ để tung nó ra cho hậu thế: đôi khi có thể tốt hơn là tạo một chuỗi ngẫu nhiên bằng cách sử dụng chuỗi ký tự ban đầu. Điều này rất hữu ích nếu chuỗi được cho là được nhập bằng tay bởi một người; không bao gồm 0, O, 1 và l có thể giúp giảm lỗi người dùng.

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

và tôi thường đặt hạt giống bên trong một init()khối. Chúng được ghi lại ở đây: http://golang.org/doc/effective_go.html#init


9
Theo như tôi hiểu một cách chính xác, không có cần thiết phải có -1trong rand.Intn(len(alpha)-1). Điều này là do rand.Intn(n)luôn trả về một số nhỏ hơn n(nói cách khác: từ 0 đến n-1bao gồm).
chụp

2
@snap là chính xác; trong thực tế, bao gồm cả -1trong len(alpha)-1sẽ đảm bảo rằng số 9 không bao giờ được sử dụng trong chuỗi.
carbocation

2
Cũng cần lưu ý rằng loại trừ 0 (không) là một ý tưởng hay vì bạn đang truyền lát cắt byte thành chuỗi và điều đó làm cho 0 trở thành byte rỗng. Ví dụ: thử tạo một tệp có byte '0' ở giữa và xem điều gì xảy ra.
Eric Lagergren

14

OK tại sao phức tạp như vậy!

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

Điều này dựa trên mã của dystroy nhưng phù hợp với nhu cầu của tôi.

Nó chết sáu (rands ints 1 =< i =< 6)

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

Các chức năng trên là điều tương tự chính xác.

Tôi hy vọng thông tin này đã được sử dụng.


Điều đó sẽ trả về tất cả thời gian theo cùng một trình tự, theo cùng một thứ tự nếu được gọi nhiều lần, điều đó không có vẻ rất ngẫu nhiên đối với tôi. Kiểm tra ví dụ trực tiếp: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis

8
@ThomasModeneis: Đó là vì họ giả thời gian trong sân chơi.
ofavre

1
Cảm ơn @ofavre, thời gian giả mạo đó thực sự đã ném tôi lúc đầu.
Jesse Chisholm

1
Bạn vẫn cần phải chọn hạt giống trước khi gọi rand.Intn(), nếu không bạn sẽ luôn nhận được cùng một số bất cứ khi nào bạn chạy chương trình của mình.
Flavio

Bất kỳ lý do cho var bytes int? Sự khác biệt để thay đổi ở trên bytes = rand.Intn(6)+1bytes := rand.Intn(6)+1gì? Cả hai dường như làm việc cho tôi, là một trong số họ tối ưu phụ vì một số lý do?
pzkpfw

0

Đó là nano giây, cơ hội nhận được hạt giống hai lần là bao nhiêu.
Dù sao, cảm ơn sự giúp đỡ, đây là giải pháp cuối cùng của tôi dựa trên tất cả các đầu vào.

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
lại: what are the chances of getting the exact the exact same [nanosecond] twice?Tuyệt vời. Tất cả phụ thuộc vào độ chính xác bên trong của việc thực hiện các thời gian chạy của golang. Mặc dù các đơn vị là nano giây, mức tăng nhỏ nhất có thể là một phần nghìn giây hoặc thậm chí là một giây.
Jesse Chisholm

0

Nếu mục đích của bạn chỉ là tạo ra một số ngẫu nhiên thì tôi nghĩ không cần thiết phải làm phức tạp nó bằng nhiều lệnh gọi hàm hoặc đặt lại hạt giống mỗi lần.

Bước quan trọng nhất là gọi hàm seed chỉ một lần trước khi thực sự chạy rand.Init(x). Seed sử dụng giá trị hạt giống được cung cấp để khởi tạo Nguồn mặc định sang trạng thái xác định. Vì vậy, nên gọi nó một lần trước khi gọi hàm thực tế đến bộ tạo số giả ngẫu nhiên.

Đây là một mã mẫu tạo ra một chuỗi các số ngẫu nhiên

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

Lý do tôi sử dụng Sprintf là vì nó cho phép định dạng chuỗi đơn giản.

Ngoài ra, trong rand.Intn(7) Intn trả về, dưới dạng int, một số giả ngẫu nhiên không âm trong [0,7).


0

@ [Denys Séguret] đã đăng đúng. Nhưng trong trường hợp của tôi, tôi cần hạt giống mới mọi lúc do đó dưới mã;

Trong trường hợp bạn cần các chức năng nhanh chóng. Tôi sử dụng như thế này.


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

nguồn


-2

Cập nhật nhỏ do thay đổi api golang, vui lòng bỏ qua .UTC ():

Hiện tại(). UTC () .UnixNano () -> thời gian. Bây giờ (). 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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.