선형 탐사와 체이닝 비교 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
"""선형 탐사와 체이닝을 같은 키로 채워 찾는 수고를 견준다.
실행: python3 51_probing-vs-chaining_verify.py
문서: 51.선형 탐사와 체이닝 비교 (결정적 차이 표, 카드 C1~C3)
세는 것: 선형 탐사는 들여다본 버킷 수, 체이닝은 들여다본 체인 노드 수(끝까지 훑는다).
주장 (실험으로 확인한 것이고 증명이 아니다)
1. 같은 키, 같은 버킷 수에서 적재율 0.5, 0.75, 0.9마다
선형 탐사는 문서의 식 (1 + 1/(1 - a))/2, (1 + 1/(1 - a)^2)/2에,
체이닝은 1 + a/2, a에 가깝다.
2. 적재율이 오를수록 선형 탐사의 "없는 키 찾기"만 가파르게 는다(0.9에서 체이닝의 50배쯤).
3. 문서 예시의 키 12개(b = 17): 있는 키 평균은 선형 탐사 29/12, 체이닝 18/12.
4. 체이닝은 키가 버킷보다 많아도(a = 3) 돌아간다. 선형 탐사는 a가 1을 넘을 수 없다.
"""
import random
def build_probing(keys, b):
table = [None] * b
for k in keys:
i = k % b
while table[i] is not None:
i = (i + 1) % b
table[i] = k
return table
def probing_success(table, k):
b = len(table)
i, count = k % b, 1
while table[i] != k:
i = (i + 1) % b
count += 1
return count
def probing_fail_all(table):
b = len(table)
first_empty = next(i for i in range(b) if table[i] is None)
out = [0] * b
run = 0
for step in range(b):
i = (first_empty - step) % b
run = 1 if table[i] is None else run + 1
out[i] = run
return out
def build_chains(keys, b):
chains = [[] for _ in range(b)]
for k in keys:
chains[k % b].append(k)
return chains
def measure(b, alpha, trials, rng):
n = int(alpha * b)
ps = pu = cs = cu = 0.0
for _ in range(trials):
keys = rng.sample(range(10 ** 9), n)
table = build_probing(keys, b)
chains = build_chains(keys, b)
ps += sum(probing_success(table, k) for k in keys) / n
pu += sum(probing_fail_all(table)) / b
cs += sum(chains[k % b].index(k) + 1 for k in keys) / n
cu += sum(len(c) for c in chains) / b # 홈 버킷이 고르면 평균 체인 길이
return ps / trials, pu / trials, cs / trials, cu / trials
if __name__ == "__main__":
rng = random.Random(51)
b = 20011
rows = {}
for alpha, trials, tol in ((0.5, 10, 0.05), (0.75, 10, 0.05), (0.9, 30, 0.15)):
ps, pu, cs, cu = measure(b, alpha, trials, rng)
rows[alpha] = (ps, pu, cs, cu)
assert abs(ps - (1 + 1 / (1 - alpha)) / 2) / ps < tol
assert abs(pu - (1 + 1 / (1 - alpha) ** 2) / 2) / pu < tol
assert abs(cs - (1 + alpha / 2)) < 0.02
assert abs(cu - alpha) < 0.01
assert cs < ps and cu < pu # 세는 단위는 다르지만 수는 체이닝이 작다
# 2. 가파르게 느는 것은 선형 탐사의 없는 키 찾기
assert rows[0.9][1] / rows[0.5][1] > 15 # 2.5쯤 -> 48쯤
assert rows[0.9][3] / rows[0.5][3] < 2 # 0.5 -> 0.9
assert 40 < rows[0.9][1] / rows[0.9][3] < 65
# 3. 문서 예시의 키
slide_keys = [6, 12, 34, 29, 28, 11, 23, 7, 0, 33, 30, 45]
table = build_probing(slide_keys, 17)
chains = build_chains(sorted(slide_keys), 17)
assert sum(probing_success(table, k) for k in slide_keys) == 29
assert sum(chains[k % 17].index(k) + 1 for k in slide_keys) == 18
# 4. a = 3인 체이닝
keys = rng.sample(range(10 ** 9), 3 * 2003)
chains = build_chains(keys, 2003)
assert sum(len(c) for c in chains) == 3 * 2003
assert abs(sum(chains[k % 2003].index(k) + 1 for k in keys) / len(keys) - 2.5) < 0.05
print("적재율 | 선형 탐사 있는 키, 없는 키 | 체이닝 있는 키, 없는 키")
for alpha in (0.5, 0.75, 0.9):
ps, pu, cs, cu = rows[alpha]
print(f"{alpha:5} | {ps:5.2f} {pu:6.2f} | {cs:5.2f} {cu:5.2f}")
print("ALL CHECKS PASSED")