diff --git a/src/main.rs b/src/main.rs index 1ec4dc3..3483763 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,72 @@ // 3.d. count the average word length // 4. (pretty) print the info +// newtype syntax +// compiler will help enforce the type and prevent internal state manipulation +// makes the API a little more intuitive +pub struct FrequencyVec(Vec<(String, usize)>); + +impl FrequencyVec { + pub fn top_n(&self, n: usize) -> &[(String, usize)] { + let n = n.min(self.0.len()); + &self.0[..n] + } + pub fn as_slice(&self) -> &[(String, usize)] { + &self.0[..] + } + + pub fn analyze_word_count(&self) -> usize { + self.0.iter().map(|(_,c)| c).sum() + } + + pub fn analyze_unique_word_count(&self) -> usize { + self.0.len() + } + + pub fn average_word_length(&self) -> f64 { + if self.0.is_empty() { + return 0.0; + } + let mut len = 0; + let mut tokens = 0; + self.0.iter().for_each(|(s,c)| { + // this will work for Unicode Scalar value characters + // ie: graphemes with multiple code points (like some emojis) will break + // but not going to pull in unicode-segmentation just to get this right + len += s.chars().count() * *c; + tokens += *c; + }); + + len as f64 /(tokens as f64) + } + + // todo: option + pub fn longest_word(&self) -> usize { + self.0.iter().map(|(w,_)| w.len()).max().unwrap_or(0) + } + + pub fn print_analysis(&self) { + if self.0.is_empty() { + println!("No words to analyze"); + return; + } + + let wc = self.analyze_word_count(); + let largest_freq = self.top_n(1)[0].1; + let digits = num_digits(largest_freq); + + let col1 = self.longest_word().max(5); + let col2 = digits.max(5); + let col3 = 10; + let end = col1 + col2 + col3.max(11) + 2; + println!("{:col3$}", w, c, pct); + } + } +} // todo: return Result for whitespace input // CTRL chars are preserved in tokens, though whitespace ones will get dropped @@ -37,7 +103,7 @@ pub fn tokenize(input: &str) -> Vec { // Since our tokenize function returns new Strings, there is not much sense in // taking &[&str] here since this would require an extra allocation to move the // string slices to a new vector. This doesn't copy heap on the way in anyway. -pub fn create_frequency_vec(input: &[String]) -> Vec<(String,usize)> { +pub fn create_frequency_vec(input: &[String]) -> FrequencyVec { // perhaps unnecessary copy, but this preserves the original token list let mut sorted = input.to_vec(); sorted.sort(); // O(n log n) @@ -46,94 +112,35 @@ pub fn create_frequency_vec(input: &[String]) -> Vec<(String,usize)> { for w in sorted { // O(n) match counts.last_mut() { Some((last, count)) if last == &w => *count += 1, - _ => counts.push((w.clone(), 1)), + _ => counts.push((w, 1)), } } counts.sort_by(|a,b| b.1.cmp(&a.1)); // sort by frequency, O(m log m) - counts + FrequencyVec(counts) } -pub fn analyze_word_count(input: &[(String,usize)]) -> usize { - input.iter().map(|(_,c)| c).sum() -} - -pub fn analyze_unique_word_count(input: &[(String,usize)]) -> usize { - input.len() -} - -pub fn average_word_length(input: &[(String, usize)]) -> f64 { - if input.is_empty() { - return 0.0; - } - let mut len = 0; - let mut tokens = 0; - input.iter().for_each(|(s,c)| { - // this will work for Unicode Scalar value characters - // ie: graphemes with multiple code points (like some emojis) will break - // but not going to pull in unicode-segmentation just to get this right - len += s.chars().count() * c; - tokens += c; - }); - - len as f64 /(tokens as f64) -} - -pub fn top_n_most_used(input: &[(String, usize)], n: usize) -> &[(String, usize)] { - let n = n.min(input.len()); - &input[..n] -} - -// todo: option -pub fn longest_word(input: &[(String, usize)]) -> usize { - input.iter().map(|(w,_)| w.len()).max().unwrap_or(0) -} - -pub fn num_digits(n: usize) -> usize { +fn num_digits(n: usize) -> usize { + if n == 0 { return 1; } let mut n = n; let mut count = 0; - while n > 0 { - count += 1; - n /= 10; - } + while n > 0 { count += 1; n /= 10; } count } -pub fn print_analysis(input: &[(String, usize)]) { - if input.is_empty() { - println!("No words to analyze"); - return; - } - - let counts = input; - let wc = analyze_word_count(&counts); - let largest_freq = top_n_most_used(&counts, 1)[0].1; - let digits = num_digits(largest_freq); - - let col1 = longest_word(&counts).max(5); - let col2 = digits.max(5); - let col3 = 5; - let end = col1 + col2 + col3.max(11) + 2; - println!("{: