셈의 기본 법칙 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""셈의 기본 법칙 검증.

문서: 14.셈의 기본 법칙 (예시, 정의, 예제, 카드 C1~C4, 자주 하는 오해)
주장 1: 곱의 법칙·합의 법칙·나눗셈 법칙을 작은 경우에서 전수로 확인한다.
주장 2: 소문자+숫자(36자) 8자리 비밀번호 36^8 = 2,821,109,907,456개, 숫자를 하나 이상 포함 36^8 - 26^8 = 2,612,282,842,880개.
        (작은 판: 3글자·기호 {a,b,1}에서 전수)
주장 3: 원탁에 n명 앉히는 방법(회전만 같은 것으로 봄)은 (n-1)!이다 (n <= 7 전수).
주장 4: 서로 다른 세 글자 문자열 26·25·24 = 15,600.
주장 5: 오해 — 첫 자리나 끝자리가 0인 네 자리 PIN은 2,000이 아니라 1,900개.
"""
import math
from itertools import permutations, product


def main():
    A, B = range(4), range(7)
    assert len(list(product(A, B))) == 4 * 7
    assert len(set(range(0, 5)) | set(range(10, 13))) == 5 + 3
    print("[OK] 주장 1: 곱·합의 법칙")

    assert 36 ** 8 == 2_821_109_907_456 and 36 ** 8 - 26 ** 8 == 2_612_282_842_880 and 26 ** 8 == 208_827_064_576
    assert round(36 ** 8 / 1e9 / 60) == 47 and round(36 ** 12 / 1e9 / (365.25 * 86400)) == 150
    small = ["".join(p) for p in product("ab1", repeat=3)]
    assert sum(1 for w in small if any(c.isdigit() for c in w)) == 3 ** 3 - 2 ** 3
    print("[OK] 주장 2·카드 C2: 여사건으로 세기")

    for n in range(1, 8):
        seen = set()
        for p in permutations(range(n)):
            i = p.index(0)
            seen.add(p[i:] + p[:i])      # 0이 맨 앞에 오게 돌린 표준형
        assert len(seen) == math.factorial(n - 1) == math.factorial(n) // n
    print("[OK] 주장 3·카드 C3: 원탁 (n-1)! (n <= 7), 나눗셈 법칙")

    assert len(list(permutations("abcdefghijklmnopqrstuvwxyz", 3))) == 26 * 25 * 24 == 15600
    print("[OK] 주장 4")

    pins = ["".join(p) for p in product("0123456789", repeat=4)]
    cnt = sum(1 for w in pins if w[0] == "0" or w[-1] == "0")
    assert cnt == 1900 and 1000 + 1000 - 100 == 1900
    print("[OK] 주장 5·오해: 1,900개")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()