Posted in

Tìm kiếm phần tử trong Dictionary với Python

Dictionary (dict) là cấu trúc dữ liệu cực kỳ phổ biến trong Python vì khả năng ánh xạ nhanh giữa key → value. Trong thực tế mình thường dùng dict để lưu config, hồ sơ người dùng, cache tạm,… và bài toán tìm kiếm xuất hiện ở mọi nơi: kiểm tra tồn tại key, lấy value an toàn, lọc theo điều kiện, hoặc tìm trong cấu trúc lồng nhau.

Bài viết này mình cùng bạn sẽ tìm hiểu chi tiết các phương pháp tìm kiếm trong dictionary, từ cơ bản đến nâng cao, thông qua những ví dụ minh họa và giải thích rõ ràng. Hy vọng rằng sau khi đọc xong, chúng ta sẽ nắm vững cách xử lý các tình huống tìm kiếm trong thực tế.

1. Cách dict hoạt động và ý nghĩa khi tìm kiếm

Trước khi đi vào kỹ thuật, cần nhớ vài điểm nền tảng:

  • dict dùng hash table: lookup theo key trung bình là O(1) (average-case).
  • Key phải hashable (immutable types: str, int, tuple nếu tuple các phần tử hashable). Mutable types như list không thể làm key.
  • Tìm value (không phải key) thường phải duyệt toàn bộ dictO(n).

Hiểu rõ điều này giúp lựa chọn đúng phương pháp: nếu bạn search theo key — dùng lookup trực tiếp; nếu bạn cần lọc theo value nhiều lần — cân nhắc indexing ngoài (ví dụ inverted index).

2. Tìm theo key: in, [], get()KeyError

Các thao tác phổ biến:

  • key in my_dict — trả True/False. Rất nhanh (O(1) average).
  • my_dict[key] — trả value hoặc KeyError nếu không tồn tại.
  • my_dict.get(key, default) — trả value hoặc default (không raise lỗi).

Ví dụ:

# Example dict
person = {"name": "Alice", "age": 30, "email": "alice@example.com"}

# 1) Check existence
if "name" in person:
    print("Key exists")

# 2) Direct index (may raise KeyError)
try:
    value = person["address"]  # may raise KeyError
except KeyError:
    print("Key 'address' not found")

# 3) Safe get
address = person.get("address", "Unknown")
print(address)  # prints "Unknown"

Gợi ý best-practice: nếu bạn chỉ cần kiểm tra tồn tạiin. Nếu bạn muốn lấy value và chấp nhận fallback → get().

3. Tìm value: values(), in, next() và comprehension

Khi cần kiểm tra hoặc tìm theo value:

  • value in my_dict.values() — đơn giản, O(n).
  • next((k for k, v in my_dict.items() if v == target), None) — trả key đầu tiên khớp.
  • Comprehension cho nhiều kết quả: [k for k, v in my_dict.items() if predicate(v)].

Ví dụ:

# Check if a value exists
if "alice@example.com" in person.values():
    print("Email present")

# Find the first key with given value
found_key = next((k for k, v in person.items() if v == "Alice"), None)
print(found_key)  # "name" or None

# Find all keys with numeric values
numeric_keys = [k for k, v in person.items() if isinstance(v, (int, float))]
print(numeric_keys)  # ['age']

Lưu ý: tìm value luôn O(n); nếu bạn cần tìm theo value rất thường xuyên, hãy xây dựng inverted index (map value → list of keys) để tra cứu O(1).

4. Tìm với nhiều điều kiện và filter nâng cao

Thực tế thường cần lọc theo nhiều điều kiện (ví dụ: người dùng có age > 25city == "Hanoi"). Dùng comprehension hoặc filter kết hợp predicate:

users = {
    "u1": {"name": "A", "age": 28, "city": "Hanoi"},
    "u2": {"name": "B", "age": 22, "city": "Hanoi"},
    "u3": {"name": "C", "age": 30, "city": "Saigon"},
}

# Find user ids with age > 25 and city Hanoi
result = [
    user_id
    for user_id, info in users.items()
    if info.get("age", 0) > 25 and info.get("city") == "Hanoi"
]
print(result)  # ['u1']

Đây là pattern phổ biến trong filter data trên dict lưu object/simple dict làm value.

5. Tìm trong dict lồng nhau — recursive search và trả về đường dẫn (path)

Khi cấu trúc lồng nhau (config, JSON), thường muốn tìm tất cả vị trí chứa key hoặc value. Mình hay dùng hàm đệ quy trả về danh sách đường dẫn (list of keys path).

Ví dụ hàm tìm key bất kỳ:

def find_key_paths(d, target_key, current_path=None):
    """Recursively search for target_key in nested dicts.
    Returns list of paths (each path is a list of keys).
    """
    if current_path is None:
        current_path = []
    paths = []
    if isinstance(d, dict):
        for k, v in d.items():
            new_path = current_path + [k]
            if k == target_key:
                paths.append(new_path)
            if isinstance(v, dict):
                paths.extend(find_key_paths(v, target_key, new_path))
            elif isinstance(v, list):
                # search inside dicts in lists
                for idx, item in enumerate(v):
                    if isinstance(item, dict):
                        paths.extend(find_key_paths(item, target_key, new_path + [idx]))
    return paths

# Example usage
config = {
    "db": {"host": "localhost", "port": 5432},
    "services": {"auth": {"host": "auth.local", "port": 8000}},
}
print(find_key_paths(config, "host"))
# e.g. [['db', 'host'], ['services', 'auth', 'host']]

Hàm trên trả đường dẫn để bạn có thể cập nhật/ghi nhận vị trí chính xác.

6. Viết hàm tìm kiếm tổng quát (robust search API)

Mình thường đóng gói thành hàm tiện dụng có option: match_key, match_value, predicate, first_only, return_path.

def search_dict(dictionary, *, match_key=None, match_value=None, predicate=None, first_only=False, return_path=False):
    """
    Search dictionary for entries that match match_key/match_value or pass predicate.
    - match_key: exact key match
    - match_value: exact value match
    - predicate: callable(k, v) -> bool
    - first_only: stop at first match
    - return_path: if True, returns (path, key, value) where path is list of keys
    """
    results = []
    def _walk(d, path):
        if isinstance(d, dict):
            for k, v in d.items():
                full_path = path + [k]
                ok = False
                if match_key is not None and k == match_key:
                    ok = True
                if match_value is not None and v == match_value:
                    ok = True
                if predicate is not None and predicate(k, v):
                    ok = True
                if ok:
                    if return_path:
                        results.append((full_path, k, v))
                    else:
                        results.append((k, v))
                    if first_only:
                        return True
                if isinstance(v, dict):
                    if _walk(v, full_path):
                        return True
                elif isinstance(v, list):
                    for idx, item in enumerate(v):
                        if isinstance(item, dict):
                            if _walk(item, full_path + [idx]):
                                return True
        return False
    _walk(dictionary, [])
    return results

# Example: find first key 'host'
print(search_dict(config, match_key="host", first_only=True, return_path=True))

Hàm này dùng đệ quy, hỗ trợ nhiều tuỳ chọn, phù hợp khi cần reuse trên nhiều project.

7. Hiệu năng, độ phức tạp và best practices

  • Key lookup: O(1) average. Value lookup / iteration: O(n).
  • Hash collisions: trong trường hợp cực hiếm (vì hash collision), worst-case có thể thoát O(n), nhưng Python dicts tối ưu tốt.
  • Tránh dùng mutable types làm key. Nếu cần composite key, dùng tuple hoặc frozenset (tuỳ mục đích).
  • Không sửa dict khi đang iterate (sẽ raise RuntimeError: dictionary changed size during iteration). Nếu cần xóa, thu thập keys cần xóa rồi lặp qua bản sao: for k in list(d.keys()): ...
  • Nếu search theo value rất nhiều, tạo inverted index: value_to_keys = defaultdict(list) rồi populate. Tradeoff: tăng memory để giảm time.
  • Normalization: khi so sánh string (case-insensitive), chuẩn hóa (.lower().strip()) trước khi so sánh.

8. Những lỗi thường gặp & cách debug nhanh

  • KeyError: do dùng dict[key] cho key không tồn tại → dùng get() hoặc try/except.
  • So sánh sai kiểu: 1 (int) khác "1" (str). Luôn kiểm tra kiểu khi so sánh.
  • Mutable key: TypeError nếu cố dùng list làm key.
  • Sửa dict khi iterateRuntimeError.
  • Không hiểu values() là viewmy_dict.values() trả một view object (không phải list). Nếu muốn snapshot, dùng list(my_dict.values()).

Debug tips: dùng print(type(value)), assert nhỏ để kiểm tra invariant, và viết test unit cho hàm tìm kiếm.

9. Kết luận

Qua bài viết này, mình đã cùng bạn đi từ những cách cơ bản như kiểm tra sự tồn tại của key với in, truy xuất an toàn bằng get(), cho đến những kỹ thuật nâng cao hơn như lọc theo điều kiện, tìm trong dictionary lồng nhau bằng đệ quy hay thậm chí xây dựng hàm tìm kiếm tổng quát.

Điều quan trọng nhất cần nhớ là:

  • Tra cứu theo key trong dictionary rất nhanh và an toàn nếu bạn dùng đúng công cụ.
  • Tìm theo value thì tốn chi phí hơn, vì vậy hãy cân nhắc đến việc tạo index khi dữ liệu lớn.
  • Với những nhu cầu đặc thù, bạn hoàn toàn có thể tự viết hàm tìm kiếm tùy chỉnh để tiết kiệm công sức trong các lần sử dụng sau.

Hy vọng sau khi đọc xong, bạn không chỉ biết cách “tìm cái mình muốn” trong dictionary, mà còn có tư duy chọn đúng phương pháp trong từng tình huống. Với mình, đây chính là sự khác biệt giữa viết code cho chạy được và viết code để dễ bảo trì, hiệu quả về lâu dài.

10. Tài liệu tham khảo (định dạng APA)

  1. Python Software Foundation. (2024). Built-in Types — dict. Python Documentation. Retrieved from https://docs.python.org/3/library/stdtypes.html#dict
  2. Ramalho, L. (2015). Fluent Python: Clear, Concise, and Effective Programming. O’Reilly Media.
  3. Beazley, D., & Jones, B. K. (2013). Python Cookbook (3rd ed.). O’Reilly Media.
  4. Real Python. (n.d.). Dictionaries in Python. Real Python. Retrieved from https://realpython.com/python-dicts/

Leave a Reply

Your email address will not be published. Required fields are marked *