난수 생성기를 올바르게 시드하는 방법


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

3
참고로 "time.Now (). UTC (). UnixNano ()"에서 UTC로 변환 할 필요는 없습니다. 유닉스 시간은 어쨌든 UTC 인 Epoch 이후 계산됩니다.
Grzegorz Luczywo

2
시드는 한 번만 설정해야하며 한 번만 설정해야합니다. 응용 프로그램이 며칠 동안 실행되는 경우 하루에 한 번 설정할 수 있습니다.
Casperah

한 번 시드해야합니다. "Z"가 절대 나타나지 않을 것 같아요? 따라서 시작 색인 포함 및 종료 색인 독점을 선호합니다.
Jaehyun Yeom

답변:


232

동일한 시드를 설정할 때마다 동일한 시퀀스를 얻습니다. 물론 빠른 루프에서 시드를 시간으로 설정하는 경우 동일한 시드로 여러 번 호출 할 수 있습니다.

귀하의 경우, randInt다른 값을 가질 때까지 함수를 호출 할 때 (Nano에서 반환 한) 시간이 변경되기를 기다리고 있습니다.

모든 의사 랜덤 라이브러리의 경우 주어진 시퀀스를 재현 할 필요가없는 경우 (일반적으로 디버깅 및 단위 테스트 용으로 만 수행됨)를 제외하고 프로그램을 초기화 할 때와 같이 시드를 한 번만 설정해야합니다.

그런 다음 단순히 Intn다음 임의의 정수를 얻기 위해 호출 합니다.

rand.Seed(time.Now().UTC().UnixNano())randInt 함수에서 메인의 시작 부분으로 라인을 이동하면 모든 것이 더 빨라집니다.

또한 문자열 작성을 단순화 할 수 있다고 생각합니다.

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

13
rand.Seed(...)함수에 추가 할 수도 있습니다 init(). init()전에 자동으로 호출됩니다 main(). 당신이 호출 할 필요는 없습니다 init()에서 main()!
Jabba

2
@ 자바 맞아. 나는 대답을 가능한 한 단순하게하고 질문에서 멀지 않은 곳에 두었지만, 당신의 관찰은 옳습니다.
Denys Séguret

7
지금까지 게시 된 답변 중 어느 것도 시드를 암호화 적으로 안전한 방식으로 초기화하지 않습니다. 응용 프로그램에 따라 전혀 문제가되지 않거나 치명적인 오류가 발생할 수 있습니다.
Ingo Blechschmidt

3
@IngoBlechschmidt math/rand는 어쨌든 암호로 안전하지 않습니다. 이것이 요구 사항 인 경우 crypto/rand사용해야합니다.
던컨 존스

39

사람들이 왜 시간 가치를 부여하는 지 이해할 수 없습니다. 이것은 내 경험상 결코 좋은 생각이 아닙니다. 예를 들어, 시스템 클럭이 나노초로 표시 될 수 있지만 시스템의 클럭 정밀도는 나노초가 아닙니다.

이 프로그램 은 Go 놀이터에서 실행해서는 안되지만 컴퓨터에서 실행하면 예상 할 수있는 정밀도 유형에 대한 대략적인 추정치를 얻을 수 있습니다. 약 1000000ns 씩 증가하므로 1ms 씩 증가합니다. 그것은 사용되지 않는 20 비트의 엔트로피입니다. 높은 비트는 항상 일정합니다.

이것이 당신에게 중요한 정도는 다를 것이지만, 단순히 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있으며 비 동시 방식으로 사용하십시오. 그럼에도 불구하고 반복 사이에 난수 생성기를 다시 시드해서는 안되며 절대 그런 식으로 사용되도록 설계되지 않았습니다.


5
이 답변은 매우 소중합니다. 특히 1 초에 여러 번 실행될 수있는 명령 행 도구의 경우 반드시 수행해야합니다. 감사합니다
saeedgnu

1
필요한 경우 PID와 호스트 이름 / MAC를 혼합 할 수 있지만 암호로 안전한 소스를 사용하여 RNG를 시드한다고해서 누군가 PRNG 내부 상태를 재구성 할 수 있으므로 암호로 안전하게 보호되지는 않습니다.
Nick T

PID는 실제로 무작위가 아닙니다. MAC을 복제 할 수 있습니다. 원치 않는 스큐 / 바이어스를 유발하지 않는 방식으로 혼합하는 방법은 무엇입니까?
John Leidegren

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
지금까지 내가 제대로 이해 가질 필요가 없습니다 -1에가 rand.Intn(len(alpha)-1). rand.Intn(n)항상 n0 보다 작은 숫자를 리턴 하기 때문입니다 (즉, 0에서 n-1포괄적).
스냅

2
@snap이 정확합니다. 사실, 포함 -1에하는 len(alpha)-1수 (9)가 순서대로 사용하지 않을 것을 보장 할 것입니다.
carbocation

2
또한 바이트 슬라이스를 문자열로 캐스트하고 0을 널 바이트로 만들므로 0을 제외하는 것이 좋습니다. 예를 들어, 중간에 '0'바이트를 가진 파일을 작성하고 어떻게되는지보십시오.
Eric Lagergren

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의 코드를 기반으로하지만 내 요구에 적합합니다.

그것은 여섯 죽는다 (rands ints 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

8
@ThomasModeneis : 놀이터에서 시간을 허비 하기 때문 입니다.
ofavre

1
@ofavre에게 감사드립니다. 그 가짜 시간은 처음에 저를 정말로 던졌습니다.
Jesse Chisholm 1

1
을 호출하기 전에 계속 시드해야합니다 rand.Intn(). 그렇지 않으면 프로그램을 실행할 때마다 항상 같은 번호가 표시됩니다.
Flavio 대처

어떤 이유가 var bytes int있습니까? 위 bytes = rand.Intn(6)+1를 변경하는 것과의 차이점은 무엇입니까 bytes := 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 런타임 구현 의 내부 정밀도에 달려 있습니다. 단위가 나노초이지만 가장 작은 단위는 밀리 초 또는 1 초일 수 있습니다.
Jesse Chisholm 1

0

당신의 목표가 난수의 찌르기를 생성하는 것이라면, 매번 여러 함수 호출이나 시드 재설정과 함께 복잡하게 할 필요가 없다고 생각합니다.

가장 중요한 단계는 실제로 실행하기 전에 seed 함수를 한 번만 호출하는 것 rand.Init(x)입니다. Seed 는 제공된 seed 값을 사용하여 기본 소스를 결정적 상태로 초기화합니다. 따라서 의사 난수 생성기에 대한 실제 함수 호출 전에 한 번 호출하는 것이 좋습니다.

다음은 임의의 숫자 문자열을 만드는 샘플 코드입니다.

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)의 음수가 아닌 의사 난수를 정수로 반환합니다.


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

출처


-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)
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.