Skip to main content

อ้างอิง API

v0.8.2

Public API ของ kham-core — ไลบรารี pure Rust แบบ no_std เอกสารฉบับเต็มอยู่ที่ docs.rs/kham-core

โมดูล / ประเภท คำอธิบาย
Tokenizer ตัดคำภาษาไทยด้วย Maximal Matching บน DAWG dictionary ในตัว
Token / TokenKind Token แบบ zero-copy พร้อม byte span, char span และประเภทอักษร
normalizer ปรับมาตรฐานข้อความไทย: ลบวรรณยุกต์ซ้ำ, รวม sara am
FtsTokenizer pipeline FTS: stopwords, synonyms, POS, NE, soundex ในครั้งเดียว
PosTagger POS tagger 13 หมวดหมู่ มาจาก ORCHID tagset
NeTagger จดจำ Named Entity: บุคคล สถานที่ องค์กร
RomanizationMap แปลงคำภาษาไทยเป็นอักษรโรมัน RTGS
number จัดการตัวเลขภาษาไทย: แปลงหลัก, อ่านจำนวน, ข้อความบาท
sentence ตรวจหาจุดสิ้นสุดประโยค แบ่งย่อหน้าเป็นประโยค
soundex Phonetic codes: lk82, udom83, MetaSound และ cross-language ไทย–อังกฤษ
SpellChecker แก้คำสะกดผิด: หา candidate ระยะ Levenshtein ≤ 2 จัดอันดับด้วย lk82 + TNC frequency
KeyExtractor สกัดคำสำคัญ: TF × inverse-corpus-frequency, ตัด stopword ออก

Tokenizer

docs.rs ↗

ตัวตัดคำหลักใช้ DAWG dictionary ในตัวและตารางความถี่ TNC ประมวลผลด้วยอัลกอริทึม newmm (maximal matching) ร่วมกับ TCC boundary Token ทุกตัวเป็น zero-copy slice ของ input string

use kham_core::Tokenizer;

let tok = Tokenizer::new();

// ง่าย — list ของ string
let words: Vec<&str> = tok.segment("กินข้าวกับปลา")
    .into_iter().map(|t| t.text).collect();
// ["กิน", "ข้าว", "กับ", "ปลา"]

// รายละเอียด — Token struct พร้อม span
let tokens = tok.segment("ธนาคาร100แห่ง");
for t in &tokens {
    println!("{:8} chars={}..{} kind={:?}", t.text,
        t.char_span.start, t.char_span.end, t.kind);
}

// dictionary เอง — merge กับ built-in
let tok2 = Tokenizer::builder()
    .dict_words("ปัญญาประดิษฐ์\nแมชชีนเลิร์นนิง\n")
    .build();
Rust key methods: Tokenizer::new() Tokenizer::builder() .segment(&str) → Vec<Token>

Token / TokenKind

docs.rs ↗

แต่ละ Token มี text (slice ไม่คัดลอก), span (byte offset), char_span (Unicode offset) และ kind

use kham_core::{TokenKind, NamedEntityKind, Tokenizer};

let tok = Tokenizer::new();
let input = "ธนาคาร100แห่ง";
let tokens = tok.segment(input);

for t in &tokens {
    // t.text      — &str (zero-copy slice ของ input)
    // t.span      — Range<usize> byte offsets
    // t.char_span   — Range<usize> Unicode scalar-value offsets
    // t.kind        — TokenKind
    // t.confidence  — f32: 0.0 (Unknown) … 1.0 (dict match ที่มีความมั่นใจสูง)

    assert_eq!(&input[t.span.clone()], t.text);
}

// TokenKind variants:
// Thai | Latin | Number | Punctuation | Emoji | Whitespace | Unknown
// Named(NamedEntityKind::Person | Place | Org)  ← กำหนดโดย NeTagger

normalizer

docs.rs ↗

ปรับมาตรฐานข้อความ 2 กฎ: (1) ลบวรรณยุกต์ซ้ำ เก็บตัวสุดท้าย; (2) รวม nikhahit (อํ U+0E4D) + sara aa (อา U+0E32) เป็น sara am (อำ U+0E33) ควรเรียกก่อน segment เมื่อ input อาจมาจากแป้นพิมพ์ผู้ใช้หรือ OCR

use kham_core::normalizer::normalize;

// กฎ 1 — ลบวรรณยุกต์ซ้ำ (เก็บตัวสุดท้าย)
assert_eq!(normalize("ข้้าว"), "ข้าว");   // mai tho ซ้ำ → ตัวเดียว
assert_eq!(normalize("ก่้"),   "ก้");      // mai ek + mai tho → mai tho

// กฎ 2 — รวม sara am
// nikhahit (U+0E4D) + sara aa (U+0E32) → sara am (U+0E33)
let decomposed = "\u{0E01}\u{0E4D}\u{0E32}"; // กํา (สอง codepoint)
assert_eq!(normalize(decomposed), "กำ");          // กำ  (หนึ่ง codepoint)

// canonical อยู่แล้ว — คืนค่าโดยไม่ allocate
assert_eq!(normalize("กินข้าว"), "กินข้าว");

FtsTokenizer

docs.rs ↗

รวม NLP pipeline ในครั้งเดียว: normalize → segment → NE → stopwords → POS → synonyms → romanization ใน Python และ WASM นี่คือทางหลักในการเข้าถึง POS และ NE metadata

use kham_core::fts::FtsTokenizer;
use kham_core::soundex::SoundexAlgorithm;
use kham_core::synonym::SynonymMap;

// pipeline เริ่มต้น
let fts = FtsTokenizer::new();
let tokens = fts.segment_for_fts("นายกรัฐมนตรีกินข้าว");
for t in &tokens {
    println!("{:8} pos={:?} ne={:?} stop={}", t.text, t.pos, t.ne, t.is_stop);
}

// index_tokens: เก็บ position, กรอง stopword สำหรับ phrase search
let indexed = fts.index_tokens("กินข้าวกับปลา");

// lexemes: Vec<String> ของ text + synonyms + trigrams (สำหรับ tsvector)
let lexemes = fts.lexemes("กินข้าวกับปลา");

// pipeline กำหนดเอง
let fts2 = FtsTokenizer::builder()
    .synonyms(SynonymMap::from_tsv("รถ\tรถยนต์\tยานพาหนะ\n"))
    .soundex(SoundexAlgorithm::Lk82)
    .build();
FtsToken fields: textpositionkindis_stopromanposnesynonymstrigramsconfidence

PosTagger

docs.rs ↗

POS tagger แบบ dictionary-lookup 13 หมวดหมู่ตาม ORCHID tagset ใน Python, WASM และ C เข้าถึง POS ผ่าน segment_fts() / kham_fts_segment() — สร้าง tagger โดยตรงได้เฉพาะ Rust

Tag หมวดหมู่ ตัวอย่าง
NOUN คำนาม คน บ้าน ปลา
VERB คำกริยา กิน ทำ ไป
ADJ คำคุณศัพท์ ดี ใหญ่ สวย
ADV คำกริยาวิเศษณ์ มาก เร็ว เสมอ
PART คำอนุภาค ครับ ค่ะ นะ
PROPN คำนามวิสามัญ กรุงเทพ ไทย
PRON คำสรรพนาม ฉัน เขา เรา
NUM คำตัวเลข หนึ่ง สิบ ร้อย
CLAS คำลักษณนาม ตัว ใบ อัน
CONJ คำสันธาน และ หรือ แต่
AUX คำช่วย ได้ ต้อง กำลัง
DET คำกำหนด นี้ นั้น ทุก
PREP คำบุพบท ใน บน ตาม
use kham_core::pos::PosTagger;

let tagger = PosTagger::builtin();

// Tag คำเดี่ยว
if let Some(pos) = tagger.tag("กิน") {
    println!("{:?}", pos); // Verb
}

// TSV กำหนดเอง: คำ<TAB>POS_TAG
let custom = PosTagger::from_tsv("GPT\tNOUN\nแชทบอท\tNOUN\n");
assert_eq!(custom.tag("แชทบอท"), Some(kham_core::pos::PosTag::Noun));

NeTagger

docs.rs ↗

NER จาก gazetteer สามประเภท: Person (บุคคล), Place (สถานที่), Org (องค์กร) ใน Python, WASM และ C เข้าถึง NE ผ่าน segment_fts() / kham_fts_segment()

use kham_core::ne::NeTagger;
use kham_core::{TokenKind, Tokenizer};

let ne = NeTagger::builtin();
println!("{:?}", ne.tag("กรุงเทพ")); // Some(Place)

// post-process tokens จาก Tokenizer::segment
let tok = Tokenizer::new();
let src = "บริษัทไทยออยล์ก่อตั้งในกรุงเทพ";
let tokens = ne.tag_tokens(tok.segment(src), src);

for t in &tokens {
    if matches!(t.kind, TokenKind::Named(_)) {
        println!("{} → {:?}", t.text, t.kind);
    }
}

// gazetteer กำหนดเอง จาก TSV: คำ<TAB>NE_TAG  (PERSON | PLACE | ORG)
let custom = NeTagger::from_tsv("แอนโทรปิก\tORG\n");

RomanizationMap

docs.rs ↗

แปลงคำภาษาไทยเป็นอักษรโรมัน RTGS (Royal Thai General System) แบบ table-lookup สำหรับคำที่ไม่อยู่ใน vocabulary จะคืนข้อความไทยต้นฉบับ

use kham_core::romanizer::RomanizationMap;

let rom = RomanizationMap::builtin();

// lookup คำเดี่ยว
println!("{:?}", rom.romanize("กรุงเทพ"));   // Some("Krung Thep")
println!("{}", rom.romanize_or_raw("ปลา"));   // "pla"
println!("{}", rom.romanize_or_raw("zzz"));   // "zzz"  (OOV → passthrough)

// lookup แบบ batch
let roman = rom.romanize_tokens(&["กรุงเทพ", "ประเทศ", "ไทย"]);
println!("{:?}", roman); // ["Krung Thep", "prathet", "Thai"]

// Whole sentence romanization
let sentence = rom.romanize_sentence("กินข้าวกับปลา 100 บาท");
println!("{sentence}"); // kinkhaokapla 100 bat

number

docs.rs ↗

จัดการตัวเลขภาษาไทย: แปลงเลขไทย (๐–๙) เป็น ASCII, parse/สร้างคำตัวเลข และแสดงข้อความเงินบาท

use kham_core::number::{
    thai_digits_to_ascii, parse_thai_word, u64_to_thai_word,
    parse_thai_baht, to_thai_baht_text,
};

// เลขไทย → ASCII
assert_eq!(thai_digits_to_ascii("ราคา ๑๒๓ บาท"), "ราคา 123 บาท");

// อ่านตัวเลขจากคำ
assert_eq!(parse_thai_word("หนึ่งร้อยยี่สิบสาม"), Some(123));
assert_eq!(parse_thai_word("สองล้าน"),             Some(2_000_000));
assert_eq!(parse_thai_word("กินข้าว"),             None); // ไม่ใช่ตัวเลข

// ตัวเลข → คำไทย
println!("{}", u64_to_thai_word(42));        // "สี่สิบสอง"
println!("{}", u64_to_thai_word(1_000_000)); // "หนึ่งล้าน"

// ข้อความบาท
println!("{}", to_thai_baht_text(1234, 50));
// "หนึ่งพันสองร้อยสามสิบสี่บาทห้าสิบสตางค์"
if let Some(amt) = parse_thai_baht("หนึ่งร้อยบาทถ้วน") {
    println!("{} baht {} satang", amt.baht, amt.satang); // 100 0
}

sentence

docs.rs ↗

ตรวจหา sentence boundary แบ่งบน newline, เครื่องหมายไทย (ฯ ๚ ๛) และเครื่องหมายตะวันตก (! ? . ตามด้วยช่องว่าง) แต่ละประโยคมี char offset สำหรับ Python/JS string slicing

use kham_core::sentence::split_sentences;

let text = "คุณชอบอาหารไทยไหม? ผมชอบต้มยำกุ้ง!\nอาหารไทยรสเผ็ด";
let sents = split_sentences(text);

for (i, s) in sents.iter().enumerate() {
    println!("S{i}: {:?}  chars={}..{}", s.text, s.char_span.start, s.char_span.end);
}
// S0: "คุณชอบอาหารไทยไหม?"     chars=0..19
// S1: " ผมชอบต้มยำกุ้ง!"       chars=19..36
// S2: "\nอาหารไทยรสเผ็ด"       chars=36..50

soundex

docs.rs ↗

Phonetic encoding ภาษาไทย: lk82 (12 กลุ่ม, 4 ตัวอักษร), udom83 (14 กลุ่ม, 4 ตัวอักษร), MetaSound (3 ตัว/พยางค์) และ cross-language ไทย–อังกฤษ (Suwanvisat & Prasitjutrakul 1998)

use kham_core::soundex::{
    soundex, sounds_like, SoundexAlgorithm,
    thai_english_soundex, sounds_like_cross_lang,
};

// Thai soundex
println!("{}", soundex("กาน", SoundexAlgorithm::Lk82));      // "1600"
println!("{}", soundex("กาน", SoundexAlgorithm::Udom83));    // "1900"
println!("{}", soundex("กาน", SoundexAlgorithm::MetaSound)); // "112"

// เปรียบเทียบเสียง
assert!(sounds_like("กาน", "ขาน", SoundexAlgorithm::Lk82));   // กลุ่มเดียวกัน
assert!(!sounds_like("ลาน", "ราน", SoundexAlgorithm::Udom83)); // ล/ร แยกกัน

// Cross-language ไทย–อังกฤษ
println!("{}", thai_english_soundex("Somchai")); // เหมือนกับ thai_english_soundex("สมชาย")
assert!(sounds_like_cross_lang("สมชาย", "Somchai")); // true

SpellChecker

docs.rs ↗

แก้คำสะกดผิดโดยค้นหาใน dictionary 62k คำที่มีในตัว คืน candidate ที่มี Levenshtein edit distance ≤ 2 จัดอันดับตาม lk82 phonetic similarity, edit distance และ TNC corpus frequency ตามลำดับ รับ คำเดียว — หาก input เป็นหลายคำ ให้ segment ก่อนแล้วตรวจแต่ละ Thai token

use kham_core::spell::SpellChecker;

// นำ checker มาใช้ซ้ำ — builtin() โหลด TNC frequency map ครั้งเดียว
let checker = SpellChecker::builtin();

let suggs = checker.suggestions("กีนข้าว", 5);
for s in &suggs {
    println!("{:12} edit={} soundex={} freq={}",
        s.word, s.edit_distance, s.soundex_match, s.freq_score);
}
// กินข้าว  edit=1  soundex=true  freq=…

// คำที่สะกดถูก → edit_distance = 0
let exact = checker.suggestions("กิน", 1);
assert_eq!(exact[0].word, "กิน");
assert_eq!(exact[0].edit_distance, 0);

// field ของ Suggestion:
// s.word          — String   คำ candidate จาก dictionary
// s.edit_distance — u8       Levenshtein distance (0–2)
// s.soundex_match — bool     lk82 code ตรงกัน
// s.freq_score    — u32      TNC corpus frequency (0 ถ้าไม่มีในตาราง)

// Single best correction
let checker = SpellChecker::builtin();
if let Some(corrected) = checker.did_you_mean("กีนข้าว") {
    println!("Did you mean: {corrected}");  // กินข้าว
}
// Correct whole text
let text = "กีนข้าวกับปลา";
let out = checker.correct_text(text);
println!("{out}");
หมายเหตุ: SpellChecker รับคำเดียว สำหรับข้อความหลายคำให้ segment ด้วย Tokenizer::segment() ก่อน แล้วตรวจ Thai token ทีละคำ

KeyExtractor

docs.rs ↗

สกัดคำสำคัญแบบ unsupervised ด้วย TF × inverse-corpus-frequency scoring คำที่หายากใน TNC corpus จะได้คะแนนสูงกว่าคำทั่วไป ตัด stopword และ token ที่มีตัวอักษรเดียวออกเสมอ ผลลัพธ์เรียงตาม score จากมากไปน้อย

use kham_core::keyword::KeyExtractor;

// นำ extractor มาใช้ซ้ำ — builtin() โหลด TNC freq + stopwords ครั้งเดียว
let extractor = KeyExtractor::builtin();

let text = "นักวิทยาศาสตร์ค้นพบดาวเคราะห์ใหม่ในระบบสุริยะ              ดาวดวงนี้โคจรอยู่ใกล้ดาวเคราะห์น้อย";

let keywords = extractor.extract(text, 5);
for kw in &keywords {
    println!("{:12} score={:.4} count={}", kw.word, kw.score, kw.count);
}

// field ของ Keyword:
// kw.word  — String  คำสำคัญ
// kw.score — f32     TF × (max_freq+1) / (corpus_freq+1)
// kw.count — usize   จำนวนครั้งที่ปรากฏในเอกสาร