Xây Dựng Trie Tree Từ Điển Trong C++: Cấu Hình, Tìm Kiếm Và Lưu Trữ Dữ Liệu

Trong giai đoạn phát triển tiếp theo của hệ thống từ điển dựa trên cây Trie, trọng tâm được chuyển sang việc hoàn thiện các chức năng cốt lõi bao gồm tìm kiếm từ, quản lý cấu hình đường dẫn và xử lý việc lưu trữ cũng như phục hồi dữ liệu từ bộ nhớ xuống ổ đĩa. Bài viết này sẽ trình bày cách mở rộng lớp từ điển hiện có để hỗ trợ các thao tác nhập/xuất dữ liệu thông qua định dạng JSON và tích hợp cơ chế tự động sinh hoặc gán định danh cho các từ vựng.

Quản Lý Cấu Hình Tập Tin

Một yếu tố quan trọng để hệ thống linh hoạt là tách biệt cấu hình khỏi mã nguồn. Thay vì cứng nhắc đường dẫn tập tin trong code, chúng ta sử dụng một lớp riêng để đọc tham số từ một tệp JSON bên ngoài. Lớp này chỉ cung cấp một phương thức truy xuất duy nhất để lấy đường dẫn đích.

class StorageConfig {
public:
    StorageConfig();
    std::string getFilePath() const;
private:
    void initializeConfiguration();
    std::string _filePath;
};

Phần cài đặt thực thi quy trình giải nén thông tin cấu hình tại thời điểm khởi tạo đối tượng:

StorageConfig::StorageConfig() {
    initializeConfiguration();
}

void StorageConfig::initializeConfiguration() {
    std::ifstream inputFile("config.json");
    if (!inputFile.is_open()) {
        // Xử lý lỗi khi không tìm thấy file cấu hình
        return; 
    }
    
    // Giả lập đối tượng Reader để parse JSON
    // ... (logic giải mã JSON tại đây) ...
    
    // Lấy giá trị 'DictionaryPath' từ root object
    _filePath = /* giá trị chuỗi từ json */;
    inputFile.close();
}

Tệp cấu hình mẫu có định dạng như sau:

{
    "DictionaryPath": "my_dictionary_data.json"
}

Cải Tiến Lớp Từ Điển Chính

Lớp chủ đạo `ChineseDictionary` cần được nâng cấp để phân tách rõ ràng giữa việc thêm từ mới với việc chèn dữ liệu có sẵn định danh (khi nhập từ file). Các hàm cũ như `push` được chuẩn hóa lại thành `addEntry` để yêu cầu người gọi truyền ID cụ thể, tránh xung đột dữ liệu.

class ChineseDictionary {
    struct Node {
        int id;
        char character;
        std::map children;
    };
    
public:
    ChineseDictionary();
    bool searchWord(const std::string& target);
    int getIdForWord(const std::string& target);
    void exportToJson();
    void importFromFile();

private:
    void buildNodeTree(const std::string& word, int id);
    void splitChars(const std::string& str, std::vector& outVec);
    
    std::shared_ptr<Node> _root;
    int _nextAvailableId;
    StorageConfig _configManager;
};

Hàm xây dựng cây sẽ duyệt qua từng ký tự của từ vựng. Nếu nút con chưa tồn tại, nó sẽ được tạo mới. Cuối cùng, nếu nút gốc còn lại chưa có ID, nó sẽ được gán giá trị hiện tại.

void ChineseDictionary::buildNodeTree(const std::string& word, int id) {
    std::vector chars;
    splitChars(word, chars);
    
    std::shared_ptr<Node> current = _root;
    for (const auto& charItem : chars) {
        if (current->children.find(charItem) == current->children.end()) {
            auto newNode = std::make_shared<Node>();
            newNode->character = charItem[0];
            current->children[charItem] = newNode;
            current = newNode;
        } else {
            current = current->children[charItem];
        }
    }
    
    if (current->id == 0) {
        current->id = id;
    }
}

Tìm Kiếm Từ Vựng

Thuật toán tìm kiếm tương tự quá trình chèn nhưng ở bước cuối cùng trả về giá trị ID thay vì cập nhật cây. Nếu trong quá trình duyệt gặp khoảng trống (nút không tồn tại), hàm sẽ trả về giá trị đặc trưng -1 biểu thị không tìm thấy.

int ChineseDictionary::getIdForWord(const std::string& target) {
    std::vector chars;
    splitChars(target, chars);
    
    std::shared_ptr<Node> current = _root;
    for (const auto& charItem : chars) {
        if (current->children.find(charItem) != current->children.end()) {
            current = current->children[charItem];
        } else {
            return -1;
        }
    }
    return (current->id > 0) ? current->id : -1;
}

Xử Lý Xuất Nhập Dữ Liệu

Vấn đề lưu trữ quyết định hiệu suất khi nạp lại chương trình. Một lựa chọn là lưu trữ toàn bộ cấu trúc cây lồng nhau dưới dạng JSON, nhưng điều này phức tạp cho việc xử lý luồng. Phương pháp tối ưu hơn là sử dụng định dạng danh sách phẳng (flat list), mỗi phần tử đại diện cho một cụm từ đã hoàn chỉnh kèm theo ID tương ứng.

Danh sách xuất ra sẽ trông như sau:

[
  {"Word": "lập trình", "WordId": 1},
  {"Word": "học lập trình", "WordId": 2},
  {"Word": "web lập trình", "WordId": 3}
]

Hàm xuất dữ liệu sẽ duyệt qua tất cả các từ trong bộ nhớ (thông qua cơ chế duyệt DFS) và ghi lại vào định dạng JSON.

void ChineseDictionary::exportToJson() {
    std::ofstream outputFile(_configManager.getFilePath());
    if (!outputFile.good()) {
        // Xử lý lỗi ghi file
        return; 
    }
    
    Json::Value rootArray;
    // Giả lập duyệt cây và collect words
    // forEachWord([&](auto& w){ rootArray.push_back(w); });
    
    outputFile << writer.write(rootArray);
    outputFile.close();
}

Ngược lại, khi tải lên, hệ thống sẽ đọc mảng JSON và gọi hàm chèn thủ công cho từng mục. Cần lưu ý rằng thứ tự nhập vào có thể thay đổi nếu bộ nhớ ban đầu đã chứa dữ liệu trùng lặp, do đó cơ chế quản lý ID cần kiểm tra kỹ lưỡng trước khi thực hiện gán.

void ChineseDictionary::importFromFile() {
    std::ifstream inputFile(_configManager.getFilePath());
    if (!inputFile.good()) return;
    
    Json::Value importedList;
    // Parse logic
    
    int size = importedList.size();
    for (int i = 0; i < size; ++i) {
        std::string key = importedList[i]["Word"].asString();
        int id = importedList[i]["WordId"].asInt();
        
        buildNodeTree(key, id);
        // Cập nhật trạng thái ID lớn nhất trong hệ thống nếu cần
    }
}

Thẻ: C++ trie-data-structure json-cpp data-serialization text-algorithms

Đăng vào ngày 19 tháng 5 lúc 16:09