Workspace IndexAlgorithms › Day 6

Probabilistic Data Structures TODO

Algorithms · Day 6 / 100 · A. Advanced Algorithms & Data Structures (Day 1-19)

Concept

Probabilistic data structures trade an exact answer for a bounded error, in exchange for a large reduction in memory. A Bloom filter tests set membership using a bit array and k hash functions; it can produce false positives but never false negatives, and doesn't support deletion. A Cuckoo filter instead stores a short fingerprint of the element in one of two candidate buckets, which lets it support deletion. A Count-Min sketch estimates frequencies using a grid of counters indexed by several hash rows; collisions can cause it to overestimate, but never underestimate. HyperLogLog estimates the cardinality (count of distinct elements) by recording, across many buckets, the maximum number of leading zero bits seen in a hash value — using almost constant memory.

In places like logs, mempools, or caches where the element count runs into the hundreds of millions, holding an exact set or counter blows the memory budget before anything else fails.

Code & Formula

# Day 6: 확률적 자료구조 — Bloom Filter로 집합 포함 여부를 확률적으로 판정
# 비트 배열 + k개의 해시로 원소를 삽입하고, 거짓 양성(false positive)이 실제로 발생함을 확인한다.

import hashlib

class BloomFilter:
    def __init__(self, size, k):
        self.size = size
        self.k = k
        self.bits = [0] * size

    def _hashes(self, item):
        for i in range(self.k):
            h = hashlib.sha256(f"{i}:{item}".encode()).digest()
            yield int.from_bytes(h, "big") % self.size

    def add(self, item):
        for idx in self._hashes(item):
            self.bits[idx] = 1

    def might_contain(self, item):
        return all(self.bits[idx] for idx in self._hashes(item))

bf = BloomFilter(size=64, k=3)
inserted = [f"tx-{i}" for i in range(20)]
for item in inserted:
    bf.add(item)

checked, false_positives = 2000, 0
for i in range(checked):
    probe = f"probe-{i}"
    if probe not in inserted and bf.might_contain(probe):
        false_positives += 1

print(f"삽입 원소 {len(inserted)}개, 비트 배열 크기 {bf.size}, 해시 개수 {bf.k}")
print(f"might_contain('tx-5') = {bf.might_contain('tx-5')} (실제 포함 원소 -> 항상 True)")
print(f"거짓 양성 {false_positives} / {checked}회 (비율 {false_positives / checked:.3%})")

Exercise

Implement a Bloom filter and, by varying the bit count m and the number of hash functions k, compare the measured false-positive rate against the theoretical value.

Practical Connection

Bloom-style filters are used in P2P gossip to avoid re-propagating transaction or block hashes that have already been seen, and Verex could apply the same idea as a cheap first-pass filter for event logs or order IDs it has already processed.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-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

Algorithms · Day 6 / 100 · A. 고급 알고리즘·자료구조 (Day 1–19)

Bloom·Cuckoo·Count-Min·HyperLogLog

개념

확률적 자료구조는 정확한 답 대신 제한된 오차를 허용하는 대가로 메모리를 크게 줄이는 구조다. Bloom filter는 비트 배열과 k개의 해시 함수를 써서 원소 포함 여부를 판정하며, 거짓 양성은 있지만 거짓 음성은 없고 원소 삭제도 되지 않는다. Cuckoo filter는 원소 대신 짧은 지문(fingerprint)을 두 후보 버킷 중 하나에 넣는 방식이라 삭제를 지원한다. Count-Min sketch는 여러 해시 행의 카운터 배열로 빈도를 추정하며, 충돌 때문에 과대추정은 하지만 과소추정은 하지 않는다. HyperLogLog는 해시값의 선행 0 개수 최대치를 여러 버킷에 나눠 기록해 서로 다른 원소의 개수(카디널리티)를 거의 상수 메모리로 추정한다.

로그·멤풀·캐시처럼 원소가 수억 개인 곳에서 정확한 집합이나 카운터를 그대로 들고 있으면 메모리가 먼저 터진다.

코드 · 수식

# Day 6: 확률적 자료구조 — Bloom Filter로 집합 포함 여부를 확률적으로 판정
# 비트 배열 + k개의 해시로 원소를 삽입하고, 거짓 양성(false positive)이 실제로 발생함을 확인한다.

import hashlib

class BloomFilter:
    def __init__(self, size, k):
        self.size = size
        self.k = k
        self.bits = [0] * size

    def _hashes(self, item):
        for i in range(self.k):
            h = hashlib.sha256(f"{i}:{item}".encode()).digest()
            yield int.from_bytes(h, "big") % self.size

    def add(self, item):
        for idx in self._hashes(item):
            self.bits[idx] = 1

    def might_contain(self, item):
        return all(self.bits[idx] for idx in self._hashes(item))

bf = BloomFilter(size=64, k=3)
inserted = [f"tx-{i}" for i in range(20)]
for item in inserted:
    bf.add(item)

checked, false_positives = 2000, 0
for i in range(checked):
    probe = f"probe-{i}"
    if probe not in inserted and bf.might_contain(probe):
        false_positives += 1

print(f"삽입 원소 {len(inserted)}개, 비트 배열 크기 {bf.size}, 해시 개수 {bf.k}")
print(f"might_contain('tx-5') = {bf.might_contain('tx-5')} (실제 포함 원소 -> 항상 True)")
print(f"거짓 양성 {false_positives} / {checked}회 (비율 {false_positives / checked:.3%})")

연습

Bloom filter를 직접 구현해 비트 수 m과 해시 개수 k를 바꿔가며 실측 거짓 양성률을 이론값과 비교하라.

실무 · Verex 연결

P2P 가십에서 이미 본 트랜잭션·블록 해시를 중복 전파하지 않도록 거르는 데 Bloom류 필터가 쓰이며, Verex에서도 이미 처리한 이벤트 로그나 주문 ID를 저렴하게 걸러내는 1차 필터로 응용할 수 있다.

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

← 5. Verkle tree7. 스트리밍/스케치 알고리즘 →