Dokumen teks mentah tidak bisa langsung diindex. “Data.” dan “data” harus diperlakukan sebagai token yang sama. “C++” harus diawetkan sebagai token tunggal, bukan dipecah menjadi “C” dan “+”. Pada bagian ini kita membangun fungsi tokenize yang mengubah teks mentah menjadi std::vector<std::string> berisi token bersih — lowercase, tanpa tanda baca, terpisah oleh spasi.

Strategi Tokenisasi #

Kita memilih pendekatan char-by-char scan alih-alih regex. Alasannya:

  1. Tanpa dependency: tidak butuh <regex> yang berat dan lambat.
  2. Kontrol penuh: kita bisa menentukan karakter apa yang membentuk token — alfanumerik, _, +, dan - (untuk C++, multi-word expression opsional).
  3. Prediksi output: setiap karakter diproses satu kali, kompleksitas O(n) jelas.

Aturan tokenisasi yang kita pakai:

  • Karakter token: huruf a-z, angka 0-9, underscore _, plus +, dash -.
  • Karakter lain (tanda baca, spasi, titik, koma) memisahkan token.
  • Semua huruf di-lowercase agar pencarian case-insensitive.

Implementasi tokenize #

cpp
#include <cctype>
#include <string>
#include <vector>

bool isTokenChar(char c) {
    return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '+' || c == '-';
}

std::vector<std::string> tokenize(const std::string& text) {
    std::vector<std::string> tokens;
    std::string current;
    for (char c : text) {
        if (isTokenChar(c)) {
            current += static_cast<char>(
                std::tolower(static_cast<unsigned char>(c)));
        } else if (!current.empty()) {
            tokens.push_back(current);
            current.clear();
        }
    }
    if (!current.empty()) tokens.push_back(current);
    return tokens;
}

Program Verifikasi #

Berikut program yang menguji tokenize pada beberapa string contoh dengan kasus edge: tanda baca di akhir, huruf kapital, dan token C++.

cpp
#include <cctype>
#include <iostream>
#include <string>
#include <vector>

bool isTokenChar(char c) {
    return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '+' || c == '-';
}

std::vector<std::string> tokenize(const std::string& text) {
    std::vector<std::string> tokens;
    std::string current;
    for (char c : text) {
        if (isTokenChar(c)) {
            current += static_cast<char>(
                std::tolower(static_cast<unsigned char>(c)));
        } else if (!current.empty()) {
            tokens.push_back(current);
            current.clear();
        }
    }
    if (!current.empty()) tokens.push_back(current);
    return tokens;
}

void printTokens(const std::string& text) {
    std::cout << "Input:  \"" << text << "\"\n";
    std::cout << "Tokens: ";
    for (const auto& t : tokenize(text)) std::cout << "[" << t << "] ";
    std::cout << "\n\n";
}

int main() {
    printTokens("Belajar struktur data dengan C++.");
    printTokens("Hello, World! C++ is great.");
    printTokens("  Multiple   spaces   between  words.  ");
    printTokens("");
    printTokens("!!!@#$%^&*()");
    return 0;
}

Output yang Dihasilkan:

text
Input:  "Belajar struktur data dengan C++."
Tokens: [belajar] [struktur] [data] [dengan] [c++] 

Input:  "Hello, World! C++ is great."
Tokens: [hello] [world] [c++] [is] [great] 

Input:  "  Multiple   spaces   between  words.  "
Tokens: [multiple] [spaces] [between] [words] 

Input:  ""
Tokens: 

Input:  "!!!@#$%^&*()"
Tokens: 

Bedah Baris Penting #

  1. std::isalnum(...): memeriksa apakah karakter alfanumerik. Kita cast ke unsigned char dulu karena std::isalnum mengharapkan nilai di rentang 0..255 atau EOF — bila char bertanda negatif, behavior tidak terdefinisi.
  2. c == '+' || c == '-': token “C++” harus tetap utuh. Tanpa pengecualian ini, “C++” akan dipecah menjadi “C” saja, kehilangan informasi penting.
  3. else if (!current.empty()): saat menemui karakter pemisah, kita finalize token yang sedang dibangun. Tanpa !current.empty(), pemisah berturut-turut akan mempush string kosong sebagai token.
  4. if (!current.empty()) tokens.push_back(current) di akhir: bila teks berakhir dengan karakter token (tanpa pemisah setelahnya), token terakhir perlu di-push. Tanpa baris ini, “C++” di akhir string tidak akan tersimpan.
  5. std::tolower: setiap karakter di-lowercase saat ditambahkan ke current. Normalisasi ini membuat “Data” dan “data” dianggap token yang sama.

Pertimbangan Stop Word Removal #

Stop word removal (menghapus kata umum seperti “the”, “dan”, “adalah”) tidak kita implementasikan pada proyek ini. Alasannya:

  • Pada korpus kecil, stop word removal bisa menghapus kata yang justru relevan,
  • Kompleksitas tambahan (perlu daftar stop word per bahasa) tidak sepadan dengan manfaat pada demo,
  • TF-IDF secara alami memberi stop word skor rendah karena IDF kecil (kata muncul di banyak dokumen).

Kompilasi dan Pengujian #

bash
g++ -std=c++17 -O2 -Wall -Wextra -o tokenize.bin tokenize.cpp
./tokenize.bin

Pastikan kelima kasus uji menghasilkan output seperti di atas. Pada Bagian 3 kita memakai tokenize untuk membangun inverted index.