Published on

Authors
  • avatar
    Name

那天看到了同學中恰恰好有兩對雙人同天生日。我一時好奇,用Python 算了一下各種組合的機率。以50人爲例,得到了較高機率的幾種組合如下:

組合機率
恰好3組(每組兩人)22.17%
恰好2組(每組兩人)20.43%
恰好4組(每組兩人)16.44%
恰好1組(每組兩人)11.48%
恰好5組(每組兩人)8.84%
恰好6組(每組兩人)3.58%
1組(每組三人)加2組(每組2人)3.06%
無人同天2.96%

所以至少有兩人同天生日的機率高達97.04%。

其算法簡單來說,先將50這整數做「無序分割」例如50=2+2+2+1+1…+1。再算其機率:365/365 1/365 364/365 1/365 363/365 1/365 362/365 361/365 ... 319/365 。這機率再乘上這分割的所有組合數目238360500(可用Python 算出),就是其發生的總機率。


Python code

def get_partitions(n, max_value=None):
     """Generates all unique integer partitions of n in decreasing order."""
     if n == 0:
         yield []
         return
 
     if max_value is None or max_value > n:
         max_value = n
 
     for i in range(max_value, 0, -1):
         for sub_partition in get_partitions(n - i, i):
             yield [i] + sub_partition
 
import math
def count_partitions(n, group_sizes):
     # Total items must match sum of group sizes
     if sum(group_sizes) != n:
         return 0
 
     # Standard formula for multinomial coefficient
     num = math.factorial(n)
     den = 1
     for size in group_sizes:
         den *= math.factorial(size)
 
     multinomial = num // den
 
     # Adjust for identical group sizes since groups are usually unlabeled
     # Count frequencies of each group size
     from collections import Counter
     counts = Counter(group_sizes)
 
     for size, freq in counts.items():
         den *= math.factorial(freq) # multiplying the denominator by freq!
 
     # Correct calculation: multinomial divided by the product of factorials of frequencies
     freq_div = 1
     for size, freq in counts.items():
         freq_div *= math.factorial(freq)
 
     total_unlabeled = multinomial // freq_div
     return multinomial, totali_unlabeled
 
def calculate_odds(partition, leng):
     """calculate birth odds foreach partition"""
     sel_day=365
     odds = ''
     p = 1.0
     for idx in range(len(partition)):
         if idx == 0:
             if partition[idx] == 1:
                 odds += str(sel_day) + '/365'
                 p = p * sel_day / 365
                 sel_day = sel_day - 1
             else:
                 for i in range(partition[idx]):
                     if i == 0:
                         odds += str(sel_day) + '/365'
                         p = p * sel_day / 365
                         sel_day = sel_day - 1
                     else:
                         odds +=  ' * ' + '1/365'
                         p = p / 365
         else:
             if partition[idx] == 1:
                 odds += ' * ' + str(sel_day) + '/365'
                 p = p * sel_day / 365
                 sel_day = sel_day - 1
             else:
                 for i in range(partition[idx]):
                     if i == 0:
                         odds += ' * ' + str(sel_day) + '/365'
                         p = p * sel_day / 365
                         sel_day = sel_day - 1
                     else:
                         odds +=  ' * ' + '1/365'
                         p = p / 365
     multinomial, unlabeled = count_partitions(leng, partition)
     p = p * unlabeled
     return odds, p, unlabeled
 
def cal_occurrance(partition, target, occurrance_times):
     target = 3
     occurrance = partition.count(no_to_occur)
     if occurrance == occurrance_times:
         print(odds)
         multinomial, unlabeled = count_partitions(leng, partition)
         print(f"Unlabeled groups (just splitting them): {unlabeled}")
         p = p * unlabeled
         print('odds = ', p)
         print(f'{no_to_occur} in one group occurrance={occurrance}')
         return p
     else:
         print(f'{no_to_occur} in one group occurrance={occurrance}')
         return 0
 
target_number = 50
print(f"Partitions for {target_number}:")
cnt = 0
total_odds = 0.0
D = []
for partition in get_partitions(target_number):
    print('----', cnt+1)
    print(partition)
    odds, p, unlabeled = calculate_odds(partition, target_number)
    D.insert(0, {'partition': partition, 'cal_string': odds, 'odds': p, 'combinations': unlabeled})
    total_odds += p
    cnt = cnt + 1
print('----')
print(f"total number of partitions: {cnt}")
print(f"total odds:{total_odds}")
print('---')
sorted_D = sorted(D, key=lambda x: x['odds'])
cnt = 0
for d in sorted_D:
    print('+++ by odds', cnt+1)
    print(d)
    cnt += 1