use crate::config::{CJK_CHARS_PER_TOKEN, WORDS_PER_TOKEN};
fn is_cjk(c: char) -> bool {
matches!(c as u32,
0x3000..=0x303F | 0x3040..=0x30FF | 0x3400..=0x4DBF | 0x4E00..=0x9FFF | 0xAC00..=0xD7AF | 0xF900..=0xFAFF | 0xFF00..=0xFFEF )
}
pub fn estimate(text: &str) -> usize {
if text.is_empty() {
return 0;
}
let cjk = text.chars().filter(|c| is_cjk(*c)).count();
if cjk == 0 {
return (text.split_whitespace().count() as f64 / WORDS_PER_TOKEN) as usize;
}
let latin: String = text
.chars()
.map(|c| if is_cjk(c) { ' ' } else { c })
.collect();
let latin_words = latin.split_whitespace().count();
(latin_words as f64 / WORDS_PER_TOKEN + cjk as f64 / CJK_CHARS_PER_TOKEN) as usize
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_is_zero() {
assert_eq!(estimate(""), 0);
}
#[test]
fn latin_counts_by_words() {
assert_eq!(estimate("one two three four five six"), 8);
}
#[test]
fn cjk_counts_by_characters() {
assert_eq!(estimate("这是一本技术书籍"), 5);
}
#[test]
fn cjk_dominates_a_spaceless_book() {
let text = "第一章".repeat(333);
assert!(estimate(&text) > 600, "got {}", estimate(&text));
}
#[test]
fn mixed_counts_both_populations() {
let n = estimate("Rust 是系统语言");
assert_eq!(
n,
(1.0 / WORDS_PER_TOKEN + 5.0 / CJK_CHARS_PER_TOKEN) as usize
);
}
}