รับฟังก์ชั่นที่สร้างจำนวนเต็มแบบสุ่มในช่วง 1 ถึง 5 ให้เขียนฟังก์ชันที่สร้างจำนวนเต็มแบบสุ่มในช่วง 1 ถึง 7
- ทางออกที่ง่ายคืออะไร?
- โซลูชั่นที่มีประสิทธิภาพในการลดการใช้หน่วยความจำหรือรันบน CPU ที่ช้ากว่าคืออะไร
7 * rand5() / 5
อะไร
รับฟังก์ชั่นที่สร้างจำนวนเต็มแบบสุ่มในช่วง 1 ถึง 5 ให้เขียนฟังก์ชันที่สร้างจำนวนเต็มแบบสุ่มในช่วง 1 ถึง 7
7 * rand5() / 5
อะไร
คำตอบ:
นี่จะเทียบเท่ากับโซลูชันของ 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 สุ่มเลือกตำแหน่งบนกระดานปาเป้าและหากเราไม่ได้ผลลัพธ์ที่ดีเราก็จะปาเป้าปาลูกดอกต่อไป
เช่นเดียวกับอดัมกล่าวว่าสิ่งนี้สามารถทำงานได้ตลอดไปในกรณีที่เลวร้ายที่สุด แต่สถิติกรณีที่เลวร้ายที่สุดไม่เคยเกิดขึ้น :)
rand5
เป็นแบบเดียวกันทุกเซลล์ในvals
กริดจะมีโอกาสเลือกเท่ากัน กริดประกอบด้วยสำเนาสามชุดของแต่ละจำนวนเต็มในช่วงเวลา [1, 7], บวกสี่ศูนย์ ดังนั้นสตรีมผลลัพธ์ "ดิบ" จึงมีแนวโน้มที่จะมีการรวมกันของค่า [1, 7] รวมทั้งเลขศูนย์ที่เกิดขึ้นบ่อยกว่าค่าที่ได้รับอนุญาตของแต่ละบุคคล แต่นั่นไม่สำคัญเพราะศูนย์ถูกตัดออกเหลือเพียงค่าผสม [1, 7]
ไม่มีวิธีแก้ปัญหา (ที่ถูกต้อง) ซึ่งจะทำงานในระยะเวลาคงที่เนื่องจาก 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 การวนซ้ำของลูป แต่มีความน่าจะเป็นที่จะเกิดการวนซ้ำน้อยที่สุดตลอดไป
N
โทรไปrand5()
ในกรณีที่เลวร้ายที่สุด จากนั้นจะมี 5 ^ N ผลลัพธ์ที่เป็นไปได้ของลำดับการเรียกไปrand5
แต่ละสายมีเอาต์พุต 1-7 ดังนั้นถ้าคุณบวกลำดับการโทรที่เป็นไปได้ทั้งหมดซึ่งมีเอาต์พุตk
สำหรับ1≤k≤7แต่ละค่าความน่าจะเป็นที่เอาต์พุตk
คือ m / 5 ^ N โดยที่ m คือจำนวนของลำดับดังกล่าว ดังนั้น m / 5 ^ N = 1/7 แต่ไม่มีวิธีแก้ปัญหาจำนวนเต็ม (N, m) ที่เป็นไปได้ ==> ความขัดแย้งนี้
ฉันต้องการที่จะเพิ่มคำตอบอื่นที่นอกเหนือไปจากคำตอบแรกของฉัน คำตอบนี้จะพยายามลดจำนวนการโทรไปยังการ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. a
1 a
2 a
3 ... ซึ่งแต่ละหลักถูกเลือกโดยการเรียกไปยัง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 มีดังนี้:
เพื่อจัดการกับปัญหาของความแม่นยำที่ไม่สิ้นสุดเราคำนวณผลลัพธ์บางส่วนและเรายังเก็บขอบเขตบนของผลลัพธ์ที่อาจเป็นไปได้ นั่นคือสมมติว่าเราเรียก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- บิต
(ฉันได้ขโมยคำตอบของ 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()
5 * rand5() + rand5()
ที่เกิดขึ้นจะเป็นกรณีในการแสดงออก
ขั้นตอนวิธีการ:
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;
}
}
}
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 );
}
}
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
ที่สามารถเกิดขึ้นได้รับการโทรไปยังn
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]
int ans = 0;
while (ans == 0)
{
for (int i=0; i<3; i++)
{
while ((r = rand5()) == 3){};
ans += (r < 3) >> i
}
}
ans += (r < 3) << i
ข้อมูลต่อไปนี้สร้างการแจกแจงแบบสม่ำเสมอใน {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;
}
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)
ปัญหาการบ้านได้รับอนุญาตที่นี่หรือไม่
ฟังก์ชันนี้ใช้เลขฐาน 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;
}
หากเราพิจารณาข้อ จำกัด เพิ่มเติมของการพยายามให้คำตอบที่มีประสิทธิภาพมากที่สุดคือคำตอบที่ได้รับจากสตรีมอินI
สแตนซ์ของความยาวเต็มจำนวนที่กระจายอย่างสม่ำเสมอm
จาก 1-5 เอาท์พุตสตรีมO
ของจำนวนเต็มกระจายสม่ำเสมอจาก 1-7 ของความยาวที่ยาวที่สุด ที่จะพูด m
L(m)
วิธีที่ง่ายที่สุดในการวิเคราะห์สิ่งนี้คือการปฏิบัติต่อสตรีม I และO
5-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)
จำนวนเต็มได้มาจากการป้อนข้อมูลสม่ำเสมอของขนาดและสมมติว่าX
5^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+s
s<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^m
m
ฉันแน่ใจว่ามีทฤษฎีมากมายที่ครอบคลุมสิ่งนี้ฉันอาจได้ดูและรายงานกลับมาในบางจุด
นี่คือการทำงานการดำเนินงานหลามของคำตอบของอดัม
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 เพื่อให้ฉันสามารถเล่นกับพวกเขาคิดว่าฉันโพสต์ไว้ที่นี่ด้วยความหวังว่ามันจะเป็นประโยชน์สำหรับคนที่ออกมีไม่นานที่จะโยนกัน
rand5()
เป็น PRNG ที่เหมาะสมแล้วลูปจะไม่สิ้นสุดเพราะในที่สุด5*(rand5() - 1) + rand5()
จะเป็น <= 21 อย่างแน่นอน)
ทำไมไม่ทำมันง่าย ๆ ?
int random7() {
return random5() + (random5() % 3);
}
โอกาสที่จะได้รับ 1 และ 7 ในโซลูชันนี้ลดลงเนื่องจากโมดูโล่อย่างไรก็ตามถ้าคุณต้องการโซลูชันที่รวดเร็วและอ่านได้นี่เป็นวิธีที่จะไป
สมมติว่า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
do ... while
เพราะงูใหญ่ไม่ได้ อาจเป็นได้1337
หรือ12345
, หรือหมายเลขใดก็ได้> 1.
หลักฐานที่อยู่เบื้องหลังคำตอบที่ถูกต้องของ Adam Rosenfield คือ:
เมื่อ n เท่ากับ 2 คุณมีความเป็นไปได้ 4 แบบ: y = {22, 23, 24, 25} หากคุณใช้ n เท่ากับ 6 คุณจะมีเพียง 1 การทิ้ง: y = {15625}
5 ^ 6 = 15625
7 * 2232 = 15624
คุณเรียก rand5 อีกครั้ง อย่างไรก็ตามคุณมีโอกาสที่ต่ำกว่ามากในการได้รับมูลค่าการโยนทิ้ง (หรือวงวนไม่สิ้นสุด) หากมีวิธีที่จะทำให้ไม่มีค่าทิ้งสำหรับ y ฉันไม่ได้พบมัน
นี่คือคำตอบของฉัน:
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 เช่นเดียวกับโซลูชันอื่น ๆ มีความเป็นไปได้น้อยที่มันจะวนซ้ำเป็นเวลานาน
ง่ายและมีประสิทธิภาพ:
int rand7 ( void )
{
return 4; // this number has been calculated using
// rand5() and is in the range 1..7
}
(แรงบันดาลใจจากการ์ตูน "โปรแกรมเมอร์" ที่คุณชื่นชอบคืออะไร )
ตราบใดที่ยังมีความเป็นไปได้ไม่เหลืออยู่ให้เลือกเจ็ดตัวเลือกตัวเลขสุ่มอีกตัวซึ่งคูณจำนวนความเป็นไปได้ห้าตัว ใน 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 จริง ๆ )
ฉันไม่ชอบช่วงที่เริ่มจาก 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;
}
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
แล้วคุณจะได้รับการติดต่อที่สม่ำเสมอและการโทรแบบ rand5
def rand7:
seed += 1
if seed >= 7:
seed = 0
yield seed
จำเป็นต้องตั้งค่าเมล็ดล่วงหน้า
ฉันรู้ว่ามันได้รับคำตอบแล้ว แต่นี่ดูเหมือนว่าจะใช้ได้ แต่ฉันไม่สามารถบอกคุณได้ว่ามันมีอคติ 'การทดสอบ' ของฉันแนะนำว่าอย่างน้อยก็สมเหตุสมผล
อดัมโรเซนฟีลด์อาจจะใจดีพอที่จะแสดงความคิดเห็น?
ความคิดที่ไร้เดียงสาของฉันคือ:
สะสม 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
มีอัลกอริทึมที่อ้างถึงข้างต้น แต่นี่เป็นวิธีหนึ่งในการเข้าถึงแม้ว่าอาจจะเป็นวงเวียน ฉันสมมติว่าค่าที่สร้างขึ้นจาก 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
นี่คือวิธีการแก้ปัญหาที่เหมาะกับจำนวนเต็มทั้งหมดและอยู่ในระยะประมาณ 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 ในแต่ละถังขยะตามที่คาดไว้จากการกระจายแบบเกาส์ประมาณ)
ด้วยการใช้ยอดรวมคุณสามารถทำได้ทั้งคู่
ปัญหาทั้งสองนี้เป็นปัญหากับ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)
คำตอบนี้เป็นการทดลองในการรับเอนโทรปีที่เป็นไปได้มากที่สุดจากฟังก์ชัน 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 ที่มีราคาแพงมาก (พูดโทรออกไปบางแหล่งภายนอกของเอนโทรปี)
ใน 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 ในการรับ หนึ่งในเจ็ดผลลัพธ์ใด ๆ
extern int r5();
int r7() {
return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}
7 = 111b
กับp(7) = 8 / 125
ฉันคิดว่าฉันมีคำตอบสี่ข้อสองคำตอบให้ตรงกับที่ @Adam Rosenfieldแต่ไม่มีปัญหาวนรอบไม่สิ้นสุดและอีกสองคำตอบที่สมบูรณ์แบบที่สุด แต่ใช้งานได้เร็วกว่าครั้งแรก
ทางออกที่ดีที่สุดต้องใช้การโทร 7 rand5
ครั้ง แต่ให้ดำเนินการต่อเพื่อให้เข้าใจ
ความแข็งแกร่งของคำตอบของอดัมคือให้การกระจายที่สมบูรณ์แบบและมีความเป็นไปได้สูงมาก (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)
)
ตอนนี้วิธีที่สองซึ่งเกือบจะเหมือนกัน แต่ต้องใช้ 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)
หากเข้าใจวิธีการที่ 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
)
นี่เป็นรูปแบบย่อยของวิธีที่สอง มันเกือบจะเหมือนกัน แต่ต้องใช้ 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
การทดลอง สำหรับจุดประสงค์ส่วนใหญ่ฉันสามารถอยู่กับสิ่งนั้นได้
i=7
ไม่มีผลเนื่องจากการเพิ่ม7*rand5()
ไปr
ยังไม่เปลี่ยนค่าของr
mod 7)
ฟังก์ชั่นที่คุณต้องการคือ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