use jieba_rs::Jieba;
use std::sync::LazyLock;
pub static JIEBA: LazyLock<Jieba> = LazyLock::new(Jieba::new);
pub fn stable_hash64(text: &str) -> u64 {
xxhash_rust::xxh3::xxh3_64(text.as_bytes())
}
pub fn simhash_similarity(a: &[u64; 4], b: &[u64; 4]) -> f32 {
let same: u32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| 64 - (x ^ y).count_ones())
.sum();
same as f32 / 256.0
}
pub fn tokenize(text: &str, language: &str) -> Vec<String> {
match language {
"zh" | "zh-CN" | "zh-TW" | "zh-HK" => tokenize_chinese(text),
_ => tokenize_english(text),
}
}
fn tokenize_chinese(text: &str) -> Vec<String> {
JIEBA
.cut(text, true) .into_iter()
.filter(|s| !s.trim().is_empty())
.map(|s| s.to_string())
.collect()
}
pub fn tag_chinese(text: &str) -> Vec<(String, String)> {
JIEBA
.tag(text, true)
.into_iter()
.map(|t| (t.word.to_string(), t.tag.to_string()))
.collect()
}
fn tokenize_english(text: &str) -> Vec<String> {
text.split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
.filter(|s| !s.is_empty())
.map(|s| s.to_lowercase())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stable_hash_deterministic() {
let h1 = stable_hash64("test text");
let h2 = stable_hash64("test text");
assert_eq!(h1, h2);
}
#[test]
fn simhash_similarity_identical_and_orthogonal() {
let a = [0x0123_4567_89AB_CDEF, 1, 2, 3];
assert_eq!(
simhash_similarity(&a, &a),
1.0,
"identical signatures → 1.0"
);
let zeros = [0u64; 4];
let ones = [u64::MAX; 4];
assert_eq!(simhash_similarity(&zeros, &ones), 0.0, "orthogonal → 0.0");
let half = [0x0000_0000_FFFF_FFFFu64; 4];
assert_eq!(simhash_similarity(&zeros, &half), 0.5);
}
}