วิธีการสร้างเครื่องกำเนิดจำนวนสุ่มอย่างถูกต้อง


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
The "if string (randInt (65,90))! = temp {" ดูเหมือนว่าคุณกำลังพยายามเพิ่มความปลอดภัยเป็นพิเศษ แต่เดี๋ยวก่อนสิ่งต่าง ๆ จะเหมือนกันหลังจากนั้นโดยบังเอิญ โดยการทำเช่นนี้คุณอาจลดเอนโทรปี
yaccz

3
ตามหมายเหตุด้านข้างไม่จำเป็นต้องแปลงเป็น UTC ใน "time.Now (). UTC (). UnixNano ()" คำนวณ Unix time ตั้งแต่ Epoch ซึ่งเป็น UTC แล้ว
Grzegorz Luczywo

2
คุณควรตั้งค่าเมล็ดพันธุ์หนึ่งครั้งเพียงครั้งเดียวและไม่เกินหนึ่งครั้ง ดีในกรณีที่ใบสมัครของคุณทำงานเป็นเวลาหลายวันคุณสามารถตั้งค่าวันละครั้ง
Casperah

คุณควรหว่านครั้งเดียว และฉันคิดว่า "Z" อาจไม่เคยปรากฏฉันเดา? ดังนั้นฉันต้องการใช้ดัชนีเริ่มต้นรวมและดัชนีเฉพาะพิเศษ
Jaehyun Yeom

คำตอบ:


232

ทุกครั้งที่คุณตั้งค่าเมล็ดเดียวกันคุณจะได้รับลำดับเดียวกัน แน่นอนถ้าคุณตั้งค่าเมล็ดเป็นเวลาในการวนรอบอย่างรวดเร็วคุณอาจเรียกมันด้วยเมล็ดเดียวกันหลายครั้ง

ในกรณีของคุณในขณะที่คุณกำลังเรียกrandIntใช้ฟังก์ชันของคุณจนกว่าคุณจะมีค่าที่แตกต่างกันคุณกำลังรอเวลา (ที่ส่งคืนโดยนาโน) เพื่อเปลี่ยน

สำหรับไลบรารีหลอกแบบสุ่มทั้งหมดคุณต้องตั้งค่า seed เพียงครั้งเดียวเช่นเมื่อเริ่มต้นโปรแกรมของคุณเว้นแต่ว่าคุณต้องการสร้างลำดับที่กำหนด (โดยเฉพาะสำหรับการดีบักและการทดสอบหน่วย)

หลังจากนั้นคุณเพียงโทร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
@ Jabba ถูกต้อง ฉันรักษาคำตอบของฉันให้ง่ายที่สุดและไม่ไกลจากคำถาม แต่การสังเกตของคุณถูกต้อง
Denys Séguret

7
โปรดทราบว่าไม่มีผู้ใด anwers โพสต์เพื่อเริ่มต้นเมล็ดในวิธีที่ปลอดภัย cryptographically ขึ้นอยู่กับแอปพลิเคชันของคุณสิ่งนี้อาจไม่สำคัญเลยหรืออาจส่งผลให้เกิดความล้มเหลวอย่างรุนแรง
Ingo Blechschmidt

3
@IngoBlechschmidt math/randนั้นไม่มีความปลอดภัยในการเข้ารหัส หากเป็นความต้องการcrypto/randควรใช้
ดันแคนโจนส์

39

ฉันไม่เข้าใจว่าเพราะเหตุใดผู้คนจึงเริ่มมีคุณค่ากับเวลา สิ่งนี้มีในประสบการณ์ของฉันไม่เคยเป็นความคิดที่ดี ตัวอย่างเช่นในขณะที่นาฬิการะบบอาจแสดงเป็นนาโนวินาทีความแม่นยำสัญญาณนาฬิกาของระบบไม่ใช่นาโนวินาที

โปรแกรมนี้ไม่ควรรันบนสนามเด็กเล่นของ Go แต่ถ้าคุณรันบนเครื่องของคุณคุณจะได้รับการประเมินคร่าวๆเกี่ยวกับประเภทของความแม่นยำที่คุณคาดหวัง ฉันเห็นการเพิ่มขึ้นประมาณ 1000000 ns ดังนั้นเพิ่มขึ้น 1 ms นั่นคือเอนโทรปี 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
คำตอบนี้ไม่ได้รับการชื่นชมมากนัก พิเศษสำหรับเครื่องมือบรรทัดคำสั่งที่อาจทำงานหลายครั้งในหนึ่งวินาทีนี่เป็นสิ่งที่ต้องทำ ขอบคุณ
saeedgnu

1
คุณสามารถผสมใน PID และชื่อโฮสต์ / MAC ได้ถ้าจำเป็น แต่ระวังว่าการ seed 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)จะส่งกลับตัวเลขที่น้อยกว่าเสมอn(ในคำอื่น ๆ : จากศูนย์ถึงn-1รวม)
snap

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
คุณยังต้องทำการ seed ก่อนที่จะโทรrand.Intn()มิฉะนั้นคุณจะได้หมายเลขเดิมทุกครั้งที่คุณเรียกใช้โปรแกรม
Flavio Copes

มีเหตุผล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 runtimes แม้ว่าหน่วยเป็นนาโนวินาทีการเพิ่มขึ้นที่เล็กที่สุดอาจเป็นมิลลิวินาทีหรือแม้แต่วินาที
Jesse Chisholm

0

หากเป้าหมายของคุณคือสร้างหมายเลขสุ่มต่อจากนั้นฉันคิดว่ามันไม่จำเป็นที่จะต้องยุ่งยากกับการเรียกฟังก์ชั่นหลายครั้งหรือรีเซ็ตเมล็ดทุกครั้ง

rand.Init(x)ขั้นตอนที่สำคัญที่สุดคือการฟังก์ชั่นเมล็ดโทรเพียงครั้งเดียวก่อนที่จะใช้จริง 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จะส่งกลับเช่นเดียวกับ int ซึ่งเป็นตัวเลขสุ่มหลอกที่ไม่เป็นลบใน [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 ():

time.Now () 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.