조건부 확률 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""조건부 확률 검증.

문서: 03.조건부 확률 (예시, 정의, 증명, 예제, 오해, 카드 C1~C4)
주장 1: 예시 — 두 주사위: P(합 8) = 5/36, P(합 8 | 첫 주사위 3) = 1/6 (조건을 주면 커질 수도 있다),
        P(합 8 | 첫 주사위 1) = 0.
주장 2: 설계 이유 — 상대도수 해석: 모의실험에서 B가 일어난 시행 중 A의 비율이 P(A ∩ B)/P(B)에 가깝다.
        조건부 확률도 공리를 만족한다(P(Ω|B) = 1, 배반 사건의 덧셈).
주장 3: 곱셈 법칙·연쇄 법칙 — 카드 C1: 52장에서 두 장 모두 에이스 1/221(전수).
주장 4: 전확률 — 카드 C3: 0.6·0.02 + 0.4·0.05 = 0.032.
주장 5: 예제·카드 C4 — 몬티 홀: 바꾸면 2/3(전수와 모의실험).
"""
from fractions import Fraction
from itertools import product, permutations
import random


def main():
    rng = random.Random(3)
    omega = list(product(range(1, 7), repeat=2))
    P = lambda A: Fraction(len(A), 36)
    s8 = {w for w in omega if sum(w) == 8}
    f3 = {w for w in omega if w[0] == 3}
    f1 = {w for w in omega if w[0] == 1}
    assert P(s8) == Fraction(5, 36) and P(s8 & f3) / P(f3) == Fraction(1, 6) and P(s8 & f1) == 0
    assert P(s8 & f3) / P(f3) > P(s8)
    print("[OK] 주장 1: 예시")

    nB = nAB = 0
    for _ in range(100000):
        w = (rng.randint(1, 6), rng.randint(1, 6))
        if w[0] == 3:
            nB += 1
            nAB += sum(w) == 8
    assert abs(nAB / nB - 1 / 6) < 0.01
    B = f3
    PB = lambda A: P(A & B) / P(B)
    assert PB(set(omega)) == 1
    A1 = {w for w in omega if w[1] <= 2}
    A2 = {w for w in omega if w[1] >= 5}
    assert PB(A1 | A2) == PB(A1) + PB(A2)
    print("[OK] 주장 2: 상대도수, 조건부 확률의 공리")

    deck = [(r, s) for r in range(13) for s in range(4)]
    pairs = list(permutations(range(52), 2))
    both = sum(1 for i, j in pairs if deck[i][0] == 0 and deck[j][0] == 0)
    assert Fraction(both, len(pairs)) == Fraction(4, 52) * Fraction(3, 51) == Fraction(1, 221)
    print("[OK] 주장 3·카드 C1: 1/221")

    assert Fraction(6, 10) * Fraction(2, 100) + Fraction(4, 10) * Fraction(5, 100) == Fraction(32, 1000)
    print("[OK] 주장 4·카드 C3: 전확률 0.032")

    win_switch = Fraction(0)
    for car in range(3):
        for pick in range(3):
            # 진행자는 차가 없고 참가자가 고르지 않은 문을 연다. 두 문이 가능하면 반반.
            opts = [d for d in range(3) if d != car and d != pick]
            for opened in opts:
                w = Fraction(1, 9) / len(opts)
                other = next(d for d in range(3) if d != pick and d != opened)
                win_switch += w * (other == car)
    assert win_switch == Fraction(2, 3)
    wins = 0
    for _ in range(60000):
        car, pick = rng.randrange(3), rng.randrange(3)
        opened = rng.choice([d for d in range(3) if d != car and d != pick])
        other = next(d for d in range(3) if d != pick and d != opened)
        wins += other == car
    assert abs(wins / 60000 - 2 / 3) < 0.01
    print("[OK] 주장 5·카드 C4: 몬티 홀 2/3")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()