อ้างอิง API
v0.8.2Public 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(); 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(); 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}"); 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 จำนวนครั้งที่ปรากฏในเอกสาร