สร้างตัวเลขสุ่มกระจายอย่างสม่ำเสมอโดยใช้เหรียญ


25

คุณมีหนึ่งเหรียญ คุณสามารถพลิกมันได้หลายครั้งตามที่คุณต้องการ

คุณต้องการที่จะสร้างตัวเลขสุ่มดังกล่าวว่าR < Bที่R , , Z +rar<br,a,bZ+

การแจกแจงของตัวเลขควรเหมือนกัน

มันง่ายถ้า :ba=2n

r = a + binary2dec(flip n times write 0 for heads and 1 for tails) 

เกิดอะไรขึ้นถ้า ?ba2n


ใช้อัลกอริทึมของ Han-Hoshi โดยทั่วไปจะแบ่งช่วงเวลาเป็นสองส่วนใช้บิตสุ่มของคุณ (พลิกเหรียญ) สุ่มเลือกช่วงเวลาใดช่วงเวลาหนึ่งจากนั้นทำซ้ำขั้นตอนนี้ตามด้านที่คุณเลือกจนกว่าคุณจะหมดบิต สิ่งนี้จะทำให้คุณมีช่วงเวลาที่กระจายอย่างสม่ำเสมอจากพาร์ติชันของบรรทัดจริง ยิ่งคุณพลิกมากเท่าไหร่ก็ยิ่งมีความแม่นยำมากขึ้นเท่านั้น
zenna

คำตอบ:


13

สิ่งที่คุณกำลังมองหานั้นมาจากการสุ่มตัวอย่างการปฏิเสธหรือวิธีการยอมรับ (โปรดทราบว่าหน้า Wiki เป็นเทคนิคเล็กน้อย)

วิธีนี้มีประโยชน์ในสถานการณ์เหล่านี้: คุณต้องการเลือกวัตถุสุ่มจากชุด (จำนวนเต็มแบบสุ่มในชุดในกรณีของคุณ) แต่คุณไม่รู้วิธีการทำเช่นนั้น แต่คุณสามารถรับวัตถุสุ่มจากชุดที่มีขนาดใหญ่ที่มีชุดแรก (ในกรณีของคุณ[ , 2 k + ]สำหรับบางkดังกล่าวที่2 k + นี้สอดคล้องกับkเหรียญพลิก)[a,b][a,2k+a]k2k+abk

ในสถานการณ์เช่นนี้คุณเพียงแค่เลือกองค์ประกอบจากชุดที่ใหญ่กว่าจนกว่าคุณจะสุ่มเลือกองค์ประกอบในชุดที่เล็กกว่า หากชุดเล็กของคุณมีขนาดใหญ่พอเทียบกับชุดที่ใหญ่กว่าของคุณ (ในกรณีของคุณมีจำนวนเต็มมากเป็นสองเท่าของจำนวน[ a , b ]ซึ่งดีพอ) นี่มีประสิทธิภาพ[a,2k+a][a,b]

ตัวอย่างอื่น: สมมติว่าคุณต้องการเลือกจุดสุ่มภายในวงกลมที่มีรัศมี 1 ทีนี้มันไม่ง่ายเลยที่จะเกิดขึ้นกับวิธีการโดยตรงสำหรับสิ่งนี้ เราเปลี่ยนเป็นวิธียอมรับ - ปฏิเสธ: เราสุ่มตัวอย่างคะแนนในจตุรัสขนาด 1x1 ที่ครอบคลุมวงกลมและทดสอบว่าจำนวนที่เราวาดนั้นอยู่ในวงกลมหรือไม่


3
โปรดทราบว่าถ้าเราปฏิเสธตัวอย่างจากเพื่อรับการแจกแจงแบบBจำนวนการทำซ้ำที่คาดไว้คือ| A |AB(ในขณะที่เราทำการทดสอบด้วยการกระจายเชิงเรขาคณิต) |A||B|
Raphael

ฉันจำได้ว่าเห็นบางที่ว่าสิ่งนี้ไม่สามารถทำได้อย่างแน่นอนยกเว้นช่วงที่เป็นพลังของ 2 (ตามที่เป็นเหตุผลเช่น 1/3 ไม่มีการยกเลิกการขยายตัวแบบไบนารี)
vonbrand

7

(technicalities: คำตอบที่เหมาะกับการเลือกหมายเลข )ax<b

เนื่องจากคุณได้รับอนุญาตให้พลิกเหรียญของคุณหลาย ๆ ครั้งตามที่คุณต้องการคุณจะได้รับความเป็นไปได้ที่จะใกล้เคียงกับคุณโดยการเลือกเศษส่วน (โดยใช้เลขฐานสอง: คุณพลิก เหรียญสำหรับแต่ละหลักหลังจุด) และคูณrด้วยb - aเพื่อรับตัวเลขระหว่าง 0 ถึง [ba-1] (ปัดเศษลงเป็นจำนวนเต็ม) เพิ่มหมายเลขนี้ไปที่และคุณทำเสร็จแล้วr[0,1]rbaa

ตัวอย่าง : การพูด 3 1/3 ในไบนารี 0.0101010101 .... แล้วถ้าคุณพลิกอยู่ระหว่าง 0 และ 0.010101 ... เลือกของคุณเป็นข ถ้ามันเป็น beween 0.010101 .. และ 0.10101010 ... เลือกของคุณจะ+ 1และมิฉะนั้นจะเป็น+ 2ba=3ba+1a+2

หากคุณพลิกเหรียญของคุณครั้งแล้วจำนวนระหว่างแต่ละและBจะได้รับเลือกด้วยความน่าจะเป็นที่ 1tab)1ba±2(t+1)


1
นี่ไม่ได้เป็นการแจกแจงแบบสม่ำเสมอ สำหรับบางแอปพลิเคชัน (เช่น crypto บางครั้ง) สิ่งนี้อาจไม่ดีมาก
Gilles 'หยุดความชั่วร้าย'

3
@Gilles: สามารถแก้ไขเพื่อให้การกระจายที่สมบูรณ์แบบโดยการพลิกจนกว่าจะไม่สามารถเปลี่ยนผลลัพธ์ได้อีกต่อไป นี่คือคำตอบที่มีประสิทธิภาพที่สุด
Neil G

@ NeilG ฉันรู้ว่ามันสามารถแก้ไขได้ แต่การแก้ไขมันจะเป็นส่วนสำคัญของคำตอบ
Gilles 'หยุดความชั่วร้าย'

2
@Gilles: ถูกต้อง เขาสามารถปรับเปลี่ยนคำตอบที่จะบอกว่าคุณสามารถผลิตจำหน่ายชุดสมบูรณ์ถ้าคุณพลิกขณะ +1 จากฉันสำหรับกรณีเฉลี่ยที่ดีที่สุดและเวลากรณีเลวร้ายที่สุด (ba)(f+2t1)(ba)(f2t1)
Neil G

@ NeilG มันไม่สามารถ "คงที่" เนื่องจากมีจำนวนเต็มค่อนข้างมากซึ่งไม่มีเศษส่วนไบนารีที่สิ้นสุด
vonbrand

7

เลือกจำนวนในการใช้พลังงานขนาดใหญ่ต่อไปของ 2 ช่วงและคำตอบทิ้งมากกว่าba

n = b-a;
N = round_to_next_larger_power_of_2(n)
while (1) {
  x = random(0 included to N excluded);
  if (x < n) break;
}
r = a + x;

4
และทำไมถึงใช้งานได้?
Raphael

@ ราฟาเอลคุณไม่เชื่อหรือคุณแค่อยากให้โปสเตอร์อธิบายรายละเอียดเพิ่มเติม?
Suresh

1
@Suresh: หลัง รหัสหลอกสามารถขัดเล็กน้อย แต่ใช้สิ่งที่ผู้ตอบคนอื่นอธิบาย คำตอบนี้ไม่คุ้มค่ากับตัวเองมากนัก
Raphael


3

หาก ba ไม่ใช่พลังของ 2 คุณอาจต้องพลิกหลายเหรียญเพื่อให้ได้ผลลัพธ์ คุณอาจไม่เคยได้ผลลัพธ์ แต่ก็ไม่น่าเป็นไปได้

วิธีการ

วิธีที่ง่ายที่สุดคือการสร้างตัวเลขใน [a, a + 2 ^ n) โดยที่ 2 ^ n> = ba จนกว่าจะเกิดการลงจอดใน [a, b) คุณทิ้งเอนโทรปีจำนวนมากด้วยวิธีนี้

วิธีที่แพงกว่าช่วยให้คุณเก็บเอนโทรปีได้ทั้งหมด แต่จะมีราคาแพงมากเมื่อคำนวณจากจำนวนการโยนเหรียญ / ลูกเต๋าเพิ่มขึ้น โดยสัญชาตญาณมันเหมือนกับการรักษาเหรียญพลิกเป็นตัวเลขของเลขฐานสองทางด้านขวาของจุดทศนิยมแปลงตัวเลขนั้นจากฐาน 2 เป็นฐาน ab หลังจากและคืนค่าตัวเลขของตัวเลขนั้นเมื่อพวกเขากลายเป็น 'ติด'

ตัวอย่าง

รหัสต่อไปนี้แปลงม้วนของแม่พิมพ์แบบตายด้าน n ไปเป็นม้วนแม่พิมพ์แบบตายด้าน m (ในกรณีของคุณ n = 2, m = ab) โดยมีค่าใช้จ่ายเพิ่มขึ้นเมื่อจำนวนม้วนเพิ่มขึ้น สังเกตความต้องการชนิด Rational number ด้วยความแม่นยำโดยพลการ คุณสมบัติที่ดีอย่างหนึ่งคือการแปลงจาก n-sided ไปเป็น m-sided และกลับไปที่ n-sided จะคืนค่าสตรีมดั้งเดิม แต่อาจล่าช้าโดยการหมุนสองครั้งเนื่องจากตัวเลขต้องติดขัด

public static IEnumerable<BigInteger> DigitConversion(this IEnumerable<BigInteger> inputStream, BigInteger modIn, BigInteger modOut) {
    //note: values are implicitly scaled so the first unfixed digit of the output ranges from 0 to 1
    Rational b = 0; //offset of the chosen range
    Rational d = 1; //size of the chosen range
    foreach (var r in inputStream) {
        //narrow the chosen range towards the real value represented by the input
        d /= modIn;
        b += d * r;
        //check for output digits that have become fixed
        while (true) {
            var i1 = (b * modOut).Floor();
            var i2 = ((b + d) * modOut).Floor(); //note: ideally b+d-epsilon, but another iteration makes that correction unnecessary
            if (i1 != i2) break; //digit became fixed?
            //fix the next output digit (rescale the range to make next digit range from 0 to 1)
            d *= modOut;
            b *= modOut;
            b -= i1;
            yield return i1;
        }
    }
}

0

2

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

(แก้ไข) คำอธิบายแบบเต็ม: สมมติว่าคุณต้องการสร้างจำนวนเต็มแบบสุ่มจาก 1 ถึง 3 โดยมีความน่าจะเป็น 1/3 แต่ละข้อ เราทำสิ่งนี้โดยการสร้างทศนิยมทศนิยมแบบสุ่มจริง x ในช่วง (0, 1) ถ้า x <1/3, คืน 1, อื่นถ้า x <2/3 return 2, อื่น ๆ คืน 3 แทนที่จะสร้างหลักสำหรับ x อย่างชัดเจนเราแค่ติดตามค่าต่ำสุดและค่าสูงสุดที่เป็นไปได้ของ x เริ่มแรกค่าต่ำสุดของ x คือ 0 และสูงสุดคือ 1 หากคุณพลิกหัวก่อนแล้วตัวเลขแรกของ x หลังจุดทศนิยม (เป็นไบนารี่) คือ 1 ค่าต่ำสุดที่เป็นไปได้ของ x (ในไบนารี) กลายเป็น 0.100000 = 1/2 และค่าสูงสุดคือ 0.111111111 = 1. ทีนี้ถ้าการพลิกครั้งต่อไปของคุณคือก้อย x เริ่มต้นด้วย 0.10 ค่าต่ำสุดที่เป็นไปได้คือ 0.1000000 = 1/2 และสูงสุดคือ 0.1011111 = 3/4 ค่าต่ำสุดที่เป็นไปได้ของ x คือ 1/2 เพื่อให้คุณรู้ว่า ' ไม่มีโอกาสกลับ 1 เนื่องจากต้องการ x <1/3 คุณยังสามารถส่งคืน 2 ได้หาก x สิ้นสุดเป็น 1/2 <x <2/3 หรือ 3 ถ้า 2/3 <x <3/4 ทีนี้สมมติว่าการพลิกครั้งที่สามเป็นหาง จากนั้น x ต้องเริ่มต้นด้วย 0.100 ต่ำสุด = 0.10000000 = 1/2 และสูงสุด = 0.100111111 = 5/8 ตอนนี้ตั้งแต่ 1/3 <1/2 <5/8 <2/3 เรารู้ว่า x ต้องอยู่ในช่วงเวลา (1/3, 2/3) ดังนั้นเราสามารถหยุดสร้างตัวเลขของ x และกลับมา 2

รหัสทำสิ่งนี้เป็นหลักยกเว้นแทนที่จะสร้าง x ระหว่าง 0 และ 1 มันสร้าง x ระหว่าง a และ b แต่หลักการเหมือนกัน

def gen(a, b):
  min_possible = a
  max_possible = b

  while True:
    floor_min_possible = floor(min_possible)
    floor_max_possible = floor(max_possible)
    if max_possible.is_integer():
      floor_max_possible -= 1

    if floor_max_possible == floor_min_possible:
      return floor_max_possible

    mid = (min_possible + max_possible)/2
    if coin_flip():
      min_possible = mid
    else:
      max_possible = mid

หมายเหตุ: ฉันทดสอบรหัสนี้กับวิธีการยอมรับ / ปฏิเสธและการแจกแจงแบบสม่ำเสมอ รหัสนี้ต้องโยนเหรียญน้อยกว่ายอมรับการปฏิเสธยกเว้นเมื่อ b - a ใกล้เคียงกับพลังต่อไปของ 2 เช่นถ้าคุณต้องการสร้าง = 0, b = 62 แล้วยอมรับ / ปฏิเสธทำได้ดีกว่า ฉันสามารถพิสูจน์ได้ว่ารหัสนี้สามารถใช้โดยเฉลี่ยไม่เกิน 2 เหรียญมากขึ้นกว่าการยอมรับ / ปฏิเสธ จากการอ่านของฉันดูเหมือนว่า Knuth และ Yao (1976) ให้วิธีการแก้ปัญหานี้และพิสูจน์ว่าวิธีการของพวกเขาดีที่สุดในการโยนเหรียญจำนวนที่คาดหวัง พวกเขาพิสูจน์แล้วว่าจำนวนของการโยนที่คาดหวังจะต้องมากกว่าเอนโทรปีของการแจกแจงแชนนอน ฉันไม่สามารถหาสำเนาข้อความในกระดาษได้และอยากรู้ว่าวิธีการของพวกเขาคืออะไร (อัปเดต: เพิ่งพบนิทรรศการของ Knuth Yao 1976 ที่นี่:http://www.nrbook.com/devroye/Devroye_files/chapter_fifteen_1.pdf แต่ฉันยังไม่ได้อ่าน) บางคนพูดถึงฮัน Hoshi ในหัวข้อนี้ซึ่งดูเหมือนจะเป็นเรื่องทั่วไปและแก้มันโดยใช้เหรียญอคติ ดูhttp://paper.ijcsns.org/07_book/200909/20090930.pdfโดย Pae (2009) เพื่อการอภิปรายที่ดีของวรรณกรรม



1

นี่เป็นวิธีแก้ปัญหาที่เสนอสำหรับกรณีเมื่อ b - a ไม่เท่ากับ 2 ^ k มันควรจะทำงานในจำนวนขั้นตอนคงที่ (ไม่จำเป็นต้องทิ้งผู้สมัครที่อยู่นอกช่วงที่คุณคาดหวังไว้)

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

ประการแรกแปลงเป็นปัญหาที่เทียบเท่ากับการสร้างตัวเลขสุ่มแบบกระจายอย่างสม่ำเสมอในช่วง [0, z-1] โดยที่ z = b - a

นอกจากนี้ให้ m = 2 ^ k เป็นพลังงานที่เล็กที่สุดที่ 2> = z

ตามวิธีการแก้ปัญหาข้างต้นเรามีตัวสร้างตัวเลขสุ่มกระจายอย่างสม่ำเสมอ R (m) ในช่วง [0, m-1] (สามารถทำได้โดยการโยนเหรียญ k หนึ่งบิตสำหรับแต่ละบิต)

    Keep a random seed s and initialize with s = R(m).   

    function random [0, z-1] :
        x = R(m) + s 
        while x >= z:
            x -= z
        s = x
        return x

ขณะที่วนซ้ำทำงานได้มากที่สุด 3 ครั้งให้หมายเลขสุ่มถัดไปในจำนวนขั้นตอนคงที่ (กรณีที่ดีที่สุด = กรณีที่แย่ที่สุด)

ดูโปรแกรมทดสอบหมายเลข [0,2] ได้ที่นี่: http://pastebin.com/zuDD2V6H


z=3m=41/2,1/4,1/4

โปรดดูที่รหัสเทียมรวมถึงรหัสที่เชื่อมโยงอย่างใกล้ชิด มันไม่ปล่อย 0, 1, 2 และเกือบจะมีความถี่เท่ากับ ...
vpathak

01/21/4

คุณสามารถแทนที่ฟังก์ชันทั้งหมดด้วยบรรทัดเดียว: return s = (s + R (m))% z;
Yuval Filmus

1

อัลกอริธึมที่เหมาะสมที่สุดในเชิงทฤษฎี

นี่คือการปรับปรุงคำตอบอื่น ๆ ที่ฉันโพสต์ คำตอบอื่น ๆ มีข้อได้เปรียบที่ง่ายต่อการขยายไปยังกรณีทั่วไปมากขึ้นในการสร้างการกระจายแบบแยกจากที่หนึ่ง ในความเป็นจริงคำตอบอื่น ๆ เป็นกรณีพิเศษของอัลกอริทึมเนื่องจากฮันและ Hoshi

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

หากต้องการอธิบายให้ลองพิจารณาวิธีการสุ่มตัวอย่างการปฏิเสธที่อธิบายโดยคำตอบอื่น ๆ ตัวอย่างเช่นสมมติว่าคุณต้องการสร้างหนึ่งใน 5 หมายเลขอย่างสม่ำเสมอ [0, 4] กำลังต่อไปของ 2 คือ 8 ดังนั้นคุณจะพลิกเหรียญ 3 ครั้งและสร้างตัวเลขสุ่มได้ถึง 8 หากตัวเลขเป็น 0 ถึง 4 คุณจะได้รับคืน ไม่เช่นนั้นคุณจะโยนออกมาและสร้างหมายเลขขึ้นอีก 8 และลองอีกครั้งจนกว่าคุณจะประสบความสำเร็จ แต่เมื่อคุณโยนหมายเลขคุณเพิ่งเสียเอนโทรปี คุณสามารถกำหนดจำนวนที่คุณโยนเพื่อลดจำนวนการโยนเหรียญในอนาคตที่คุณต้องการ เมื่อคุณสร้างหมายเลข [0, 7] หากเป็น [0, 4] ให้ส่งคืน มิฉะนั้นมันคือ 5, 6 หรือ 7 และคุณทำสิ่งที่แตกต่างในแต่ละกรณี ถ้าเป็น 5 ให้พลิกเหรียญอีกครั้งแล้วส่งกลับเป็น 0 หรือ 1 ตามการพลิก ถ้าเป็น 6 พลิกเหรียญแล้วกลับทั้ง 2 หรือ 3 ถ้าเป็น 7 ให้พลิกเหรียญ ถ้ามันเป็นหัวส่งคืน 4 ถ้าหางเริ่มต้นใหม่

เอนโทรปีที่เหลือจากความพยายามครั้งแรกของเราล้มเหลวทำให้เรามี 3 ราย (5, 6, หรือ 7) ถ้าเราเพิ่งโยนมันออกไปเราก็ทิ้งเหรียญโยน log2 (3) เราเก็บมันไว้และรวมเข้ากับผลลัพธ์ของการพลิกอีกครั้งเพื่อสร้าง 6 กรณีที่เป็นไปได้ (5H, 5T, 6H, 6T, 7H, 7T) ซึ่งให้เราลองใหม่อีกครั้งเพื่อสร้างคำตอบสุดท้ายด้วยความน่าจะเป็นที่ประสบความสำเร็จ 5/6 .

นี่คือรหัส:

# returns an int from [0, b)
def __gen(b):
  rand_num = 0
  num_choices = 1

  while True:
    num_choices *= 2
    rand_num *= 2
    if coin.flip():
      rand_num += 1

    if num_choices >= b:
      if rand_num < b:
        return rand_num
      num_choices -= b
      rand_num -= b

# returns an int from [a, b)
def gen(a, b):
  return a + __gen(b - a)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.