220 lines
6.6 KiB
Python
220 lines
6.6 KiB
Python
"""
|
|
TEST & GIẢI THÍCH: Module Trie
|
|
================================
|
|
|
|
File này test và giải thích chi tiết cách Trie hoạt động
|
|
"""
|
|
|
|
import sys
|
|
sys.path.append('src')
|
|
|
|
from search.trie import Trie, TrieNode
|
|
|
|
|
|
def explain_trie():
|
|
"""Giải thích chi tiết Trie"""
|
|
|
|
print("=" * 80)
|
|
print("GIẢI THÍCH: TRIE (PREFIX TREE)")
|
|
print("=" * 80)
|
|
|
|
print("""
|
|
TRIE LÀ GÌ?
|
|
-----------
|
|
- Cấu trúc dữ liệu dạng CÂY
|
|
- Mỗi NODE = 1 ký tự
|
|
- Dùng để tìm PREFIX (từ bắt đầu bằng...)
|
|
|
|
VÍ DỤ TRỰC QUAN:
|
|
----------------
|
|
Lưu 3 từ: "làm", "lau", "lên"
|
|
|
|
(root)
|
|
|
|
|
l
|
|
/|\\
|
|
/ | \\
|
|
à a ê
|
|
| | |
|
|
m u n
|
|
|
|
- Duyệt từ root → l → à → m = "làm"
|
|
- Duyệt từ root → l → a → u = "lau"
|
|
- Duyệt từ root → l → ê → n = "lên"
|
|
|
|
TẠI SAO NHANH?
|
|
--------------
|
|
- Tìm "la" chỉ cần: root → l → a → thu thập nhánh con
|
|
- Độ phức tạp: O(m) với m = độ dài query
|
|
- KHÔNG cần duyệt tất cả documents!
|
|
""")
|
|
|
|
input("\nNhấn Enter để xem demo...")
|
|
|
|
|
|
def demo_step_by_step():
|
|
"""Demo từng bước"""
|
|
|
|
print("\n" + "=" * 80)
|
|
print("DEMO TỪNG BƯỚC")
|
|
print("=" * 80)
|
|
|
|
# Tạo Trie
|
|
trie = Trie()
|
|
|
|
# Dữ liệu test
|
|
documents = [
|
|
"làm sạch nhà vệ sinh", # ID: 0
|
|
"làm sạch văn phòng", # ID: 1
|
|
"làm wc", # ID: 2
|
|
"lau sàn nhà", # ID: 3
|
|
"lau kính cửa sổ", # ID: 4
|
|
"vệ sinh toilet", # ID: 5
|
|
]
|
|
|
|
print("\n📥 BƯỚC 1: THÊM DOCUMENTS VÀO TRIE")
|
|
print("-" * 80)
|
|
print("Mỗi document được lưu theo từng ký tự trong cây\n")
|
|
|
|
for doc_id, doc in enumerate(documents):
|
|
trie.insert(doc.lower(), doc_id)
|
|
print(f"✓ Đã thêm [{doc_id}]: {doc}")
|
|
|
|
# Giải thích cụ thể cho 2 documents đầu
|
|
if doc_id == 0:
|
|
print(" → Tạo đường: root → l → à → m → → s → ạ → c → h...")
|
|
elif doc_id == 1:
|
|
print(" → Tạo đường: root → l → à → m → → s → ạ → c → h... (chia sẻ prefix với doc 0)")
|
|
elif doc_id == 2:
|
|
print(" → Tạo đường: root → l → à → m → → w → c (chia sẻ 'làm ' với doc 0,1)")
|
|
|
|
input("\nNhấn Enter để test tìm kiếm...")
|
|
|
|
print("\n" + "=" * 80)
|
|
print("🔍 BƯỚC 2: TEST TÌM KIẾM")
|
|
print("=" * 80)
|
|
|
|
# Test case 1
|
|
print("\n📌 TEST 1: Tìm 'làm'")
|
|
print("-" * 80)
|
|
query = "làm"
|
|
results = trie.search_prefix(query)
|
|
|
|
print(f"Query: '{query}'")
|
|
print(f"Cách hoạt động:")
|
|
print(f" 1. Duyệt: root → l → à → m")
|
|
print(f" 2. Đến node 'm', thu thập TẤT CẢ documents từ đây xuống")
|
|
print(f" 3. Tìm thấy {len(results)} documents:")
|
|
for doc_id in results:
|
|
print(f" [{doc_id}] {documents[doc_id]}")
|
|
|
|
input("\nNhấn Enter tiếp...")
|
|
|
|
# Test case 2
|
|
print("\n📌 TEST 2: Tìm 'làm s'")
|
|
print("-" * 80)
|
|
query = "làm s"
|
|
results = trie.search_prefix(query)
|
|
|
|
print(f"Query: '{query}'")
|
|
print(f"Cách hoạt động:")
|
|
print(f" 1. Duyệt: root → l → à → m → → s")
|
|
print(f" 2. Đến node 's', thu thập documents")
|
|
print(f" 3. KẾT QUẢ chính xác hơn (loại bỏ 'làm wc'):")
|
|
print(f" 4. Tìm thấy {len(results)} documents:")
|
|
for doc_id in results:
|
|
print(f" [{doc_id}] {documents[doc_id]}")
|
|
|
|
input("\nNhấn Enter tiếp...")
|
|
|
|
# Test case 3
|
|
print("\n📌 TEST 3: Tìm 'lau'")
|
|
print("-" * 80)
|
|
query = "lau"
|
|
results = trie.search_prefix(query)
|
|
|
|
print(f"Query: '{query}'")
|
|
print(f"Cách hoạt động:")
|
|
print(f" 1. Duyệt: root → l → a → u")
|
|
print(f" 2. Thu thập documents từ node 'u'")
|
|
print(f" 3. Tìm thấy {len(results)} documents:")
|
|
for doc_id in results:
|
|
print(f" [{doc_id}] {documents[doc_id]}")
|
|
|
|
input("\nNhấn Enter tiếp...")
|
|
|
|
# Test case 4 - KHÔNG TÌM THẤY
|
|
print("\n📌 TEST 4: Tìm 'wc' ❌")
|
|
print("-" * 80)
|
|
query = "wc"
|
|
results = trie.search_prefix(query)
|
|
|
|
print(f"Query: '{query}'")
|
|
print(f"Cách hoạt động:")
|
|
print(f" 1. Duyệt: root → w")
|
|
print(f" 2. ❌ KHÔNG tìm thấy node 'w' ở root")
|
|
print(f" 3. Return []")
|
|
print(f"")
|
|
print(f"❓ TẠI SAO KHÔNG TÌM THẤY?")
|
|
print(f" - 'wc' NẰM Ở GIỮA câu: 'làm wc'")
|
|
print(f" - Trie chỉ tìm được từ Ở ĐẦU câu")
|
|
print(f" - Trong cây, 'w' nằm sau 'làm ' chứ không ở root")
|
|
print(f"")
|
|
print(f"💡 GIẢI PHÁP: Dùng INVERTED INDEX (module tiếp theo)")
|
|
|
|
input("\nNhấn Enter để xem kết luận...")
|
|
|
|
|
|
def conclusion():
|
|
"""Kết luận"""
|
|
|
|
print("\n" + "=" * 80)
|
|
print("📝 KẾT LUẬN")
|
|
print("=" * 80)
|
|
|
|
print("""
|
|
✅ TRIE TÌM ĐƯỢC:
|
|
-----------------
|
|
✓ "làm" → tất cả bắt đầu bằng "làm"
|
|
✓ "làm s" → tất cả bắt đầu bằng "làm s" (chính xác hơn)
|
|
✓ "lau" → tất cả bắt đầu bằng "lau"
|
|
✓ "vệ" → tất cả bắt đầu bằng "vệ"
|
|
|
|
❌ TRIE KHÔNG TÌM ĐƯỢC:
|
|
-----------------------
|
|
✗ "wc" → vì "wc" nằm Ở GIỮA câu "làm wc"
|
|
✗ "sạch" → vì "sạch" nằm sau "làm " (không phải prefix)
|
|
✗ "toilet" → vì "toilet" nằm sau "vệ sinh "
|
|
|
|
📊 ĐỘ PHỨC TẠP:
|
|
---------------
|
|
- Thêm document: O(m) với m = độ dài text
|
|
- Tìm kiếm: O(m + n) với m = độ dài query, n = số kết quả
|
|
- Memory: O(tổng số ký tự của tất cả documents)
|
|
|
|
🎯 KHI NÀO DÙNG TRIE?
|
|
---------------------
|
|
✓ Autocomplete (gợi ý khi gõ)
|
|
✓ Prefix search (tìm từ bắt đầu bằng...)
|
|
✓ Spell checker
|
|
✓ IP routing
|
|
|
|
⏭️ MODULE TIẾP THEO:
|
|
---------------------
|
|
→ INVERTED INDEX
|
|
→ Tìm keyword ở BẤT KỲ ĐÂU trong text
|
|
→ Giải quyết vấn đề của Trie
|
|
""")
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
explain_trie()
|
|
demo_step_by_step()
|
|
conclusion()
|
|
|
|
print("\n✅ Hoàn thành! Bạn đã hiểu Trie chưa?")
|
|
print("📂 Code thực tế: src/search/trie.py")
|
|
print("⏭️ Tiếp theo: tests/test_2_inverted_index.py\n")
|