#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::format;
use crate::{dfa, analyze, DfaResult, StructuralLaw};
use core::fmt;
#[derive(Debug, Clone)]
pub struct TextStructure {
pub sentence_count: usize,
pub mean_sentence_len: f64,
pub dfa: DfaResult,
pub law: StructuralLaw,
pub sentence_lengths: Vec<usize>,
}
impl fmt::Display for TextStructure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sentences={} mean_len={:.1} α={:.3} R²={:.4} quality={}",
self.sentence_count, self.mean_sentence_len,
self.dfa.alpha, self.dfa.r_squared, self.law.quality)
}
}
pub fn sentence_lengths(text: &str) -> Vec<usize> {
let mut lengths = Vec::new();
let mut current_len = 0;
let chars: Vec<char> = text.chars().filter(|&c| c != '\r').collect();
let n = chars.len();
for i in 0..n {
current_len += 1;
let is_terminal = chars[i] == '.' || chars[i] == '!' || chars[i] == '?';
let followed_by_space_or_end = if i + 1 >= n {
true
} else {
chars[i + 1].is_whitespace() || chars[i + 1] == '"' || chars[i + 1] == '\''
};
if is_terminal && followed_by_space_or_end && current_len >= 3 {
lengths.push(current_len);
current_len = 0;
}
}
if current_len >= 10 {
lengths.push(current_len);
}
lengths
}
pub fn text_structure(text: &str) -> TextStructure {
let lens = sentence_lengths(text);
let values: Vec<f64> = lens.iter().map(|&l| l as f64).collect();
let n = lens.len();
let mean = if n > 0 {
values.iter().sum::<f64>() / n as f64
} else {
0.0
};
let dfa_result = dfa(&values);
let law = analyze(&values);
TextStructure {
sentence_count: n,
mean_sentence_len: mean,
dfa: dfa_result,
law,
sentence_lengths: lens,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sentence_splitting() {
let text = "Hello world. This is a test! Is it working? Yes it is.";
let lens = sentence_lengths(text);
assert_eq!(lens.len(), 4);
}
#[test]
fn crlf_and_lf_give_identical_lengths() {
let lf = "First line of a sentence\nthat wraps. Second one\nhere.";
let crlf = lf.replace('\n', "\r\n");
assert_eq!(sentence_lengths(lf), sentence_lengths(&crlf));
}
#[test]
fn short_text_still_works() {
let text = "Short text. More text. And here.";
let result = text_structure(text);
assert!(result.sentence_count >= 2);
}
#[test]
fn builtin_text_demo() {
let text = "The bearing data was collected at Case Western Reserve University. \
Vibration data was recorded using accelerometers. \
Normal bearings show a random vibration pattern. \
Inner race faults produce characteristic frequencies. \
Ball faults are harder to detect due to signal modulation. \
Outer race faults are typically the clearest. \
Each fault type changes the DFA scaling exponent. \
The structural signature shifts before performance degrades. \
This is the principle behind struktura.";
let result = text_structure(text);
assert!(result.sentence_count >= 8, "got {} sentences", result.sentence_count);
}
}