Cara benar seed generator angka acak

160

Saya mencoba menghasilkan string acak di Go dan di sini adalah kode yang saya tulis sejauh ini:

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

Implementasi saya sangat lambat. Pembibitan menggunakan timenomor acak yang sama untuk waktu tertentu, sehingga loop berulang dan berulang. Bagaimana saya bisa meningkatkan kode saya?

tembaga
sumber
2
The "if string (randInt (65,90))! = Temp {" sepertinya Anda mencoba menambahkan keamanan ekstra tapi hei, semuanya mendapatkan satu demi satu secara kebetulan. Dengan melakukan ini, Anda mungkin benar-benar menurunkan entropi.
Jan Matějka
3
Sebagai catatan tambahan, tidak perlu mengonversi ke UTC dalam "waktu. Sekarang (). UTC (). UnixNano ()". Waktu Unix dihitung sejak Zaman yang merupakan UTC pula.
Grzegorz Luczywo
2
Anda harus mengatur benih sekali, hanya satu kali, dan tidak pernah lebih dari sekali. baik, jika aplikasi Anda berjalan selama berhari-hari Anda bisa mengaturnya sekali sehari.
Casperah
Anda harus menyemai sekali. Dan saya pikir "Z" mungkin tidak pernah muncul, saya kira? Jadi saya lebih suka menggunakan indeks awal inklusif dan indeks akhir eksklusif.
Jaehyun Yeom

Jawaban:

232

Setiap kali Anda mengatur seed yang sama, Anda mendapatkan urutan yang sama. Jadi tentu saja jika Anda menyetel seed ke waktu dalam loop cepat, Anda mungkin akan menyebutnya dengan seed yang sama beberapa kali.

Dalam kasus Anda, saat Anda memanggil randIntfungsi Anda hingga Anda memiliki nilai yang berbeda, Anda menunggu waktu (seperti dikembalikan oleh Nano) untuk berubah.

Sedangkan untuk semua pseudo-random library , Anda harus menetapkan seed hanya sekali, misalnya saat menginisialisasi program Anda kecuali Anda secara khusus perlu mereproduksi urutan tertentu (yang biasanya hanya dilakukan untuk debugging dan pengujian unit).

Setelah itu Anda cukup menelepon Intnuntuk mendapatkan bilangan bulat acak berikutnya.

Pindahkan rand.Seed(time.Now().UTC().UnixNano())garis dari fungsi randInt ke awal main dan semuanya akan lebih cepat.

Perhatikan juga bahwa saya pikir Anda dapat menyederhanakan pembuatan string:

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)
}
Denys Séguret
sumber
Terima kasih telah menjelaskan itu, saya pikir ini perlu diunggulkan setiap kali.
copperMan
13
Anda juga dapat menambah rand.Seed(...)fungsi init(). init()dipanggil secara otomatis sebelumnya main(). Perhatikan bahwa Anda tidak perlu menelepon init()dari main()!
Jabba
2
@ Jabba Benar. Saya menjaga jawaban saya sesederhana mungkin dan tidak terlalu jauh dari pertanyaan, tetapi pengamatan Anda benar.
Denys Séguret
7
Harap dicatat bahwa tidak ada jawaban yang sejauh ini menginisialisasi benih dengan cara yang aman secara kriptografis. Tergantung pada aplikasi Anda, ini mungkin tidak masalah sama sekali atau mungkin mengakibatkan kegagalan bencana.
Ingo Blechschmidt
3
@IngoBlechschmidt math/randtidak aman secara kriptografis. Jika itu merupakan persyaratan, crypto/randharus digunakan.
Duncan Jones
39

Saya tidak mengerti mengapa orang menyemai nilai waktu. Ini dalam pengalaman saya tidak pernah menjadi ide bagus. Misalnya, sementara jam sistem mungkin direpresentasikan dalam nanodetik, presisi jam sistem bukanlah nanodetik.

Program ini tidak boleh dijalankan di taman bermain Go tetapi jika Anda menjalankannya di mesin Anda, Anda mendapatkan perkiraan kasar tentang jenis presisi yang dapat Anda harapkan. Saya melihat peningkatan sekitar 1000000 ns, jadi kenaikan 1 ms. Itu 20 bit entropi yang tidak digunakan. Sementara bit tinggi sebagian besar konstan.

Tingkat yang penting bagi Anda akan bervariasi tetapi Anda dapat menghindari jebakan nilai benih berdasarkan jam hanya dengan menggunakan crypto/rand.Readsumber sebagai sumber untuk benih Anda. Ini akan memberi Anda kualitas non-deterministik yang mungkin Anda cari dalam angka acak Anda (bahkan jika implementasi aktual itu sendiri terbatas pada serangkaian urutan acak yang berbeda dan deterministik).

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

Sebagai catatan tetapi terkait dengan pertanyaan Anda. Anda dapat membuat sendiri rand.Sourcemenggunakan metode ini untuk menghindari biaya memiliki kunci yang melindungi sumber. The randfungsi paket utilitas yang nyaman tetapi mereka juga menggunakan kunci di bawah tenda untuk mencegah sumber dari yang digunakan secara bersamaan. Jika Anda tidak membutuhkannya, Anda bisa menghindarinya dengan membuat milik Anda sendiri Sourcedan menggunakannya secara tidak bersamaan. Terlepas dari itu, Anda TIDAK boleh me-reseed generator nomor acak Anda di antara iterasi, itu tidak pernah dirancang untuk digunakan seperti itu.

John Leidegren
sumber
5
Jawaban ini sangat kurang dihargai. Khusus untuk alat-alat baris perintah yang dapat berjalan beberapa kali dalam satu detik, ini harus dilakukan. Terima kasih
saeedgnu
1
Anda dapat mencampur PID dan nama host / MAC jika diperlukan, tetapi berhati-hatilah bahwa menaburkan RNG dengan sumber yang aman secara kriptografis tidak membuatnya aman secara kriptografis karena seseorang dapat merekonstruksi keadaan internal PRNG.
Nick T
PID tidak benar-benar acak. MAC dapat dikloning. Bagaimana Anda mencampurkannya dengan cara yang tidak menghasilkan bias / bias yang tidak diinginkan?
John Leidegren
16

hanya untuk membuangnya untuk anak cucu: kadang-kadang lebih disukai untuk menghasilkan string acak menggunakan string set karakter awal. Ini berguna jika string seharusnya dimasukkan secara manual oleh manusia; tidak termasuk 0, O, 1, dan l dapat membantu mengurangi kesalahan pengguna.

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

dan saya biasanya mengatur benih di dalam init()blok. Mereka didokumentasikan di sini: http://golang.org/doc/effective_go.html#init

jorelli
sumber
9
Sejauh yang saya mengerti benar, tidak perlu ada -1di rand.Intn(len(alpha)-1). Ini karena rand.Intn(n)selalu mengembalikan angka yang kurang dari n(dengan kata lain: dari nol hingga n-1inklusif).
jepret
2
@ jepret sudah benar; bahkan, termasuk -1dalam len(alpha)-1akan menjamin bahwa angka 9 tidak pernah digunakan dalam urutan.
carbocation
2
Perlu juga dicatat bahwa mengecualikan 0 (nol) adalah ide yang baik karena Anda melemparkan slice byte ke string, dan itu menyebabkan 0 menjadi byte nol. Misalnya, coba buat file dengan byte '0' di tengah dan lihat apa yang terjadi.
Eric Lagergren
14

OK kenapa begitu rumit!

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

Ini didasarkan pada kode dystroy tetapi cocok untuk kebutuhan saya.

Itu mati enam (rands ints 1 =< i =< 6)

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

Fungsi di atas adalah hal yang persis sama.

Saya harap informasi ini bermanfaat.

Luviz
sumber
Itu akan mengembalikan semua waktu urutan yang sama, dalam urutan yang sama jika dipanggil berkali-kali, itu tidak terlihat sangat acak bagi saya. Lihat contoh langsung: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis
8
@ThomasModeneis: Itu karena mereka memalsukan waktu di taman bermain.
ofavre
1
Terima kasih @ofavre, waktu palsu itu benar-benar membuat saya marah.
Jesse Chisholm
1
Anda masih perlu melakukan seed sebelum menelepon rand.Intn(), jika tidak, Anda akan selalu mendapatkan nomor yang sama setiap kali Anda menjalankan program Anda.
Flavio Copes
Apa alasannya untuk var bytes int? Apa perbedaan untuk mengubah atas bytes = rand.Intn(6)+1ke bytes := rand.Intn(6)+1? Mereka berdua sepertinya bekerja untuk saya, apakah salah satu dari mereka kurang optimal karena alasan tertentu?
pzkpfw
0

Ini nano detik, berapa peluang untuk mendapatkan seed yang sama dua kali.
Bagaimanapun, terima kasih atas bantuannya, inilah solusi akhir saya berdasarkan semua input.

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))
}
RoboTamer
sumber
1
re: what are the chances of getting the exact the exact same [nanosecond] twice?Luar biasa. Itu semua tergantung pada ketepatan internal implementasi runtime golang. Meskipun unit adalah nano-detik, kenaikan terkecil mungkin satu mili detik atau bahkan satu detik.
Jesse Chisholm
0

Jika tujuan Anda hanya untuk menghasilkan sengatan angka acak maka saya pikir tidak perlu menyulitkannya dengan beberapa panggilan fungsi atau mengatur ulang seed setiap waktu.

Langkah paling penting adalah memanggil fungsi seed sekali saja sebelum benar-benar berjalan rand.Init(x). Seed menggunakan nilai seed yang disediakan untuk menginisialisasi Sumber default ke keadaan deterministik. Jadi, akan disarankan untuk memanggilnya sekali sebelum pemanggilan fungsi sebenarnya ke pseudo-random number generator.

Berikut adalah contoh kode yang membuat string angka acak

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

Alasan saya menggunakan Sprintf adalah karena memungkinkan pemformatan string sederhana.

Juga, In rand.Intn(7) Intn mengembalikan, sebagai int, nomor pseudo-acak non-negatif dalam [0,7).

Kapten Levi
sumber
0

@ [Denys Séguret] telah diposting dengan benar. Tetapi dalam kasus saya, saya perlu seed baru setiap kali karenanya di bawah kode;

Jika Anda membutuhkan fungsi cepat. Saya menggunakan seperti ini.


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

sumber

BAJA
sumber
-2

Pembaruan kecil karena perubahan golang api, harap hapus .UTC ():

waktu sekarang(). UTC () .UnixNano () -> waktu.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)
}
letanthang
sumber