Perluas rentang acak dari 1-5 ke 1-7

692

Diberikan fungsi yang menghasilkan bilangan bulat acak dalam kisaran 1 hingga 5, tulis fungsi yang menghasilkan bilangan bulat acak dalam kisaran 1 hingga 7.

  1. Apa itu solusi sederhana?
  2. Apa solusi yang efektif untuk mengurangi penggunaan memori atau berjalan pada CPU yang lebih lambat?
Roger Pate
sumber
Itu terbukti menjadi masalah yang tak terduga menarik, saya masih berpikir bagaimana 1) melakukannya dalam waktu yang tetap dan 2) tidak merusak distribusi seragam (jika ada)
eugensk
Kami memiliki masalah yang sama saat memilih satu pemain dari 5 dengan dadu. Kami melemparkan dadu secara bergiliran, orang yang mendapat skor maksimal dipilih. Keseragaman dicapai, tetapi bukan waktu yang konstan :)
eugensk
Apakah saya akan downvoted jika saya memposting jawaban yang mengatakan bahwa masalahnya tidak mengharuskan Anda harus menggunakan fungsi yang diberikan dan hanya menulis yang mengembalikan 1-7 secara acak?
Dokter Biru
Bagaimana dengan 7 * rand5() / 5?
kiwixz
@kiwixz, yang akan menghasilkan "antara 1 dan 7", tetapi Anda tidak akan mendapatkan 3 atau 6: {1: 19.96, 2: 20.02, 4: 20.01, 5: 19.99, 7: 20.02} persentase kasar pengujian secara manual. 7 * .2, 7 * .4, 7 * .6, 7 * .8, 7 * 1.
pythonlarry

Jawaban:

572

Ini setara dengan solusi Adam Rosenfield, tetapi mungkin sedikit lebih jelas bagi sebagian pembaca. Ini mengasumsikan rand5 () adalah fungsi yang mengembalikan bilangan bulat acak secara statistik dalam kisaran 1 hingga 5 inklusif.

int rand7()
{
    int vals[5][5] = {
        { 1, 2, 3, 4, 5 },
        { 6, 7, 1, 2, 3 },
        { 4, 5, 6, 7, 1 },
        { 2, 3, 4, 5, 6 },
        { 7, 0, 0, 0, 0 }
    };

    int result = 0;
    while (result == 0)
    {
        int i = rand5();
        int j = rand5();
        result = vals[i-1][j-1];
    }
    return result;
}

Bagaimana cara kerjanya? Pikirkan seperti ini: bayangkan mencetak array dua dimensi ini di atas kertas, menempelkannya ke papan panah dan melemparkan panah secara acak ke sana. Jika Anda menekan nilai bukan nol, itu adalah nilai acak statistik antara 1 dan 7, karena ada jumlah yang sama dari nilai bukan nol untuk dipilih. Jika Anda menekan nol, terus lempar panah sampai Anda memukul nol. Itulah yang dilakukan kode ini: indeks i dan j secara acak memilih lokasi pada papan panah, dan jika kami tidak mendapatkan hasil yang baik, kami terus melempar panah.

Seperti kata Adam, ini bisa berjalan selamanya dalam kasus terburuk, tetapi secara statistik, kasus terburuk tidak pernah terjadi. :)

Rob McAfee
sumber
5
Saya memahami logika di balik solusi ini tetapi tidak dapat memahami bahwa bagaimana hal itu menghasilkan probabilitas yang seragam? Bisakah seseorang menjelaskan matematika?
user1071840
6
@ user1071840 - jika rand5seragam, setiap sel di valsgrid memiliki probabilitas yang sama untuk dipilih. Kotak berisi persis tiga salinan dari masing-masing bilangan bulat dalam interval [1, 7], ditambah empat nol. Jadi aliran "mentah" hasil cenderung ke campuran nilai [1, 7] yang rata, ditambah beberapa angka nol yang terjadi sedikit lebih sering daripada nilai individual yang diizinkan. Tapi itu tidak masalah karena nol dilucuti, hanya menyisakan campuran nilai [1, 7] yang merata.
Daniel Earwicker
3
Cara pintas untuk menyadari masalah dengan itu: jika Anda hanya memanggil rand5 () satu kali, maka Anda hanya memiliki 5 kemungkinan hasil. Jelas tidak ada cara untuk mengubahnya menjadi lebih dari 5 hasil yang mungkin tanpa menambahkan lebih banyak keacakan.
Daniel Earwicker
1
Versi yang lebih panjang: rand5 () hanya dapat memiliki nilai (1, 2, 3, 4, 5). Karenanya rand5 () * 5 hanya dapat memiliki nilai (5, 10, 15, 20, 25), yang tidak sama dengan rentang lengkap (1 ... 25). Jika ya, mengurangi 4 akan membuatnya (-3 ... 21), tetapi dalam kasus ini menjadi (1, 6, 11, 16, 21), sehingga titik akhir sudah benar tetapi ada empat lubang besar: ( 2..5), (7..10), (12.15), (17..21). Akhirnya Anda melakukan mod 7 dan menambahkan 1, memberi (2, 7, 5, 3, 1). Jadi 4 atau 6 tidak pernah terjadi. Tapi (lihat jalan pintas di atas) kami tahu hanya akan ada 5 angka dalam rentang yang dihasilkan selama ini, jadi harus ada dua celah.
Daniel Earwicker
1
Ah, karena kita hanya punya rand5 (), bukan rand2 () :-)
gzak
352

Tidak ada solusi (tepat benar) yang akan berjalan dalam jumlah waktu yang konstan, karena 1/7 adalah desimal tak terbatas dalam basis 5. Salah satu solusi sederhana adalah dengan menggunakan sampel penolakan, misalnya:


int i;
do
{
  i = 5 * (rand5() - 1) + rand5();  // i is now uniformly random between 1 and 25
} while(i > 21);
// i is now uniformly random between 1 and 21
return i % 7 + 1;  // result is now uniformly random between 1 and 7

Ini memiliki runtime yang diharapkan dari 25/21 = 1,19 iterasi loop, tetapi ada kemungkinan sangat kecil untuk mengulang selamanya.

Adam Rosenfield
sumber
7
-1 tidak diperlukan jika> 21 dibalik ke> 26 b / c tidak masalah ke mana peta batas bawah saya,
BCS
26
Pandangan saya menjelaskan mengapa ini benar: Katakan bahwa saya ingin menulis sebuah program yang menghasilkan aliran angka acak seragam dari 1 hingga 25; untuk itu saya baru saja mengembalikan 5 * (rand5 () - 1) + rand5 () seperti pada kode di jawabannya. Sekarang, jika saya ingin membangun aliran angka acak seragam antara 1 dan 21, jika saya hanya menggunakan aliran pertama tetapi menyaringnya sehingga angka dalam [22, 25] ditolak, saya bisa membangun aliran itu juga. Selanjutnya, jika saya mengambil aliran ini dan memfilternya sehingga untuk setiap elemen x I menghasilkan x% 7 +1, saya memiliki aliran angka acak seragam dari 1 hingga 7! Cukup sederhana, bukan? : D
Paggas
6
Dan Anda benar bahwa itu bermuara pada apakah Anda ingin distribusi yang sempurna dengan runtime kasus terburuk yang tidak terbatas, atau distribusi yang tidak sempurna dengan runtime terbatas. Ini adalah konsekuensi dari kenyataan bahwa semua kekuatan 5 tidak dapat dibagi oleh 7, atau setara jika Anda memiliki 5 ^ n sama-sama mungkin urutan panjang n, tidak ada cara untuk menetapkan setiap urutan nomor dari 1 hingga 7 sehingga masing-masing dari 1..7 sama-sama mungkin.
Adam Rosenfield
5
@ Jules Olléon: Misalkan ada solusi yang berjalan dalam waktu konstan yang dijamin tidak membuat lebih dari Npanggilan rand5()dalam kasus terburuk. Kemudian, ada 5 ^ N hasil yang mungkin dari urutan panggilan rand5, yang masing-masing memiliki output 1-7. Jadi, jika Anda menjumlahkan semua kemungkinan urutan panggilan yang outputnya kuntuk setiap 1≤k≤7, maka probabilitas bahwa outputnya kadalah m / 5 ^ N, di mana m adalah jumlah urutan tersebut. Jadi, m / 5 ^ N = 1/7, tetapi tidak ada solusi integer yang memungkinkan (N, m) untuk ini ==> kontradiksi.
Adam Rosenfield
4
@paxdiablo: Anda salah. Peluang RNG sejati menghasilkan urutan tak terbatas dari 5 adalah tepat 0, menggunakan alasan yang sama dengan fakta bahwa membalik koin dalam jumlah tak terbatas kali dijamin tidak menghasilkan jumlah tak terbatas dari kepala berturut-turut . Ini juga berarti kemungkinan pengulangan kode ini selamanya adalah tepat 0 (meskipun ada kemungkinan positif akan berulang untuk setiap iterasi yang berubah-ubah).
BlueRaja - Danny Pflughoeft
153

Saya ingin menambahkan jawaban lain, selain jawaban pertama saya . Jawaban ini mencoba untuk meminimalkan jumlah panggilan rand5()per panggilan rand7(), untuk memaksimalkan penggunaan keacakan. Artinya, jika Anda menganggap keacakan sebagai sumber daya yang berharga, kami ingin menggunakan sebanyak mungkin itu, tanpa membuang bit acak. Jawaban ini juga memiliki beberapa kesamaan dengan logika yang disajikan dalam jawaban Ivan .

The entropi dari suatu variabel acak adalah kuantitas didefinisikan dengan baik. Untuk variabel acak yang mengambil status N dengan probabilitas yang sama (distribusi seragam), entropinya adalah log 2 N. Dengan demikian, rand5()memiliki sekitar 2,32193 bit entropi, dan rand7()memiliki sekitar 2,80735 bit entropi. Jika kita berharap untuk memaksimalkan penggunaan keacakan kita, kita perlu menggunakan semua 2,32193 bit entropi dari setiap panggilan rand5(), dan menerapkannya untuk menghasilkan 2,80735 bit entropi yang diperlukan untuk setiap panggilan rand7(). Batas mendasar, maka, adalah bahwa kita tidak dapat melakukan lebih baik daripada log (7) / log (5) = 1,20906 panggilan kerand5() per panggilan ke rand7().

Catatan samping: semua logaritma dalam jawaban ini akan menjadi basis 2 kecuali ditentukan sebaliknya. rand5()akan diasumsikan untuk mengembalikan angka dalam kisaran [0, 4], dan rand7()akan diasumsikan untuk mengembalikan angka dalam kisaran [0, 6]. Menyesuaikan rentang ke [1, 5] dan [1, 7] masing-masing adalah sepele.

jadi bagaimana kita melakukannya? Kami menghasilkan bilangan real acak tak terhingga yang akurat antara 0 dan 1 (berpura-pura saat itu kami benar-benar dapat menghitung dan menyimpan bilangan yang tepat tak terhingga - kami akan memperbaikinya nanti). Kita dapat menghasilkan angka seperti itu dengan menghasilkan digitnya di basis 5: kita memilih angka acak 0. a1 a2 a3 ..., di mana setiap digit a idipilih oleh panggilan ke rand5(). Misalnya, jika RNG kami memilih a i= 1 untuk semua i, maka abaikan fakta bahwa itu tidak terlalu acak, itu akan sesuai dengan bilangan real 1/5 + 1/5 2 + 1/5 3 + ... = 1/4 (jumlah deret geometri).

Ok, jadi kami telah memilih bilangan real acak antara 0 dan 1. Saya sekarang mengklaim bahwa bilangan acak tersebut didistribusikan secara seragam. Secara intuitif, ini mudah dimengerti, karena setiap digit dipilih secara seragam, dan jumlahnya sangat tepat. Namun, bukti formal ini agak lebih terlibat, karena sekarang kita berurusan dengan distribusi kontinu, bukan distribusi diskrit, jadi kita perlu membuktikan bahwa probabilitas bahwa nomor kita terletak pada interval [ a, b] sama dengan panjang interval itu,b - a ,. Buktinya dibiarkan sebagai latihan untuk pembaca =).

Sekarang kita memiliki bilangan real acak yang dipilih secara seragam dari rentang [0, 1], kita perlu mengonversinya menjadi serangkaian angka acak seragam dalam rentang [0, 6] untuk menghasilkan output rand7(). Bagaimana kita melakukan ini? Kebalikan dari apa yang baru saja kita lakukan - kita mengonversinya menjadi desimal yang sangat tepat pada basis 7, dan kemudian setiap basis 7 digit akan sesuai dengan satu output rand7().

Mengambil contoh dari sebelumnya, jika kami rand5()menghasilkan aliran tak terbatas 1, maka bilangan real acak kami akan menjadi 1/4. Mengubah 1/4 ke basis 7, kita mendapatkan desimal tak terhingga 0.15151515 ..., jadi kita akan menghasilkan sebagai output 1, 5, 1, 5, 1, 5, dll.

Oke, jadi kita punya ide utama, tetapi kita punya dua masalah tersisa: kita tidak bisa benar-benar menghitung atau menyimpan bilangan real yang sangat tepat, jadi bagaimana kita berurusan dengan hanya sebagian yang terbatas? Kedua, bagaimana kita benar-benar mengubahnya menjadi basis 7?

Salah satu cara kita dapat mengonversi angka antara 0 dan 1 ke basis 7 adalah sebagai berikut:

  1. Kalikan dengan 7
  2. Bagian integral dari hasilnya adalah basis 7 digit berikutnya
  3. Kurangi bagian integral, hanya menyisakan bagian fraksional
  4. Pergi langkah 1

Untuk menangani masalah ketelitian tak terbatas, kami menghitung hasil parsial, dan kami juga menyimpan batas atas pada hasil apa yang mungkin terjadi. Artinya, misalkan kita sudah menelepon rand5()dua kali dan kembali 1 kali. Jumlah yang kami hasilkan sejauh ini adalah 0,11 (basis 5). Apa pun sisa dari serangkaian panggilan tak terbatas untuk rand5()diproduksi, bilangan real acak yang kami hasilkan tidak akan pernah lebih besar dari 0,12: selalu benar bahwa 0,11 ≤ 0,11xyz ... <0,12.

Jadi, melacak nomor saat ini sejauh ini, dan nilai maksimum yang bisa diambil, kami mengonversi kedua angka menjadi basis 7. Jika mereka menyetujui kangka pertama , maka kami dapat dengan aman menampilkan kangka berikutnya - terlepas dari apa yang Aliran tak terbatas dari basis 5 digit adalah, mereka tidak akan pernah mempengaruhi kdigit berikutnya dari representasi basis 7!

Dan itulah algoritme - untuk menghasilkan output selanjutnya rand7(), kami hanya menghasilkan sebanyak digit yang rand5()kami perlukan untuk memastikan bahwa kami tahu dengan pasti nilai digit berikutnya dalam konversi bilangan real acak ke basis 7. Berikut ini adalah implementasi Python, dengan test harness:

import random

rand5_calls = 0
def rand5():
    global rand5_calls
    rand5_calls += 1
    return random.randint(0, 4)

def rand7_gen():
    state = 0
    pow5 = 1
    pow7 = 7
    while True:
        if state / pow5 == (state + pow7) / pow5:
            result = state / pow5
            state = (state - result * pow5) * 7
            pow7 *= 7
            yield result
        else:
            state = 5 * state + pow7 * rand5()
            pow5 *= 5

if __name__ == '__main__':
    r7 = rand7_gen()
    N = 10000
    x = list(next(r7) for i in range(N))
    distr = [x.count(i) for i in range(7)]
    expmean = N / 7.0
    expstddev = math.sqrt(N * (1.0/7.0) * (6.0/7.0))

    print '%d TRIALS' % N
    print 'Expected mean: %.1f' % expmean
    print 'Expected standard deviation: %.1f' % expstddev
    print
    print 'DISTRIBUTION:'
    for i in range(7):
        print '%d: %d   (%+.3f stddevs)' % (i, distr[i], (distr[i] - expmean) / expstddev)
    print
    print 'Calls to rand5: %d (average of %f per call to rand7)' % (rand5_calls, float(rand5_calls) / N)

Catatan yang rand7_gen()mengembalikan generator, karena memiliki keadaan internal yang melibatkan konversi angka ke basis 7. Uji harness memanggil next(r7)10.000 kali untuk menghasilkan 10.000 angka acak, dan kemudian mengukur distribusi mereka. Hanya matematika bilangan bulat yang digunakan, sehingga hasilnya persis benar.

Perhatikan juga bahwa angka-angka di sini menjadi sangat besar, sangat cepat. Kekuatan 5 dan 7 tumbuh dengan cepat. Oleh karena itu, kinerja akan mulai menurun secara nyata setelah menghasilkan banyak angka acak, karena aritmatika bignum. Tapi ingat di sini, tujuan saya adalah untuk memaksimalkan penggunaan bit acak, bukan untuk memaksimalkan kinerja (meskipun itu adalah tujuan sekunder).

Dalam satu menjalankan ini, saya membuat 12091 panggilan ke rand5()untuk 10.000 panggilan ke rand7(), mencapai minimum panggilan log (7) / log (5) rata-rata untuk 4 angka signifikan, dan hasil yang dihasilkan seragam.

Untuk mem-porting kode ini ke bahasa yang tidak memiliki bilangan bulat besar yang built-in, Anda harus membatasi nilai pow5dan pow7nilai maksimum tipe integral asli Anda - jika terlalu besar, maka setel ulang segalanya dan mulai lagi dari awal. Ini akan meningkatkan jumlah rata-rata panggilan rand5()per panggilan menjadi rand7()sangat sedikit, tetapi mudah-mudahan itu tidak akan meningkat terlalu banyak bahkan untuk bilangan bulat 32 atau 64-bit.

Adam Rosenfield
sumber
7
+1 untuk jawaban yang sangat menarik. Apakah mungkin, daripada mengatur ulang pada nilai tertentu, hanya dengan menggeser bit yang telah digunakan, dan memindahkan bit lainnya, dan pada dasarnya hanya menjaga bit yang akan digunakan? Atau apakah saya melewatkan sesuatu?
Chris Lutz
1
Saya tidak 100% yakin, tetapi saya percaya jika Anda melakukan itu, Anda akan condong sedikit distribusi (walaupun saya ragu bahwa kemiringan seperti itu akan dapat diukur tanpa triliunan uji coba).
Adam Rosenfield
FTW! Saya mencoba membuat bignum lebih kecil tetapi tidak bisa dilakukan karena tidak ada kekuatan 5 yang memiliki faktor yang sama dengan kekuatan 7! Juga, gunakan kata kunci hasil yang bagus. Bagus sekali.
Eyal
2
Sangat bagus! Bisakah kita mempertahankan entropi ekstra tanpa menumbuhkan keadaan? Caranya adalah dengan memperhatikan bahwa batas atas dan bawah adalah bilangan rasional. Kita dapat menambah, mengurangi, dan melipatgandakannya tanpa kehilangan presisi. Jika kita melakukan semuanya di base-35, kita hampir sampai. Sisanya (dikalikan dengan tujuh dan mempertahankan bagian fraksional) dibiarkan sebagai latihan.
Ian
@adam Anda harus merujuk ke "tutup nilai pow5 dan pow7 dengan nilai maksimum dari tipe integral asli Anda". Saya percaya Anda kedua bahwa ini akan condong distribusi, setidaknya jika dilakukan secara naif.
katalis
36

(Saya telah mencuri jawaban Adam Rosenfeld dan membuatnya berjalan sekitar 7% lebih cepat.)

Asumsikan bahwa rand5 () mengembalikan salah satu dari {0,1,2,3,4} dengan distribusi yang sama dan tujuannya adalah mengembalikan {0,1,2,3,4,5,6} dengan distribusi yang sama.

int rand7() {
  i = 5 * rand5() + rand5();
  max = 25;
  //i is uniform among {0 ... max-1}
  while(i < max%7) {
    //i is uniform among {0 ... (max%7 - 1)}
    i *= 5;
    i += rand5(); //i is uniform {0 ... (((max%7)*5) - 1)}
    max %= 7;
    max *= 5; //once again, i is uniform among {0 ... max-1}
  }
  return(i%7);
}

Kami melacak nilai terbesar yang dapat dihasilkan loop dalam variabel max. Jika reult sejauh ini adalah antara max% 7 dan max-1 maka hasilnya akan terdistribusi secara seragam dalam kisaran itu. Jika tidak, kami menggunakan sisanya, yang acak antara 0 dan maks% 7-1, dan panggilan lain ke rand () untuk membuat nomor baru dan maks baru. Lalu kita mulai lagi.

Sunting: Mengharapkan beberapa kali untuk memanggil rand5 () adalah x dalam persamaan ini:

x =  2     * 21/25
   + 3     *  4/25 * 14/20
   + 4     *  4/25 *  6/20 * 28/30
   + 5     *  4/25 *  6/20 *  2/30 * 7/10
   + 6     *  4/25 *  6/20 *  2/30 * 3/10 * 14/15
   + (6+x) *  4/25 *  6/20 *  2/30 * 3/10 *  1/15
x = about 2.21 calls to rand5()
Eyal
sumber
2
Hasil di katalog dalam 1.000.000 percobaan: 1 = 47216; 2 = 127444; 3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536; 7 = 167465. Seperti yang Anda lihat, distribusi kurang sehubungan dengan kemungkinan mendapatkan 1.
Robert K
2
@The Flea Jahat: Saya pikir Anda salah. Apakah Anda yakin bahwa input rand5 () yang Anda gunakan untuk pengujian Anda menghasilkan 0-4 bukannya 1-5, seperti yang ditentukan dalam solusi ini?
Adam Rosenfield
5
menambahkan nomor terdistribusi secara merata tidak menghasilkan nomor yang terdistribusi secara seragam. Bahkan, Anda hanya perlu menjumlahkan 6 variabel yang terdistribusi secara merata untuk mendapatkan perkiraan yang masuk akal untuk distribusi normal.
Mitch Wheat
2
@MitchWheat - Menambahkan dua bilangan bulat terdistribusi merata, pada kenyataannya, menghasilkan bilangan bulat acak terdistribusi seragam asalkan setiap jumlah yang mungkin dapat dihasilkan dengan tepat satu cara. Itulah yang terjadi dalam ekspresi 5 * rand5() + rand5().
Ted Hopp
28

Algoritma:

7 dapat direpresentasikan dalam urutan 3 bit

Gunakan rand (5) untuk secara acak mengisi setiap bit dengan 0 atau 1.
Untuk mis: panggil rand (5) dan

jika hasilnya 1 atau 2, isi bit dengan 0
jika hasilnya 4 atau 5, isi bit dengan 1
jika hasilnya 3, abaikan dan lakukan lagi (penolakan)

Dengan cara ini kita dapat mengisi 3 bit secara acak dengan 0/1 dan karenanya mendapatkan angka dari 1-7.

EDIT: Ini sepertinya jawaban yang paling sederhana dan paling efisien, jadi inilah beberapa kode untuk itu:

public static int random_7() {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + random_5_output_2();
        }
    }
    return returnValue;
}

private static int random_5_output_2() {
    while (true) {
        int flip = random_5();

        if (flip < 3) {
            return 0;
        }
        else if (flip > 3) {
            return 1;
        }
    }
}
Lance Roberts
sumber
1
Selalu ada momok samar dari masalah penghentian, karena generator bilangan acak yang buruk hanya dapat menghasilkan banyak bertiga di beberapa titik.
Alex North-Keys
"jika hasilnya 1 atau 2, isi bit dengan 0 jika hasilnya 4 atau 5, isi bit dengan 1" Apa logika dengan mana 1,2,4,5 diterima dan 3 ditolak? Bisakah Anda menjelaskan ini?
gkns
@gkns Tidak ada logika, Anda dapat memiliki 1 dan 2 rata-rata isi dengan 0 bit dan 3 dan 4 rata-rata isi dengan 1. Yang penting adalah bahwa setiap opsi memiliki peluang 50% terjadi, sehingga menjamin bahwa keacakan fungsi Anda adalah setidaknya acak seperti fungsi rand (5) asli. Ini solusi hebat!
Mo Beigi
Ini tidak sederhana dan juga tidak efisien. Jumlah cals ke random_5 per random_7 paling banyak 3 biasanya lebih. Solusi lain pada halaman ini lebih dekat dengan yang terbaik sebenarnya yaitu sekitar 2.2.
Eyal
1
Nevermind, saya melewatkan bagian "while returnValue == 0"
NicholasFolk
19
int randbit( void )
{
    while( 1 )
    {
        int r = rand5();
        if( r <= 4 ) return(r & 1);
    }
}

int randint( int nbits )
{
    int result = 0;
    while( nbits-- )
    {
        result = (result<<1) | randbit();
    }
    return( result );
}

int rand7( void )
{
    while( 1 )
    {
        int r = randint( 3 ) + 1;
        if( r <= 7 ) return( r );
    }
}
Mike F
sumber
2
Solusi yang benar, membuat rata-rata 30/7 = 4,29 panggilan ke rand5 () per panggilan ke rand7 ().
Adam Rosenfield
17
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

Sunting: Itu tidak berhasil. Ini mati sekitar 2 bagian dalam 1000 (dengan asumsi rand5 sempurna). Ember mendapatkan:

value   Count  Error%
1       11158  -0.0035
2       11144  -0.0214
3       11144  -0.0214
4       11158  -0.0035
5       11172  +0.0144
6       11177  +0.0208
7       11172  +0.0144

Dengan beralih ke jumlah

n   Error%
10  +/- 1e-3,
12  +/- 1e-4,
14  +/- 1e-5,
16  +/- 1e-6,
...
28  +/- 3e-11

tampaknya mendapatkan urutan besarnya untuk setiap 2 ditambahkan

BTW: tabel kesalahan di atas tidak dihasilkan melalui pengambilan sampel tetapi oleh relasi perulangan berikut:

p[x,n]adalah nomor cara output=xdapat terjadi diberikan npanggilan ke rand5.

  p[1,1] ... p[5,1] = 1
  p[6,1] ... p[7,1] = 0

  p[1,n] = p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1]
  p[2,n] = p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1]
  p[3,n] = p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1]
  p[4,n] = p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1]
  p[5,n] = p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1]
  p[6,n] = p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1]
  p[7,n] = p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1]
BCS
sumber
8
Ini bukan distribusi yang seragam. Sangat dekat dengan seragam, tetapi tidak seragam sempurna.
Adam Rosenfield
Ah! Dadu dan 7's. Jika Anda akan mengatakan saya salah, Anda tidak harus meninggalkan buktinya sebagai latihan untuk pembaca.
BCS
45
Bukti bahwa itu tidak seragam adalah sederhana: ada 5 ^ 7 kemungkinan cara keacakan, dan karena 5 ^ 7 bukan kelipatan dari 7, tidak mungkin bahwa semua 7 jumlah kemungkinan sama. (Pada dasarnya, itu bermuara pada 7 yang relatif prima ke 5, atau ekuivalen 1/7 tidak menjadi desimal terminasi dalam basis 5.) Bahkan itu bahkan bukan "yang paling seragam" yang mungkin dalam batasan ini: perhitungan langsung menunjukkan bahwa dari 5 ^ 7 = 78125 jumlah, berapa kali Anda mendapatkan nilai 1 hingga 7 adalah {1: 11145, 2: 11120, 3: 11120, 4: 11145, 5: 11190, 6: 11215, 6: 11215, 7: 11190}.
ShreevatsaR
@ShreevatsaR Jadi bagaimana jika alih-alih mengambil jumlah rand5 () tujuh kali, kami melakukannya 5 * 7 mengambil - bukankah itu berhasil? 35 ^ 7% 7 = 35 ^ 5% 7 = 0.
kba
4
@KristianAntonsen: Berapa kali Anda melakukan rand5 (), Anda tidak akan mendapatkan distribusi yang seragam. Jika Anda melakukannya N kali, ada 5 ^ N output yang mungkin, yang tidak habis dibagi 7. (Jika Anda melakukannya 35 kali, ada 5 ^ 35, bukan 35 ^ 7.) Anda akan semakin dekat dan dekat dengan seragam dengan jumlah panggilan yang lebih besar yang Anda gunakan (dan itu bisa berupa angka apa pun, tidak harus dibagi oleh 7), tetapi IMHO alih-alih menggunakan jumlah panggilan yang sangat besar untuk rand (), Anda juga dapat menggunakan probabilistik algoritma dalam jawaban teratas, yang memberikan distribusi seragam yang tepat dan jumlah panggilan yang diharapkan untuk rand () kecil.
ShreevatsaR
15
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}
Nescio
sumber
2
Solusi yang benar, membuat rata-rata 30/7 = 4,29 panggilan ke rand5 () per panggilan ke rand7 ().
Adam Rosenfield
3
Perlu dibiarkan bergeser agar algoritme berfungsi:ans += (r < 3) << i
woolfie
13

Berikut ini menghasilkan distribusi seragam pada {1, 2, 3, 4, 5, 6, 7} menggunakan generator nomor acak yang menghasilkan distribusi seragam pada {1, 2, 3, 4, 5}. Kode ini berantakan, tetapi logikanya jelas.

public static int random_7(Random rg) {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + SimulateFairCoin(rg);
        }
    }
    return returnValue;
}

private static int SimulateFairCoin(Random rg) {
    while (true) {
        int flipOne = random_5_mod_2(rg);
        int flipTwo = random_5_mod_2(rg);

        if (flipOne == 0 && flipTwo == 1) {
            return 0;
        }
        else if (flipOne == 1 && flipTwo == 0) {
            return 1;
        }
    }
}

private static int random_5_mod_2(Random rg) {
    return random_5(rg) % 2;
}

private static int random_5(Random rg) {
    return rg.Next(5) + 1;
}    
jason
sumber
2
Solusi yang benar (yang menempatkan Anda di depan kurva), meskipun tidak terlalu efisien. Ini membuat rata-rata 25/6 = 4,17 panggilan ke random_5_mod_2 per flip koin adil, untuk total rata-rata 100/7 = 14,3 panggilan ke random_5 () per panggilan ke random_7 ().
Adam Rosenfield
Keuntungan dari solusi ini dibandingkan yang lain adalah bahwa hal itu dapat dengan mudah diperluas untuk menghasilkan jangkauan didistribusikan secara seragam. Cukup pilih masing-masing bit secara acak, gulirkan kembali pada nilai yang tidak valid (seperti nilai 0 dalam solusi kami saat ini yang menghasilkan 8 angka).
DenTheMan
1
mungkin loop tak terbatas, dll.
robermorales
1
@robermorales: Sangat tidak mungkin.
jason
13
int rand7() {
    int value = rand5()
              + rand5() * 2
              + rand5() * 3
              + rand5() * 4
              + rand5() * 5
              + rand5() * 6;
    return value%7;
}

Berbeda dengan solusi yang dipilih, algoritma akan berjalan dalam waktu yang konstan. Namun itu membuat 2 lebih banyak panggilan ke rand5 daripada rata-rata waktu berjalan dari solusi yang dipilih.

Perhatikan bahwa generator ini tidak sempurna (angka 0 memiliki peluang 0,0064% lebih banyak daripada angka lainnya), tetapi untuk sebagian besar tujuan praktis jaminan waktu konstan mungkin lebih besar daripada ketidakakuratan ini.

Penjelasan

Solusi ini berasal dari kenyataan bahwa angka 15.624 dapat dibagi oleh 7 dan dengan demikian jika kita dapat secara acak dan seragam menghasilkan angka dari 0 hingga 15.624 dan kemudian mengambil mod 7 kita bisa mendapatkan generator rand7 yang hampir seragam. Angka dari 0 hingga 15.624 dapat dihasilkan secara seragam dengan menggulirkan rand5 6 kali dan menggunakannya untuk membentuk digit nomor basis 5 sebagai berikut:

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

Namun, properti mod 7 memungkinkan kita untuk menyederhanakan persamaan:

5^5 = 3 mod 7
5^4 = 2 mod 7
5^3 = 6 mod 7
5^2 = 4 mod 7
5^1 = 5 mod 7

Begitu

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

menjadi

rand5 * 3 + rand5 * 2 + rand5 * 6 + rand5 * 4 + rand5 * 5 + rand5

Teori

Angka 15.624 tidak dipilih secara acak, tetapi dapat ditemukan menggunakan teorema kecil fermat, yang menyatakan bahwa jika p adalah bilangan prima maka

a^(p-1) = 1 mod p

Jadi ini memberi kita,

(5^6)-1 = 0 mod 7

(5 ^ 6) -1 sama dengan

4 * 5^5 + 4 * 5^4 + 4 * 5^3 + 4 * 5^2 + 4 * 5 + 4

Ini adalah angka dalam bentuk basis 5 dan dengan demikian kita dapat melihat bahwa metode ini dapat digunakan untuk beralih dari generator nomor acak apa pun ke generator nomor acak lainnya. Meskipun bias kecil terhadap 0 selalu diperkenalkan saat menggunakan eksponen p-1.

Untuk menggeneralisasikan pendekatan ini dan agar lebih akurat kita dapat memiliki fungsi seperti ini:

def getRandomconverted(frm, to):
    s = 0
    for i in range(to):
        s += getRandomUniform(frm)*frm**i
    mx = 0
    for i in range(to):
        mx = (to-1)*frm**i 
    mx = int(mx/to)*to # maximum value till which we can take mod
    if s < mx:
        return s%to
    else:
        return getRandomconverted(frm, to)
Thirlan
sumber
1
Generator ini akurat, tetapi tidak seragam sempurna. Untuk melihat ini, pertimbangkan fakta bahwa generator yang seragam di [0,15624] memiliki 15625 hasil yang mungkin, yang tidak dapat habis oleh 7. Ini menimbulkan bias ke angka 0 (yang memiliki peluang 2233/15625, dan yang lainnya hanya 2232/15625). Bagaimanapun, ketika menggunakan teorema kecil Fermat mungkin tampak benar pada pandangan pertama, dikatakan bahwa (5 ^ 6)% 7 = 1, dan tidak (5 ^ 6)% 7 = 0. Yang terakhir jelas tidak mungkin untuk eksponen apa pun karena 5 dan 7 keduanya bilangan prima. Saya pikir ini masih merupakan solusi yang dapat diterima, dan saya telah mengedit posting Anda untuk mencerminkan hal ini.
penerbang
12

Apakah masalah pekerjaan rumah diperbolehkan di sini?

Fungsi ini tidak kasar "basis 5" matematika untuk menghasilkan angka antara 0 dan 6.

function rnd7() {
    do {
        r1 = rnd5() - 1;
        do {
            r2=rnd5() - 1;
        } while (r2 > 1);
        result = r2 * 5 + r1;
    } while (result > 6);
    return result + 1;
}
Will Hartung
sumber
3
Solusi yang benar (yang menempatkan Anda di depan kurva), meskipun tidak terlalu efisien. Ini membuat rata-rata 5 panggilan ke rnd5 () untuk setiap panggilan ke rnd7 ().
Adam Rosenfield
butuh penjelasan lebih lanjut pls
Barry
1
@ Larry - Pertama, Anda tidak bisa hanya menambahkan dua angka acak bersama-sama, Anda tidak mendapatkan solusi linier (pertimbangkan sepasang dadu). Sekarang pertimbangkan "Basis 5": 00, 01, 02, 03, 04, 10, 11. Bahwa 0-6 di basis 5. Jadi, kita hanya perlu menghasilkan 2 digit dari nomor basis 5, dan menambahkannya hingga kita dapatkan satu yang ada dalam jangkauan. Itulah yang dilakukan r2 * 5 + r1. Lingkaran r2> 1 ada di sana karena kita tidak akan pernah menginginkan angka> 1 yang tinggi.
Will Hartung
Solusi ini tidak menghasilkan distribusi yang seragam. Angka 1 dan 7 hanya dapat dihasilkan dalam satu cara, tetapi 2 hingga 6 masing-masing dapat dihasilkan dalam dua cara: dengan r1 sama dengan angka minus 1 dan r2 sama dengan 0 atau dengan r1 sama dengan angka minus 2 dan r2 sama dengan 1. Dengan demikian 2 hingga 6 akan dikembalikan rata-rata dua kali lebih sering dari 1 atau 7.
Ted Hopp
12

Jika kita mempertimbangkan kendala tambahan untuk mencoba memberikan jawaban yang paling efisien yaitu yang memberikan aliran input,, Ibilangan bulat yang terdistribusi seragam mdari 1-5 output aliran O, bilangan bulat terdistribusi seragam dari 1-7 dari relatif panjang terpanjang untuk m, katakan L(m).

Cara paling sederhana untuk menganalisis ini adalah dengan memperlakukan aliran I dan Osebagai bilangan 5-ary dan 7-ary masing-masing. Ini dicapai dengan ide jawaban utama untuk mengambil aliran a1, a2, a3,... -> a1+5*a2+5^2*a3+..dan juga untuk aliran O.

Kemudian jika kita mengambil bagian dari aliran input panjang di m choose n s.t. 5^m-7^n=cmana c>0dan sekecil mungkin. Lalu ada peta yang seragam dari aliran input panjang m ke bilangan bulat dari 1ke 5^mdan peta seragam lainnya dari bilangan bulat dari 1 ke 7^naliran keluaran panjang n di mana kita mungkin harus kehilangan beberapa kasus dari aliran input ketika bilangan bulat dipetakan melebihi 7^n.

Jadi ini memberikan nilai L(m)sekitar m (log5/log7)yang kira-kira .82m.

Kesulitan dengan analisis di atas adalah persamaan 5^m-7^n=cyang tidak mudah untuk memecahkan persis dan kasus di mana nilai seragam dari 1ke 5^mmelebihi 7^ndan kami kehilangan efisiensi.

Pertanyaannya adalah seberapa dekat dengan nilai terbaik m (log5 / log7) dapat dicapai. Sebagai contoh ketika angka ini mendekati mendekati integer, dapatkah kita menemukan cara untuk mencapai jumlah integral dari nilai output?

Jika 5^m-7^n=ckemudian dari aliran input, kami secara efektif menghasilkan angka acak seragam dari 0ke (5^m)-1dan tidak menggunakan nilai lebih tinggi dari 7^n. Namun nilai-nilai ini dapat diselamatkan dan digunakan lagi. Mereka secara efektif menghasilkan urutan angka yang seragam dari 1 hingga 5^m-7^n. Jadi kita dapat mencoba menggunakan ini dan mengubahnya menjadi angka 7-ary sehingga kita dapat membuat lebih banyak nilai output.

Jika kita biarkan T7(X)menjadi panjang rata-rata dari urutan output random(1-7)bilangan bulat yang berasal dari ukuran input yang seragam X, dan dengan asumsi itu 5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7.

Maka T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)karena kita memiliki panjang tanpa urutan dengan probabilitas 7 ^ n0 / 5 ^ m dengan sisa panjang 5^m-7^n0dengan probabilitas (5^m-7^n0)/5^m).

Jika kami terus mengganti, kami memperoleh:

T7(5^m) = n0x7^n0/5^m + n1x7^n1/5^m + ... + nrx7^nr/5^m  = (n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/5^m

Karenanya

L(m)=T7(5^m)=(n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/(7^n0+7^n1+7^n2+...+7^nr+s)

Cara lain untuk mengatakan ini adalah:

If 5^m has 7-ary representation `a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r
Then L(m) = (a1*7 + 2a2*7^2 + 3a3*7^3+...+rar*7^r)/(a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r)

Kasing terbaik adalah yang asli saya di atas di mana 5^m=7^n+s, di mana s<7.

Lalu T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)seperti sebelumnya.

Kasus terburuk adalah ketika kita hanya dapat menemukan k dan st 5 ^ m = kx7 + s.

Then T7(5^m) = 1x(k.7)/(k.7+s) = 1+o(1)

Kasus-kasus lain ada di suatu tempat di antara. Akan menarik untuk melihat seberapa baik kita dapat melakukan untuk m yang sangat besar, yaitu seberapa baik kita bisa mendapatkan istilah kesalahan:

T7(5^m) = m (Log5/Log7)+e(m)

Tampaknya mustahil untuk mencapainya e(m) = o(1)secara umum tetapi semoga kita bisa membuktikannya e(m)=o(m).

Semuanya kemudian bertumpu pada distribusi digit 7-ary 5^muntuk berbagai nilai m.

Saya yakin ada banyak teori di luar sana yang membahas hal ini. Saya mungkin akan melihat dan melaporkan kembali di beberapa titik.

Ivan
sumber
+2 (jika saya bisa) - ini adalah satu-satunya jawaban yang baik (bukan hanya memadai). Anda mendapatkan jawaban terbaik kedua yang sesuai dengan bilangan bulat 32 bit.
Rex Kerr
10

Berikut ini adalah implementasi Python implementasi jawaban Adam .

import random

def rand5():
    return random.randint(1, 5)

def rand7():
    while True:
        r = 5 * (rand5() - 1) + rand5()
        #r is now uniformly random between 1 and 25
        if (r <= 21):
            break
    #result is now uniformly random between 1 and 7
    return r % 7 + 1

Saya suka melempar algoritma yang saya lihat ke Python sehingga saya bisa bermain-main dengan mereka, saya pikir saya akan mempostingnya di sini dengan harapan itu berguna bagi seseorang di luar sana, bukan karena butuh waktu lama untuk dilempar bersama.

James McMahon
sumber
Tidak, itu sangat berbeda dari jawaban saya. Anda mengulang sebanyak 21 kali dan membuang hasil 20 iterasi pertama. Anda juga menggunakan rand4 () dan rand5 () sebagai input, yang jelas-jelas melanggar aturan hanya menggunakan rand5 (). Akhirnya, Anda menghasilkan distribusi yang tidak seragam.
Adam Rosenfield
Maaf soal itu. Saya sangat lelah ketika saya melihat pertanyaan ini, cukup lelah sehingga saya salah membaca algoritma Anda. Saya benar-benar melemparkannya ke Python karena saya tidak mengerti mengapa Anda berulang kali 21 kali. Lebih masuk akal sekarang. Saya melakukan hal acak. Merkint (1, 4) sebagai steno tetapi saya kira Anda benar, itu bertentangan dengan semangat pertanyaan. Saya sudah memperbaiki kode.
James McMahon
@robermorales - Seperti yang dijelaskan Adam Rosenfeld dalam jawabannya , setiap solusi yang memberikan distribusi seragam yang benar pada [1, 7] akan melibatkan semacam loop terima-tolak yang berpotensi tak terbatas. (Namun, jika rand5()PRNG yang layak, maka loop tidak akan terbatas karena pada akhirnya 5*(rand5() - 1) + rand5()pasti akan menjadi <= 21.)
Ted Hopp
10

Mengapa tidak melakukannya secara sederhana?

int random7() {
  return random5() + (random5() % 3);
}

Peluang mendapatkan 1 dan 7 dalam solusi ini lebih rendah karena modulo, namun, jika Anda hanya menginginkan solusi yang cepat dan mudah dibaca, inilah cara yang harus ditempuh.

Sokongan
sumber
13
Ini tidak menghasilkan distribusi yang seragam. Ini menghasilkan angka 0-6 dengan probabilitas 2/25, 4/25, 5/25, 5/25, 5/25, 3/25, 1/25, seperti yang dapat diverifikasi dengan menghitung semua 25 hasil yang mungkin.
Adam Rosenfield
8

Dengan asumsi bahwa rand (n) di sini berarti "bilangan bulat acak dalam distribusi seragam dari 0 hingga n-1 ", inilah contoh kode menggunakan randint Python, yang memiliki efek itu. Hanya menggunakan randint (5) , dan konstanta, untuk menghasilkan efek randint (7) . Sebenarnya agak konyol

from random import randint
sum = 7
while sum >= 7:
    first = randint(0,5)   
    toadd = 9999
    while toadd>1:
        toadd = randint(0,5)
    if toadd:
        sum = first+5
    else:
        sum = first

assert 7>sum>=0 
print sum
Joshua Fox
sumber
1
@robermorales Karena Python tidak punya do ... while. Bisa jadi 1337, atau 12345, atau nomor apa pun> 1.
tckmn
8

Premis di balik jawaban yang benar Adam Rosenfield adalah:

  • x = 5 ^ n (dalam kasusnya: n = 2)
  • memanipulasi panggilan n rand5 untuk mendapatkan nomor y dalam jangkauan [1, x]
  • z = ((int) (x / 7)) * 7
  • jika y> z, coba lagi. lain kembalikan y% 7 +1

Ketika n sama dengan 2, Anda memiliki 4 kemungkinan membuang: y = {22, 23, 24, 25}. Jika Anda menggunakan n sama dengan 6, Anda hanya memiliki 1 membuang: y = {15625}.

5 ^ 6 = 15625
7 * 2232 = 15624

Anda menelepon rand5 lebih banyak. Namun, Anda memiliki peluang jauh lebih rendah untuk mendapatkan nilai throw-away (atau infinite loop). Jika ada cara untuk tidak mendapatkan nilai membuang yang mungkin untuk y, saya belum menemukannya.

Dinah
sumber
1
Terbukti tidak ada kasus tanpa nilai throwaway - jika tidak ada throwaway, 5 ^ n dan 7 ^ m akan memiliki faktor yang sama. Tapi mereka (kekuatan) bilangan prima, jadi mereka tidak.
Rex Kerr
8

Inilah jawaban saya:

static struct rand_buffer {
  unsigned v, count;
} buf2, buf3;

void push (struct rand_buffer *buf, unsigned n, unsigned v)
{
  buf->v = buf->v * n + v;
  ++buf->count;
}

#define PUSH(n, v)  push (&buf##n, n, v)

int rand16 (void)
{
  int v = buf2.v & 0xf;
  buf2.v >>= 4;
  buf2.count -= 4;
  return v;
}

int rand9 (void)
{
  int v = buf3.v % 9;
  buf3.v /= 9;
  buf3.count -= 2;
  return v;
}

int rand7 (void)
{
  if (buf3.count >= 2) {
    int v = rand9 ();

    if (v < 7)
      return v % 7 + 1;

    PUSH (2, v - 7);
  }

  for (;;) {
    if (buf2.count >= 4) {
      int v = rand16 ();

      if (v < 14) {
        PUSH (2, v / 7);
        return v % 7 + 1;
      }

      PUSH (2, v - 14);
    }

    // Get a number between 0 & 25
    int v = 5 * (rand5 () - 1) + rand5 () - 1;

    if (v < 21) {
      PUSH (3, v / 7);
      return v % 7 + 1;
    }

    v -= 21;
    PUSH (2, v & 1);
    PUSH (2, v >> 1);
  }
}

Ini sedikit lebih rumit daripada yang lain, tapi saya percaya ini meminimalkan panggilan ke rand5. Seperti dengan solusi lain, ada kemungkinan kecil bahwa itu bisa berulang untuk waktu yang lama.

Chris Suter
sumber
Ini menghasilkan distribusi yang tidak jauh berbeda dari solusi lain tetapi memiliki kelemahan tambahan menjadi tidak perlu rumit. Ini juga menderita dari kemungkinan loop-selamanya non-deterministik terbukti salah jika angka benar-benar acak. Saya masih berpikir yang menghasilkan distribusi yang sedikit kurang seragam (meskipun masih jauh lebih dari cukup) tetapi menjamin perilaku deterministik lebih baik.
paxdiablo
@ Max: Tolong beri tahu saya bagaimana ini menghasilkan distribusi yang tidak seragam. Analisis saya terhadap kode, serta pengujian saya sendiri, menunjukkan bahwa ini menghasilkan distribusi yang seragam. Seperti yang telah kita bahas sebelumnya, tidak mungkin untuk keduanya menghasilkan distribusi seragam yang sempurna dan memiliki batas atas waktu berjalan dijamin konstan waktu.
Adam Rosenfield
7

Sederhana dan efisien:

int rand7 ( void )
{
    return 4; // this number has been calculated using
              // rand5() and is in the range 1..7
}

(Terinspirasi oleh Apa kartun "programmer" favorit Anda? ).

chiccodoro
sumber
6

Selama tidak ada tujuh kemungkinan yang tersisa untuk dipilih, gambarkan nomor acak lain, yang mengalikan jumlah kemungkinan dengan lima. Dalam Perl:

$num = 0;
$possibilities = 1;

sub rand7
{
  while( $possibilities < 7 )
  {
    $num = $num * 5 + int(rand(5));
    $possibilities *= 5;
  }
  my $result = $num % 7;
  $num = int( $num / 7 );
  $possibilities /= 7;
  return $result;
}
pengguna223264
sumber
distribusi Anda tidak seragam, setidaknya pada panggilan pertama. Memang, $possibilitiesharus selalu bertambah menjadi 25 untuk keluar dari loop dan kembali. Jadi, hasil pertama Anda adalah [0-124] % 7, yang tidak terdistribusi secara merata karena 125 % 7 != 0(ini sebenarnya 6).
bernard paulus
6

Saya tidak suka rentang mulai dari 1, jadi saya akan mulai dari 0 :-)

unsigned rand5()
{
    return rand() % 5;
}

unsigned rand7()
{
    int r;

    do
    {
        r =         rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
    } while (r > 15623);

    return r / 2232;
}
fredoverflow
sumber
Ini adalah pemenang. Ini menghasilkan semua 7 hasil dengan probabilitas yang sama. from collections import defaultdict def r7(n): if not n: yield [] else: for i in range(1, 6): for j in r7(n-1): yield [i] + j def test_r7(): d = defaultdict(int) for x in r7(6): s = (((((((((x[5] * 5) + x[4]) * 5) + x[3]) * 5) + x[2]) * 5) + x[1]) * 5) + x[0] if s <= 15623: d[s % 7] += 1 print d
hughdbrown
5

Di sana Anda pergi, distribusi seragam dan nol panggilan Rand5.

def rand7:
    seed += 1
    if seed >= 7:
        seed = 0
    yield seed

Perlu mengatur benih sebelumnya.

Kugel
sumber
5

Saya tahu itu telah dijawab, tetapi apakah ini tampaknya berhasil, tetapi saya tidak dapat memberi tahu Anda jika ada bias. 'Pengujian' saya menunjukkan itu, setidaknya, masuk akal.

Mungkin Adam Rosenfield akan berbaik hati berkomentar?

Ide saya (naif?) Adalah ini:

Akumulasi rand5 sampai ada bit acak yang cukup untuk membuat rand7. Ini memakan waktu paling banyak 2 rand5. Untuk mendapatkan nomor rand7 saya menggunakan nilai akumulasi mod 7.

Untuk menghindari overloading akumulator, dan karena akumulator adalah mod 7 maka saya mengambil mod 7 dari akumulator:

(5a + rand5) % 7 = (k*7 + (5a%7) + rand5) % 7 = ( (5a%7) + rand5) % 7

Fungsi rand7 () mengikuti:

(Saya membiarkan rentang rand5 menjadi 0-4 dan rand7 juga 0-6.)

int rand7(){
  static int    a=0;
  static int    e=0;
  int       r;
  a = a * 5 + rand5();
  e = e + 5;        // added 5/7ths of a rand7 number
  if ( e<7 ){
    a = a * 5 + rand5();
    e = e + 5;  // another 5/7ths
  }
  r = a % 7;
  e = e - 7;        // removed a rand7 number
  a = a % 7;
  return r;
}

Sunting: Hasil tambahan untuk 100 juta percobaan.

Fungsi 'Real' rand mod 5 atau 7

rand5: avg = 1.999802 0: 20003944 1: 19999889 2: 20003690 3: 19996938 4: 19995539 rand7: avg = 3.000111 0: 14282851 1: 14282851 1: 14282879 2: 14284554 3: 14288554 4: 14292388 5: 142292388 5: 14229853836 6: 14280046

Rand7 saya

Rata-rata terlihat ok dan distribusi angka juga terlihat ok.

Randand: rata-rata = 3,000080 0: 14288793 1: 14280135 2: 14287848 3: 14285277 4: 14286341 5: 14278663 6: 14292943

philcolbourn
sumber
Anda mungkin harus melihat korelasi berurutan. Saya pikir jika Anda mengambil pasangan berturut-turut (setiap nomor "acak" dipasangkan dengan pendahulunya) maka Anda mungkin menemukan hal-hal yang mengejutkan. Anda belum menjelaskan MENGAPA harus menjaga seragam distribusi, bagaimanapun juga. Program yang bekerja biasanya harus dimulai dengan penjelasan mengapa ia bekerja.
Ian
Apakah korelasi berurutan berlaku untuk banyak solusi ini?
philcolbourn
Apakah korelasi berurutan berlaku untuk banyak solusi ini? Sudah lama sejak saya mencoba ini dan saya pikir saya menjelaskannya. Melihat itu sekarang, sepertinya saya mengumpulkan bit acak di kolam dari rand5, memastikan cukup telah terakumulasi sebelum menarik cukup untuk membuat nomor rand7 dan memastikan saya tidak meluap akumulator saya.
philcolbourn
4

Ada beberapa algoritma elegan yang dikutip di atas, tetapi inilah salah satu cara untuk mendekatinya, meskipun mungkin bundaran. Saya mengasumsikan nilai yang dihasilkan dari 0.

R2 = generator angka acak yang memberikan nilai kurang dari 2 (ruang sampel = {0, 1})
R8 = generator angka acak yang memberikan nilai kurang dari 8 (ruang sampel = {0, 1, 2, 3, 4, 5, 5, 6, 7 })

Untuk menghasilkan R8 dari R2, Anda akan menjalankan R2 tiga kali, dan menggunakan hasil gabungan dari semua 3 berjalan sebagai angka biner dengan 3 digit. Berikut adalah rentang nilai ketika R2 dijalankan tiga kali:

0 0 0 -> 0
.
.
1 1 1 -> 7

Sekarang untuk menghasilkan R7 dari R8, kita cukup menjalankan R7 lagi jika mengembalikan 7:

int R7() {
  do {
    x = R8();
  } while (x > 6)
  return x;
}

Solusi bundaran adalah untuk menghasilkan R2 dari R5 (sama seperti kita menghasilkan R7 dari R8), kemudian R8 dari R2 dan kemudian R7 dari R8.

Ashwin
sumber
seperti sejumlah lainnya, pendekatan ini bisa memakan waktu lama secara sewenang-wenang per panggilan R7, karena Anda bisa mendapatkan string panjang tujuh dari R8.
Alex North-Keys
4

Berikut adalah solusi yang sepenuhnya sesuai dengan bilangan bulat dan berada dalam kisaran 4% dari yang optimal (yaitu menggunakan 1.26 angka acak dalam {0..4} untuk setiap orang di {0..6}). Kode dalam Scala, tetapi matematika harus cukup jelas dalam bahasa apa pun: Anda memanfaatkan fakta bahwa 7 ^ 9 + 7 ^ 8 sangat dekat dengan 5 ^ 11. Jadi Anda memilih angka 11 digit pada basis 5, dan kemudian menafsirkannya sebagai angka 9 digit pada basis 7 jika berada dalam kisaran (memberikan 9 basis 7 angka), atau sebagai angka 8 digit jika melebihi angka 9 digit, dll. .:

abstract class RNG {
  def apply(): Int
}

class Random5 extends RNG {
  val rng = new scala.util.Random
  var count = 0
  def apply() = { count += 1 ; rng.nextInt(5) }
}

class FiveSevener(five: RNG) {
  val sevens = new Array[Int](9)
  var nsevens = 0
  val to9 = 40353607;
  val to8 = 5764801;
  val to7 = 823543;
  def loadSevens(value: Int, count: Int) {
    nsevens = 0;
    var remaining = value;
    while (nsevens < count) {
      sevens(nsevens) = remaining % 7
      remaining /= 7
      nsevens += 1
    }
  }
  def loadSevens {
    var fivepow11 = 0;
    var i=0
    while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
    if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
    fivepow11 -= to9
    if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
    fivepow11 -= to8
    if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
    else loadSevens
  }
  def apply() = {
    if (nsevens==0) loadSevens
    nsevens -= 1
    sevens(nsevens)
  }
}

Jika Anda menempelkan tes ke interpreter (REPL sebenarnya), Anda mendapatkan:

scala> val five = new Random5
five: Random5 = Random5@e9c592

scala> val seven = new FiveSevener(five)
seven: FiveSevener = FiveSevener@143c423

scala> val counts = new Array[Int](7)
counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)

scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
i: Int = 100000000

scala> counts
res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
14289332, 14283684)

scala> five.count
res1: Int = 125902876

Distribusinya bagus dan rata (dalam kisaran 10k 1/7 dari 10 ^ 8 di setiap nampan, seperti yang diharapkan dari distribusi kira-kira Gaussian).

Rex Kerr
sumber
3

Dengan menggunakan total bergulir , Anda dapat keduanya

  • mempertahankan distribusi yang sama; dan
  • tidak harus mengorbankan elemen apa pun dalam urutan acak.

Kedua masalah ini adalah masalah dengan rand(5)+rand(5)...solusi tipe sederhana . Kode Python berikut ini menunjukkan cara mengimplementasikannya (sebagian besar membuktikan distribusi).

import random
x = []
for i in range (0,7):
    x.append (0)
t = 0
tt = 0
for i in range (0,700000):
    ########################################
    #####            qq.py             #####
    r = int (random.random () * 5)
    t = (t + r) % 7
    ########################################
    #####       qq_notsogood.py        #####
    #r = 20
    #while r > 6:
        #r =     int (random.random () * 5)
        #r = r + int (random.random () * 5)
    #t = r
    ########################################
    x[t] = x[t] + 1
    tt = tt + 1
high = x[0]
low = x[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, x[i], 100.0 * x[i] / tt)
    if x[i] < low:
        low = x[i]
    if x[i] > high:
        high = x[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / tt)

Dan output ini menunjukkan hasilnya:

pax$ python qq.py
0:   99908 14.27257
1:  100029 14.28986
2:  100327 14.33243
3:  100395 14.34214
4:   99104 14.15771
5:   99829 14.26129
6:  100408 14.34400
Variation = 1304 (0.18629%)

pax$ python qq.py
0:   99547 14.22100
1:  100229 14.31843
2:  100078 14.29686
3:   99451 14.20729
4:  100284 14.32629
5:  100038 14.29114
6:  100373 14.33900
Variation = 922 (0.13171%)

pax$ python qq.py
0:  100481 14.35443
1:   99188 14.16971
2:  100284 14.32629
3:  100222 14.31743
4:   99960 14.28000
5:   99426 14.20371
6:  100439 14.34843
Variation = 1293 (0.18471%)

Sederhana rand(5)+rand(5), mengabaikan kasus-kasus di mana ini mengembalikan lebih dari 6 memiliki variasi khas 18%, 100 kali lipat dari metode yang ditunjukkan di atas:

pax$ python qq_notsogood.py
0:   31756 4.53657
1:   63304 9.04343
2:   95507 13.64386
3:  127825 18.26071
4:  158851 22.69300
5:  127567 18.22386
6:   95190 13.59857
Variation = 127095 (18.15643%)

pax$ python qq_notsogood.py
0:   31792 4.54171
1:   63637 9.09100
2:   95641 13.66300
3:  127627 18.23243
4:  158751 22.67871
5:  126782 18.11171
6:   95770 13.68143
Variation = 126959 (18.13700%)

pax$ python qq_notsogood.py
0:   31955 4.56500
1:   63485 9.06929
2:   94849 13.54986
3:  127737 18.24814
4:  159687 22.81243
5:  127391 18.19871
6:   94896 13.55657
Variation = 127732 (18.24743%)

Dan, atas saran Nixuz, saya sudah membersihkan skrip sehingga Anda bisa mengekstrak dan menggunakan rand7...barang - barang:

import random

# rand5() returns 0 through 4 inclusive.

def rand5():
    return int (random.random () * 5)

# rand7() generator returns 0 through 6 inclusive (using rand5()).

def rand7():
    rand7ret = 0
    while True:
        rand7ret = (rand7ret + rand5()) % 7
        yield rand7ret

# Number of test runs.

count = 700000

# Work out distribution.

distrib = [0,0,0,0,0,0,0]
rgen =rand7()
for i in range (0,count):
    r = rgen.next()
    distrib[r] = distrib[r] + 1

# Print distributions and calculate variation.

high = distrib[0]
low = distrib[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, distrib[i], 100.0 * distrib[i] / count)
    if distrib[i] < low:
        low = distrib[i]
    if distrib[i] > high:
        high = distrib[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / count)
paxdiablo
sumber
2
Err, izinkan saya ulangi. Mengingat bahwa x tertentu diproduksi di beberapa titik dalam urutan, hanya 5 dari 7 angka yang dapat diproduksi untuk angka berikutnya dalam urutan. RNG yang benar akan membuat semua sampel independen satu sama lain, tetapi dalam hal ini jelas tidak.
Adam Rosenfield
3
Memang benar bahwa pertanyaan awal tidak menentukan apakah fungsi input dan output menghasilkan sampel yang independen dan terdistribusi secara identik (iid), tapi saya pikir itu adalah harapan yang masuk akal bahwa jika input rand5 () adalah iid, maka output rand7 () juga harus iid. Jika menurut Anda itu tidak masuk akal, bersenang-senanglah menggunakan RNG non-iid Anda.
Adam Rosenfield
1
Jadi, apa kata dari para ahli matematika di universitas?
Adam Rosenfield
1
Solusi ini jelas rusak. Sudah jelas bahwa Anda perlu menelepon rand5 (rata-rata) lebih dari sekali per panggilan ke rand7 dan solusi ini tidak. Karena itu hasilnya tidak dapat acak dengan definisi acak apa pun yang acak.
Chris Suter
1
@ Max Pada setiap iterasi dari fungsi Anda, itu hanya dapat mengembalikan satu dari lima nilai yang berbeda (meskipun dalam rentang 0-6). Iterasi pertama hanya dapat mengembalikan angka di kisaran 0-4. Jadi, harus jelas bahwa sementara fungsi Anda mungkin memiliki distribusi seragam, sampel tidak independen yaitu mereka berkorelasi yang bukan sesuatu yang Anda inginkan dalam generator angka acak.
Chris Suter
3

Jawaban ini lebih merupakan eksperimen dalam memperoleh entropi sebanyak mungkin dari fungsi Rand5. Oleh karena itu agak tidak jelas dan hampir pasti jauh lebih lambat daripada implementasi lainnya.

Dengan asumsi distribusi seragam dari 0-4 dan menghasilkan distribusi seragam dari 0-6:

public class SevenFromFive
{
  public SevenFromFive()
  {
    // this outputs a uniform ditribution but for some reason including it 
    // screws up the output distribution
    // open question Why?
    this.fifth = new ProbabilityCondensor(5, b => {});
    this.eigth = new ProbabilityCondensor(8, AddEntropy);
  } 

  private static Random r = new Random();
  private static uint Rand5()
  {
    return (uint)r.Next(0,5);
  }

  private class ProbabilityCondensor
  {
    private readonly int samples;
    private int counter;
    private int store;
    private readonly Action<bool> output;

    public ProbabilityCondensor(int chanceOfTrueReciprocal,
      Action<bool> output)
    {
      this.output = output;
      this.samples = chanceOfTrueReciprocal - 1;  
    }

    public void Add(bool bit)
    {
      this.counter++;
      if (bit)
        this.store++;   
      if (counter == samples)
      {
        bool? e;
        if (store == 0)
          e = false;
        else if (store == 1)
          e = true;
        else
          e = null;// discard for now       
        counter = 0;
        store = 0;
        if (e.HasValue)
          output(e.Value);
      }
    }
  }

  ulong buffer = 0;
  const ulong Mask = 7UL;
  int bitsAvail = 0;
  private readonly ProbabilityCondensor fifth;
  private readonly ProbabilityCondensor eigth;

  private void AddEntropy(bool bit)
  {
    buffer <<= 1;
    if (bit)
      buffer |= 1;      
    bitsAvail++;
  }

  private void AddTwoBitsEntropy(uint u)
  {
    buffer <<= 2;
    buffer |= (u & 3UL);    
    bitsAvail += 2;
  }

  public uint Rand7()
  {
    uint selection;   
    do
    {
      while (bitsAvail < 3)
      {
        var x = Rand5();
        if (x < 4)
        {
          // put the two low order bits straight in
          AddTwoBitsEntropy(x);
          fifth.Add(false);
        }
        else
        { 
          fifth.Add(true);
        }
      }
      // read 3 bits
      selection = (uint)((buffer & Mask));
      bitsAvail -= 3;     
      buffer >>= 3;
      if (selection == 7)
        eigth.Add(true);
      else
        eigth.Add(false);
    }
    while (selection == 7);   
    return selection;
  }
}

Jumlah bit yang ditambahkan ke buffer per panggilan ke Rand5 saat ini 4/5 * 2 jadi 1,6. Jika nilai probabilitas 1/5 disertakan yang meningkat sebesar 0,05 maka 1,65 tetapi lihat komentar dalam kode di mana saya harus menonaktifkan ini.

Bit dikonsumsi melalui panggilan ke Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
Ini 3 + 3/8 + 3/64 + 3/512 ... jadi sekitar 3,42

Dengan mengekstraksi informasi dari tujuh, saya mendapatkan kembali 1/8 * 1/7 bit per panggilan jadi sekitar 0,018

Ini memberikan konsumsi bersih 3,4 bit per panggilan yang berarti rasionya adalah 2,125 panggilan ke Rand5 untuk setiap Rand7. Yang optimal harus 2.1.

Saya akan membayangkan pendekatan ini secara signifikan lebih lambat daripada banyak yang lain di sini kecuali biaya panggilan ke Rand5 sangat mahal (katakanlah memanggil beberapa sumber eksternal entropi).

ShuggyCoUk
sumber
Solusi Anda tampak benar, selain dari beberapa kesalahan sederhana: "jika (hitung> 1)" harus "jika (hitung <= 1)", dan "i ++" yang muncul tidak lama kemudian harus berada di dalam kurung kurawal yang mendahuluinya. Saya tidak yakin apakah BitsSet () benar, tapi itu agak tidak relevan.
Adam Rosenfield
Namun secara keseluruhan, fungsi Anda sangat sulit untuk dipahami. Itu memang membuat penggunaan entropi sedikit lebih baik daripada yang bisa dilakukan, dengan biaya lebih banyak komplikasi. Pada awalnya tidak ada alasan untuk mengisi buffer dengan 35 bit acak pada panggilan pertama, ketika 3 sudah cukup.
Adam Rosenfield
Saya mengoreksi <= terima kasih, i ++ benar-benar harus ada di sana. Ini harus terjadi pada kasus nol dan 1 (masing-masing menambahkan 1 atau nol ke buffer). Ini sama sekali bukan apa yang saya sarankan untuk digunakan, ini sangat rumit. Saya hanya tertarik seberapa dekat saya bisa sampai ke batas entropi teoritis yang melekat dalam masalah ... Terima kasih atas umpan baliknya. Ironisnya pengisian buffer pada panggilan pertama adalah untuk membuatnya lebih sederhana untuk menulis :)
ShuggyCoUk
Saya mengerjakan ulang ini agar lebih mudah dipahami (dengan biaya kecepatan) tetapi juga membuatnya benar. Itu belum optimal, untuk beberapa alasan 1/5 bit menyebabkan masalah meskipun seragam dalam hitungan.
ShuggyCoUk
3

dalam php

function rand1to7() {
    do {
        $output_value = 0;
        for ($i = 0; $i < 28; $i++) {
            $output_value += rand1to5();
        }
    while ($output_value != 140);
    $output_value -= 12;
    return floor($output_value / 16);
}

loop untuk menghasilkan angka acak antara 16 dan 127, membaginya dengan enam belas untuk membuat float antara 1 dan 7.9375, kemudian membulatkan ke bawah untuk mendapatkan int antara 1 dan 7. jika saya tidak salah, ada peluang 16/112 untuk mendapatkan salah satu dari 7 hasil.

dqhendricks
sumber
meskipun mungkin ada jawaban yang lebih mudah mirip dengan ini tanpa menggunakan loop kondisional, dan modulo bukan lantai. saya tidak bisa menghitung angka sekarang.
dqhendricks
3
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}
maxchengcn
sumber
masalah: ini mengembalikan tidak seragam dalam kisaran 0-7, bukan 0-6. Memang, Anda dapat melakukannya 7 = 111bdenganp(7) = 8 / 125
bernard paulus
3

Saya pikir saya punya empat jawaban, dua memberikan solusi tepat seperti yang dari @Adam Rosenfield tetapi tanpa masalah loop tak terbatas, dan dua lainnya dengan solusi yang hampir sempurna tetapi implementasi lebih cepat daripada yang pertama.

Solusi tepat terbaik membutuhkan 7 panggilan rand5, tetapi mari kita lanjutkan untuk memahami.

Metode 1 - Tepat

Kekuatan jawaban Adam adalah bahwa ia memberikan distribusi seragam yang sempurna, dan ada probabilitas yang sangat tinggi (21/25) bahwa hanya dua panggilan ke rand5 () yang akan dibutuhkan. Namun, kasus terburuk adalah infinite loop.

Solusi pertama di bawah ini juga memberikan distribusi seragam yang sempurna, tetapi membutuhkan total 42 panggilan ke rand5. Tidak ada loop tanpa batas.

Berikut ini adalah implementasi R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1

Bagi orang yang tidak terbiasa dengan R, ini adalah versi yang disederhanakan:

rand7 = function(){
  r = 0 
  for(i in 0:6){
    r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
  }
  return r %% 7 + 1
}

Distribusi rand5akan dipertahankan. Jika kita menghitungnya, masing-masing dari 7 iterasi loop memiliki 5 ^ 6 kombinasi yang memungkinkan, sehingga jumlah total kombinasi yang mungkin adalah (7 * 5^6) %% 7 = 0. Dengan demikian kita dapat membagi angka acak yang dihasilkan dalam kelompok yang sama dari 7. Lihat metode dua untuk diskusi lebih lanjut tentang ini.

Berikut ini semua kemungkinan kombinasi:

table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
15625 15625 15625 15625 15625 15625 15625 

Saya pikir ini sangat jelas untuk menunjukkan bahwa metode Adam akan berjalan jauh lebih cepat. Probabilitas bahwa ada 42 atau lebih panggilan ke rand5dalam solusi Adam sangat kecil ( (4/25)^21 ~ 10^(-17)).

Metode 2 - Tidak Tepat

Sekarang metode kedua, yang hampir seragam, tetapi membutuhkan 6 panggilan ke rand5:

rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Ini adalah versi yang disederhanakan:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return r %% 7 + 1
}

Ini pada dasarnya adalah satu iterasi dari metode 1. Jika kami menghasilkan semua kombinasi yang mungkin, berikut adalah jumlah yang dihasilkan:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

   1    2    3    4    5    6    7 
2233 2232 2232 2232 2232 2232 2232

Satu nomor akan muncul sekali lagi dalam 5^6 = 15625uji coba.

Sekarang, dalam Metode 1, dengan menambahkan 1 hingga 6, kami memindahkan angka 2233 ke setiap titik berurutan. Dengan demikian jumlah total kombinasi akan cocok. Ini berhasil karena 5 ^ 6 %% 7 = 1, dan kemudian kita melakukan 7 variasi yang sesuai, jadi (7 * 5 ^ 6 %% 7 = 0).

Metode 3 - Tepat

Jika argumen metode 1 dan 2 dipahami, metode 3 mengikuti, dan hanya membutuhkan 7 panggilan ke rand5. Pada titik ini, saya merasa ini adalah jumlah minimum panggilan yang diperlukan untuk solusi yang tepat.

Berikut ini adalah implementasi R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1

Bagi orang yang tidak terbiasa dengan R, ini adalah versi yang disederhanakan:

rand7 = function(){
  r = 0 
  for(i in 1:7){
    r = r + i * rand5()
  }
  return r %% 7 + 1
}

Distribusi rand5akan dipertahankan. Jika kita menghitungnya, masing-masing dari 7 iterasi loop memiliki 5 hasil yang mungkin, dengan demikian jumlah total kombinasi yang mungkin adalah (7 * 5) %% 7 = 0. Dengan demikian kita dapat membagi angka acak yang dihasilkan dalam kelompok yang sama dari 7. Lihat metode satu dan dua untuk diskusi lebih lanjut tentang ini.

Berikut ini semua kemungkinan kombinasi:

table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)

1 2 3 4 5 6 7  
5 5 5 5 5 5 5 

Saya pikir ini sangat jelas untuk menunjukkan bahwa metode Adam masih akan berjalan lebih cepat. Probabilitas bahwa ada 7 panggilan atau lebih rand5dalam solusi Adam masih kecil ( (4/25)^3 ~ 0.004).

Metode 4 - Tidak Tepat

Ini adalah variasi kecil dari metode kedua. Hampir seragam, tetapi membutuhkan 7 panggilan ke rand5, itu adalah satu tambahan untuk metode 2:

rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Ini adalah versi yang disederhanakan:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return (r+rand5()) %% 7 + 1
}

Jika kami menghasilkan semua kombinasi yang mungkin, berikut ini penghitungannya:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
11160 11161 11161 11161 11161 11161 11160

Dua angka akan muncul sekali kurang dalam 5^7 = 78125uji coba. Untuk sebagian besar tujuan, saya bisa hidup dengan itu.

Shambho
sumber
1
Saya tidak terbiasa dengan R, tetapi kecuali saya salah paham bagaimana cara kerjanya, maka metode 1 tidak tepat. Ia memiliki (5 ^ 6) ^ 7 = 5 ^ 42 hasil yang mungkin, tidak (5 ^ 6) * 7; 5 ^ 42 tidak habis dibagi 7. Demikian juga metode 3 tidak tepat. Ini memiliki 5 ^ 7 hasil yang mungkin, bukan 5 * 7. (Iterasi loop terakhir dalam metode 3 dengan i=7juga tidak memiliki efek, karena menambahkan 7*rand5()ke rtidak mengubah nilai rmod 7.)
Adam Rosenfield
2

Fungsi yang Anda butuhkan adalah rand1_7 () , saya menulis rand1_5 () sehingga Anda dapat mengujinya dan memplotnya.

import numpy
def rand1_5():
    return numpy.random.randint(5)+1

def rand1_7():
    q = 0
    for i in xrange(7):  q+= rand1_5()
    return q%7 + 1
Andrea Ambu
sumber