ขยายช่วงแบบสุ่มจาก 1-5 เป็น 1–7


693

รับฟังก์ชั่นที่สร้างจำนวนเต็มแบบสุ่มในช่วง 1 ถึง 5 ให้เขียนฟังก์ชันที่สร้างจำนวนเต็มแบบสุ่มในช่วง 1 ถึง 7

  1. ทางออกที่ง่ายคืออะไร?
  2. โซลูชั่นที่มีประสิทธิภาพในการลดการใช้หน่วยความจำหรือรันบน CPU ที่ช้ากว่าคืออะไร

มันพิสูจน์แล้วว่าเป็นปัญหาที่น่าสนใจโดยไม่คาดคิดผมยังคิดว่าวิธีการ 1) ทำมันในเวลาที่กำหนดและ 2) ไม่เสียการกระจายชุด (ถ้ามี)
eugensk

เราพบปัญหาที่คล้ายกันในขณะที่เลือกผู้เล่นหนึ่งคนจาก 5 คนด้วยลูกเต๋า เราโยนลูกเต๋าทีละคนผู้ที่ได้คะแนนสูงสุดจะถูกเลือก ความสม่ำเสมอถูก achived แต่ไม่ใช่เวลา constantness :)
eugensk

ฉันจะถูกลดระดับลงหรือไม่ถ้าฉันโพสต์คำตอบที่บอกว่าปัญหาไม่ได้บังคับคุณต้องใช้ฟังก์ชั่นที่กำหนดและเพียงแค่เขียนหนึ่งที่ส่งกลับ 1-7 แบบสุ่ม?
Doctor Blue

เกี่ยวกับ7 * rand5() / 5อะไร
kiwixz

@kiwixz ที่จะสร้าง "ระหว่าง 1 และ 7" แต่คุณจะไม่ได้รับ 3 หรือ 6: {1: 19.96, 2: 20.02, 4: 20.01, 5: 19.99, 7: 20.02} การทดสอบเปอร์เซ็นต์แบบคร่าวๆด้วยตนเอง 7 * .2, 7 * .4, 7 * .6, 7 * .8, 7 * 1
pythonlarry

คำตอบ:


572

นี่จะเทียบเท่ากับโซลูชันของ Adam Rosenfield แต่อาจมีความชัดเจนมากขึ้นสำหรับผู้อ่านบางคน มันถือว่า rand5 () เป็นฟังก์ชั่นที่ส่งกลับจำนวนเต็มแบบสุ่มทางสถิติในช่วง 1 ถึง 5 รวม

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

มันทำงานยังไง? ลองคิดแบบนี้ลองจินตนาการถึงการพิมพ์อาเรย์สองมิติบนกระดาษตรึงมันไว้ที่กระดานปาเป้าและโยนปาเป้าแบบสุ่ม หากคุณกดค่าที่ไม่เป็นศูนย์มันจะสุ่มค่าทางสถิติระหว่าง 1 และ 7 เนื่องจากมีค่าที่ไม่ใช่ศูนย์ให้เลือกจำนวนเท่ากัน หากคุณโดนศูนย์ให้โยนปาเป้าต่อไปเรื่อย ๆ จนกว่าคุณจะตีที่ไม่ใช่ศูนย์ นั่นคือสิ่งที่รหัสนี้กำลังทำอยู่: ดัชนี i และ j สุ่มเลือกตำแหน่งบนกระดานปาเป้าและหากเราไม่ได้ผลลัพธ์ที่ดีเราก็จะปาเป้าปาลูกดอกต่อไป

เช่นเดียวกับอดัมกล่าวว่าสิ่งนี้สามารถทำงานได้ตลอดไปในกรณีที่เลวร้ายที่สุด แต่สถิติกรณีที่เลวร้ายที่สุดไม่เคยเกิดขึ้น :)


5
ฉันเข้าใจตรรกะที่อยู่เบื้องหลังโซลูชันนี้ แต่ไม่สามารถเข้าใจได้ว่ามันส่งผลให้เกิดความน่าจะเป็นเหมือนกันอย่างไร มีคนอธิบายคณิตศาสตร์ได้ไหม
user1071840

6
@ user1071840 - ถ้าrand5เป็นแบบเดียวกันทุกเซลล์ในvalsกริดจะมีโอกาสเลือกเท่ากัน กริดประกอบด้วยสำเนาสามชุดของแต่ละจำนวนเต็มในช่วงเวลา [1, 7], บวกสี่ศูนย์ ดังนั้นสตรีมผลลัพธ์ "ดิบ" จึงมีแนวโน้มที่จะมีการรวมกันของค่า [1, 7] รวมทั้งเลขศูนย์ที่เกิดขึ้นบ่อยกว่าค่าที่ได้รับอนุญาตของแต่ละบุคคล แต่นั่นไม่สำคัญเพราะศูนย์ถูกตัดออกเหลือเพียงค่าผสม [1, 7]
Daniel Earwicker

3
วิธีลัดในการตระหนักถึงปัญหานั้น: หากคุณโทรหา rand5 () เพียงครั้งเดียวคุณจะได้ผลลัพธ์ที่เป็นไปได้เพียง 5 รายการเท่านั้น เห็นได้ชัดว่าไม่มีทางที่จะเปลี่ยนสิ่งนั้นให้กลายเป็นผลลัพธ์ที่เป็นไปได้มากกว่า 5 รายการโดยไม่เพิ่มการสุ่มมากขึ้น
Daniel Earwicker

1
เวอร์ชันที่ยาวกว่า: rand5 () สามารถมีค่าได้ (1, 2, 3, 4, 5) เท่านั้น ดังนั้น rand5 () * 5 สามารถมีค่าได้ (5, 10, 15, 20, 25) ซึ่งไม่เหมือนกับช่วงที่สมบูรณ์ (1 ... 25) หากทำได้การลบ 4 จะทำให้ (-3 ... 21) แต่ในกรณีนี้มันจะกลายเป็น (1, 6, 11, 16, 21) ดังนั้นจุดสิ้นสุดนั้นถูกต้อง แต่มีสี่หลุมใหญ่: ( 2..5), (7..10), (12 .. 15), (17..21) ในที่สุดคุณทำ mod 7 และเพิ่ม 1 ให้ (2, 7, 5, 3, 1) ดังนั้นทั้ง 4 และ 6 จึงไม่เคยเกิดขึ้น แต่ (ดูทางลัดด้านบน) เรารู้ว่ามีเพียง 5 หมายเลขในช่วงผลลัพธ์ตลอดดังนั้นจึงต้องมีสองช่องว่าง
Daniel Earwicker

1
อ่าเพราะเรามีแค่ rand5 () ไม่ใช่ rand2 () :-)
gzak

353

ไม่มีวิธีแก้ปัญหา (ที่ถูกต้อง) ซึ่งจะทำงานในระยะเวลาคงที่เนื่องจาก 1/7 เป็นทศนิยมที่ไม่มีที่สิ้นสุดในฐาน 5 วิธีแก้ปัญหาอย่างง่ายอย่างหนึ่งคือใช้การสุ่มตัวอย่างการปฏิเสธเช่น:


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

สิ่งนี้มีความคาดหวังรันไทม์ของ 25/21 = 1.19 การวนซ้ำของลูป แต่มีความน่าจะเป็นที่จะเกิดการวนซ้ำน้อยที่สุดตลอดไป


7
ไม่จำเป็นต้องใช้ -1 หากเปิด 21> ไปที่> 26 b / c ไม่สำคัญว่าแผนที่ของฉันจะอยู่ตรงไหน
BCS

26
สิ่งที่ฉันอธิบายว่าทำไมสิ่งนี้ถูกต้อง: บอกฉันว่าฉันต้องการเขียนโปรแกรมที่ให้ผลลัพธ์สตรีมของตัวเลขสุ่มสม่ำเสมอตั้งแต่ 1 ถึง 25; สำหรับสิ่งที่ฉันเพิ่งกลับ 5 * (rand5 () - 1) + rand5 () เช่นเดียวกับในรหัสในคำตอบ ตอนนี้ถ้าฉันต้องการสร้างสตรีมของตัวเลขสุ่มสม่ำเสมอระหว่าง 1 ถึง 21 ถ้าฉันใช้สตรีมแรก แต่กรองเพื่อให้ตัวเลขใน [22, 25] ถูกปฏิเสธฉันสามารถสร้างสตรีมนั้นได้เช่นกัน ต่อไปถ้าฉันใช้สตรีมนี้และกรองมันเพื่อให้แต่ละองค์ประกอบ x ฉันส่งออก x% 7 + 1 ฉันมีกระแสของตัวเลขสุ่มสม่ำเสมอตั้งแต่ 1 ถึง 7! ค่อนข้างง่ายใช่มั้ย : D
Paggas

6
และคุณถูกต้องแล้วว่ามันจะลดลงไปไม่ว่าคุณจะต้องการการกระจายที่สมบูรณ์แบบด้วย runtime case ที่แย่ที่สุดหรือขอบเขตการกระจายที่ไม่สมบูรณ์พร้อม runtime runtime นี่เป็นผลมาจากข้อเท็จจริงที่ว่าพลังทั้งหมด 5 ไม่หารด้วย 7 หรือเท่ากันถ้าคุณมี 5 ^ n เท่ากันอาจเป็นลำดับของความยาว n ไม่มีวิธีกำหนดให้แต่ละลำดับตัวเลขจาก 1 ถึง 7 ดังที่แต่ละ 1..7 น่าจะเท่ากัน
Adam Rosenfield

5
@Jules Olléon: สมมติว่ามีวิธีการแก้ปัญหาในเวลาคงที่ที่รับประกันว่าจะไม่เกินNโทรไปrand5()ในกรณีที่เลวร้ายที่สุด จากนั้นจะมี 5 ^ N ผลลัพธ์ที่เป็นไปได้ของลำดับการเรียกไปrand5แต่ละสายมีเอาต์พุต 1-7 ดังนั้นถ้าคุณบวกลำดับการโทรที่เป็นไปได้ทั้งหมดซึ่งมีเอาต์พุตkสำหรับ1≤k≤7แต่ละค่าความน่าจะเป็นที่เอาต์พุตkคือ m / 5 ^ N โดยที่ m คือจำนวนของลำดับดังกล่าว ดังนั้น m / 5 ^ N = 1/7 แต่ไม่มีวิธีแก้ปัญหาจำนวนเต็ม (N, m) ที่เป็นไปได้ ==> ความขัดแย้งนี้
Adam Rosenfield

4
@paxdiablo: คุณไม่ถูกต้อง โอกาสของการ RNG จริงสร้างลำดับอนันต์ 5 คือตรง 0, ใช้เหตุผลคล้ายกับความจริงที่ว่าพลิกเหรียญจำนวนอนันต์ครั้งที่มีการประกันที่จะไม่สร้างจำนวนอนันต์ของหัวต่อเนื่อง นี่ยังหมายถึงโอกาสของการวนซ้ำรหัสนี้ตลอดกาลเป็น 0 อย่างแน่นอน (แม้ว่าจะมีโอกาสเชิงบวกที่มันจะวนซ้ำสำหรับจำนวนการวนซ้ำโดยพลการใด ๆ )
BlueRaja - Danny Pflughoeft

153

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

เอนโทรปีของตัวแปรสุ่มเป็นปริมาณที่ดีที่กำหนด สำหรับตัวแปรสุ่มที่ใช้กับ N สหรัฐฯที่มีความน่าจะเป็นเท่ากัน (การกระจายแบบสม่ำเสมอ) เอนโทรปีคือล็อก2เอ็นดังนั้นจึงrand5()มีเอนโทรปีประมาณ 2.32193 บิตและrand7()มีเอนโทรปีประมาณ 2.80735 บิต หากเราหวังว่าจะใช้การสุ่มให้เกิดประโยชน์สูงสุดเราต้องใช้เอนโทรปีทั้งหมด 2.32193 บิตจากการโทรแต่ละครั้งrand5()และนำไปใช้กับการสร้างเอนโทรปี 2.80735 บิตที่จำเป็นสำหรับการโทรแต่ละrand7()ครั้ง ขีด จำกัด พื้นฐานแล้วคือการที่เราสามารถทำได้ไม่ดีกว่าล็อก (7) / log (5) = 1.20906 โทรไปต่อการเรียกร้องให้rand5()rand7()

หมายเหตุด้านข้าง: ลอการิทึมทั้งหมดในคำตอบนี้จะเป็นฐาน 2 เว้นแต่จะระบุไว้เป็นอย่างอื่น rand5()จะถูกสมมติให้ส่งคืนตัวเลขในช่วง [0, 4] และrand7()จะถูกสมมติให้ส่งคืนตัวเลขในช่วง [0, 6] การปรับช่วงเป็น [1, 5] และ [1, 7] ตามลำดับนั้นไม่สำคัญ

แล้วเราจะทำอย่างไร เราสร้างจำนวนจริงสุ่มที่แม่นยำอย่างไม่สิ้นสุดระหว่าง 0 และ 1 (ทำเป็นช่วงเวลาที่เราสามารถคำนวณและจัดเก็บหมายเลขที่แม่นยำอย่างไม่สิ้นสุด - เราจะแก้ไขในภายหลัง) เราสามารถสร้างเลขที่ดังกล่าวโดยการสร้างตัวเลขในฐานที่ 5: เราเลือกจำนวนสุ่ม 0. a1 a2 a3 ... ซึ่งแต่ละหลักถูกเลือกโดยการเรียกไปยังi rand5()ตัวอย่างเช่นหาก RNG ของเราเลือกi= 1 สำหรับทุกคนiดังนั้นให้เพิกเฉยต่อความจริงที่ว่าไม่สุ่มมากนั่นจะสอดคล้องกับจำนวนจริง 1/5 + 1/5 2 + 1/5 3 + ... = 1/4 (ผลรวมของอนุกรมเรขาคณิต)

ตกลงดังนั้นเราจึงเลือกสุ่มจำนวนจริงระหว่าง 0 และ 1 ตอนนี้ฉันอ้างว่าหมายเลขสุ่มดังกล่าวมีการกระจายอย่างสม่ำเสมอ ง่ายต่อการเข้าใจเนื่องจากตัวเลขแต่ละตัวถูกเลือกอย่างสม่ำเสมอและตัวเลขมีความแม่นยำอย่างไม่สิ้นสุด อย่างไรก็ตามหลักฐานอย่างเป็นทางการของการนี้จะมีส่วนร่วมค่อนข้างมากเนื่องจากตอนนี้เรากำลังจัดการกับการจัดจำหน่ายอย่างต่อเนื่องแทนการกระจายที่ไม่ต่อเนื่องดังนั้นเราจึงจำเป็นที่จะต้องพิสูจน์ให้เห็นว่าน่าจะเป็นที่จำนวนโกหกเราในช่วงเวลา [ a, b] เท่ากับความยาวของ ช่วงเวลานั้นb - a. หลักฐานจะถูกทิ้งไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน =)

ตอนนี้เรามีจำนวนจริงสุ่มเลือกสม่ำเสมอจากช่วง [0, 1] เราต้องแปลงเป็นชุดของตัวเลขสุ่มสม่ำเสมอในช่วง [0, 6] rand7()เพื่อสร้างการส่งออกของ เราจะทำเช่นนี้ได้อย่างไร? เพียงแค่ย้อนกลับของสิ่งที่เราก็ไม่ได้ - เราแปลงเป็นทศนิยมอนันต์แม่นยำในฐานที่ 7 แล้วแต่ละฐาน 7 rand7()หลักจะสอดคล้องกับหนึ่งในการส่งออกของ

ยกตัวอย่างจากก่อนหน้านี้ถ้าเราrand5()สร้างกระแสที่ไม่สิ้นสุดของ 1 จำนวนสุ่มจริงของเราจะเป็น 1/4 การแปลง 1/4 ถึงฐาน 7 เราจะได้ทศนิยมไม่สิ้นสุด 0.15151515 ... ดังนั้นเราจะสร้างผลลัพธ์เป็น 1, 5, 1, 5, 1, 5, ฯลฯ

ตกลงดังนั้นเรามีความคิดหลัก แต่เรามีสองปัญหาที่เหลือ: เราไม่สามารถคำนวณหรือเก็บจำนวนจริงที่แม่นยำอย่างไร้ขีด จำกัด ดังนั้นเราจะจัดการกับส่วนที่ จำกัด ได้อย่างไร ประการที่สองเราจะแปลงเป็นฐาน 7 ได้อย่างไร

วิธีหนึ่งที่เราสามารถแปลงตัวเลขระหว่าง 0 ถึง 1 ถึงฐาน 7 มีดังนี้:

  1. คูณด้วย 7
  2. ส่วนที่สำคัญของผลลัพธ์คือฐาน 7 หลักถัดไป
  3. ลบออกส่วนที่เหลือออกเพียงส่วนที่เป็นเศษส่วน
  4. ไปที่ขั้นตอนที่ 1

เพื่อจัดการกับปัญหาของความแม่นยำที่ไม่สิ้นสุดเราคำนวณผลลัพธ์บางส่วนและเรายังเก็บขอบเขตบนของผลลัพธ์ที่อาจเป็นไปได้ นั่นคือสมมติว่าเราเรียกrand5()สองครั้งแล้วส่งคืน 1 ทั้งสองครั้ง จำนวนที่เราสร้างขึ้นจนถึงตอนนี้คือ 0.11 (ฐาน 5) ไม่ว่าส่วนที่เหลือของชุดการโทรที่ไม่ จำกัดrand5()จำนวนจริงที่เราสร้างขึ้นจะไม่ใหญ่กว่า 0.12: เป็นจริงเสมอที่ 0.11 ≤ 0.11xyz ... <0.12

ดังนั้นการติดตามตัวเลขปัจจุบันและค่าสูงสุดที่เราสามารถทำได้เราแปลงทั้งสองตัวเลขเป็นฐาน 7 หากพวกเขาเห็นด้วยกับkตัวเลขตัวแรกเราสามารถส่งออกkตัวเลขถัดไปได้อย่างปลอดภัยโดยไม่คำนึงถึงสิ่งที่ สตรีมไม่สิ้นสุดของฐาน 5 หลักคือพวกเขาจะไม่ส่งผลกระทบต่อkตัวเลขถัดไปของการแสดงฐาน 7!

และนั่นคืออัลกอริทึม - เพื่อสร้างผลลัพธ์ต่อไปของrand7()เราสร้างเพียงตัวเลขจำนวนมากrand5()เท่าที่เราต้องการเพื่อให้แน่ใจว่าเรารู้ด้วยความแน่นอนมูลค่าของตัวเลขถัดไปในการแปลงของจำนวนจริงสุ่มเป็นฐาน 7 นี่คือ การใช้งาน Python โดยใช้ชุดทดสอบ:

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)

โปรดทราบว่าrand7_gen()ส่งคืนตัวกำเนิดเนื่องจากมีสถานะภายในที่เกี่ยวข้องกับการแปลงของตัวเลขเป็นฐาน 7 ชุดควบคุมการทดสอบเรียกnext(r7)10000 ครั้งเพื่อสร้างหมายเลขสุ่ม 10,000 แล้วจึงวัดการกระจายของพวกเขา ใช้คณิตศาสตร์จำนวนเต็มเท่านั้นผลลัพธ์จึงถูกต้องทั้งหมด

โปรดทราบว่าตัวเลขที่นี่มีขนาดใหญ่มากเร็วมาก พลังของ 5 และ 7 เติบโตอย่างรวดเร็ว ดังนั้นประสิทธิภาพจะเริ่มลดลงอย่างเห็นได้ชัดหลังจากสร้างตัวเลขสุ่มจำนวนมากเนื่องจากเลขคณิต bignum แต่จำไว้ว่าที่นี่เป้าหมายของฉันคือการใช้บิตสุ่มให้เกิดประโยชน์สูงสุดเพื่อไม่ให้เกิดประสิทธิภาพสูงสุด (แม้ว่าจะเป็นเป้าหมายรอง)

ในการดำเนินการนี้ฉันทำการโทร 12091 ครั้งrand5()สำหรับการโทร 10,000 ครั้งเพื่อให้ได้จำนวนการโทรrand7()ขั้นต่ำ (7) / บันทึก (5) ถึงตัวเลขที่มีนัยสำคัญ 4 ตัวและผลลัพธ์ที่ได้นั้นเหมือนกัน

ในการย้ายรหัสนี้เป็นภาษาที่ไม่มีเลขจำนวนเต็มขนาดใหญ่โดยพลการคุณจะต้องใส่ค่าของpow5และpow7เป็นค่าสูงสุดของประเภทอินทิกรัลในประเทศของคุณ - ถ้ามันใหญ่เกินไปให้รีเซ็ต ทุกอย่างและเริ่มต้นใหม่ สิ่งนี้จะเพิ่มจำนวนการโทรโดยเฉลี่ยrand5()ต่อการโทรrand7()เล็กน้อย แต่หวังว่ามันจะไม่เพิ่มขึ้นมากเกินไปสำหรับจำนวนเต็ม 32- หรือ 64- บิต


7
+1 สำหรับคำตอบที่น่าสนใจจริงๆ มันจะเป็นไปได้หรือไม่แทนที่จะรีเซ็ตตามค่าที่ตั้งไว้เพียงแค่เลื่อนบิตที่ใช้ไปแล้วเลื่อนบิตอื่น ๆ ขึ้นไปและโดยทั่วไปจะเก็บบิตที่จะใช้เท่านั้น หรือว่าฉันขาดอะไรไป?
คริส Lutz

1
ฉันไม่แน่ใจ 100% แต่ฉันเชื่อว่าถ้าคุณทำอย่างนั้นคุณจะเอียงการกระจายตัวเล็กน้อย (แม้ว่าฉันจะสงสัยว่าการเอียงแบบนี้จะสามารถวัดได้โดยไม่ต้องทดสอบหลายล้านล้านครั้ง)
Adam Rosenfield

FTW! ฉันพยายามทำให้ bignums เล็กลง แต่ไม่สามารถทำได้เพราะไม่มีพลัง 5 มีปัจจัยร่วมกันกับพลัง 7! นอกจากนี้ยังใช้ประโยชน์จากคำหลักผลตอบแทน ทำได้ดีมาก
Eyal

2
ดีมาก! เราสามารถรักษาเอนโทรปีที่เพิ่มขึ้นโดยไม่เพิ่มสถานะได้หรือไม่? เคล็ดลับคือการสังเกตว่าทั้งบนและล่างเป็นจำนวนตรรกยะทุกเวลา เราสามารถเพิ่มลบและคูณมันได้โดยไม่เสียความแม่นยำ ถ้าเราทำทั้งหมดในฐาน -35 เราเกือบจะอยู่ตรงนั้น ส่วนที่เหลือ (คูณด้วยเจ็ดและรักษาส่วนที่เป็นเศษส่วน) จะถูกทิ้งไว้เป็นแบบฝึกหัด
เอียน

@adam คุณต้องอ้างถึง "กำหนดค่า pow5 และ pow7 ให้เป็นค่าสูงสุดของประเภทอินทิกรัลในประเทศของคุณ" ฉันสองของคุณเชื่อว่านี่จะบิดเบือนการกระจายอย่างน้อยถ้าทำไร้เดียงสา
ตัวเร่งปฏิกิริยา

36

(ฉันได้ขโมยคำตอบของ Adam Rosenfeldและทำให้มันทำงานได้เร็วขึ้นประมาณ 7%)

สมมติว่า rand5 () ส่งคืนหนึ่งใน {0,1,2,3,4} โดยมีการแจกแจงเท่ากันและเป้าหมายคือส่งคืน {0,1,2,3,4,5,6} พร้อมการกระจายเท่ากัน

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

maxเรากำลังติดตามความเคลื่อนไหวของค่าที่มากที่สุดที่วงสามารถทำในตัวแปร หากจนถึงขณะนี้อยู่ระหว่าง max% 7 และ max-1 ผลลัพธ์จะถูกกระจายอย่างสม่ำเสมอในช่วงนั้น หากไม่ใช้เราจะใช้ส่วนที่เหลือซึ่งจะสุ่มระหว่าง 0 และสูงสุด% 7-1 และการเรียกไปที่ rand () อีกครั้งเพื่อสร้างหมายเลขใหม่และค่าสูงสุดใหม่ จากนั้นเราก็เริ่มใหม่อีกครั้ง

แก้ไข: คาดว่าจำนวนครั้งที่จะเรียก rand5 () คือ x ในสมการนี้:

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

2
ผลลัพธ์ในแค็ตตาล็อก 1,000,000 ครั้งพยายาม: 1 = 47216; 2 = 127444; 3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536; 7 = 167465 ที่คุณสามารถดูการกระจายจะขาดในส่วนที่เกี่ยวกับการต่อรองในการได้รับ 1
โรเบิร์ต K

2
@ The Wicked Flea: ฉันคิดว่าคุณเข้าใจผิด คุณแน่ใจหรือว่าอินพุต rand5 () ที่คุณใช้สำหรับการทดสอบของคุณสร้าง 0-4 แทนที่จะเป็น 1-5 ตามที่ระบุในโซลูชันนี้
Adam Rosenfield

5
การเพิ่มหมายเลขที่กระจายอย่างสม่ำเสมอจะไม่ส่งผลให้มีการแจกแจงแบบสม่ำเสมอ ในความเป็นจริงคุณจะต้องรวม 6 ตัวแปรที่กระจายอย่างสม่ำเสมอเพื่อให้ได้ค่าประมาณที่เหมาะสมกับการแจกแจงแบบปกติ
Mitch Wheat เมื่อ

2
@MitchWheat - การเพิ่มจำนวนเต็มสองจำนวนที่กระจายกันอย่างสม่ำเสมอในความเป็นจริงส่งผลให้จำนวนเต็มแบบสุ่มกระจายอย่างสม่ำเสมอให้ผลรวมที่เป็นไปได้แต่ละครั้งสามารถสร้างในวิธีเดียว 5 * rand5() + rand5()ที่เกิดขึ้นจะเป็นกรณีในการแสดงออก
Ted Hopp

28

ขั้นตอนวิธีการ:

7 สามารถแสดงลำดับ 3 บิต

ใช้แรนด์ (5) เพื่อสุ่มเติมแต่ละบิตด้วย 0 หรือ 1
ตัวอย่างเช่น: call rand (5) และ

หากผลลัพธ์เป็น 1 หรือ 2 ให้เติมบิตด้วย 0
หากผลลัพธ์เป็น 4 หรือ 5 ให้เติมบิตด้วย 1
หากผลลัพธ์เป็น 3 จากนั้นให้ละเว้นและทำอีกครั้ง (การปฏิเสธ)

วิธีนี้เราสามารถเติม 3 บิตสุ่มด้วย 0/1 และทำให้ได้ตัวเลขจาก 1-7

แก้ไข: ดูเหมือนว่าคำตอบที่ง่ายและมีประสิทธิภาพมากที่สุดดังนั้นนี่คือรหัสสำหรับ:

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

1
มีเสมอปีศาจลมของลังเลปัญหาเนื่องจากเครื่องกำเนิดไฟฟ้าจำนวนสุ่มที่ไม่ดีก็สามารถสร้างจำนวนมากของสามในบางจุด
Alex North-Keys

"ถ้าผลลัพธ์เป็น 1 หรือ 2 ให้เติมบิตด้วย 0 ถ้าผลลัพธ์คือ 4 หรือ 5 เติมบิตด้วย 1" ตรรกะใดที่ 1,2,4,5 ยอมรับและ 3 ถูกปฏิเสธ? คุณอธิบายได้ไหม
gkns

@gkns ไม่มีเหตุผลคุณสามารถมี 1 และ 2 หมายถึงการเติมด้วย 0 บิตและ 3 และ 4 หมายถึงการเติมด้วย 1 สิ่งที่สำคัญคือแต่ละตัวเลือกมีโอกาสเกิดขึ้น 50% จึงรับประกันได้ว่าการสุ่มของฟังก์ชันของคุณคือ อย่างน้อยสุ่มเท่ากับฟังก์ชั่นแรนด์ดั้งเดิม (5) มันเป็นทางออกที่ดี!
Mo Beigi

มันไม่ง่ายและไม่มีประสิทธิภาพ จำนวน cals ถึง random_5 ต่อ random_7 นั้นดีที่สุด 3 ปกติ โซลูชันอื่น ๆ ในหน้านี้ใกล้เคียงกับความเป็นจริงที่ดีที่สุดซึ่งอยู่ที่ประมาณ 2.2
Eyal

1
ไม่เป็นไรฉันพลาดส่วน "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 );
    }
}

2
วิธีการแก้ปัญหาที่ถูกต้องทำให้ค่าเฉลี่ยของ 30/7 = 4.29 การโทรไปยัง rand5 () ต่อการโทรไปยัง rand7 ()
Adam Rosenfield

17
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

แก้ไข: นั่นยังไม่ได้ผล มันปิดประมาณ 2 ส่วนใน 1,000 (สมมติว่าเป็น rand5 ที่สมบูรณ์แบบ) ถังรับ:

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

โดยเปลี่ยนเป็นผลรวมของ

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

ดูเหมือนว่าจะได้รับลำดับความสำคัญสำหรับทุก ๆ 2 เพิ่ม

BTW: ตารางข้อผิดพลาดด้านบนไม่ได้ถูกสร้างขึ้นผ่านการสุ่มตัวอย่าง แต่โดยความสัมพันธ์ที่เกิดซ้ำดังต่อไปนี้:

p[x,n]เป็นวิธีการที่จำนวนoutput=xที่สามารถเกิดขึ้นได้รับการโทรไปยังnrand5

  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]

8
นี่ไม่ใช่การกระจายที่สม่ำเสมอ มันใกล้เคียงกับเครื่องแบบมาก แต่ไม่เหมือนกันอย่างสมบูรณ์
Adam Rosenfield

อา! ลูกเต๋าและ 7 ของ หากคุณกำลังจะบอกว่าฉันผิดคุณไม่ควรทิ้งหลักฐานไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน
BCS

45
การพิสูจน์ว่ามันไม่เหมือนกันนั้นง่าย: มี 5 ^ 7 วิธีที่เป็นไปได้ของการสุ่มและ 5 ^ 7 ไม่ใช่หลาย 7 มันเป็นไปไม่ได้ที่ทั้ง 7 ผลรวมมีแนวโน้มเท่ากัน (โดยทั่วไปมันเดือดลงไปที่ 7 ซึ่งค่อนข้างดีถึง 5 หรือเทียบเท่า 1/7 ที่ไม่ได้เป็นทศนิยมที่ยุติในฐาน 5) ที่จริงแล้วมันไม่ใช่แม้แต่ "เครื่องแบบที่สุด" ที่เป็นไปได้ภายใต้ข้อ จำกัด นี้: การคำนวณโดยตรงแสดงให้เห็นว่า 5 ^ 7 = 78125 ผลรวมจำนวนครั้งที่คุณได้รับค่า 1 ถึง 7 คือ {1: 11145, 2: 11120, 3: 11120, 4: 11145, 5: 11190, 6: 11215, 7: 11190}
ShreevatsaR

@ShreevatsaR ดังนั้นจะเกิดอะไรขึ้นถ้าแทนที่จะรวม rand5 () เจ็ดครั้งเราก็ทำได้ 5 * 7 - มันจะไม่ทำงานเหรอ? 35 ^ 7% 7 = 35 ^ 5% 7 = 0.
kba

4
@ KristianAntonsen: กี่ครั้งที่คุณทำ rand5 () คุณจะไม่ได้รับการแจกแจงแบบสม่ำเสมอ ถ้าคุณทำมัน N ครั้งมีผลลัพธ์ที่เป็นไปได้ 5 ^ N ซึ่งไม่หารด้วย 7 (ถ้าคุณทำ 35 ครั้งมี 5 ^ 35 ไม่ใช่ 35 ^ 7) คุณจะเข้าใกล้ ใส่จำนวนการโทรที่คุณใช้มากขึ้น (และอาจเป็นตัวเลขใด ๆ ก็ได้โดยไม่ต้องหารด้วย 7) แต่ IMHO แทนที่จะใช้การโทรเพื่อแรนด์จำนวนมาก () คุณอาจใช้ความน่าจะเป็น อัลกอริทึมในคำตอบยอดนิยมซึ่งให้การกระจายที่แน่นอนและมีจำนวนการเรียกไปยัง rand () มีขนาดเล็ก
ShreevatsaR

15
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}

2
วิธีการแก้ปัญหาที่ถูกต้องทำให้ค่าเฉลี่ยของ 30/7 = 4.29 การโทรไปยัง rand5 () ต่อการโทรไปยัง rand7 ()
Adam Rosenfield

3
จะต้องมีการเปลี่ยนแปลงด้านซ้ายสำหรับอัลกอริทึมในการทำงาน:ans += (r < 3) << i
woolfie

13

ข้อมูลต่อไปนี้สร้างการแจกแจงแบบสม่ำเสมอใน {1, 2, 3, 4, 5, 6, 7} โดยใช้ตัวสร้างตัวเลขสุ่มสร้างการกระจายแบบสม่ำเสมอบน {1, 2, 3, 4, 5} รหัสยุ่ง แต่ลอจิกชัดเจน

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

2
วิธีแก้ไขที่ถูกต้อง (ซึ่งทำให้คุณก้าวไปข้างหน้าของเส้นโค้ง) แม้ว่าจะไม่มีประสิทธิภาพมากนัก สิ่งนี้ทำให้ค่าเฉลี่ยของ 25/6 = 4.17 การเรียกไปยัง random_5_mod_2 ต่อการโยนเหรียญที่เป็นธรรมสำหรับค่าเฉลี่ยทั้งหมดของ 100/7 = 14.3 การเรียกไปยัง random_5 () ต่อการโทรสุ่ม_7 ()
Adam Rosenfield

ข้อดีของการแก้ปัญหานี้เหนือผู้อื่นคือมันสามารถขยายได้อย่างง่ายดายเพื่อผลิตช่วงกระจายอย่างสม่ำเสมอ เพียงแค่เลือกสุ่มแต่ละบิตสุ่มค่าที่ไม่ถูกต้องอีกครั้ง (เช่นค่า 0 ในโซลูชันปัจจุบันของเราที่สร้างตัวเลข 8 ตัว)
DenTheMan

1
ลูปไม่มีที่สิ้นสุดที่เป็นไปได้ ฯลฯ
robermorales

1
@robermorales: ไม่น่าเป็นไปได้
jason

13
int rand7() {
    int value = rand5()
              + rand5() * 2
              + rand5() * 3
              + rand5() * 4
              + rand5() * 5
              + rand5() * 6;
    return value%7;
}

ซึ่งแตกต่างจากโซลูชันที่เลือกอัลกอริทึมจะทำงานในเวลาคงที่ อย่างไรก็ตามมันทำการโทร 2 ครั้งไปยัง rand5 มากกว่าเวลาทำงานเฉลี่ยของโซลูชันที่เลือก

โปรดทราบว่าตัวสร้างนี้ไม่สมบูรณ์ (หมายเลข 0 มีโอกาสมากกว่า 0.0064% มากกว่าหมายเลขอื่น ๆ ) แต่สำหรับการใช้งานจริงการรับประกันเวลาคงที่อาจเทียบกับความไม่ถูกต้องนี้ได้

คำอธิบาย

วิธีการแก้ปัญหานี้ได้มาจากความจริงที่ว่าจำนวน 15,624 หารด้วย 7 และถ้าเราสามารถสุ่มสร้างตัวเลขจาก 0 ถึง 15,624 และสุ่มจากนั้นใช้ mod 7 เราจะได้ตัวกำเนิด rand7 ใกล้เคียงกัน หมายเลขตั้งแต่ 0 ถึง 15,624 สามารถสร้างได้อย่างสม่ำเสมอโดยการหมุน rand5 6 ครั้งและใช้เพื่อสร้างตัวเลขของฐาน 5 หมายเลขดังต่อไปนี้:

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

คุณสมบัติของ mod 7 ช่วยให้เราสามารถทำให้สมการง่ายขึ้นเล็กน้อย:

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

ดังนั้น

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

กลายเป็น

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

ทฤษฎี

หมายเลข 15,624 ไม่ได้ถูกสุ่มเลือก แต่สามารถค้นพบได้โดยใช้ทฤษฎีบทเล็ก ๆ ของแฟร์มาต์ซึ่งระบุว่าถ้า p เป็นจำนวนเฉพาะแล้ว

a^(p-1) = 1 mod p

ดังนั้นสิ่งนี้ทำให้เรา

(5^6)-1 = 0 mod 7

(5 ^ 6) -1 เท่ากับ

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

นี่คือตัวเลขในรูปแบบฐาน 5 และดังนั้นเราจะเห็นว่าวิธีนี้สามารถใช้เพื่อไปจากตัวสร้างตัวเลขสุ่มใด ๆ ไปยังตัวสร้างตัวเลขสุ่มอื่น ๆ แม้ว่าจะมีอคติเล็ก ๆ น้อย ๆ ต่อ 0 ถูกนำมาใช้เสมอเมื่อใช้เลขชี้กำลัง p-1

เพื่อสรุปแนวทางนี้และให้แม่นยำยิ่งขึ้นเราสามารถมีฟังก์ชั่นดังนี้

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)

1
เครื่องกำเนิดไฟฟ้านี้มีความแม่นยำ แต่ไม่เหมือนกันอย่างสมบูรณ์ หากต้องการดูสิ่งนี้ให้พิจารณาข้อเท็จจริงที่ว่าเครื่องกำเนิดไฟฟ้าแบบสม่ำเสมอใน [0,15624] มีผลลัพธ์ที่เป็นไปได้ 15625 ข้อซึ่งไม่สามารถหารด้วย 7 ได้ซึ่งจะทำให้อคติกับตัวเลข 0 (ซึ่งมีโอกาส 2233/15625 และอื่น ๆ เพียงแค่ 2232/15625) ท้ายที่สุดขณะที่ใช้ทฤษฎีบทเล็ก ๆ ของแฟร์มาต์อาจดูเหมือนว่าถูกต้องในครั้งแรกมันบอกว่า (5 ^ 6)% 7 = 1 และไม่ใช่ (5 ^ 6)% 7 = 0 เห็นได้ชัดว่าเป็นไปไม่ได้สำหรับเลขชี้กำลังใด ๆ เพราะ 5 และ 7 เป็นจำนวนเฉพาะ ฉันคิดว่ามันยังคงเป็นทางออกที่ยอมรับได้และฉันได้แก้ไขโพสต์ของคุณเพื่อสะท้อนถึงสิ่งนี้
นักบิน

12

ปัญหาการบ้านได้รับอนุญาตที่นี่หรือไม่

ฟังก์ชันนี้ใช้เลขฐาน 5 เพื่อคำนวณตัวเลขตั้งแต่ 0 ถึง 6

function rnd7() {
    do {
        r1 = rnd5() - 1;
        do {
            r2=rnd5() - 1;
        } while (r2 > 1);
        result = r2 * 5 + r1;
    } while (result > 6);
    return result + 1;
}

3
วิธีแก้ไขที่ถูกต้อง (ซึ่งทำให้คุณก้าวไปข้างหน้าของเส้นโค้ง) แม้ว่าจะไม่มีประสิทธิภาพมากนัก ทำให้ค่าเฉลี่ย 5 การเรียกไปยัง rnd5 () สำหรับการโทรแต่ละครั้งไปยัง rnd7 ()
Adam Rosenfield

ต้องการคำอธิบายเพิ่มเติมกรุณา
Barry

1
@Barry - อันดับแรกคุณไม่สามารถเพิ่มตัวเลขสุ่มสองหมายเลขเข้าด้วยกันคุณไม่ได้รับการแก้ปัญหาเชิงเส้น (พิจารณาคู่ของลูกเต๋า) ตอนนี้ให้พิจารณา "ฐาน 5": 00, 01, 02, 03, 04, 10, 11 นั่น 0-6 ในฐาน 5 ดังนั้นเราเพียงแค่ต้องสร้างตัวเลข 2 หลักของเลขฐาน 5 และเพิ่มขึ้นจนกว่าเราจะ รับหนึ่งที่อยู่ในช่วง นั่นคือสิ่งที่ r2 * 5 + r1 ทำ r2> 1 loop อยู่ที่นั่นเพราะเราจะไม่ต้องการตัวเลขที่สูงของ> 1
Will Hartung

วิธีนี้ไม่ได้สร้างการกระจายที่สม่ำเสมอ หมายเลข 1 และ 7 สามารถสร้างได้ในวิธีเดียว แต่สามารถสร้างได้ตั้งแต่ 2 ถึง 6 ในสองวิธี: ด้วย r1 เท่ากับจำนวนลบ 1 และ r2 เท่ากับ 0 หรือกับ r1 เท่ากับจำนวนลบ 2 และ r2 เท่ากับ 1. ดังนั้น 2 ถึง 6 จะถูกส่งกลับโดยเฉลี่ยสองครั้งบ่อยครั้งเท่ากับ 1 หรือ 7
Ted Hopp

12

หากเราพิจารณาข้อ จำกัด เพิ่มเติมของการพยายามให้คำตอบที่มีประสิทธิภาพมากที่สุดคือคำตอบที่ได้รับจากสตรีมอินIสแตนซ์ของความยาวเต็มจำนวนที่กระจายอย่างสม่ำเสมอmจาก 1-5 เอาท์พุตสตรีมOของจำนวนเต็มกระจายสม่ำเสมอจาก 1-7 ของความยาวที่ยาวที่สุด ที่จะพูด mL(m)

วิธีที่ง่ายที่สุดในการวิเคราะห์สิ่งนี้คือการปฏิบัติต่อสตรีม I และO5-ary และ 7-ary ตามลำดับ นี้จะทำได้โดยคิดคำตอบที่หลักของการสตรีมและในทำนองเดียวกันสำหรับกระแสa1, a2, a3,... -> a1+5*a2+5^2*a3+..O

จากนั้นถ้าเรานำส่วนของความยาวอินพุตm choose n s.t. 5^m-7^n=cที่ใดc>0และมีขนาดเล็กที่สุด จากนั้นก็จะมีแผนที่แบบสม่ำเสมอจากอินพุตสตรีมของความยาว m ไปเป็นจำนวนเต็มจาก1ไปถึง5^mและแผนที่แบบสม่ำเสมอจากจำนวนเต็มตั้งแต่ 1 ถึง7^nไปยังเอาต์พุตสตรีมที่มีความยาว n ซึ่งเราอาจต้องสูญเสียบางกรณีจากอินพุตสตรีม 7^nเกิน

ดังนั้นนี้จะช่วยให้ค่าL(m)ประมาณซึ่งจะอยู่ที่ประมาณm (log5/log7).82m

ความยากลำบากกับการวิเคราะห์ข้างต้นเป็นสมการ5^m-7^n=cซึ่งไม่ได้เป็นเรื่องง่ายที่จะแก้ตรงและกรณีที่ค่าเครื่องแบบจาก1การ5^mเกิน7^nและเราสูญเสียประสิทธิภาพ

คำถามคือความสามารถในการบรรลุถึงค่าที่ดีที่สุดของ m (log5 / log7) ตัวอย่างเช่นเมื่อจำนวนนี้เข้าใกล้จำนวนเต็มเราสามารถหาวิธีที่จะบรรลุจำนวนอินพุทที่แน่นอนนี้ได้หรือไม่?

หาก5^m-7^n=cแล้วจากกระแสการป้อนข้อมูลที่เราได้อย่างมีประสิทธิภาพสร้างตัวเลขสุ่มเครื่องแบบจาก0ไปและไม่ได้ใช้ค่าใดสูงกว่า(5^m)-1 7^nอย่างไรก็ตามค่าเหล่านี้สามารถได้รับการช่วยเหลือและนำมาใช้อีกครั้ง พวกเขาได้อย่างมีประสิทธิภาพสร้างลำดับชุดของตัวเลขตั้งแต่ 1 5^m-7^nถึง ดังนั้นเราจึงสามารถลองใช้สิ่งเหล่านี้และแปลงเป็นตัวเลข 7-ary เพื่อให้เราสามารถสร้างมูลค่าส่งออกมากขึ้น

ถ้าเราปล่อยT7(X)ให้เป็นความยาวเฉลี่ยของลำดับการส่งออกของrandom(1-7)จำนวนเต็มได้มาจากการป้อนข้อมูลสม่ำเสมอของขนาดและสมมติว่าX5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7

จากนั้นT7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)เนื่องจากเรามีความยาวไม่มีลำดับที่มีความน่าจะเป็น 7 ^ n0 / 5 ^ m ที่มีความยาวคงเหลือ5^m-7^n0กับความน่าจะ(5^m-7^n0)/5^m)เป็น

ถ้าเราแค่ทำการทดแทนเราจะได้รับ:

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

ด้วยเหตุนี้

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

อีกวิธีในการใส่นี่คือ:

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)

กรณีที่เป็นไปได้ดีที่สุดคือคนเดิมของฉันข้างต้นที่ที่5^m=7^n+ss<7

แล้วT7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)เหมือนเมื่อก่อน

กรณีที่เลวร้ายที่สุดคือเมื่อเราสามารถหา k และ st 5 ^ m = kx7 + s เท่านั้น

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

อีกกรณีหนึ่งอยู่ในระหว่าง มันน่าสนใจที่จะเห็นว่าเราทำได้ดีสำหรับ m ที่มีขนาดใหญ่มากเช่นที่เราสามารถรับเทอม error ได้ดี

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

ดูเหมือนเป็นไปไม่ได้ที่จะบรรลุe(m) = o(1)โดยทั่วไป แต่หวังว่าเราจะสามารถพิสูจน์e(m)=o(m)ได้

สิ่งทั้งหมดนั้นวางอยู่บนการกระจายของตัวเลข 7 Ary ของค่าต่างๆของ5^mm

ฉันแน่ใจว่ามีทฤษฎีมากมายที่ครอบคลุมสิ่งนี้ฉันอาจได้ดูและรายงานกลับมาในบางจุด


+2 (ถ้าทำได้) - นี่เป็นคำตอบที่ดีเพียงข้อเดียว (ซึ่งตรงข้ามกับคำว่าเพียงพอ) คุณได้คำตอบที่ดีที่สุดอันดับสองที่พอดีกับจำนวนเต็ม 32 บิต
Rex Kerr

10

นี่คือการทำงานการดำเนินงานหลามของคำตอบของอดัม

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

ฉันชอบที่จะโยนอัลกอริทึมที่ฉันกำลังมองหาใน Python เพื่อให้ฉันสามารถเล่นกับพวกเขาคิดว่าฉันโพสต์ไว้ที่นี่ด้วยความหวังว่ามันจะเป็นประโยชน์สำหรับคนที่ออกมีไม่นานที่จะโยนกัน


ไม่นั่นค่อนข้างแตกต่างจากคำตอบของฉัน คุณวนซ้ำ 21 ครั้งและยกเลิกผลลัพธ์การวนซ้ำ 20 รายการแรก คุณยังใช้ rand4 () และ rand5 () เป็นอินพุตซึ่งค่อนข้างชัดเจนว่ากฎการใช้ rand5 () เท่านั้น ในที่สุดคุณสร้างการกระจายที่ไม่สม่ำเสมอ
Adam Rosenfield

ขอโทษด้วยกับเรื่องนั้น. ฉันเหนื่อยมากเมื่อฉันดูคำถามนี้เหนื่อยพอที่ฉันจะเข้าใจอัลกอริทึมของคุณผิด ที่จริงฉันโยนมันลงใน Python เพราะฉันไม่เข้าใจว่าทำไมคุณถึงวนซ้ำ 21 ครั้ง ทำให้รู้สึกมากขึ้นในขณะนี้ ฉันสุ่มเลือกแรนท์ (1, 4) เป็นชวเลข แต่ฉันคิดว่าคุณถูกต้องมันขัดกับจิตวิญญาณของคำถาม ฉันแก้ไขรหัสแล้ว
James McMahon

@robermorales - ตามที่ Adam Rosenfeld อธิบายไว้ในคำตอบของเขาทุกคำตอบที่ให้การกระจายที่เหมือนกันอย่างแท้จริงใน [1, 7] จะเกี่ยวข้องกับการยอมรับ - ปฏิเสธวงที่อาจไม่มีที่สิ้นสุด (อย่างไรก็ตามถ้าrand5()เป็น PRNG ที่เหมาะสมแล้วลูปจะไม่สิ้นสุดเพราะในที่สุด5*(rand5() - 1) + rand5()จะเป็น <= 21 อย่างแน่นอน)
Ted Hopp

10

ทำไมไม่ทำมันง่าย ๆ ?

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

โอกาสที่จะได้รับ 1 และ 7 ในโซลูชันนี้ลดลงเนื่องจากโมดูโล่อย่างไรก็ตามถ้าคุณต้องการโซลูชันที่รวดเร็วและอ่านได้นี่เป็นวิธีที่จะไป


13
สิ่งนี้ไม่ก่อให้เกิดการกระจายตัวที่สม่ำเสมอ สิ่งนี้สร้างตัวเลข 0-6 ด้วยความน่าจะเป็น 2/25, 4/25, 5/25, 5/25, 5/25, 3/25, 1/25 ซึ่งสามารถตรวจสอบได้โดยการนับผลลัพธ์ทั้งหมดที่เป็นไปได้ทั้งหมด 25 รายการ
Adam Rosenfield

8

สมมติว่าrand (n) ตรง นี้หมายถึง "จำนวนเต็มแบบสุ่มในการแจกแจงแบบสม่ำเสมอจาก0ถึงn-1 " นี่คือตัวอย่างโค้ดโดยใช้ Python ของ randint ซึ่งมีผลกระทบนั้น มันใช้randintเท่านั้น(5)และค่าคงที่ในการผลิตผลกระทบของrandint (7) โง่จริง ๆ แล้ว

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

1
@robermorales do ... whileเพราะงูใหญ่ไม่ได้ อาจเป็นได้1337หรือ12345, หรือหมายเลขใดก็ได้> 1.
tckmn

8

หลักฐานที่อยู่เบื้องหลังคำตอบที่ถูกต้องของ Adam Rosenfield คือ:

  • x = 5 ^ n (ในกรณีของเขา: n = 2)
  • จัดการการโทร n rand5 เพื่อรับหมายเลขyภายในช่วง [1, x]
  • z = ((int) (x / 7)) * 7
  • ถ้า y> z ลองอีกครั้ง มิฉะนั้นจะส่งคืน y% 7 + 1

เมื่อ n เท่ากับ 2 คุณมีความเป็นไปได้ 4 แบบ: y = {22, 23, 24, 25} หากคุณใช้ n เท่ากับ 6 คุณจะมีเพียง 1 การทิ้ง: y = {15625}

5 ^ 6 = 15625
7 * 2232 = 15624

คุณเรียก rand5 อีกครั้ง อย่างไรก็ตามคุณมีโอกาสที่ต่ำกว่ามากในการได้รับมูลค่าการโยนทิ้ง (หรือวงวนไม่สิ้นสุด) หากมีวิธีที่จะทำให้ไม่มีค่าทิ้งสำหรับ y ฉันไม่ได้พบมัน


1
ไม่มีกรณีที่พิสูจน์ได้โดยไม่มีค่าเป็นใบปลิว - หากไม่มีค่าใช้จ่าย 5 ^ n และ 7 ^ m จะมีปัจจัยร่วมกัน แต่พวกเขากำลัง (อำนาจ) ดังนั้นพวกเขาจึงไม่
Rex Kerr

8

นี่คือคำตอบของฉัน:

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

มันซับซ้อนกว่าคนอื่นเล็กน้อย แต่ฉันเชื่อว่ามันจะลดการเรียกไปสู่ ​​r5 เช่นเดียวกับโซลูชันอื่น ๆ มีความเป็นไปได้น้อยที่มันจะวนซ้ำเป็นเวลานาน


สิ่งนี้ก่อให้เกิดการกระจายตัวไม่แตกต่างจากโซลูชันอื่น ๆ มากนัก แต่มีข้อเสียที่เพิ่มขึ้นจากความซับซ้อนที่ไม่จำเป็น นอกจากนี้ยังได้รับความทุกข์ทรมานจากความเป็นไปได้ของการวนซ้ำแบบไม่ จำกัด ที่ไม่ถูกต้องที่พิสูจน์ได้อย่างไม่ถูกต้องหากตัวเลขนั้นสุ่มอย่างแท้จริง ฉันยังคงคิดว่าสิ่งที่สร้างการแจกแจงแบบสม่ำเสมอน้อยกว่าเล็กน้อย (แม้ว่ายังมากกว่าเพียงพอ) แต่รับประกันพฤติกรรมที่กำหนดขึ้นได้ดีกว่า
paxdiablo

@Pax: โปรดให้ความกระจ่างแก่ฉันว่าวิธีนี้ทำให้เกิดการกระจายที่ไม่สม่ำเสมอ การวิเคราะห์รหัสของฉันรวมถึงการทดสอบของฉันเองแสดงให้เห็นว่าสิ่งนี้ทำให้เกิดการแจกแจงแบบสม่ำเสมอ ดังที่เราได้กล่าวไปแล้วก่อนหน้านี้มันเป็นไปไม่ได้ที่ทั้งคู่จะสร้างการกระจายตัวที่สมบูรณ์แบบและมีการรับประกันเวลาคงที่ที่แน่นอนของเวลาทำงาน
Adam Rosenfield


6

ตราบใดที่ยังมีความเป็นไปได้ไม่เหลืออยู่ให้เลือกเจ็ดตัวเลือกตัวเลขสุ่มอีกตัวซึ่งคูณจำนวนความเป็นไปได้ห้าตัว ใน 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;
}

การกระจายของคุณไม่สม่ำเสมออย่างน้อยในสายแรก แน่นอน$possibilitiesต้องเติบโตเป็น 25 เสมอเพื่อออกจากลูปและกลับมา ดังนั้นผลลัพธ์แรกของคุณคือ[0-124] % 7ซึ่งไม่ได้กระจายอย่างสม่ำเสมอเพราะ125 % 7 != 0(นี่คือ 6 จริง ๆ )
bernard paulus

6

ฉันไม่ชอบช่วงที่เริ่มจาก 1 ดังนั้นฉันจะเริ่มจาก 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;
}

นี่คือผู้ชนะ สิ่งนี้สร้างผลลัพธ์ทั้งหมด 7 รายการที่มีความน่าจะเป็นที่เท่ากัน 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

แล้วคุณจะได้รับการติดต่อที่สม่ำเสมอและการโทรแบบ rand5

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

จำเป็นต้องตั้งค่าเมล็ดล่วงหน้า


5

ฉันรู้ว่ามันได้รับคำตอบแล้ว แต่นี่ดูเหมือนว่าจะใช้ได้ แต่ฉันไม่สามารถบอกคุณได้ว่ามันมีอคติ 'การทดสอบ' ของฉันแนะนำว่าอย่างน้อยก็สมเหตุสมผล

อดัมโรเซนฟีลด์อาจจะใจดีพอที่จะแสดงความคิดเห็น?

ความคิดที่ไร้เดียงสาของฉันคือ:

สะสม rand5 จนกระทั่งมีบิตสุ่มเพียงพอที่จะสร้าง rand7 สิ่งนี้ใช้เวลาไม่เกิน 2 rand5 เพื่อให้ได้ตัวเลข rand7 ฉันใช้ค่าสะสม mod 7

เพื่อหลีกเลี่ยงการสะสมมากเกินไปและเนื่องจากการสะสมเป็น mod 7 แล้วฉันจะใช้ mod 7 ของการสะสม:

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

ฟังก์ชัน rand7 () ดังต่อไปนี้:

(ฉันปล่อยให้ช่วงของ rand5 เท่ากับ 0-4 และ rand7 เท่ากับ 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;
}

แก้ไข: เพิ่มผลลัพธ์สำหรับการทดสอบ 100 ล้านครั้ง

ฟังก์ชั่น rand 'Real' mod 5 หรือ 7

rand5: avg = 1.999802 0: 20003944 1: 19999889 2: 20003690 3: 19996938 4: 19995539 rand7: avg = 3.000111 0: 14282851 1: 14282854 3: 14288546 3: 14288546 4: 1429853846 5: 14288736

rand7 ของฉัน

ค่าเฉลี่ยดูดีและการแจกแจงตัวเลขก็โอเคเช่นกัน

randt: avg = 3.000080 0: 14288793 1: 14280135 2: 14287848 3: 14285277 4: 14286341 5: 14278663 6: 14292943


คุณควรดูความสัมพันธ์แบบต่อเนื่อง ฉันคิดว่าถ้าคุณใช้คู่ต่อเนื่อง (แต่ละหมายเลข "สุ่ม" จับคู่กับรุ่นก่อน) คุณอาจพบสิ่งที่น่าแปลกใจ คุณไม่ได้อธิบายว่าทำไมมันควรจะรักษาชุดการกระจายไว้ไม่ว่าในกรณีใด ปกติแล้วโปรแกรมการทำงานควรเริ่มต้นด้วยคำอธิบายว่าทำไมมันถึงใช้ได้
เอียน

ความสัมพันธ์ตามลำดับจะมีผลกับโซลูชันเหล่านี้หรือไม่
philcolbourn

ความสัมพันธ์ตามลำดับจะมีผลกับโซลูชันเหล่านี้หรือไม่ ไม่นานหลังจากที่ฉันลองและฉันคิดว่าฉันอธิบายมัน ดูตอนนี้ดูเหมือนว่าฉันกำลังสะสมบิตสุ่มในกลุ่มจาก rand5 เพื่อให้แน่ใจว่ามีจำนวนมากพอที่จะสะสมก่อนที่จะถอนออกพอที่จะทำหมายเลข rand7 และทำให้มั่นใจว่าฉันจะไม่สะสมมากเกินไป
philcolbourn

4

มีอัลกอริทึมที่อ้างถึงข้างต้น แต่นี่เป็นวิธีหนึ่งในการเข้าถึงแม้ว่าอาจจะเป็นวงเวียน ฉันสมมติว่าค่าที่สร้างขึ้นจาก 0

R2 = ตัวสร้างตัวเลขสุ่มให้ค่าน้อยกว่า 2 (พื้นที่ตัวอย่าง = {0, 1})
R8 = ตัวสร้างตัวเลขสุ่มให้ค่าน้อยกว่า 8 (พื้นที่ตัวอย่าง = {0, 1, 2, 3, 4, 5, 6, 7 })

ในการสร้าง R8 จาก R2 คุณจะใช้ R2 สามครั้งและใช้ผลรวมของการวิ่งทั้ง 3 ครั้งเป็นเลขฐานสองที่มี 3 หลัก นี่คือช่วงของค่าเมื่อมีการรัน R2 สามครั้ง:

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

ตอนนี้เพื่อสร้าง R7 จาก R8 เราเพียงเรียกใช้ R7 อีกครั้งถ้ามันคืนค่า 7:

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

โซลูชันวงเวียนคือการสร้าง R2 จาก R5 (เช่นเดียวกับที่เราสร้าง R7 จาก R8) จากนั้น R8 จาก R2 และ R7 จาก R8


เช่นเดียวกับผู้อื่นจำนวนมากวิธีการนี้อาจใช้เวลานานโดยพลการต่อการเรียก R7 เนื่องจากคุณสามารถได้รับ sevens จำนวนมากจาก R8
Alex North-Keys

4

นี่คือวิธีการแก้ปัญหาที่เหมาะกับจำนวนเต็มทั้งหมดและอยู่ในระยะประมาณ 4% ของค่าที่เหมาะสม (เช่นใช้ตัวเลขสุ่ม 1.26 ใน {0..4} สำหรับทุก ๆ คนใน {0..6}) รหัสใน Scala แต่คณิตศาสตร์ควรมีเหตุผลชัดเจนในภาษาใด ๆ : คุณใช้ประโยชน์จากข้อเท็จจริงที่ว่า 7 ^ 9 + 7 ^ 8 นั้นใกล้เคียงกับ 5 ^ 11 มาก ดังนั้นคุณเลือกตัวเลข 11 หลักในฐาน 5 แล้วตีความมันเป็นตัวเลข 9 หลักในฐาน 7 หากอยู่ในช่วง (ให้ 9 ฐาน 7 ตัวเลข) หรือเป็นตัวเลข 8 หลักถ้ามันเกิน 9 หลักเป็นต้น .:

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

หากคุณวางการทดสอบลงในล่าม (REPL จริง) คุณจะได้รับ:

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

การกระจายค่อนข้างดีและแบน (ภายในประมาณ 10k จาก 1/7 จาก 10 ^ 8 ในแต่ละถังขยะตามที่คาดไว้จากการกระจายแบบเกาส์ประมาณ)


3

ด้วยการใช้ยอดรวมคุณสามารถทำได้ทั้งคู่

  • รักษาการกระจายตัวที่เท่าเทียมกัน; และ
  • ไม่ต้องเสียสละองค์ประกอบใด ๆ ในลำดับแบบสุ่ม

ปัญหาทั้งสองนี้เป็นปัญหากับrand(5)+rand(5)...วิธีแก้ไขปัญหาแบบง่าย ๆ รหัส Python ต่อไปนี้แสดงวิธีการใช้ (ส่วนใหญ่เป็นการพิสูจน์การกระจาย)

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)

และผลลัพธ์นี้แสดงผลลัพธ์:

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

แบบง่ายๆrand(5)+rand(5)ไม่สนใจกรณีที่ให้ผลตอบแทนมากกว่า 6 มีรูปแบบทั่วไป 18%, 100 เท่าของวิธีที่แสดงด้านบน:

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

และตามคำแนะนำของ Nixuz ฉันได้ทำความสะอาดสคริปต์ขึ้นเพื่อให้คุณสามารถแยกและใช้rand7...เนื้อหา:

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)

2
เอาละฉันขอใช้ถ้อยคำนี้ซ้ำอีก เนื่องจากมีการสร้าง x เฉพาะในบางจุดในลำดับเพียง 5 ใน 7 หมายเลขเท่านั้นที่สามารถผลิตสำหรับหมายเลขถัดไปในลำดับ RNG ที่แท้จริงจะมีตัวอย่างทั้งหมดเป็นอิสระจากกัน แต่ในกรณีนี้พวกเขาจะไม่ชัดเจน
Adam Rosenfield

3
มันเป็นความจริงที่คำถามเดิมไม่ได้ระบุว่าฟังก์ชั่นอินพุทและเอาท์พุทสร้างตัวอย่างที่เป็นอิสระและกระจายตัว (iid) แต่ฉันคิดว่ามันเป็นความคาดหวังที่สมเหตุสมผลว่าถ้าอินพุต rand5 () เป็น iid ดังนั้นเอาต์พุต rand7 () ควรเป็น iid ด้วย หากคุณคิดว่าไม่สมเหตุสมผลลองใช้ RNG ที่ไม่ใช่ของคุณ
Adam Rosenfield

1
ดังนั้นคำพูดจากนักคณิตศาสตร์ที่มหาวิทยาลัยคืออะไร?
Adam Rosenfield

1
วิธีนี้แตกได้ชัดเจน เห็นได้ชัดว่าคุณต้องโทรหา rand5 (โดยเฉลี่ย) มากกว่าหนึ่งครั้งต่อการโทรไปที่ rand7 และวิธีนี้ไม่ได้ ดังนั้นผลลัพธ์ไม่สามารถสุ่มโดยนิยามที่มีเหตุผลของการสุ่ม
Chris Suter

1
@Pax ในทุกการวนซ้ำของฟังก์ชั่นของคุณมันสามารถคืนค่าหนึ่งในห้าค่าที่แตกต่างกัน (แม้ว่าจะอยู่ในช่วง 0-6) การวนซ้ำครั้งแรกสามารถส่งกลับตัวเลขในช่วง 0-4 เท่านั้น ดังนั้นจึงควรมีความชัดเจนว่าในขณะที่ฟังก์ชั่นของคุณอาจมีการแจกแจงแบบสม่ำเสมอตัวอย่างไม่เป็นอิสระนั่นคือมันมีความสัมพันธ์ซึ่งไม่ใช่สิ่งที่คุณต้องการในเครื่องกำเนิดตัวเลขแบบสุ่ม
Chris Suter

3

คำตอบนี้เป็นการทดลองในการรับเอนโทรปีที่เป็นไปได้มากที่สุดจากฟังก์ชัน Rand5 t จึงค่อนข้างชัดเจนและเกือบจะช้ากว่าการใช้งานอื่น ๆ

สมมติว่าการกระจายเครื่องแบบจาก 0-4 และส่งผลการกระจายสม่ำเสมอจาก 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;
  }
}

จำนวนบิตที่เพิ่มให้กับบัฟเฟอร์ต่อการเรียกใช้ Rand5 ปัจจุบันคือ 4/5 * 2 ดังนั้น 1.6 หากรวมค่าความน่าจะเป็น 1/5 ที่เพิ่มขึ้น 0.05 ดังนั้น 1.65 แต่เห็นความคิดเห็นในรหัสที่ฉันต้องปิดการใช้งาน

บิตบริโภคโดยการเรียกไปที่ Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
นี่คือ 3 + 3/8 + 3/64 + 3/512 ... ดังนั้น ประมาณ 3.42

โดยดึงข้อมูลจาก sevens ฉันเรียกคืน 1/8 * 1/7 บิตต่อการโทรประมาณ 0.018

สิ่งนี้ให้ปริมาณการใช้สุทธิ 3.4 บิตต่อการโทรซึ่งหมายความว่าอัตราส่วนคือ 2.125 การโทรถึง Rand5 สำหรับทุก ๆ Rand7 ค่าที่เหมาะสมควรเป็น 2.1

ฉันคิดวิธีนี้อย่างมีนัยสำคัญช้ากว่ามากของคนอื่น ๆ ที่นี่เว้นแต่ค่าใช้จ่ายของการเรียกร้องให้ Rand5 ที่มีราคาแพงมาก (พูดโทรออกไปบางแหล่งภายนอกของเอนโทรปี)


วิธีแก้ปัญหาของคุณปรากฏขึ้นถูกต้องนอกเหนือจากข้อผิดพลาดง่ายๆ: "ถ้า (นับ> 1)" ควรเป็น "ถ้า (นับ <= 1)" และ "i ++" ที่เกิดขึ้นในไม่ช้าหลังจากนั้นควรอยู่ในวงเล็บปีกกาที่อยู่ข้างหน้า ฉันไม่แน่ใจว่า BitsSet () ถูกต้องหรือไม่ แต่นั่นไม่เกี่ยวข้องเลย
Adam Rosenfield

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

ฉันแก้ไข <= ขอบคุณ i ++ ควรอยู่ที่นั่นจริงๆ มันควรจะเกิดขึ้นในศูนย์และกรณีที่ 1 (เพิ่ม 1 หรือศูนย์ตามลำดับเพื่อบัฟเฟอร์) นี่ไม่ใช่สิ่งที่ฉันแนะนำให้ใช้อย่างแน่นอนมันซับซ้อนอย่างน่ากลัว ฉันแค่สนใจฉันว่าใกล้ถึงขีด จำกัด ของเอนโทรปีทางทฤษฎีในปัญหา ... ขอบคุณสำหรับความคิดเห็น กระแทกแดกดันไส้ของบัฟเฟอร์ในสายแรกคือการทำให้มันง่ายที่จะเขียน :)
ShuggyCoUk

ฉันทำใหม่เพื่อให้เข้าใจได้ง่ายขึ้น (ที่ความเร็ว) แต่ก็ทำให้ถูกต้อง มันยังไม่เหมาะสม แต่ด้วยเหตุผลบางอย่างบิต 1/5 ทำให้เกิดปัญหาแม้ว่าจะมีจำนวนเท่ากันก็ตาม
ShuggyCoUk

3

ใน 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);
}

วนรอบในการสร้างตัวเลขสุ่มระหว่าง 16 และ 127 หารด้วยสิบหกเพื่อสร้าง float ระหว่าง 1 และ 7.9375 จากนั้นปัดเศษลงเพื่อรับ int ระหว่าง 1 และ 7 หากฉันไม่เข้าใจผิดมีโอกาส 16/112 ในการรับ หนึ่งในเจ็ดผลลัพธ์ใด ๆ


แม้ว่าอาจมีคำตอบที่ง่ายกว่าเช่นนี้โดยไม่ต้องวนซ้ำแบบมีเงื่อนไขและแบบโมดูโลแทนที่จะเป็นแบบพื้น ฉันไม่สามารถกระทืบตัวเลขในตอนนี้
dqhendricks


3

ฉันคิดว่าฉันมีคำตอบสี่ข้อสองคำตอบให้ตรงกับที่ @Adam Rosenfieldแต่ไม่มีปัญหาวนรอบไม่สิ้นสุดและอีกสองคำตอบที่สมบูรณ์แบบที่สุด แต่ใช้งานได้เร็วกว่าครั้งแรก

ทางออกที่ดีที่สุดต้องใช้การโทร 7 rand5ครั้ง แต่ให้ดำเนินการต่อเพื่อให้เข้าใจ

วิธีที่ 1 - แน่นอน

ความแข็งแกร่งของคำตอบของอดัมคือให้การกระจายที่สมบูรณ์แบบและมีความเป็นไปได้สูงมาก (21/25) ที่ต้องการเพียงแค่การโทรสองสายไปยัง r5 () อย่างไรก็ตามกรณีที่เลวร้ายที่สุดคือการวนซ้ำไม่สิ้นสุด

โซลูชั่นแรกด้านล่างนี้ให้การกระจายที่สมบูรณ์แบบ แต่ต้องการการโทรทั้งหมด 42 rand5ครั้ง ไม่มีลูปไม่มีที่สิ้นสุด

นี่คือการใช้ 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

สำหรับคนที่ไม่คุ้นเคยกับ R นี่เป็นเวอร์ชั่นที่เรียบง่าย:

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
}

การกระจายของrand5จะถูกเก็บรักษาไว้ ถ้าเราทำคณิตศาสตร์แต่ละ 7 ซ้ำของวงมี 5 ^ 6 (7 * 5^6) %% 7 = 0ชุดเป็นไปได้จำนวนทั้งหมดจึงรวมกันได้จะ ดังนั้นเราสามารถแบ่งตัวเลขสุ่มที่สร้างขึ้นในกลุ่มเท่ากับ 7 ดูวิธีที่สองสำหรับการอภิปรายเพิ่มเติมเกี่ยวกับเรื่องนี้

นี่คือชุดค่าผสมที่เป็นไปได้ทั้งหมด:

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 

ฉันคิดว่ามันตรงไปตรงมาเพื่อแสดงว่าวิธีการของอดัมจะทำงานได้เร็วขึ้นมาก ความน่าจะเป็นที่มีการเรียก 42 ครั้งขึ้นไปrand5ในโซลูชันของ Adam มีขนาดเล็กมาก ( (4/25)^21 ~ 10^(-17))

วิธีที่ 2 - ไม่แน่นอน

ตอนนี้วิธีที่สองซึ่งเกือบจะเหมือนกัน แต่ต้องใช้ 6 การเรียกไปที่rand5:

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

นี่เป็นเวอร์ชั่นที่เรียบง่าย:

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

นี่คือการวนซ้ำของวิธีที่ 1 หากเราสร้างชุดค่าผสมที่เป็นไปได้ทั้งหมดนี่คือผลการนับ:

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

หมายเลขหนึ่งจะปรากฏขึ้นอีกครั้งใน5^6 = 15625การทดลอง

ตอนนี้ในวิธีที่ 1 โดยการเพิ่ม 1 ถึง 6 เราจะย้ายหมายเลข 2233 ไปยังแต่ละจุดที่ต่อเนื่องกัน ดังนั้นจำนวนชุดค่าผสมทั้งหมดจะตรงกัน สิ่งนี้ใช้ได้เพราะ 5 ^ 6 %% 7 = 1 และจากนั้นเราทำรูปแบบที่เหมาะสม 7 แบบดังนั้น (7 * 5 ^ 6 %% 7 = 0)

วิธีที่ 3 - แน่นอน

หากเข้าใจวิธีการที่ 1 และ 2 วิธีที่ 3 จะตามมาและต้องใช้การเรียกเพียง 7 rand5ครั้งเท่านั้น ณ จุดนี้ฉันรู้สึกว่านี่เป็นจำนวนขั้นต่ำของการโทรที่จำเป็นสำหรับโซลูชันที่แน่นอน

นี่คือการใช้ R:

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

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

สำหรับคนที่ไม่คุ้นเคยกับ R นี่เป็นเวอร์ชั่นที่เรียบง่าย:

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

การกระจายของrand5จะถูกเก็บรักษาไว้ ถ้าเราทำคณิตศาสตร์แต่ละ 7 ซ้ำของวงมี 5 (7 * 5) %% 7 = 0ผลลัพธ์ที่เป็นไปจำนวนจึงรวมรวมกันได้จะ ดังนั้นเราสามารถแบ่งตัวเลขสุ่มที่สร้างขึ้นในกลุ่มเท่ากับ 7 ดูวิธีที่หนึ่งและสองสำหรับการอภิปรายเพิ่มเติมเกี่ยวกับเรื่องนี้

นี่คือชุดค่าผสมที่เป็นไปได้ทั้งหมด:

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 

ฉันคิดว่ามันตรงไปตรงมาเพื่อแสดงว่าวิธีการของอดัมจะยังคงทำงานได้เร็วขึ้น ความน่าจะเป็นที่มีการโทร 7 ครั้งขึ้นไปrand5ในโซลูชันของ Adam ยังมีขนาดเล็ก ( (4/25)^3 ~ 0.004)

วิธีที่ 4 - ไม่แน่นอน

นี่เป็นรูปแบบย่อยของวิธีที่สอง มันเกือบจะเหมือนกัน แต่ต้องใช้ 7 การโทรrand5นั่นคืออีกวิธีการหนึ่งสำหรับวิธีที่ 2:

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

นี่เป็นเวอร์ชั่นที่เรียบง่าย:

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

หากเราสร้างชุดค่าผสมที่เป็นไปได้ทั้งหมดนี่คือผลการนับ:

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

ตัวเลขสองจำนวนจะปรากฏขึ้นน้อยลงหนึ่งครั้งใน5^7 = 78125การทดลอง สำหรับจุดประสงค์ส่วนใหญ่ฉันสามารถอยู่กับสิ่งนั้นได้


1
ฉันไม่คุ้นเคยกับ R แต่ถ้าฉันไม่เข้าใจผิดว่างานเหล่านี้ได้อย่างไรวิธีที่ 1 ไม่ถูกต้อง มันมี (5 ^ 6) ^ 7 = 5 ^ 42 ผลลัพธ์ที่เป็นไปได้ไม่ใช่ (5 ^ 6) * 7; 5 ^ 42 ไม่สามารถหารด้วย 7 เช่นเดียวกันวิธีที่ 3 ไม่แน่นอน มันมีผลลัพธ์ที่เป็นไปได้ 5 ^ 7 ไม่ใช่ 5 * 7 (การวนซ้ำครั้งสุดท้ายในวิธีที่ 3 ที่i=7ไม่มีผลเนื่องจากการเพิ่ม7*rand5()ไปrยังไม่เปลี่ยนค่าของrmod 7)
Adam Rosenfield

2

ฟังก์ชั่นที่คุณต้องการคือrand1_7 ()ฉันเขียน rand1_5 () เพื่อให้คุณสามารถทดสอบและแปลงมันได้

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.