Hướng dẫn dùng Dict Python đếm tần suất xuất hiện của từ vựng
Hướng dẫn dùng Dict Python đếm tần suất xuất hiện của từ vựng

Bài toán thống kê tần suất từ vựng là một trong những bài tập kinh điển nhất khi bạn bắt đầu học về cấu trúc dữ liệu trong lập trình. Trong bài viết này, thầy sẽ hướng dẫn các bạn sinh viên CNTT cách sử dụng dict (Dictionary) trong Python để đếm số lần xuất hiện của từng từ trong một câu văn một cách tối ưu và chuẩn xác nhất.

💡 Trả lời nhanh: Để đếm số lần xuất hiện của từ trong câu bằng Python, bạn cần chuyển câu văn về dạng chữ thường bằng .lower(), tách các từ thành danh sách bằng .split(), sau đó dùng vòng lặp for kết hợp phương thức dict.get(word, 0) + 1 để lưu trữ từ khóa (key) và tần suất (value) vào một dictionary. Nếu muốn code ngắn gọn chuẩn “Pythonic”, hãy sử dụng collections.Counter(text.split()).


Đề bài 

Viết một chương trình Python nhận đầu vào là một chuỗi ký tự (một câu văn) và trả về số lần xuất hiện của mỗi từ trong câu đó.

Input: Một chuỗi văn bản (String). Ví dụ: "Học Python để code Python".

Output: Một Dictionary chứa các từ khóa và số lần xuất hiện. Ví dụ: {'học': 1, 'python': 2, 'để': 1, 'code': 1}.

Ràng buộc: – Chương trình không phân biệt chữ hoa, chữ thường (ví dụ “Python” và “python” được tính chung là 1 từ).

  • Các khoảng trắng thừa giữa các từ phải được loại bỏ để không sinh ra các từ rỗng.


Phân tích 

Bài toán đếm số lần xuất hiện của từ trong Python yêu cầu phân tách câu thành một danh sách các từ đơn lẻ, sau đó sử dụng cấu trúc dữ liệu Dictionary (dict) để ánh xạ mỗi từ (đóng vai trò là key) với tần suất xuất hiện tương ứng của nó (đóng vai trò là value).

Tại sao chúng ta lại dùng dict mà không phải là List hay Tuple? Bản chất của bài toán thống kê là tạo ra một bản đồ ánh xạ (mapping). Cấu trúc dữ liệu Dictionary trong Python được xây dựng dựa trên cơ chế Bảng băm (Hash Table).

Điều này cho phép thời gian tìm kiếm, thêm mới và cập nhật một từ khóa diễn ra cực kỳ nhanh chóng với độ phức tạp trung bình là O(1). Nếu bạn dùng List, bạn sẽ phải duyệt qua toàn bộ List mỗi lần muốn kiểm tra xem một từ đã tồn tại hay chưa, khiến độ phức tạp tăng lên O(N), điều này rất kém hiệu quả khi xử lý các đoạn văn bản dài.

📌 Góc nhìn thực tế: Trong thực tế giảng dạy, thầy nhận thấy sinh viên năm 1-2 thường hay quên mất việc chuẩn hóa dữ liệu đầu vào. Các bạn hay vội vàng nhảy ngay vào viết vòng lặp đếm từ mà quên mất rằng máy tính hiểu “Python” và “python” là hai chuỗi hoàn toàn khác nhau về mã ASCII. Do đó, bước tiền xử lý chuỗi (chuyển về in thường và cắt khoảng trắng) là quan trọng nhất.

Giả định

Trong phạm vi bài viết ở mức độ cơ bản này, chúng ta sẽ giả định chuỗi đầu vào chỉ chứa các ký tự chữ cái và khoảng trắng (không chứa các dấu câu phức tạp như dấu phẩy, chấm, hỏi chấm dính liền vào từ). Nếu có dấu câu, chúng ta có thể cần thêm module re (Regular Expression) hoặc phương thức .replace() để làm sạch chuỗi trước khi đếm.

Từ những phân tích trên, thầy sẽ giới thiệu cho các bạn 2 hướng tiếp cận: một hướng thuần tư duy thuật toán nền tảng (Cách 1) và một hướng sử dụng công cụ mạnh mẽ có sẵn của Python (Cách 2).


Cách giải 1: Đếm Tần Suất Dùng Vòng Lặp For Và dict.get() 

Sử dụng vòng lặp duyệt qua từng từ và dùng phương thức dict.get() là cách tiếp cận an toàn, rõ ràng và giúp người học hiểu sâu sắc nhất về cách Dictionary hoạt động dưới nền tảng.

Ý tưởng cốt lõi

Đầu tiên, chúng ta biến đổi toàn bộ câu văn thành chữ thường để đảm bảo tính nhất quán. Sau đó, ta “chặt” câu văn đó ra thành từng khúc (từng từ) dựa trên các khoảng cách (space) để tạo thành một List. Tiếp theo, ta tạo một dict rỗng. Bắt đầu vòng lặp duyệt qua từng từ trong List: nếu từ đó chưa có trong dict, ta khởi tạo giá trị cho nó là 1; nếu từ đó đã có mặt trong dict, ta lấy giá trị hiện tại của nó và cộng thêm 1.

Các bước thực hiện

  1. Dùng .lower() để chuyển toàn bộ chuỗi đầu vào thành chữ in thường.

  2. Dùng .split() để tách chuỗi thành một danh sách (List) các từ. Mặc định .split() sẽ tự động xử lý và bỏ qua các khoảng trắng thừa.

  3. Khởi tạo một dictionary rỗng tên là word_count.

  4. Mở vòng lặp for duyệt qua từng word trong danh sách vừa tách.

  5. Sử dụng hàm word_count.get(word, 0) để lấy số đếm hiện tại. Nếu word chưa tồn tại trong dict, hàm này an toàn trả về số 0.

  6. Cộng thêm 1 vào giá trị vừa lấy được và gán ngược lại cho key word trong dict word_count.

  7. Trả về dict word_count sau khi vòng lặp kết thúc.

Minh họa tay

Hãy cùng thầy chạy bằng tay thuật toán trên với câu input: "Python code python"

  • Bước 1 & 2: Chữ thường và tách -> danh_sach_tu = ['python', 'code', 'python']

  • Bước 3: Khởi tạo word_count = {}

  • Bước 4 (Vòng lặp lần 1): Lấy từ 'python'

    • word_count.get('python', 0) trả về 0 (vì dict đang rỗng).

    • Cập nhật: word_count['python'] = 0 + 1 = 1.

    • Lúc này: word_count = {'python': 1}

  • Bước 4 (Vòng lặp lần 2): Lấy từ 'code'

    • word_count.get('code', 0) trả về 0.

    • Cập nhật: word_count['code'] = 0 + 1 = 1.

    • Lúc này: word_count = {'python': 1, 'code': 1}

  • Bước 4 (Vòng lặp lần 3): Lấy từ 'python'

    • word_count.get('python', 0) lúc này sẽ tìm thấy key ‘python’ và trả về giá trị hiện hành là 1.

    • Cập nhật: word_count['python'] = 1 + 1 = 2.

    • Kết thúc lặp: word_count = {'python': 2, 'code': 1}

Đánh giá

  • Phù hợp người mới vì: Thuật toán rèn luyện tư duy thao tác với mảng (List) và từ điển (Dictionary) rất bài bản, không giấu logic đi bằng các hàm thư viện.

  • Ưu điểm: Dễ hiểu, dễ debug, kiểm soát hoàn toàn được quá trình đếm. Bạn có thể chèn thêm các điều kiện (if/else) vào trong vòng lặp nếu muốn bỏ qua một số từ cụ thể (ví dụ: stop words).

  • Nhược điểm: Phải viết nhiều dòng code hơn so với việc dùng thư viện có sẵn.

  • Độ phức tạp: O(N) thời gian / O(K) bộ nhớ (Với N là tổng số từ trong câu, K là số lượng từ vựng duy nhất).


Cách giải 2: Tối Ưu Bằng collections.Counter (Chuẩn Pythonic) 

Khác với Cách 1 phải tự xây dựng vòng lặp, cách này tận dụng lớp Counter thuộc module collections được thiết kế riêng biệt và tối ưu hóa ở mức ngôn ngữ C cho bài toán đếm phần tử trong Python.

Ý tưởng cốt lõi

Lớp Counter bản chất là một lớp con (subclass) của Dictionary. Nó nhận đầu vào là một cấu trúc dữ liệu có thể lặp (iterable) như List, Tuple, hoặc String, và tự động thực hiện toàn bộ quá trình nhóm các phần tử giống nhau lại và đếm số lượng của chúng. Công việc của chúng ta chỉ là tiền xử lý chuỗi và ném mảng từ vựng đó cho Counter xử lý.

Các bước thực hiện

  1. Import module Counter từ thư viện collections.

  2. Chuẩn hóa chuỗi bằng .lower().split() giống như Cách 1 để có được danh sách các từ.

  3. Truyền trực tiếp danh sách các từ đó vào hàm khởi tạo Counter().

  4. Kết quả trả về là một đối tượng Counter (hoạt động hệt như một dictionary), ép kiểu về dict nếu đề bài bắt buộc output phải là kiểu dict thuần, hoặc giữ nguyên trả về.

Minh họa tay

Vẫn với input: "Python code python"

  • Chuẩn hóa: "Python code python".lower().split() trả về list ['python', 'code', 'python'].

  • Gọi hàm: Counter(['python', 'code', 'python']).

  • Bên dưới lớp vỏ, Python tự động gom các phần tử giống nhau và đếm. Kết quả trả về ngay lập tức là đối tượng: Counter({'python': 2, 'code': 1}).

  • Ép kiểu về dict thuần túy: dict(Counter(...)) -> {'python': 2, 'code': 1}.

Khi nào nên dùng Cách 2?

Cách giải này nên được ưu tiên sử dụng khi bạn làm các bài test tuyển dụng trên HackerRank, LeetCode, hoặc khi xây dựng các ứng dụng thực tế (production code). Khi khối lượng văn bản lớn (ví dụ cả một cuốn sách), Counter sẽ cho tốc độ thực thi nhanh hơn đáng kể so với vòng lặp for thông thường trong Python.

Đánh giá

  • Ưu điểm: Code cực kỳ ngắn gọn (chỉ mất 1 dòng cốt lõi), dễ đọc, tốc độ thực thi được tối ưu hóa ở tầng C thấp hơn. Ngoài ra Counter còn hỗ trợ các hàm cực hay như .most_common() để lấy ra top các từ xuất hiện nhiều nhất.

  • Nhược điểm: Sinh viên mới học có thể bị “ảo tưởng sức mạnh”, lạm dụng thư viện mà không thực sự hiểu bản chất thuật toán đếm hoạt động ra sao trong bộ nhớ.

  • Độ phức tạp: O(N) thời gian / O(K) bộ nhớ.


So sánh nhanh 2 cách 

Bảng này giúp bạn quyết định nên dùng cách nào mà không cần đọc lại toàn bài. Nhìn chung cả hai đều có cùng độ phức tạp thuật toán, nhưng cách cài đặt lại mang mục đích khác nhau.

Tiêu chí Cách 1: Vòng Lặp For Và dict.get() Cách 2: Tối Ưu Bằng collections.Counter
Ý tưởng cốt lõi Duyệt tay từng từ, kiểm tra tồn tại và cộng dồn giá trị trong dict. Uỷ thác toàn bộ mảng từ vựng cho class Counter chuyên dụng xử lý.
Độ phức tạp O(N) O(N)
Dễ hiểu logic nền tảng ★★★★★ ★★★☆☆
Tốc độ viết code (Ngắn gọn) ★★★☆☆ ★★★★★
Phù hợp khi Mới học Python, muốn nắm vững cơ chế vòng lặp và Dictionary. Đi làm thực tế, giải thuật cạnh tranh, cần tối ưu thời gian gõ code.
Không phù hợp khi Cần xử lý các đoạn văn bản khổng lồ tốn nhiều tài nguyên CPU. Bị cấm sử dụng các module import bên ngoài trong bài thi trên lớp.

Code Python đầy đủ 

Cách 1 — Vòng Lặp For Và dict.get():

import unicodedata
def solve(text):
    """
    Hàm nhận vào một chuỗi câu, chuẩn hóa chữ thường,
    tách từ và dùng vòng lặp for đếm tần suất vào dict.
    """
    # Xử lý an toàn: Nếu đầu vào không phải chuỗi hoặc rỗng
    if not isinstance(text, str) or not text.strip():
        return {}
        
    # Chuẩn hóa Unicode tiếng Việt (NFC) và chuyển in thường
    normalized_text = unicodedata.normalize('NFC', text).lower()
    
    # Tách chuỗi thành danh sách các từ
    words_list = normalized_text.split()
    
    # Khởi tạo dict chứa kết quả
    word_count = {}
    
    # Duyệt và đếm
    for word in words_list:
        # get(word, 0) lấy giá trị hiện hành, nếu chưa có thì là 0
        word_count[word] = word_count.get(word, 0) + 1
        
    return word_count
# --- TEST NHANH KẾT QUẢ ---
# assert solve("Python code python") == {'python': 2, 'code': 1}
# assert solve("   ") == {}
# --- Nhập liệu từ người dùng ---
# Giả định người dùng nhập đúng chuỗi văn bản thông thường
if __name__ == "__main__":
    cau_van = input("Nhập câu văn của bạn: ")
    ket_qua = solve(cau_van)
    print("Tần suất xuất hiện của các từ là:")
    print(ket_qua)

Cách 2 — Tối Ưu Bằng collections.Counter:

import unicodedata
from collections import Counter
def solve_v2(text):
    """
    Sử dụng collections.Counter để đếm từ vựng nhanh gọn nhất.
    Trả về cấu trúc chuẩn dict thuần túy.
    """
    if not isinstance(text, str) or not text.strip():
        return {}
        
    # Chuẩn hóa văn bản tương tự cách 1
    normalized_text = unicodedata.normalize('NFC', text).lower()
    
    # Counter tự động nhận list từ việc split() và đếm luôn
    counted_data = Counter(normalized_text.split())
    
    # Ép kiểu đối tượng Counter về lại dict cơ bản
    return dict(counted_data)
# --- TEST NHANH KẾT QUẢ ---
# assert solve_v2("Học Python để code Python") == {'học': 1, 'python': 2, 'để': 1, 'code': 1}
if __name__ == "__main__":
    cau_van = input("Nhập câu văn của bạn: ")
    print("Kết quả đếm:", solve_v2(cau_van))

Ví dụ chạy thử 

STT Input (Câu văn) Output (Dictionary trả về) Giải thích nguyên nhân
1 "Học Python để code Python" {'học': 1, 'python': 2, 'để': 1, 'code': 1} Hoạt động bình thường. Từ ‘Python’ ở cuối câu và giữa câu được gom chung vì đã chuyển về chữ thường.
2 "Lỗi khoảng trắng" {'lỗi': 1, 'khoảng': 1, 'trắng': 1} Hàm .split() mặc định bỏ qua toàn bộ các khoảng trắng liền kề nhau, không sinh ra các từ rỗng ''.
3 " " (Chỉ toàn dấu cách) {} Do điều kiện kiểm tra text.strip() ở đầu hàm, chuỗi rỗng hoặc toàn khoảng trắng sẽ trả về dict rỗng lập tức.

Lỗi thường gặp khi làm bài 

Mỗi lỗi dưới đây tự đứng được — bạn có thể đọc thẳng mô tả lỗi mình gặp mà không cần đọc lại từ đầu bài viết để tìm cách khắc phục nhanh chóng.

Lỗi 1: Chương trình văng lỗi KeyError khi đếm

Khi sử dụng vòng lặp for để cập nhật dict, bạn nhận được một thông báo đỏ chót KeyError: 'python' trên màn hình console và chương trình bị treo hoàn toàn.

Nguyên nhân: Lỗi này xảy ra do bạn đang cố gắng truy cập hoặc tăng giá trị của một khóa (key) chưa từng tồn tại trong dictionary bằng cú pháp truy cập trực tiếp []. Trong Python, dict không tự động tạo key mới nếu bạn thực hiện phép tính cộng dồn trên một key rỗng. Bạn phải thiết lập giá trị ban đầu cho nó trước.

Code sai:

word_count = {}
for word in words:
    # Lỗi KeyError xảy ra ngay tại dòng này ở vòng lặp đầu tiên
    word_count[word] += 1 

Code đúng:

word_count = {}
for word in words:
    # Dùng get() cấp sẵn giá trị 0 nếu từ chưa xuất hiện
    word_count[word] = word_count.get(word, 0) + 1

Lỗi 2: Phân biệt sai chữ hoa, chữ thường (Case-sensitive mismatch)

Output sinh ra chứa hai từ giống hệt nhau nhưng bị tách làm hai key riêng biệt, ví dụ: {'Python': 1, 'python': 1} thay vì gộp chung lại thành giá trị 2.

Nguyên nhân: Máy tính rất máy móc, mã ASCII của chữ P viết hoa hoàn toàn khác với chữ p viết thường. Nếu trước khi tách chuỗi thành mảng, bạn không tiến hành chuẩn hóa văn bản về cùng một định dạng (thường là in thường toàn bộ), Python sẽ hiểu đây là 2 từ vựng độc lập và lưu vào 2 ô nhớ dict khác nhau.

Code sai:

# Quên biến đổi text thành chữ thường
words = text.split() 
counted = Counter(words)

Code đúng:

# Gọi hạ in thường .lower() trước khi split
words = text.lower().split()
counted = Counter(words)

Lỗi 3: Xuất hiện các chuỗi rỗng rác trong dict kết quả

Khi in kết quả ra màn hình, bạn thấy xuất hiện các key rất lạ như: {'': 3, 'hello': 1}. Từ điển của bạn vô tình đếm cả những khoảng trắng.

Nguyên nhân: Vấn đề nằm ở cách bạn gọi hàm tách chuỗi .split(). Nếu bạn truyền một dấu cách cụ thể vào bên trong hàm, ví dụ .split(" "), Python sẽ chia cắt chuỗi một cách cứng nhắc. Nếu giữa hai từ có nhiều hơn 1 dấu cách, các dấu cách thừa sẽ bị biến thành chuỗi rỗng ''. Để khắc phục, hãy dùng .split() không có tham số.

Code sai:

# Cắt cứng nhắc bằng ký tự khoảng trắng
text = "hello   world"
words = text.split(" ") # Sinh ra ['hello', '', '', 'world']

Code đúng:

text = "hello   world"
# Hàm split() trống tự động gom toàn bộ khoảng trắng thừa
words = text.split() # Sinh ra ['hello', 'world']

Lỗi 4: Thay đổi kích thước Dictionary trong lúc đang lặp qua nó

Trong trường hợp nâng cao, sau khi đếm xong, bạn dùng vòng lặp duyệt qua chính cái dict đó để xóa đi các từ xuất hiện 1 lần, nhưng nhận được lỗi RuntimeError: dictionary changed size during iteration.

Nguyên nhân: Python cấm việc bạn vừa duyệt qua các key của một dictionary lại vừa tiến hành lệnh del dict[key]. Hành động này làm hỏng cấu trúc lặp nội bộ (iterator) của dictionary đang diễn ra trong bộ nhớ.

Code sai:

for word in word_count.keys():
    if word_count[word] == 1:
        del word_count[word] # Gây RuntimeError lập tức

Code đúng:

# Ép danh sách key thành dạng List tĩnh trước khi lặp
for word in list(word_count.keys()):
    if word_count[word] == 1:
        del word_count[word] # Chạy an toàn

Lỗi 5: Đếm sai do sự cố mã hóa Unicode tiếng Việt

Bạn nhận thấy từ “hoá” và “hóa” (khác nhau cách bỏ dấu) bị tính là 2 từ khác nhau, hoặc độ dài chuỗi bị sai lệch khó hiểu khi xử lý văn bản tiếng Việt lấy từ các nguồn web khác nhau.

Nguyên nhân: Tiếng Việt có hai kiểu gõ Unicode: tổ hợp (dấu rời) và dựng sẵn (dấu gắn liền chữ). Nếu input trộn lẫn hai chuẩn này, phép so sánh chuỗi mặc định của Python sẽ trả về False dù mắt người nhìn thấy giống nhau. Phải sử dụng hàm chuẩn hóa ký tự unicodedata.normalize('NFC', text) trước khi đếm.

Code sai:

text1 = "Tiếng Việt" # dựng sẵn
text2 = "Tiếng Việt" # tổ hợp
print(text1 == text2) # Trả về False!

Code đúng:

import unicodedata
clean_text1 = unicodedata.normalize('NFC', text1)
clean_text2 = unicodedata.normalize('NFC', text2)
print(clean_text1 == clean_text2) # Trả về True an toàn

Câu hỏi thường gặp 

Kiểu dữ liệu dict trong Python là gì?

Kiểu dữ liệu dict (viết tắt của dictionary) là một cấu trúc dữ liệu dạng bảng băm tích hợp sẵn trong Python, dùng để lưu trữ các cặp dữ liệu dưới dạng khóa và giá trị (key-value). Nó cho phép tìm kiếm, thêm và xóa phần tử với tốc độ cực nhanh, rất phù hợp để làm bộ đếm tần suất.

Cặp key-value trong bài toán đếm từ này mang ý nghĩa gì?

Cặp key-value là đơn vị dữ liệu cơ sở của dictionary. Trong bài toán thống kê từ vựng này, key (khóa) chính là chuỗi ký tự đại diện cho từ vựng đó (đảm bảo tính duy nhất không trùng lặp), còn value (giá trị) là một số nguyên (integer) thể hiện số lần từ đó xuất hiện trong câu.

Làm thế nào để sắp xếp dict theo số lần xuất hiện giảm dần bằng Python?

Sau khi có dict chứa tần suất, bạn có thể dùng hàm sorted() kết hợp với hàm lambda hoặc module operator.itemgetter. Cụ thể: sorted_dict = dict(sorted(word_count.items(), key=lambda item: item[1], reverse=True)). Thao tác này sẽ trả về một dict mới chứa các từ có tần suất cao nhất đứng đầu.

Làm thế nào để loại bỏ các dấu câu (, . ? !) trong câu chữ Python trước khi đếm?

Cách đơn giản nhất là bạn import module string và dùng hàm translate(). Ví dụ: text.translate(str.maketrans('', '', string.punctuation)) sẽ gỡ bỏ toàn bộ dấu câu đặc biệt. Xử lý bước này trước khi gọi hàm .split() để đảm bảo từ khóa sạch sẽ hoàn toàn.

Nên dùng dict.get() hay defaultdict để giải quyết bài toán đếm chữ?

Cả hai đều tốt. dict.get(key, 0) tiện lợi vì không cần import thêm thư viện, rất tốt cho người mới học cơ bản. Tuy nhiên, collections.defaultdict(int) là cách viết tinh gọn hơn ở môi trường thực tế, vì nó tự động cấp giá trị số 0 cho bất kỳ khóa mới nào mà không cần bạn phải tự viết logic kiểm tra tồn tại.

Sự khác biệt thực sự giữa Counter và một dict bình thường là gì?

Counter thực chất là một lớp con kế thừa toàn bộ tính năng của dict, nhưng nó được ghi đè và bổ sung thêm các phương thức chuyên dụng cho việc thống kê toán học. Ví dụ, Counter tự động đếm đầu vào là iterable, và hỗ trợ hàm most_common(n) để truy xuất nhanh top n phần tử cao nhất mà dict thường không làm được.

Tại sao kết quả in dict ra màn hình lại bị thay đổi thứ tự so với lúc viết code?

Bắt đầu từ phiên bản Python 3.7 trở đi, dictionary chính thức đảm bảo duy trì thứ tự chèn (insertion order) của các key. Nếu bạn đang dùng bản Python cũ hơn, dict sẽ hiển thị các key theo một thứ tự ngẫu nhiên do cơ chế trộn của bảng băm. Hãy nâng cấp Python lên phiên bản mới nhất (3.12+) để tránh lỗi khó hiểu này.


Kết luận

Bài toán dùng dict để đếm số lần xuất hiện của từ trong câu là một ví dụ tuyệt vời để bạn hiểu sức mạnh của lưu trữ theo cặp key-value. Nếu bạn là sinh viên mới học rèn luyện tư duy, hãy tự tay code lại Cách 1 dùng vòng lặp và dict.get(). Khi đã thành thạo, làm dự án lớn hay đi làm thực tế, hãy ưu tiên dùng Cách 2 với collections.Counter để tiết kiệm thời gian và tối ưu hiệu năng chạy của chương trình. Chúc bạn code Python vui vẻ!

Các khóa học liên quan: 

Một số sản phẩm từ Python:

Một số sách lập trình Python bạn hãy tham khảo

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *