Mở rộng phạm vi ngẫu nhiên từ 1 Lần5 đến 1 Lần7


692

Cho một hàm tạo ra một số nguyên ngẫu nhiên trong phạm vi từ 1 đến 5, hãy viết một hàm tạo ra một số nguyên ngẫu nhiên trong phạm vi từ 1 đến 7.

  1. Một giải pháp đơn giản là gì?
  2. Giải pháp hiệu quả để giảm mức sử dụng bộ nhớ hoặc chạy trên CPU chậm hơn là gì?

Đây là một vấn đề thú vị không ngờ, tôi vẫn nghĩ làm thế nào để 1) làm điều đó trong thời gian cố định và 2) không làm hỏng việc phân phối đồng phục (nếu có)
eugensk

Chúng tôi đã có vấn đề tương tự trong khi chọn một người chơi trong số 5 người có xúc xắc. Chúng tôi lần lượt ném xúc xắc, một người đạt được số điểm tối đa là được chọn. Tính đồng nhất đã đạt được, nhưng không phải là hằng số thời gian :)
eugensk

Tôi có bị từ chối không nếu tôi đăng câu trả lời nói rằng vấn đề không bắt buộc bạn phải sử dụng hàm đã cho và chỉ cần viết một hàm trả về ngẫu nhiên 1-7?
Bác sĩ Blue

Thế còn 7 * rand5() / 5?
kiwixz

@kiwixz, sẽ tạo ra "từ 1 đến 7", nhưng bạn sẽ không nhận được 3 hoặc 6: {1: 19.96, 2: 20.02, 4: 20.01, 5: 19.99, 7: 20.02} kiểm tra tỷ lệ phần trăm thô bằng tay. 7 * .2, 7 * .4, 7 * .6, 7 * .8, 7 * 1.
pythonlarry

Câu trả lời:


572

Điều này tương đương với giải pháp của Adam Rosenfield, nhưng có thể rõ ràng hơn một chút đối với một số độc giả. Nó giả sử rand5 () là một hàm trả về một số nguyên ngẫu nhiên thống kê trong phạm vi từ 1 đến 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;
}

Làm thế nào nó hoạt động? Hãy nghĩ về nó như thế này: tưởng tượng in ra mảng hai chiều này trên giấy, giải quyết nó lên một bảng phi tiêu và ném ngẫu nhiên phi tiêu vào nó. Nếu bạn đạt giá trị khác không, thì đó là giá trị ngẫu nhiên thống kê trong khoảng từ 1 đến 7, vì có một số lượng giá trị khác không bằng nhau để lựa chọn. Nếu bạn đánh số 0, chỉ cần tiếp tục ném phi tiêu cho đến khi bạn đạt được số không. Đó là những gì mã này đang làm: các chỉ số i và j chọn ngẫu nhiên một vị trí trên bảng phi tiêu và nếu chúng tôi không có kết quả tốt, chúng tôi tiếp tục ném phi tiêu.

Giống như Adam đã nói, điều này có thể chạy mãi trong trường hợp xấu nhất, nhưng theo thống kê thì trường hợp xấu nhất không bao giờ xảy ra. :)


5
Tôi hiểu logic đằng sau giải pháp này nhưng không thể hiểu rằng làm thế nào nó dẫn đến xác suất thống nhất? Ai đó có thể giải thích toán học?
dùng1071840

6
@ user1071840 - nếu rand5là đồng nhất, mọi ô trong valslưới có xác suất được chọn bằng nhau. Lưới chứa chính xác ba bản sao của mỗi số nguyên trong khoảng [1, 7], cộng với bốn số không. Vì vậy, luồng kết quả "thô" có xu hướng kết hợp đồng đều các giá trị [1, 7], cộng với một số số không xảy ra thường xuyên hơn bất kỳ giá trị cho phép riêng lẻ nào. Nhưng điều đó không thành vấn đề bởi vì các số 0 bị loại bỏ, chỉ còn lại một hỗn hợp các giá trị [1, 7].
Daniel Earwicker

3
Cách tắt để nhận ra vấn đề với điều đó: nếu bạn chỉ gọi rand5 () một lần, thì bạn chỉ có 5 kết quả có thể xảy ra. Rõ ràng không có cách nào để biến điều đó thành hơn 5 kết quả có thể xảy ra mà không cần thêm sự ngẫu nhiên.
Daniel Earwicker

1
Phiên bản dài hơn: rand5 () chỉ có thể có các giá trị (1, 2, 3, 4, 5). Do đó, rand5 () * 5 chỉ có thể có các giá trị (5, 10, 15, 20, 25), không giống với phạm vi hoàn chỉnh (1 ... 25). Nếu có, trừ 4 sẽ làm cho nó (-3 ... 21), nhưng trong trường hợp này, nó trở thành (1, 6, 11, 16, 21), vì vậy điểm cuối là chính xác nhưng có bốn lỗ lớn: ( 2..5), (7..10), (12..15), (17..21). Cuối cùng bạn làm mod 7 và thêm 1, cho (2, 7, 5, 3, 1). Vì vậy, cả 4 và 6 không bao giờ xảy ra. Nhưng (xem lối tắt ở trên) chúng tôi biết chỉ có thể có 5 số trong phạm vi kết quả, do đó phải có hai khoảng trống.
Daniel Earwicker

1
À, bởi vì chúng tôi chỉ có rand5 (), không phải rand2 () :-)
gzak

352

Không có giải pháp (chính xác) sẽ chạy trong một khoảng thời gian không đổi, vì 1/7 là số thập phân vô hạn trong cơ sở 5. Một giải pháp đơn giản sẽ là sử dụng lấy mẫu từ chối, ví dụ:


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

Điều này có thời gian chạy dự kiến ​​là 25/21 = 1,19 lần lặp của vòng lặp, nhưng có một xác suất cực kỳ nhỏ của vòng lặp mãi mãi.


7
-1 là không cần thiết nếu> 21 được lật xuống> 26 b / c, không quan trọng là tôi có bản đồ giới hạn thấp hơn ở đâu,
BCS

26
Tôi giải thích lý do tại sao điều này là chính xác: Giả sử tôi muốn viết một chương trình tạo ra một luồng các số ngẫu nhiên thống nhất từ ​​1 đến 25; cho điều đó tôi chỉ cần trả về 5 * (rand5 () - 1) + rand5 () như trong mã trong câu trả lời. Bây giờ, nếu tôi muốn xây dựng một luồng các số ngẫu nhiên thống nhất trong khoảng từ 1 đến 21, nếu tôi chỉ sử dụng luồng đầu tiên nhưng lọc nó để các số trong [22, 25] bị từ chối, tôi cũng có thể xây dựng luồng đó. Tiếp theo, nếu tôi lấy luồng này và lọc nó sao cho mỗi phần tử x tôi xuất x% 7 + 1, tôi có một luồng các số ngẫu nhiên thống nhất từ ​​1 đến 7! Khá đơn giản phải không? : D
Paggas

6
Và bạn chính xác rằng nó tập trung vào việc bạn muốn phân phối hoàn hảo với thời gian chạy trường hợp xấu nhất không bị ràng buộc hay phân phối không hoàn hảo với thời gian chạy bị ràng buộc. Đây là kết quả của thực tế là tất cả các lũy thừa 5 không chia hết cho 7 hoặc tương đương nếu bạn có 5 ^ n có thể là các chuỗi có độ dài n, không có cách nào để gán cho mỗi chuỗi một số từ 1 đến 7 sao cho mỗi chuỗi 1..7 có lẽ là như nhau.
Adam Rosenfield

5
@Jules Olléon: Giả sử có một giải pháp chạy trong thời gian liên tục được đảm bảo để thực hiện không nhiều hơn Ncác cuộc gọi đến rand5()trong trường hợp xấu nhất. Sau đó, có 5 ^ N kết quả có thể xảy ra của chuỗi các cuộc gọi đến rand5, mỗi cuộc gọi có đầu ra 1-7. Vì vậy, nếu bạn cộng tất cả các chuỗi cuộc gọi có thể có đầu ra kcho mỗi 1≤k≤7, thì xác suất đầu ra klà m / 5 ^ N, trong đó m là số lượng các chuỗi đó. Vì vậy, m / 5 ^ N = 1/7, nhưng không có giải pháp số nguyên khả dĩ nào (N, m) cho điều này ==> mâu thuẫn.
Adam Rosenfield

4
@paxdiablo: Bạn không chính xác. Cơ hội của một RNG thực sự tạo ra một chuỗi vô hạn 5 giây là chính xác 0, sử dụng lý do tương tự với thực tế là lật một đồng xu vô số lần được đảm bảo không tạo ra vô số đầu liên tiếp . Điều này cũng có nghĩa là cơ hội lặp mã này mãi mãi là 0 (mặc dù có khả năng tích cực nó sẽ lặp cho bất kỳ số lần lặp tùy ý nào).
BlueRaja - Daniel Pflughoeft

153

Tôi muốn thêm một câu trả lời, ngoài câu trả lời đầu tiên của tôi . Câu trả lời này cố gắng giảm thiểu số lượng cuộc gọi cho rand5()mỗi cuộc gọi đến rand7(), để tối đa hóa việc sử dụng ngẫu nhiên. Đó là, nếu bạn coi tính ngẫu nhiên là một tài nguyên quý giá, chúng tôi muốn sử dụng nó càng nhiều càng tốt, mà không vứt bỏ bất kỳ bit ngẫu nhiên nào. Câu trả lời này cũng có một số điểm tương đồng với logic được trình bày trong câu trả lời của Ivan .

Các entropy của một biến ngẫu nhiên là một đại lượng được xác định rõ. Đối với một biến ngẫu nhiên có N trạng thái có xác suất bằng nhau (phân phối đồng đều), entropy là log 2 N. Do đó, rand5()có khoảng 2.32193 bit entropy và rand7()có khoảng 2.80735 bit entropy. Nếu chúng tôi hy vọng tối đa hóa việc sử dụng tính ngẫu nhiên, chúng tôi cần sử dụng tất cả 2.32193 bit entropy từ mỗi cuộc gọi đến rand5()và áp dụng chúng để tạo ra 2.80735 bit entropy cần thiết cho mỗi cuộc gọi rand7(). Sau đó, giới hạn cơ bản là chúng ta không thể làm tốt hơn log (7) / log (5) = 1.20906 cuộc gọi cho rand5()mỗi cuộc gọi đến rand7().

Lưu ý bên lề: tất cả các logarit trong câu trả lời này sẽ là cơ sở 2 trừ khi có quy định khác. rand5()sẽ được giả sử trả về các số trong phạm vi [0, 4] và rand7()sẽ được giả định trả về các số trong phạm vi [0, 6]. Điều chỉnh các phạm vi thành [1, 5] và [1, 7] tương ứng là không đáng kể.

Vậy làm thế nào để chúng ta làm điều đó? Chúng tôi tạo ra một số thực ngẫu nhiên chính xác vô cùng chính xác trong khoảng từ 0 đến 1 (giả sử tại thời điểm chúng tôi thực sự có thể tính toán và lưu trữ một số chính xác vô hạn như vậy - chúng tôi sẽ sửa lỗi này sau). Chúng ta có thể tạo một số như vậy bằng cách tạo các chữ số của nó trong cơ sở 5: chúng ta chọn số ngẫu nhiên 0. a1 a2 a3 ..., trong đó mỗi chữ số a iđược chọn bởi một lệnh gọi đến rand5(). Ví dụ: nếu RNG của chúng tôi chọn a i= 1 cho tất cả i, thì bỏ qua thực tế là điều đó không ngẫu nhiên, điều đó sẽ tương ứng với số thực 1/5 + 1/5 2 + 1/5 3 + ... = 1/4 (tổng của một chuỗi hình học).

Ok, vì vậy chúng tôi đã chọn một số thực ngẫu nhiên trong khoảng từ 0 đến 1. Bây giờ tôi cho rằng một số ngẫu nhiên như vậy được phân phối đồng đều. Theo trực giác, điều này là dễ hiểu, vì mỗi chữ số được chọn thống nhất và con số này là vô cùng chính xác. Tuy nhiên, một bằng chứng chính thức về điều này có phần liên quan nhiều hơn, vì hiện tại chúng tôi đang xử lý phân phối liên tục thay vì phân phối rời rạc, vì vậy chúng tôi cần chứng minh rằng xác suất số của chúng tôi nằm trong một khoảng [ a, b] bằng với độ dài của khoảng đó , b - a. Bằng chứng được để lại như một bài tập cho người đọc =).

Bây giờ chúng ta có một số thực ngẫu nhiên được chọn thống nhất từ ​​phạm vi [0, 1], chúng ta cần chuyển đổi nó thành một chuỗi các số ngẫu nhiên thống nhất trong phạm vi [0, 6] để tạo đầu ra rand7(). Chung ta se lam như thê nao? Ngược lại với những gì chúng ta vừa làm - chúng ta chuyển đổi nó thành một số thập phân chính xác vô hạn trong cơ sở 7, và sau đó mỗi chữ số 7 cơ sở sẽ tương ứng với một đầu ra rand7().

Lấy ví dụ từ trước đó, nếu chúng ta rand5()tạo ra một luồng vô hạn 1 giây thì số thực ngẫu nhiên của chúng ta sẽ là 1/4. Chuyển đổi 1/4 sang cơ sở 7, chúng tôi nhận được số thập phân vô hạn 0,15151515 ..., vì vậy chúng tôi sẽ sản xuất như đầu ra 1, 5, 1, 5, 1, 5, v.v.

Ok, vì vậy chúng tôi có ý tưởng chính, nhưng chúng tôi còn hai vấn đề: chúng tôi thực sự không thể tính toán hoặc lưu trữ một số thực chính xác vô cùng, vậy làm thế nào để chúng tôi chỉ giải quyết một phần hữu hạn của nó? Thứ hai, làm thế nào để chúng ta thực sự chuyển đổi nó thành cơ sở 7?

Một cách chúng ta có thể chuyển đổi một số từ 0 đến 1 thành cơ sở 7 như sau:

  1. Nhân với 7
  2. Phần không thể thiếu của kết quả là 7 chữ số cơ sở tiếp theo
  3. Trừ đi phần tích phân, chỉ để lại phần phân số
  4. Bước 1

Để giải quyết vấn đề về độ chính xác vô hạn, chúng tôi tính toán một kết quả một phần và chúng tôi cũng lưu trữ giới hạn trên về kết quả có thể là gì. Đó là, giả sử chúng tôi đã gọi rand5()hai lần và nó trả lại 1 cả hai lần. Số chúng tôi đã tạo cho đến nay là 0,11 (cơ sở 5). Bất kể phần còn lại của chuỗi cuộc gọi vô hạn để rand5()tạo ra, số thực ngẫu nhiên chúng tôi tạo ra sẽ không bao giờ lớn hơn 0,12: luôn luôn đúng là 0,11 0,11xyz ... <0,12.

Vì vậy, theo dõi số hiện tại cho đến nay và giá trị tối đa có thể đạt được, chúng tôi chuyển đổi cả hai số thành cơ sở 7. Nếu chúng đồng ý với các kchữ số đầu tiên , thì chúng tôi có thể xuất các kchữ số tiếp theo một cách an toàn - bất kể đó là gì dòng vô hạn của 5 chữ số cơ sở là, chúng sẽ không bao giờ ảnh hưởng đến các kchữ số tiếp theo của biểu diễn cơ sở 7!

Và đó là thuật toán - để tạo đầu ra tiếp theo rand7(), chúng tôi chỉ tạo ra bao nhiêu chữ số rand5()mà chúng tôi cần để đảm bảo rằng chúng tôi biết chắc chắn giá trị của chữ số tiếp theo trong quá trình chuyển đổi số thực ngẫu nhiên thành cơ sở 7. Đây là triển khai Python, với khai thác thử nghiệm:

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)

Lưu ý rằng rand7_gen()trả về một trình tạo, vì nó có trạng thái bên trong liên quan đến việc chuyển đổi số thành cơ sở 7. Khai thác thử nghiệm gọi next(r7)10000 lần để tạo ra 10000 số ngẫu nhiên, sau đó nó đo phân phối của chúng. Chỉ toán học số nguyên được sử dụng, vì vậy kết quả là chính xác.

Cũng lưu ý rằng những con số ở đây nhận được rất lớn, rất nhanh. Quyền hạn của 5 và 7 tăng nhanh. Do đó, hiệu suất sẽ bắt đầu suy giảm đáng kể sau khi tạo ra nhiều số ngẫu nhiên, do số học bignum. Nhưng hãy nhớ ở đây, mục tiêu của tôi là tối đa hóa việc sử dụng các bit ngẫu nhiên, không phải để tối đa hóa hiệu suất (mặc dù đó là mục tiêu phụ).

Trong một lần chạy này, tôi đã thực hiện 12091 cuộc gọi rand5()cho 10000 cuộc gọi đến rand7(), đạt được tối thiểu các cuộc gọi log (7) / log (5) trung bình tới 4 con số quan trọng và kết quả đầu ra là thống nhất.

Để chuyển mã này sang ngôn ngữ không có số nguyên lớn tùy ý, bạn sẽ phải giới hạn các giá trị pow5pow7với giá trị tối đa của loại tích phân gốc của bạn - nếu chúng quá lớn, sau đó đặt lại tất cả mọi thứ và bắt đầu lại. Điều này sẽ tăng số lượng cuộc gọi trung bình cho rand5()mỗi cuộc gọi lên rand7()rất ít, nhưng hy vọng nó không nên tăng quá nhiều ngay cả đối với số nguyên 32 hoặc 64 bit.


7
+1 cho một câu trả lời thực sự thú vị. Liệu có thể, thay vì đặt lại ở một giá trị nhất định, chỉ đơn giản là tắt các bit đã được sử dụng và di chuyển các bit khác lên, và về cơ bản chỉ giữ các bit sẽ được sử dụng? Hay tôi đang thiếu một cái gì đó?
Chris Lutz

1
Tôi không chắc chắn 100%, nhưng tôi tin rằng nếu bạn làm điều đó, bạn sẽ làm lệch phân phối rất nhẹ (mặc dù tôi nghi ngờ rằng độ lệch như vậy sẽ có thể đo lường được nếu không có hàng nghìn tỷ thử nghiệm).
Adam Rosenfield

FTW! Tôi đã cố gắng làm cho các bignums nhỏ hơn nhưng không thể thực hiện được vì không có lũy thừa 5 nào có các yếu tố chung với sức mạnh là 7! Ngoài ra, sử dụng tốt các từ khóa năng suất. Làm rất tốt
Mắt

2
Rất đẹp! Chúng ta có thể giữ lại entropy thêm mà không cần tăng trưởng không? Bí quyết là để ý rằng cả hai giới hạn trên và dưới đều luôn là số hữu tỷ. Chúng ta có thể cộng, trừ và nhân chúng mà không làm mất độ chính xác. Nếu chúng tôi làm tất cả trong căn cứ 35, chúng tôi sẽ ở gần đó. Phần còn lại (nhân với bảy và giữ lại phần phân số) được để lại như một bài tập.
Ian

@adam Bạn phải tham khảo "giới hạn các giá trị của pow5 và pow7 với giá trị tối đa của loại tích phân riêng của bạn". Tôi thứ hai bạn tin rằng điều này sẽ làm lệch phân phối, ít nhất là nếu được thực hiện một cách ngây thơ.
chất xúc tác

36

(Tôi đã đánh cắp câu trả lời của Adam Rosenfeld và khiến nó chạy nhanh hơn khoảng 7%.)

Giả sử rằng rand5 () trả về một trong {0,1,2,3,4} với phân phối bằng nhau và mục tiêu là trả về {0,1,2,3,4,5,6} với phân phối bằng nhau.

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

Chúng tôi đang theo dõi giá trị lớn nhất mà vòng lặp có thể tạo ra trong biến max. Nếu reult cho đến nay là giữa max% 7 và max-1 thì kết quả sẽ được phân phối đồng đều trong phạm vi đó. Nếu không, chúng tôi sử dụng phần còn lại, ngẫu nhiên trong khoảng từ 0 đến max% 7-1 và một lệnh gọi khác đến rand () để tạo số mới và số max mới. Sau đó, chúng tôi bắt đầu lại.

Chỉnh sửa: Dự kiến ​​số lần gọi rand5 () là x trong phương trình này:

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
Kết quả được liệt kê trong 1.000.000 lần thử: 1 = 47216; 2 = 127444; 3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536; 7 = 167465. Như bạn có thể thấy, phân phối thiếu liên quan đến tỷ lệ nhận được 1.
Robert K

2
@ The Wicky Flea: Tôi nghĩ bạn đã nhầm. Bạn có chắc chắn rằng rand5 () đầu vào mà bạn đang sử dụng cho thử nghiệm của mình được tạo ra 0-4 thay vì 1-5, như được chỉ định trong giải pháp này không?
Adam Rosenfield

5
thêm số phân phối đồng đều không dẫn đến số phân phối đồng đều. Trong thực tế, bạn chỉ cần tổng hợp 6 biến phân phối đồng đều như vậy để có được xấp xỉ hợp lý cho phân phối bình thường.
Mitch Wheat

2
@MitchWheat - Trên thực tế, việc thêm hai số nguyên được phân phối đồng đều sẽ dẫn đến một số nguyên ngẫu nhiên được phân phối đồng đều với điều kiện mỗi tổng có thể có thể được tạo theo đúng một cách. Điều đó xảy ra là trường hợp trong biểu thức 5 * rand5() + rand5().
Ted Hopp

28

Thuật toán:

7 có thể được biểu diễn trong một chuỗi 3 bit

Sử dụng rand (5) để điền ngẫu nhiên từng bit bằng 0 hoặc 1.
Ví dụ: gọi rand (5) và

nếu kết quả là 1 hoặc 2, điền bit bằng 0
nếu kết quả là 4 hoặc 5, điền bit bằng 1
nếu kết quả là 3, sau đó bỏ qua và thực hiện lại (từ chối)

Bằng cách này, chúng ta có thể điền 3 bit ngẫu nhiên với 0/1 và do đó nhận được một số từ 1-7.

EDIT: Đây có vẻ như là câu trả lời đơn giản và hiệu quả nhất, vì vậy đây là một số mã cho nó:

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
Luôn luôn có bóng ma mờ nhạt của vấn đề tạm dừng, vì một trình tạo số ngẫu nhiên kém có thể tạo ra rất nhiều số ba tại một số điểm.
Alex North-Keys

"Nếu kết quả là 1 hoặc 2, hãy điền bit bằng 0 nếu kết quả là 4 hoặc 5, điền bit bằng 1" Logic của 1,2,4,5 được chấp nhận là gì và 3 bị từ chối? Bạn có thể giải thích điều này?
gkns

@gkns Không có logic, bạn có thể điền vào trung bình 1 và 2 với 0 bit và 3 và 4 có nghĩa là 1. Điều quan trọng là mỗi tùy chọn có 50% cơ hội xảy ra, do đó đảm bảo rằng tính ngẫu nhiên của chức năng của bạn là ít nhất là ngẫu nhiên như hàm rand (5) ban đầu. Đó là một giải pháp tuyệt vời!
Mo Beigi

Điều này không đơn giản cũng không hiệu quả. Số lượng cals đến Random_5 mỗi Random_7 nhiều nhất là 3 thường nhiều hơn. Các giải pháp khác trên trang này gần với giải pháp thực sự tốt nhất là khoảng 2.2.
Mắt

1
Không bao giờ, tôi đã bỏ lỡ phần "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
Một giải pháp chính xác, thực hiện trung bình 30/7 = 4,29 cuộc gọi đến rand5 () mỗi cuộc gọi đến rand7 ().
Adam Rosenfield

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

Chỉnh sửa: Điều đó không hoàn toàn làm việc. Nó tắt khoảng 2 phần trong 1000 (giả sử một rand5 hoàn hảo). Các thùng nhận được:

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

Bằng cách chuyển sang một tổng số

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

dường như đạt được một thứ tự cường độ cho mỗi 2 được thêm vào

BTW: bảng lỗi ở trên không được tạo thông qua lấy mẫu mà theo mối quan hệ lặp lại sau:

p[x,n]là cách số output=xcó thể xảy ra ncác cuộc gọi được đưa ra 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]

8
Đây không phải là một phân phối thống nhất. Nó rất gần với đồng phục, nhưng không đồng nhất hoàn hảo.
Adam Rosenfield

Ah! Xúc xắc và 7 giây. Nếu bạn định nói tôi sai, bạn không nên để lại bằng chứng như một bài tập cho người đọc.
BCS

45
Bằng chứng là nó không đồng nhất là đơn giản: có 5 ^ 7 cách có thể ngẫu nhiên có thể đi, và vì 5 ^ 7 không phải là bội số của 7, không thể có tất cả 7 khoản tiền đều có khả năng như nhau. (Về cơ bản, nó giảm xuống còn 7 tương đối nguyên tố thành 5 hoặc tương đương 1/7 không phải là số thập phân kết thúc trong cơ sở 5.) Trên thực tế, nó thậm chí không phải là "thống nhất" nhất có thể theo ràng buộc này: tính toán trực tiếp cho thấy 5 ^ 7 = 78125 tổng, số lần bạn nhận được các giá trị 1 đến 7 là {1: 11145, 2: 11120, 3: 11120, 4: 11145, 5: 11190, 6: 11215, 7: 11190}.
ShreevatsaR

@ShreevatsaR Vậy điều gì sẽ xảy ra nếu thay vì lấy tổng số rand5 () bảy lần, chúng tôi đã thực hiện 5 * 7 - điều đó có hiệu quả không? 35 ^ 7% 7 = 35 ^ 5% 7 = 0.
kba

4
@KristianAntonsen: Bao nhiêu lần bạn làm rand5 (), bạn sẽ không nhận được phân phối thống nhất. Nếu bạn thực hiện N lần, có 5 ^ N kết quả đầu ra có thể không chia hết cho 7. (Nếu bạn thực hiện 35 lần, có 5 ^ 35, không phải 35 ^ 7.) Bạn sẽ tiến gần hơn và gần hơn với thống nhất số lượng cuộc gọi lớn hơn mà bạn sử dụng (và có thể là bất kỳ số nào, không phải chia hết cho 7), nhưng IMHO thay vì sử dụng số lượng cuộc gọi rất lớn để rand (), bạn cũng có thể sử dụng xác suất thuật toán trong các câu trả lời hàng đầu, cung cấp phân phối thống nhất chính xác và có số lượng cuộc gọi dự kiến ​​đến rand () là nhỏ.
ShreevatsaR

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

2
Một giải pháp chính xác, thực hiện trung bình 30/7 = 4,29 cuộc gọi đến rand5 () mỗi cuộc gọi đến rand7 ().
Adam Rosenfield

3
Cần phải thay đổi trái để thuật toán hoạt động:ans += (r < 3) << i
Woolfie

13

Sau đây tạo ra phân phối đồng đều trên {1, 2, 3, 4, 5, 6, 7} bằng cách sử dụng trình tạo số ngẫu nhiên tạo ra phân phối đồng đều trên {1, 2, 3, 4, 5}. Mã lộn xộn, nhưng logic rõ ràng.

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
Một giải pháp chính xác (đưa bạn đi trước đường cong), mặc dù không hiệu quả lắm. Điều này làm cho trung bình 25/6 = 4,17 cuộc gọi đến Random_5_mod_2 mỗi lần lật đồng xu công bằng, với tổng số trung bình là 100/7 = 14,3 cuộc gọi đến Random_5 () mỗi cuộc gọi đến Random_7 ().
Adam Rosenfield

Ưu điểm của giải pháp này so với các giải pháp khác là nó có thể dễ dàng mở rộng để tạo ra bất kỳ phạm vi phân phối đồng đều nào khác. Chỉ cần chọn ngẫu nhiên từng bit một, cuộn lại các giá trị không hợp lệ (như giá trị 0 trong giải pháp hiện tại của chúng tôi tạo ra 8 số).
DenTheMan

1
vòng lặp vô hạn có thể, v.v.
robermorales

1
@robermorales: Vô cùng khó xảy ra.
jason

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

Không giống như giải pháp đã chọn, thuật toán sẽ chạy trong thời gian không đổi. Tuy nhiên, nó thực hiện thêm 2 cuộc gọi đến rand5 so với thời gian chạy trung bình của giải pháp đã chọn.

Lưu ý rằng trình tạo này không hoàn hảo (số 0 có cơ hội nhiều hơn 0,0064% so với bất kỳ số nào khác), nhưng đối với hầu hết các mục đích thực tế, việc đảm bảo thời gian liên tục có thể vượt xa sự không chính xác này.

Giải trình

Giải pháp này bắt nguồn từ thực tế là số 15.624 chia hết cho 7 và do đó nếu chúng ta có thể tạo ngẫu nhiên và thống nhất các số từ 0 đến 15.624 và sau đó lấy mod 7, chúng ta có thể có được một bộ tạo rand7 gần như thống nhất. Các số từ 0 đến 15.624 có thể được tạo đồng đều bằng cách cuộn rand5 6 lần và sử dụng chúng để tạo thành các chữ số của một số cơ sở 5 như sau:

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

Các thuộc tính của mod 7 tuy nhiên cho phép chúng ta đơn giản hóa phương trình một chút:

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

Vì thế

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

trở thành

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

Học thuyết

Số 15.624 không được chọn ngẫu nhiên, nhưng có thể được phát hiện bằng định lý nhỏ của fermat, trong đó nêu rõ rằng nếu p là số nguyên tố thì

a^(p-1) = 1 mod p

Vì vậy, điều này mang lại cho chúng ta,

(5^6)-1 = 0 mod 7

(5 ^ 6) -1 bằng

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

Đây là một số ở dạng cơ sở 5 và do đó chúng ta có thể thấy rằng phương pháp này có thể được sử dụng để đi từ bất kỳ trình tạo số ngẫu nhiên nào đến bất kỳ trình tạo số ngẫu nhiên nào khác. Mặc dù độ lệch nhỏ về 0 luôn được đưa vào khi sử dụng số mũ p-1.

Để khái quát hóa phương pháp này và chính xác hơn, chúng ta có thể có một chức năng như thế này:

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
Máy phát điện này là chính xác, nhưng không hoàn toàn thống nhất. Để thấy điều này, hãy xem xét thực tế rằng một trình tạo thống nhất trong [0,15624] có 15625 kết quả có thể xảy ra, không chia hết cho 7. Điều này đưa ra sự thiên vị cho số 0 (có cơ hội 2233/15625 và các kết quả khác 2232/15625). Xét cho cùng, trong khi sử dụng định lý nhỏ của Fermat có vẻ đúng ngay từ cái nhìn đầu tiên, nó nói rằng (5 ^ 6)% 7 = 1, và không (5 ^ 6)% 7 = 0. Cái sau rõ ràng là không thể đối với bất kỳ số mũ nào vì 5 và 7 đều là số nguyên tố. Tôi nghĩ đó vẫn là một giải pháp chấp nhận được và tôi đã chỉnh sửa bài đăng của mình để phản ánh điều này.
phi công

12

Các vấn đề bài tập về nhà được cho phép ở đây?

Hàm này thực hiện phép toán "cơ sở 5" thô để tạo ra một số từ 0 đến 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
Một giải pháp chính xác (đưa bạn đi trước đường cong), mặc dù không hiệu quả lắm. Điều này thực hiện trung bình 5 cuộc gọi đến rnd5 () cho mỗi cuộc gọi đến rnd7 ().
Adam Rosenfield

cần giải thích thêm xin vui lòng
Barry

1
@Barry - Đầu tiên, bạn không thể chỉ cộng hai số ngẫu nhiên lại với nhau, bạn không có được một giải pháp tuyến tính (xem xét một cặp súc sắc). Bây giờ hãy xem xét "Cơ sở 5": 00, 01, 02, 03, 04, 10, 11. Đó là 0-6 trong cơ sở 5. Vì vậy, chúng tôi chỉ cần tạo 2 chữ số của số 5 cơ sở và thêm chúng cho đến khi chúng tôi có được một trong phạm vi. Đó là những gì r2 * 5 + r1 làm. Vòng lặp r2> 1 ở đó bởi vì chúng tôi sẽ không bao giờ muốn có một chữ số cao> 1.
Will Hartung

Giải pháp này không tạo ra một phân phối thống nhất. Các số 1 và 7 chỉ có thể được tạo theo một cách, nhưng mỗi số 2 đến 6 có thể được tạo theo hai cách: với r1 bằng số trừ 1 và r2 bằng 0 hoặc với r1 bằng số trừ 2 và r2 bằng 1. Do đó, 2 đến 6 sẽ được trả lại trung bình gấp đôi so với 1 hoặc 7.
Ted Hopp

12

Nếu chúng tôi xem xét các ràng buộc bổ sung của việc cố gắng đưa ra câu trả lời hiệu quả nhất, tức là một câu đã đưa ra một luồng đầu vào I, có các số nguyên được phân phối đồng đều có độ dài mtừ 1-5 tạo ra một luồng O, các số nguyên được phân phối đồng đều từ 1-7 độ dài dài nhất để m, nói L(m).

Cách đơn giản nhất để phân tích điều này là xử lý các luồng I và O5-ary và 7-ary tương ứng. Điều này đạt được bằng ý tưởng của câu trả lời chính là lấy luồng a1, a2, a3,... -> a1+5*a2+5^2*a3+..và tương tự cho luồng O.

Sau đó, nếu chúng ta lấy một phần của luồng đầu vào có độ dài m choose n s.t. 5^m-7^n=cở đâu c>0và càng nhỏ càng tốt. Sau đó, có một bản đồ thống nhất từ ​​luồng đầu vào có độ dài m đến các số nguyên từ 1đến 5^mvà một bản đồ thống nhất khác từ các số nguyên từ 1 đến 7^nluồng đầu ra có độ dài n trong đó chúng ta có thể phải mất một vài trường hợp từ luồng đầu vào khi số nguyên được ánh xạ vượt quá 7^n.

Vì vậy, điều này mang lại một giá trị cho L(m)khoảng m (log5/log7)đó là khoảng .82m.

Khó khăn với những phân tích trên là phương trình 5^m-7^n=cmà không phải là dễ dàng để giải quyết chính xác và trường hợp giá trị thống nhất từ 1để 5^mvượt 7^nvà chúng ta mất đi tính hiệu quả.

Câu hỏi là làm thế nào gần với giá trị tốt nhất có thể của m (log5 / log7) có thể đạt được. Ví dụ: khi số này tiếp cận gần với một số nguyên, chúng ta có thể tìm cách đạt được số nguyên chính xác của các giá trị đầu ra không?

Nếu 5^m-7^n=csau đó từ luồng đầu vào, chúng tôi tạo ra một số ngẫu nhiên thống nhất từ 0đến (5^m)-1và không sử dụng bất kỳ giá trị nào cao hơn 7^n. Tuy nhiên, các giá trị này có thể được giải cứu và sử dụng lại. Họ có hiệu quả tạo ra một chuỗi số thống nhất từ ​​1 đến 5^m-7^n. Vì vậy, sau đó chúng ta có thể thử sử dụng chúng và chuyển đổi chúng thành số 7 số để chúng ta có thể tạo ra nhiều giá trị đầu ra hơn.

Nếu chúng ta T7(X)để độ dài trung bình của chuỗi đầu ra của các random(1-7)số nguyên xuất phát từ một đầu vào có kích thước đồng nhất Xvà giả sử rằng 5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7.

Sau đó, T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)vì chúng ta có độ dài không có chuỗi với xác suất 7 ^ n0 / 5 ^ m với độ dài còn lại 5^m-7^n0với xác suất (5^m-7^n0)/5^m).

Nếu chúng ta cứ thay thế, chúng ta sẽ có được:

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

Vì thế

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

Một cách khác để đặt điều này là:

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)

Trường hợp tốt nhất có thể là trường hợp ban đầu của tôi ở trên 5^m=7^n+s, ở đâu s<7.

Rồi T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)như trước.

Trường hợp xấu nhất là khi chúng ta chỉ có thể tìm thấy k và st 5 ^ m = kx7 + s.

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

Các trường hợp khác là một nơi nào đó giữa. Thật thú vị khi xem chúng tôi có thể làm tốt như thế nào với m rất lớn, tức là chúng tôi có thể nhận được thuật ngữ lỗi tốt như thế nào:

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

Có vẻ như không thể đạt được e(m) = o(1)nói chung nhưng hy vọng chúng ta có thể chứng minh e(m)=o(m).

Toàn bộ sau đó dựa vào phân phối của 7 chữ số ary 5^mcho các giá trị khác nhau của m.

Tôi chắc chắn có rất nhiều lý thuyết ngoài kia bao gồm điều này tôi có thể xem và báo cáo lại tại một số điểm.


+2 (nếu tôi có thể) - đây là câu trả lời tốt duy nhất (trái ngược với chỉ đơn thuần là đầy đủ). Bạn đã có câu trả lời tốt thứ hai phù hợp với số nguyên 32 bit.
Rex Kerr

10

Đây là một triển khai Python hoạt động của câu trả lời của Adam .

import random

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

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

Tôi thích ném các thuật toán mà tôi đang xem vào Python để tôi có thể chơi xung quanh chúng, nghĩ rằng tôi đã đăng nó ở đây với hy vọng rằng nó hữu ích cho ai đó ngoài kia, không phải mất nhiều thời gian để ném cùng nhau.


Không, đó là khá khác nhau từ câu trả lời của tôi. Bạn đang lặp lại 21 lần và loại bỏ kết quả của 20 lần lặp đầu tiên. Bạn cũng đang sử dụng rand4 () và rand5 () làm đầu vào, điều này rõ ràng phá vỡ các quy tắc chỉ sử dụng rand5 (). Cuối cùng, bạn sản xuất một phân phối không đồng đều.
Adam Rosenfield

Xin lỗi vì điều đó. Tôi đã khá mệt mỏi khi xem qua câu hỏi này, đủ mệt đến nỗi tôi hoàn toàn hiểu sai thuật toán của bạn. Tôi thực sự đã ném nó vào Python vì tôi không thể hiểu tại sao bạn lại lặp 21 lần. Làm cho nhiều ý nghĩa hơn bây giờ. Tôi đã làm điều ngẫu nhiên.randint (1, 4) như một cách viết tắt nhưng tôi đoán bạn đúng, nó trái với tinh thần của câu hỏi. Tôi đã sửa mã.
James McMahon

@robermorales - Như Adam Rosenfeld đã giải thích trong câu trả lời của mình , mọi giải pháp đưa ra phân phối thống nhất thực sự trên [1, 7] sẽ liên quan đến một số vòng lặp từ chối chấp nhận có khả năng vô hạn. (Tuy nhiên, nếu rand5()là một PRNG đàng hoàng, thì vòng lặp sẽ không phải là vô hạn vì cuối cùng 5*(rand5() - 1) + rand5()chắc chắn sẽ là <= 21.)
Ted Hopp

10

Tại sao không làm điều đó đơn giản?

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

Cơ hội nhận được 1 và 7 trong giải pháp này thấp hơn do modulo, tuy nhiên, nếu bạn chỉ muốn một giải pháp nhanh chóng và dễ đọc, đây là cách để đi.


13
Điều này không tạo ra một phân phối thống nhất. Điều này tạo ra các số 0-6 với xác suất 2/25, 4/25, 5/25, 5/25, 5/25, 3/25, 1/25, như có thể được xác minh bằng cách đếm tất cả 25 kết quả có thể xảy ra.
Adam Rosenfield

8

Giả sử rand (n) ở đây có nghĩa là "số nguyên ngẫu nhiên trong phân phối đồng đều từ 0 đến n-1 ", đây là một mẫu mã sử dụng randint của Python, có tác dụng đó. Nó chỉ sử dụng randint (5) và hằng số, để tạo ra hiệu ứng của randint (7) . Một chút ngớ ngẩn, thực sự

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 Vì Python không có do ... while. Nó có thể là 1337, hoặc 12345, hoặc bất kỳ số nào> 1.
tckmn

8

Tiền đề đằng sau câu trả lời đúng của Adam Rosenfield là:

  • x = 5 ^ n (trong trường hợp của anh ấy: n = 2)
  • thao tác n cuộc gọi rand5 để nhận số y trong phạm vi [1, x]
  • z = ((int) (x / 7)) * 7
  • nếu y> z, hãy thử lại. khác trả về y% 7 + 1

Khi n bằng 2, bạn có 4 khả năng ném xa: y = {22, 23, 24, 25}. Nếu bạn sử dụng n bằng 6, bạn chỉ có 1 lần ném: y = {15625}.

5 ^ 6 = 15625
7 * 2232 = 15624

Bạn gọi rand5 nhiều lần nữa. Tuy nhiên, bạn có cơ hội nhận được giá trị ném đi thấp hơn (hoặc vòng lặp vô hạn). Nếu có một cách để không có giá trị ném đi có thể có cho y, tôi chưa tìm thấy nó.


1
Có thể không có trường hợp nào không có giá trị vứt bỏ - nếu không có sự vứt bỏ, 5 ^ n và 7 ^ m sẽ có một yếu tố chung. Nhưng họ (quyền hạn) của số nguyên tố, vì vậy họ không.
Rex Kerr

8

Đây là câu trả lời của tôi:

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

Nó phức tạp hơn một chút so với những người khác, nhưng tôi tin rằng nó giảm thiểu các cuộc gọi đến rand5. Cũng như các giải pháp khác, có một xác suất nhỏ rằng nó có thể lặp lại trong một thời gian dài.


Điều này tạo ra một phân phối không khác nhiều so với các giải pháp khác nhưng có thêm nhược điểm là phức tạp không cần thiết. Nó cũng chịu khả năng lặp lại không xác định không chính xác có thể chứng minh được nếu các con số thực sự ngẫu nhiên. Tôi vẫn nghĩ rằng những cái tạo ra phân phối đồng đều hơn một chút (mặc dù vẫn còn nhiều hơn mức phù hợp) nhưng đảm bảo hành vi xác định là tốt hơn.
paxdiablo

@Pax: Xin hãy cho tôi biết làm thế nào điều này tạo ra một phân phối không đồng đều. Phân tích của tôi về mã, cũng như thử nghiệm của riêng tôi, chỉ ra rằng điều này tạo ra một phân phối thống nhất. Như chúng ta đã thảo luận trước đây, không thể vừa tạo ra một phân phối thống nhất hoàn hảo vừa có thời gian bảo đảm liên tục trên giới hạn thời gian chạy.
Adam Rosenfield


6

Miễn là không còn bảy khả năng để lựa chọn, hãy rút ra một số ngẫu nhiên khác, nhân số lượng khả năng đó với năm. Trong 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;
}

phân phối của bạn không đồng nhất, ít nhất là trong cuộc gọi đầu tiên. Thật vậy, $possibilitiesđã luôn luôn tăng lên 25 để thoát khỏi vòng lặp và trở lại. Vì vậy, kết quả đầu tiên của bạn là [0-124] % 7, không được phân phối đồng đều bởi vì 125 % 7 != 0(thực tế đây là 6).
bernard paulus

6

Tôi không thích phạm vi bắt đầu từ 1, vì vậy tôi sẽ bắt đầu từ 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;
}

Đây là một người chiến thắng. Điều này tạo ra tất cả 7 kết quả với xác suất như nhau. 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

Ở đó bạn đi, phân phối thống nhất và các cuộc gọi rand5 bằng không.

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

Cần đặt hạt giống trước.


5

Tôi biết nó đã được trả lời, nhưng điều này có vẻ hoạt động tốt, nhưng tôi không thể nói cho bạn biết nếu nó có thành kiến. 'Thử nghiệm' của tôi cho thấy nó, ít nhất là hợp lý.

Có lẽ Adam Rosenfield sẽ tốt bụng để bình luận?

Ý tưởng của tôi (ngây thơ?) Là thế này:

Tích lũy rand5 'cho đến khi có đủ bit ngẫu nhiên để tạo rand7. Điều này mất tối đa 2 rand5's. Để lấy số rand7 tôi sử dụng mod 7 giá trị tích lũy.

Để tránh bộ tích lũy tràn, và vì bộ tích là mod 7 nên tôi lấy mod 7 của bộ tích:

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

Hàm rand7 () theo sau:

(Tôi để phạm vi của rand5 là 0-4 và rand7 cũng tương tự 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;
}

Chỉnh sửa: Đã thêm kết quả cho 100 triệu thử nghiệm.

Chức năng rand 'Real' mod 5 hoặc 7

rand5: avg = 1.999802 0: 20003944 1: 19999889 2: 20003690 3: 19996938 4: 19995539 rand7: avg = 3.000111 0: 14282851 1: 14282879 2: 14284554 3: 14288546 4: 14288546

Rand7 của tôi

Trung bình có vẻ ok và phân phối số trông cũng ok.

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


Bạn có lẽ nên nhìn vào mối tương quan tuần tự. Tôi nghĩ rằng nếu bạn lấy các cặp liên tiếp (mỗi số "ngẫu nhiên" được ghép với tiền thân của nó) thì bạn có thể tìm thấy những điều đáng ngạc nhiên. Bạn chưa giải thích TẠI SAO nên giữ đồng phục phân phối, ở mức nào. Một chương trình làm việc bình thường nên bắt đầu với một lời giải thích tại sao nó hoạt động.
Ian

Tương quan tuần tự sẽ áp dụng cho nhiều giải pháp này?
philcolbourn

Tương quan tuần tự sẽ áp dụng cho nhiều giải pháp này? Đã được một thời gian kể từ khi tôi thử điều này và tôi nghĩ rằng tôi đã giải thích nó. Nhìn vào nó, có vẻ như tôi đang tích lũy các bit ngẫu nhiên trong một nhóm từ rand5, đảm bảo đã tích lũy đủ trước khi rút đủ để tạo số rand7 và đảm bảo tôi không tràn bộ tích lũy của mình.
philcolbourn

4

Có những thuật toán tao nhã được trích dẫn ở trên, nhưng đây là một cách để tiếp cận nó, mặc dù nó có thể là đường vòng. Tôi giả sử các giá trị được tạo từ 0.

R2 = trình tạo số ngẫu nhiên cho các giá trị nhỏ hơn 2 (không gian mẫu = {0, 1})
R8 = trình tạo số ngẫu nhiên cho các giá trị nhỏ hơn 8 (không gian mẫu = {0, 1, 2, 3, 4, 5, 6, 7 })

Để tạo R8 từ R2, bạn sẽ chạy ba lần R2 và sử dụng kết quả tổng hợp của cả 3 lần chạy dưới dạng số nhị phân có 3 chữ số. Dưới đây là phạm vi giá trị khi R2 được chạy ba lần:

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

Bây giờ để tạo R7 từ R8, chúng tôi chỉ cần chạy lại R7 nếu nó trả về 7:

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

Giải pháp bùng binh là tạo R2 từ R5 (giống như chúng ta đã tạo R7 từ R8), rồi R8 từ R2 và sau đó là R7 từ R8.


Giống như một số người khác, cách tiếp cận này có thể mất một thời gian dài tùy ý cho mỗi cuộc gọi R7, vì bạn có thể nhận được một chuỗi dài bảy mươi từ R8.
Alex North-Keys

4

Đây là một giải pháp phù hợp hoàn toàn với số nguyên và nằm trong khoảng 4% mức tối ưu (nghĩa là sử dụng 1,26 số ngẫu nhiên trong {0..4} cho mỗi số trong {0..6}). Mã trong Scala, nhưng toán học phải rõ ràng một cách hợp lý trong bất kỳ ngôn ngữ nào: bạn tận dụng thực tế là 7 ^ 9 + 7 ^ 8 rất gần với 5 ^ 11. Vì vậy, bạn chọn một số gồm 11 chữ số trong cơ sở 5, và sau đó hiểu nó là một số có 9 chữ số trong cơ sở 7 nếu nó nằm trong phạm vi (đưa ra 9 số cơ sở 7) hoặc là một số có 8 chữ số nếu vượt quá số có 9 chữ số, v.v. .:

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

Nếu bạn dán một bài kiểm tra vào trình thông dịch (thực sự REPL), bạn sẽ nhận được:

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

Phân phối là tốt và bằng phẳng (trong khoảng 10k của 1/7 của 10 ^ 8 trong mỗi thùng, như mong đợi từ một phân phối xấp xỉ Gaussian).


3

Bằng cách sử dụng tổng số cán , bạn có thể cả hai

  • duy trì sự phân phối đồng đều; và
  • không phải hy sinh bất kỳ yếu tố nào trong chuỗi ngẫu nhiên.

Cả hai vấn đề này là một vấn đề với các rand(5)+rand(5)...giải pháp -type đơn giản . Mã Python sau đây cho thấy cách triển khai nó (hầu hết điều này đang chứng minh sự phân phối).

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)

Và kết quả này cho thấy kết quả:

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

Một cách đơn giản rand(5)+rand(5), bỏ qua những trường hợp mà giá trị này trả về hơn 6 có biến thể điển hình là 18%, gấp 100 lần so với phương pháp được hiển thị ở trên:

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

Và, theo lời khuyên của Nixuz, tôi đã dọn sạch tập lệnh để bạn có thể giải nén và sử dụng rand7...nội dung:

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
Err, hãy để tôi nói lại rằng. Cho rằng một x cụ thể được tạo ra tại một số điểm trong chuỗi, chỉ có 5 trong số 7 số có thể được tạo cho số tiếp theo trong chuỗi. Một RNG thực sự sẽ có tất cả các mẫu độc lập với nhau, nhưng trong trường hợp này rõ ràng là không.
Adam Rosenfield

3
Đúng là câu hỏi ban đầu không chỉ định nếu các hàm đầu vào và đầu ra tạo ra các mẫu (iid) độc lập và phân phối giống hệt nhau, nhưng tôi nghĩ rằng đó là một kỳ vọng hợp lý rằng nếu rand5 () là iid, thì đầu ra rand7 () cũng nên iid. Nếu bạn không nghĩ rằng điều đó hợp lý, hãy vui vẻ sử dụng RNG không iid của bạn.
Adam Rosenfield

1
Vì vậy, những gì từ các nhà toán học tại trường đại học?
Adam Rosenfield

1
Giải pháp này rõ ràng bị phá vỡ. Rõ ràng là bạn cần gọi rand5 (trung bình) nhiều hơn một lần cho mỗi cuộc gọi đến rand7 và giải pháp này thì không. Do đó, kết quả không thể ngẫu nhiên theo bất kỳ định nghĩa lành mạnh nào về ngẫu nhiên.
Chris Suter

1
@Pax Tại mỗi lần lặp chức năng của bạn, nó chỉ có thể trả về một trong năm giá trị khác nhau (mặc dù trong phạm vi 0-6). Lặp lại đầu tiên chỉ có thể trả về một số trong phạm vi 0-4. Vì vậy, cần phải rõ ràng rằng trong khi chức năng của bạn có thể có phân phối đồng đều, các mẫu không độc lập tức là chúng có tương quan không phải là thứ bạn muốn trong trình tạo số ngẫu nhiên.
Chris Suter

3

Câu trả lời này là một thử nghiệm nhiều hơn trong việc thu được nhiều entropy nhất có thể từ hàm Rand5. Do đó có phần không rõ ràng và gần như chắc chắn chậm hơn rất nhiều so với các triển khai khác.

Giả sử phân phối đồng đều từ 0-4 và dẫn đến phân phối đồng đều từ 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;
  }
}

Số lượng bit được thêm vào bộ đệm trên mỗi cuộc gọi đến Rand5 hiện là 4/5 * 2 nên 1.6. Nếu giá trị xác suất 1/5 được bao gồm tăng 0,05 thì 1,65 nhưng hãy xem nhận xét trong mã nơi tôi đã phải vô hiệu hóa điều này.

Số bit được tiêu thụ bằng cách gọi tới Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
Đây là 3 + 3/8 + 3/64 + 3/512 ... vì vậy khoảng 3,42

Bằng cách trích xuất thông tin từ số bảy, tôi lấy lại 1/8 * 1/7 bit cho mỗi cuộc gọi, khoảng 0,008

Điều này mang lại mức tiêu thụ ròng 3,4 bit cho mỗi cuộc gọi, có nghĩa là tỷ lệ này là 2,125 cuộc gọi đến Rand5 cho mỗi Rand7. Tối ưu phải là 2.1.

Tôi sẽ tưởng tượng cách tiếp cận này chậm hơn đáng kể so với nhiều cách khác ở đây trừ khi chi phí của cuộc gọi đến Rand5 là rất tốn kém (nói gọi ra một số nguồn entropy bên ngoài).


Giải pháp của bạn có vẻ đúng, ngoài một số lỗi đơn giản: "if (Count> 1)" nên là "if (Count <= 1)" và "i ++" xảy ra ngay sau đó sẽ nằm trong dấu ngoặc nhọn trước nó. Tôi không chắc liệu BitsSet () có đúng hay không, nhưng điều đó có phần không liên quan.
Adam Rosenfield

Nhìn chung, mặc dù, chức năng của bạn là rất khó hiểu. Nó làm cho việc sử dụng entropy tốt hơn một chút so với cách khác, với chi phí phức tạp hơn. Cũng không có lý do gì để lấp đầy bộ đệm với 35 bit ngẫu nhiên trong cuộc gọi đầu tiên, khi 3 sẽ đủ.
Adam Rosenfield

Tôi đã sửa <= cảm ơn, i ++ thực sự nên ở đó. Nó sẽ xảy ra trên trường hợp 0 ​​và 1 (thêm 1 hoặc 0 tương ứng vào bộ đệm). Đây hoàn toàn không phải là những gì tôi sẽ đề nghị sử dụng, nó phức tạp khủng khiếp. Tôi chỉ quan tâm đến việc tôi có thể tiến gần đến giới hạn entropy lý thuyết vốn có trong vấn đề như thế nào ... Cảm ơn đã phản hồi. Trớ trêu thay, việc điền vào bộ đệm trong cuộc gọi đầu tiên là để viết đơn giản hơn :)
ShuggyCoUk

Tôi đã làm lại điều này để dễ hiểu hơn (với chi phí tốc độ) nhưng cũng làm cho nó chính xác. Vẫn chưa tối ưu, vì một số lý do, các bit 1/5 gây ra sự cố mặc dù chúng có tính đồng nhất về số lượng.
ShuggyCoUk

3

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

các vòng lặp để tạo ra một số ngẫu nhiên trong khoảng từ 16 đến 127, chia cho mười sáu để tạo ra một số float giữa 1 và 7.9375, sau đó làm tròn xuống để có một số nguyên từ 1 đến 7. nếu tôi không nhầm, có cơ hội nhận được 16/112 bất kỳ một trong 7 kết quả.


mặc dù có lẽ có một câu trả lời dễ dàng hơn tương tự như thế này bằng cách sử dụng không có vòng lặp có điều kiện và modulo thay vì sàn. Tôi chỉ không thể bẻ số ngay bây giờ.
dqhendricks

3
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}

vấn đề: điều này trả về không đồng đều trong phạm vi 0-7, không phải 0-6. Thật vậy, bạn có thể có 7 = 111bvớip(7) = 8 / 125
bernard paulus

3

Tôi nghĩ rằng tôi có bốn câu trả lời, hai câu trả lời chính xác như của @Adam Rosenfield nhưng không có vấn đề vòng lặp vô hạn và hai câu hỏi khác với giải pháp gần như hoàn hảo nhưng thực hiện nhanh hơn lần đầu tiên.

Giải pháp chính xác tốt nhất yêu cầu 7 cuộc gọi đến rand5, nhưng hãy tiếp tục để hiểu.

Phương pháp 1 - Chính xác

Điểm mạnh của câu trả lời của Adam là nó mang lại sự phân phối đồng đều hoàn hảo và có xác suất rất cao (21/25) chỉ cần hai cuộc gọi đến rand5 (). Tuy nhiên, trường hợp xấu nhất là vòng lặp vô hạn.

Giải pháp đầu tiên dưới đây cũng cung cấp một phân phối thống nhất hoàn hảo, nhưng yêu cầu tổng cộng 42 cuộc gọi đến rand5. Không có vòng lặp vô hạn.

Đây là một triển khai 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

Đối với những người không quen thuộc với R, đây là phiên bản đơn giản hóa:

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
}

Việc phân phối rand5sẽ được bảo tồn. Nếu chúng ta làm toán, mỗi trong số 7 lần lặp của vòng lặp có 5 ^ 6 kết hợp có thể, do đó tổng số kết hợp có thể là (7 * 5^6) %% 7 = 0. Do đó chúng ta có thể chia các số ngẫu nhiên được tạo thành các nhóm bằng nhau 7. Xem phương pháp hai để thảo luận thêm về điều này.

Dưới đây là tất cả các kết hợp có thể:

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 

Tôi nghĩ rằng nó thẳng tiến để cho thấy rằng phương pháp của Adam sẽ chạy nhanh hơn nhiều. Xác suất có từ 42 cuộc gọi trở lên rand5trong giải pháp của Adam là rất nhỏ ( (4/25)^21 ~ 10^(-17)).

Phương pháp 2 - Không chính xác

Bây giờ phương thức thứ hai, gần như thống nhất, nhưng yêu cầu 6 cuộc gọi đến rand5:

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

Đây là một phiên bản đơn giản hóa:

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

Đây thực chất là một lần lặp của phương thức 1. Nếu chúng ta tạo ra tất cả các kết hợp có thể, thì đây là kết quả tính:

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

Một số sẽ xuất hiện một lần nữa trong 5^6 = 15625các thử nghiệm.

Bây giờ, trong Phương pháp 1, bằng cách thêm 1 đến 6, chúng tôi di chuyển số 2233 đến từng điểm liên tiếp. Do đó, tổng số kết hợp sẽ khớp với nhau. Điều này hoạt động vì 5 ^ 6 %% 7 = 1, và sau đó chúng tôi thực hiện 7 biến thể phù hợp, vì vậy (7 * 5 ^ 6 %% 7 = 0).

Phương pháp 3 - Chính xác

Nếu đối số của phương thức 1 và 2 được hiểu, phương thức 3 theo sau và chỉ yêu cầu 7 cuộc gọi đến rand5. Tại thời điểm này, tôi cảm thấy đây là số lượng cuộc gọi tối thiểu cần thiết cho một giải pháp chính xác.

Đây là một triển khai R:

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

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

Đối với những người không quen thuộc với R, đây là phiên bản đơn giản hóa:

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

Việc phân phối rand5sẽ được bảo tồn. Nếu chúng ta làm toán, mỗi trong số 7 lần lặp của vòng lặp có 5 kết quả có thể xảy ra, do đó tổng số kết hợp có thể là (7 * 5) %% 7 = 0. Do đó, chúng ta có thể chia các số ngẫu nhiên được tạo thành các nhóm bằng nhau 7. Xem phương pháp một và hai để thảo luận thêm về điều này.

Dưới đây là tất cả các kết hợp có thể:

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 

Tôi nghĩ rằng nó thẳng tiến để cho thấy rằng phương pháp của Adam vẫn sẽ chạy nhanh hơn. Xác suất có 7 cuộc gọi trở lên rand5trong giải pháp của Adam vẫn còn nhỏ ( (4/25)^3 ~ 0.004).

Phương pháp 4 - Không chính xác

Đây là một biến thể nhỏ của phương pháp thứ hai. Nó gần như thống nhất, nhưng yêu cầu 7 cuộc gọi đến rand5, đó là một bổ sung cho phương thức 2:

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

Đây là một phiên bản đơn giản hóa:

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

Nếu chúng tôi tạo ra tất cả các kết hợp có thể, đây là kết quả tính:

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

Hai số sẽ xuất hiện một lần ít hơn trong 5^7 = 78125các thử nghiệm. Đối với hầu hết các mục đích, tôi có thể sống với điều đó.


1
Tôi không quen thuộc với R, nhưng trừ khi tôi hiểu sai về cách thức hoạt động của chúng, thì phương pháp 1 không chính xác. Nó có (5 ^ 6) ^ 7 = 5 ^ 42 kết quả có thể xảy ra, không (5 ^ 6) * 7; 5 ^ 42 không chia hết cho 7. Tương tự như vậy, phương pháp 3 không chính xác. Nó có 5 ^ 7 kết quả có thể xảy ra, không phải 5 * 7. (Lặp lại vòng lặp cuối cùng trong phương thức 3 i=7cũng không có hiệu lực, vì việc thêm 7*rand5()vào rkhông làm thay đổi giá trị của rmod 7.)
Adam Rosenfield

2

Hàm bạn cần là rand1_7 () , tôi đã viết rand1_5 () để bạn có thể kiểm tra và vẽ nó.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.