refactor: extract parser module with muncher and for_loops submodules from main.rs
Move Stats processing methods into dedicated parser module with separate muncher and for_loops implementations, update .gitignore to exclude test_books directory, and simplify README to remove outdated benchmark instructions
This commit is contained in:
+15
-157
@@ -1,3 +1,4 @@
|
||||
mod parser;
|
||||
mod stats;
|
||||
mod trie;
|
||||
|
||||
@@ -50,114 +51,6 @@ static FORBIDDEN_WORDS: LazyLock<Trie> = LazyLock::new(|| {
|
||||
trie
|
||||
});
|
||||
|
||||
impl Stats {
|
||||
pub fn process(&mut self, text: &str) {
|
||||
// self.muncher(&text);
|
||||
self.for_loops(&text);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
/// probably buggy. for example, are new lines sentences? what if the text has no last period?
|
||||
/// 500ms is without forbidden words check, but...
|
||||
/// 6000ms if adding forbidden words.. so not faster
|
||||
/// with trie this is 2600ms
|
||||
fn muncher(&mut self, text: &str) {
|
||||
let mut capitalized = true;
|
||||
let mut whitespaced = false;
|
||||
let mut dotted = false;
|
||||
let mut word = String::new();
|
||||
for char in text.chars() {
|
||||
if whitespaced {
|
||||
if !char.is_whitespace() {
|
||||
whitespaced = false; //end whiteness
|
||||
}
|
||||
continue;
|
||||
} else if char.is_whitespace() {
|
||||
whitespaced = true;
|
||||
self.word_count += 1; //end of word
|
||||
if capitalized {
|
||||
self.capitalized_count += 1;
|
||||
} else {
|
||||
//reset capitalized word
|
||||
capitalized = true;
|
||||
}
|
||||
let lowercase_word = word.to_lowercase();
|
||||
if FORBIDDEN_WORDS.contains(&lowercase_word) {
|
||||
self.forbidden_count += 1;
|
||||
}
|
||||
word = String::new();
|
||||
continue;
|
||||
}
|
||||
if dotted {
|
||||
if char != '.' {
|
||||
dotted = false; //end sentencing
|
||||
}
|
||||
continue;
|
||||
} else if char == '.' {
|
||||
dotted = true;
|
||||
self.sentence_count += 1;
|
||||
self.word_count += 1; //end of word
|
||||
if capitalized {
|
||||
self.capitalized_count += 1;
|
||||
} else {
|
||||
//reset capitalized word
|
||||
capitalized = true;
|
||||
}
|
||||
let lowercase_word = word.to_lowercase();
|
||||
if FORBIDDEN_WORDS.contains(&lowercase_word) {
|
||||
self.forbidden_count += 1;
|
||||
}
|
||||
word = String::new();
|
||||
continue;
|
||||
}
|
||||
word += &char.to_string();
|
||||
if char.is_numeric() {
|
||||
self.numeric_count += 1;
|
||||
capitalized = false;
|
||||
}
|
||||
if !char.is_ascii_uppercase() {
|
||||
capitalized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
/// typically 5000ms
|
||||
/// with trie this is 1600ms
|
||||
fn for_loops(&mut self, text: &str) {
|
||||
for sentence in text
|
||||
.split('.')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
{
|
||||
self.sentence_count += 1;
|
||||
for word in sentence
|
||||
.split_whitespace()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
{
|
||||
self.word_count += 1;
|
||||
//get all numbers counted
|
||||
let mut all_capitalized = true;
|
||||
for char in word.chars() {
|
||||
if char.is_numeric() {
|
||||
self.numeric_count += 1;
|
||||
//TODO are numbers capitalized or not? I don't know!
|
||||
}
|
||||
if !char.is_ascii_uppercase() {
|
||||
all_capitalized = false;
|
||||
}
|
||||
}
|
||||
if all_capitalized {
|
||||
self.capitalized_count += 1;
|
||||
}
|
||||
let lowercase_word = word.to_lowercase();
|
||||
if FORBIDDEN_WORDS.contains(&lowercase_word) {
|
||||
self.forbidden_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let files = env::args().skip(1);
|
||||
@@ -166,17 +59,17 @@ async fn main() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
for file in files {
|
||||
//reading files not sequentially average shaves 30ms (of 1250ms), and that's on a NVMe SSD so why not
|
||||
let Ok(text) = fs::read_to_string(&file) else {
|
||||
if let Ok(text) = fs::read_to_string(&file) {
|
||||
stats.file_count += 1;
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut stats = Stats::default();
|
||||
parser::for_loops::parse(&mut stats, &text);
|
||||
let _ = tx.send(stats);
|
||||
});
|
||||
} else {
|
||||
stats.failed_file_count += 1;
|
||||
continue;
|
||||
};
|
||||
stats.file_count += 1;
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut stats = Stats::default();
|
||||
stats.process(&text);
|
||||
tx.send(stats).unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
rx
|
||||
};
|
||||
@@ -186,6 +79,7 @@ async fn main() {
|
||||
println!("{stats}");
|
||||
}
|
||||
|
||||
/// needs ../books.tar.gz to be extracted
|
||||
#[test]
|
||||
fn test() {
|
||||
use std::{env, fs, process::Command, time::Instant};
|
||||
@@ -199,44 +93,6 @@ fn test() {
|
||||
Err(err) => eprintln!("compile failed: {err}"),
|
||||
}
|
||||
|
||||
//get test files
|
||||
let files = fs::read_dir("test_files")
|
||||
.unwrap()
|
||||
.map(|f| {
|
||||
f.unwrap()
|
||||
.path()
|
||||
.canonicalize()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
println!("test files found: {:#?}", files);
|
||||
|
||||
//benchmark run
|
||||
let benchmark = Instant::now();
|
||||
let mut run = Command::new("target/release/jisspam");
|
||||
let run_arged = run.args(files);
|
||||
match run_arged.output() {
|
||||
Ok(output) => println!("{}", String::from_utf8_lossy(&output.stdout)),
|
||||
Err(err) => eprintln!("run failed: {err}"),
|
||||
}
|
||||
println!("benchmark: {}ms", benchmark.elapsed().as_millis());
|
||||
}
|
||||
#[test]
|
||||
fn books_test() {
|
||||
use std::{env, fs, process::Command, time::Instant};
|
||||
println!("cwd: {}", env::current_dir().unwrap().display());
|
||||
|
||||
//compile
|
||||
let mut compile = Command::new("cargo");
|
||||
let compile_arged = compile.arg("build").arg("--release");
|
||||
match compile_arged.output() {
|
||||
Ok(output) => println!("compiled {}", String::from_utf8_lossy(&output.stdout)),
|
||||
Err(err) => eprintln!("compile failed: {err}"),
|
||||
}
|
||||
|
||||
//get test files
|
||||
let files = fs::read_dir("../books")
|
||||
.unwrap()
|
||||
@@ -250,7 +106,9 @@ fn books_test() {
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
println!("test files found: {:#?}", files);
|
||||
println!("test files found: {}", files.len());
|
||||
|
||||
println!();
|
||||
|
||||
//benchmark run
|
||||
let benchmark = Instant::now();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::{FORBIDDEN_WORDS, stats::Stats};
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// typically 5000ms
|
||||
/// with trie this is 1600ms
|
||||
pub fn parse(stats: &mut Stats, text: &str) {
|
||||
for sentence in text
|
||||
.split('.')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
{
|
||||
stats.sentence_count += 1;
|
||||
for word in sentence
|
||||
.split_whitespace()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
{
|
||||
stats.word_count += 1;
|
||||
//get all numbers counted
|
||||
let mut all_capitalized = true;
|
||||
for char in word.chars() {
|
||||
if char.is_numeric() {
|
||||
stats.numeric_count += 1;
|
||||
//TODO are numbers capitalized or not? I don't know!
|
||||
}
|
||||
if !char.is_ascii_uppercase() {
|
||||
all_capitalized = false;
|
||||
}
|
||||
}
|
||||
if all_capitalized {
|
||||
stats.capitalized_count += 1;
|
||||
}
|
||||
let lowercase_word = word.to_lowercase();
|
||||
if FORBIDDEN_WORDS.contains(&lowercase_word) {
|
||||
stats.forbidden_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod muncher;
|
||||
pub mod for_loops;
|
||||
@@ -0,0 +1,66 @@
|
||||
use crate::{FORBIDDEN_WORDS, stats::Stats};
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// probably buggy. for example, are new lines sentences? what if the text has no last period?
|
||||
/// 500ms is without forbidden words check, but...
|
||||
/// 6000ms if adding forbidden words.. so not faster
|
||||
/// with trie this is 2600ms
|
||||
pub fn parse(stats: &mut Stats, text: &str) {
|
||||
let mut capitalized = true;
|
||||
let mut whitespaced = false;
|
||||
let mut dotted = false;
|
||||
let mut word = String::new();
|
||||
for char in text.chars() {
|
||||
if whitespaced {
|
||||
if !char.is_whitespace() {
|
||||
whitespaced = false; //end whiteness
|
||||
}
|
||||
continue;
|
||||
} else if char.is_whitespace() {
|
||||
whitespaced = true;
|
||||
stats.word_count += 1; //end of word
|
||||
if capitalized {
|
||||
stats.capitalized_count += 1;
|
||||
} else {
|
||||
//reset capitalized word
|
||||
capitalized = true;
|
||||
}
|
||||
let lowercase_word = word.to_lowercase();
|
||||
if FORBIDDEN_WORDS.contains(&lowercase_word) {
|
||||
stats.forbidden_count += 1;
|
||||
}
|
||||
word = String::new();
|
||||
continue;
|
||||
}
|
||||
if dotted {
|
||||
if char != '.' {
|
||||
dotted = false; //end sentencing
|
||||
}
|
||||
continue;
|
||||
} else if char == '.' {
|
||||
dotted = true;
|
||||
stats.sentence_count += 1;
|
||||
stats.word_count += 1; //end of word
|
||||
if capitalized {
|
||||
stats.capitalized_count += 1;
|
||||
} else {
|
||||
//reset capitalized word
|
||||
capitalized = true;
|
||||
}
|
||||
let lowercase_word = word.to_lowercase();
|
||||
if FORBIDDEN_WORDS.contains(&lowercase_word) {
|
||||
stats.forbidden_count += 1;
|
||||
}
|
||||
word = String::new();
|
||||
continue;
|
||||
}
|
||||
word += &char.to_string();
|
||||
if char.is_numeric() {
|
||||
stats.numeric_count += 1;
|
||||
capitalized = false;
|
||||
}
|
||||
if !char.is_ascii_uppercase() {
|
||||
capitalized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user