Bloom filter: cơ chế bit, false positive và ứng dụng
Hiểu bộ lọc Bloom qua bài toán danh bạ, cơ chế mảng bit và k hàm băm, công thức dương tính giả và các biến thể.
Trung bìnhCấu trúc dữ liệu ~60 phút
Ôn lại trước khi học
Giới thiệu
Bài này học bộ lọc siêu nhẹ kiểm tra tồn tại. Bạn chặn nhanh cái chắc chắn không có.
Kiến thức cần nhớ
Bài toán danh bạ
- Mỗi lần gửi mail cần hỏi có trong danh bạ không.
- Gọi kho cũ mỗi lần thì quá chậm.
- Tải cả danh bạ về máy rồi hỏi cục bộ.
- Ví dụ đời thường: danh sách đen dán ở cửa.
Mảng bit và k hàm băm
- Bắt đầu mảng toàn số 0 dài m bit.
- Lưu 1 món thì băm k lần, bật k bit lên 1.
- Hỏi 1 món thì đọc k bit đó.
- Ví dụ đời thường: đóng k dấu mực lên thẻ.
- Thiếu 1 bit 1 là chắc chắn không có.
Sai được 1 chiều
- Báo không thì luôn đúng, tin được.
- Báo có thì có thể nhầm vì bit dùng chung.
- Nhầm này gọi là dương tính giả.
- Ví dụ đời thường: thấy dấu mực nhưng nhầm chủ.
- Quy tắc: báo không thì thôi, báo có thì kiểm lại.
| Hỏi món | Kết luận |
|---|---|
| Thiếu bit 1 | Chắc chắn không có |
| Đủ k bit 1 | Có thể có, cần kiểm lại |
| Kho rỗng | Luôn báo không |
| Kho quá đầy | Hay báo có nhầm |
Các biến thể của Bloom filter
| Biến thể | Thêm gì | Dùng khi |
|---|---|---|
| Counting Bloom | Bộ đếm nhỏ thay cho bit | Cần xóa phần tử |
| Scalable Bloom | Chuỗi nhiều bộ lọc, bộ sau lớn hơn | Không biết trước số phần tử |
| Layered Bloom | Nhiều tầng bộ lọc | Đếm số lần một khóa đã xuất hiện |
| Compressed Bloom | Nén mảng bit khi gửi qua mạng | Bộ lọc phải truyền đi nhiều |
| Bloomier filter | Lưu thêm giá trị kèm khóa | Cần ánh xạ khóa sang giá trị nhỏ |
- Ghép hai bộ lọc cùng
m, cùngkhàm băm: phép OR cho bộ lọc của hợp hai tập.
Ví dụ
- Input: lưu số 2 bật 2 bit, hỏi số 3 lạ mà trùng 2 bit. Output: báo có nhầm.
- Giải thích: bit không ghi tên ai nên bị trùng.
bits = [0] * 8 # mảng bit ban đầu
def add(h1, h2): # lưu: bật 2 bit
bits[h1] = 1
bits[h2] = 1
def ask(h1, h2): # hỏi: đủ 2 bit 1 mới báo có
return bits[h1] == 1 and bits[h2] == 1
💻 Bài tập
Ngôn ngữ: C++. Nộp vào day-030/.
Bài 1: Bloom filter
class BloomFilter {
public:
BloomFilter(size_t m, int k); // m bit, k hàm băm
void add(const std::string &s);
bool mightContain(const std::string &s) const;
double fillRatio() const; // tỉ lệ bit đang là 1
private:
std::vector<uint64_t> bits; // tự đóng gói bit, không dùng std::vector<bool>
};
Hàm băm: double hashing. Tính h1 = FNV-1a(s), h2 = murmur-style mix(h1) | 1. Hàm thứ i là (h1 + i * h2) % m.
m=1000, k=3; add "a@x.com", "b@y.com"
mightContain("a@x.com") -> true (luôn luôn)
bộ lọc rỗng -> mọi truy vấn trả false
Bài 2: Đo false positive
Với n = 10000 email đã thêm, thử 100.000 email chưa thêm. In tỉ lệ dương tính giả thực tế và so với công thức:
m k thực_tế lý_thuyết
50000 1 ... 0.181
50000 3 ... ...
100000 7 ... 0.008
Viết hàm optimalParams(n, p) trả m = -n ln p / (ln 2)^2 và k = (m/n) ln 2. Kiểm tra với n = 10^6, p = 0.01 ra m khoảng 9,6 triệu bit, k = 7.
Bài 3: Lọc trước khi hỏi kho chậm (nâng cao)
Giả lập kho dữ liệu chậm: SlowDB::exists(email) có sleep 1 ms.
- Hàm
checkEmail(email): hỏi Bloom trước. Bloom nói "không" thì trảfalsengay. Nói "có" thì hỏiSlowDB. - Chạy 10.000 truy vấn, 90% là email không có. In số lần gọi
SlowDBvà tổng thời gian, có và không có Bloom. - Viết
CountingBloomdùng bộ đếm 4 bit thay cho 1 bit, hỗ trợremove. Test: thêm rồi xóa thìmightContaintrảfalse(nếu không đụng phần tử khác).
✅ Tiêu chí hoàn thành
- Không bao giờ có âm tính giả (test 10.000 phần tử đã thêm).
- Bảng Bài 2 có số thật lệch lý thuyết không quá vài phần trăm.
- Bài 3 cho thấy số lần gọi kho chậm giảm rõ rệt.
- Giải thích được vì sao Bloom filter thường không xóa được phần tử.
❓ Câu hỏi ôn tập
- Bloom filter dùng để hỏi gì?
- Lưu và hỏi làm mấy bước băm?
- Báo không và báo có bên nào chắc chắn?
- Muốn chính xác hơn thì chỉnh gì?
🌕 Bạn đã hoàn thành ngày 30. Làm hết bài tập phía trên rồi sang ngày tiếp theo.