use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FeatureType {
State,
Transition,
Unigram,
Bigram,
CharacterTrigram,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureFunction {
pub id: u32,
pub feature_type: FeatureType,
pub source: u32,
pub target: u32,
pub weight: f64,
}
impl FeatureFunction {
pub fn new_state(id: u32, attr_id: u32, label_id: u32, weight: f64) -> Self {
Self {
id,
feature_type: FeatureType::State,
source: attr_id,
target: label_id,
weight,
}
}
pub fn new_transition(id: u32, from_label: u32, to_label: u32, weight: f64) -> Self {
Self {
id,
feature_type: FeatureType::Transition,
source: from_label,
target: to_label,
weight,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AttributeIndex {
attr_to_id: HashMap<String, u32>,
id_to_attr: Vec<String>,
}
impl AttributeIndex {
pub fn new() -> Self {
Self::default()
}
pub fn get_or_insert(&mut self, attr: &str) -> u32 {
if let Some(&id) = self.attr_to_id.get(attr) {
id
} else {
let id = self.id_to_attr.len() as u32;
self.attr_to_id.insert(attr.to_string(), id);
self.id_to_attr.push(attr.to_string());
id
}
}
pub fn get(&self, attr: &str) -> Option<u32> {
self.attr_to_id.get(attr).copied()
}
pub fn get_attr(&self, id: u32) -> Option<&str> {
self.id_to_attr.get(id as usize).map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.id_to_attr.len()
}
pub fn is_empty(&self) -> bool {
self.id_to_attr.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (u32, &str)> {
self.id_to_attr
.iter()
.enumerate()
.map(|(id, attr)| (id as u32, attr.as_str()))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LabelIndex {
label_to_id: HashMap<String, u32>,
id_to_label: Vec<String>,
}
impl LabelIndex {
pub fn new() -> Self {
Self::default()
}
pub fn get_or_insert(&mut self, label: &str) -> u32 {
if let Some(&id) = self.label_to_id.get(label) {
id
} else {
let id = self.id_to_label.len() as u32;
self.label_to_id.insert(label.to_string(), id);
self.id_to_label.push(label.to_string());
id
}
}
pub fn get(&self, label: &str) -> Option<u32> {
self.label_to_id.get(label).copied()
}
pub fn get_label(&self, id: u32) -> Option<&str> {
self.id_to_label.get(id as usize).map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.id_to_label.len()
}
pub fn is_empty(&self) -> bool {
self.id_to_label.is_empty()
}
pub fn labels(&self) -> &[String] {
&self.id_to_label
}
pub fn iter(&self) -> impl Iterator<Item = (u32, &str)> {
self.id_to_label
.iter()
.enumerate()
.map(|(id, label)| (id as u32, label.as_str()))
}
}
pub fn extract_char_trigrams(text: &str) -> Vec<String> {
let chars: Vec<char> = text.chars().collect();
if chars.len() < 3 {
return vec![text.to_string()];
}
chars
.windows(3)
.map(|w| w.iter().collect::<String>())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attribute_index() {
let mut index = AttributeIndex::new();
let id1 = index.get_or_insert("word=hello");
let id2 = index.get_or_insert("word=world");
let id3 = index.get_or_insert("word=hello");
assert_eq!(id1, 0);
assert_eq!(id2, 1);
assert_eq!(id1, id3); assert_eq!(index.len(), 2);
assert_eq!(index.get("word=hello"), Some(0));
assert_eq!(index.get_attr(0), Some("word=hello"));
}
#[test]
fn test_label_index() {
let mut index = LabelIndex::new();
let id1 = index.get_or_insert("B-PER");
let id2 = index.get_or_insert("I-PER");
let id3 = index.get_or_insert("O");
assert_eq!(id1, 0);
assert_eq!(id2, 1);
assert_eq!(id3, 2);
assert_eq!(index.len(), 3);
assert_eq!(index.get("B-PER"), Some(0));
assert_eq!(index.get_label(1), Some("I-PER"));
}
#[test]
fn test_char_trigrams() {
let trigrams = extract_char_trigrams("hello");
assert_eq!(trigrams, vec!["hel", "ell", "llo"]);
let short = extract_char_trigrams("ab");
assert_eq!(short, vec!["ab"]);
}
#[test]
fn test_feature_function() {
let state = FeatureFunction::new_state(0, 1, 2, 0.5);
assert_eq!(state.feature_type, FeatureType::State);
assert_eq!(state.source, 1);
assert_eq!(state.target, 2);
assert_eq!(state.weight, 0.5);
let trans = FeatureFunction::new_transition(1, 0, 1, -0.3);
assert_eq!(trans.feature_type, FeatureType::Transition);
assert_eq!(trans.source, 0);
assert_eq!(trans.target, 1);
}
}