Workspace IndexMath › Day 9

Counting Principles: Permutations, Combinations, and Binomial Coefficients TODO

Math · Day 9 / 52 · July — Discrete Math & Logic (Day 3-10)

Concept

Counting rests on two rules: mutually exclusive choices add (the sum rule), and independent sequential steps multiply (the product rule). The number of ways to pick k items from n, order-sensitive, is the permutation count n!/(n-k)!, and order-insensitive, it's the binomial coefficient C(n,k) = n!/(k!(n-k)!). The binomial coefficients are exactly the expansion coefficients of (x+y)^n, and Pascal's identity C(n,k) = C(n-1,k-1) + C(n-1,k) falls straight out of a combinatorial argument splitting on "cases that include a particular element vs. cases that don't." When repetition is allowed or conditions overlap, this extends to combinations with repetition and inclusion-exclusion, and counting problems are usually half-solved just by first defining what counts as "the same."

Real-world calculations like counting an algorithm's cases, estimating hash collision probability, or designing random sampling all rest on these basic rules, and getting them wrong throws off the entire probability estimate.

Code & Formula

# 카운팅 원리(순열·조합·이항계수) — nPr, nCr 을 직접 구현해 math.perm/math.comb 와 대조하고,
# 파스칼 항등식 C(n,k) = C(n-1,k-1) + C(n-1,k) 도 조합적으로 검증.

import math
from itertools import permutations, combinations

def n_perm(n, r):
    return math.factorial(n) // math.factorial(n - r)

def n_comb(n, r):
    return math.factorial(n) // (math.factorial(r) * math.factorial(n - r))

n, r = 8, 3
my_perm, my_comb = n_perm(n, r), n_comb(n, r)
print(f"P({n},{r}) 직접계산={my_perm}  math.perm={math.perm(n, r)}  일치? {my_perm == math.perm(n, r)}")
print(f"C({n},{r}) 직접계산={my_comb}  math.comb={math.comb(n, r)}  일치? {my_comb == math.comb(n, r)}")

# 실제로 나열해서 개수를 세어도 같은지 확인 (작은 n으로).
items = list(range(5))
listed_perm = len(list(permutations(items, 3)))
listed_comb = len(list(combinations(items, 3)))
print(f"\n{items} 에서 3개 뽑기: 나열해서 센 순열 수={listed_perm} (공식={n_perm(5,3)}), "
      f"조합 수={listed_comb} (공식={n_comb(5,3)})")

# 파스칼 항등식: 특정 원소를 포함하는 경우(C(n-1,k-1)) + 포함하지 않는 경우(C(n-1,k))
for n in (6, 9, 12):
    for k in range(1, n):
        lhs = math.comb(n, k)
        rhs = math.comb(n - 1, k - 1) + math.comb(n - 1, k)
        assert lhs == rhs, f"파스칼 항등식 불일치: n={n}, k={k}"
print("\n파스칼 항등식 C(n,k)=C(n-1,k-1)+C(n-1,k) 모든 표본에서 성립 확인 완료.")

Exercise

Implement C(n,k) two ways — as DP on Pascal's identity, and via the multiplicative formula — compare how overflow and precision diverge for large n, and prove the identity by hand with a combinatorial argument.

Practical Connection

This is the most basic tool for counting outcome combinations in a prediction market, computing the probability that a set of nodes reaches quorum, or working the hash-collision birthday problem.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/math-50-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

카운팅 원리(순열·조합·이항계수) TODO

Math · Day 9 / 52 · 7월 — 이산수학·논리 (Day 3–10)

개념

카운팅의 토대는 두 규칙이다. 서로 배타적인 선택지는 더하고(합의 법칙), 독립적으로 이어지는 단계는 곱한다(곱의 법칙). 순서를 구분해 n개 중 k개를 뽑는 순열의 수는 n!/(n-k)!이고, 순서를 구분하지 않는 조합의 수는 이항계수 C(n,k)=n!/(k!(n-k)!)이다. 이항계수는 (x+y)^n의 전개 계수와 같고, 파스칼 항등식 C(n,k)=C(n-1,k-1)+C(n-1,k)는 '특정 원소를 포함하는 경우와 포함하지 않는 경우'로 나눈 조합적 논증에서 바로 나온다. 중복을 허용하거나 조건이 겹칠 때는 중복조합과 포함배제 원리로 확장하며, 카운팅 문제는 대개 '무엇을 같다고 볼 것인가'를 먼저 정의하는 데서 절반이 풀린다.

알고리즘의 경우의 수 분석, 해시 충돌 확률, 무작위 샘플링 설계 같은 실무 계산이 전부 이 기본 규칙 위에 서 있고, 여기서 어긋나면 확률 추정 전체가 틀어진다.

코드 · 수식

# 카운팅 원리(순열·조합·이항계수) — nPr, nCr 을 직접 구현해 math.perm/math.comb 와 대조하고,
# 파스칼 항등식 C(n,k) = C(n-1,k-1) + C(n-1,k) 도 조합적으로 검증.

import math
from itertools import permutations, combinations

def n_perm(n, r):
    return math.factorial(n) // math.factorial(n - r)

def n_comb(n, r):
    return math.factorial(n) // (math.factorial(r) * math.factorial(n - r))

n, r = 8, 3
my_perm, my_comb = n_perm(n, r), n_comb(n, r)
print(f"P({n},{r}) 직접계산={my_perm}  math.perm={math.perm(n, r)}  일치? {my_perm == math.perm(n, r)}")
print(f"C({n},{r}) 직접계산={my_comb}  math.comb={math.comb(n, r)}  일치? {my_comb == math.comb(n, r)}")

# 실제로 나열해서 개수를 세어도 같은지 확인 (작은 n으로).
items = list(range(5))
listed_perm = len(list(permutations(items, 3)))
listed_comb = len(list(combinations(items, 3)))
print(f"\n{items} 에서 3개 뽑기: 나열해서 센 순열 수={listed_perm} (공식={n_perm(5,3)}), "
      f"조합 수={listed_comb} (공식={n_comb(5,3)})")

# 파스칼 항등식: 특정 원소를 포함하는 경우(C(n-1,k-1)) + 포함하지 않는 경우(C(n-1,k))
for n in (6, 9, 12):
    for k in range(1, n):
        lhs = math.comb(n, k)
        rhs = math.comb(n - 1, k - 1) + math.comb(n - 1, k)
        assert lhs == rhs, f"파스칼 항등식 불일치: n={n}, k={k}"
print("\n파스칼 항등식 C(n,k)=C(n-1,k-1)+C(n-1,k) 모든 표본에서 성립 확인 완료.")

연습

C(n,k)를 파스칼 항등식 기반 DP와 곱셈식 기반 두 방식으로 구현해, 큰 n에서 오버플로와 정밀도가 어떻게 달라지는지 비교하고 조합적 논증으로 항등식을 손으로 증명하라.

실무 · Verex 연결

예측시장에서 여러 결과 조합의 경우의 수를 세거나, 노드 집합에서 정족수를 이룰 확률·해시 충돌 생일 문제를 계산할 때 쓰는 가장 기본적인 도구다.

공부한 날 원본 커리큘럼(docs/knowledge/math-50-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 8. 관계와 동치류10. 재귀관계와 생성함수(가볍게) →