diff --git a/src/main.rs b/src/main.rs index 682eed0..6daa0a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ // Write some functions to: // 1. tokenize a string -// 2. manually make a map of tokens:their_counts +// 2. manually make a map of tokens:their_counts (the purpose of this exercise was to exercise +// understanding of vectors/arrays and their slices, not to use hashmap) // 3. analyze the token structure: // 3.a. count the words // 3.b. count the unique words @@ -9,18 +10,21 @@ // 3.d. count the average word length // 4. (pretty) print the info -const PUNC: &str = ",.?:;'~|!@#$%^&*\""; - // todo: what about CTRL chars? // todo: what about feeding Bytes? // todo: return Result for whitespace input -fn tokenize(input: &str) -> Vec { +pub fn tokenize(input: &str) -> Vec { // filter punctuation let mut s = String::new(); for c in input.chars() { - if !PUNC.contains(c) { - s.push(c.to_lowercase().next().unwrap()); + if !c.is_ascii_punctuation() { + // to_lowercase on a char will always yield at least one char + // if a char lowercases to two chars then we don't truncate by using + // a for loop + for lc in c.to_lowercase() { + s.push(lc); + } } } @@ -29,29 +33,36 @@ fn tokenize(input: &str) -> Vec { } // todo: can the input type be better? -fn count_words(input: &[String]) -> Vec<(String,usize)> { +// total runtime complexity worst-case O(n log n) +pub fn create_frequency_vec(input: &[String]) -> Vec<(String,usize)> { + let mut sorted = input.to_vec(); + sorted.sort(); // O(n log n) + let mut counts : Vec<(String, usize)> = Vec::new(); - for w in input { - match counts.iter_mut().find(|(c,_)| c == w) { - Some((_, count)) => *count += 1, + for w in sorted { // O(n) + match counts.last_mut() { + Some((last, count)) if last == &w => *count += 1, _ => counts.push((w.to_string(), 1)), } } - counts.sort_by(|a,b| b.1.cmp(&a.1)); + counts.sort_by(|a,b| b.1.cmp(&a.1)); // sort by frequency, O(m log m) counts } -fn analyze_word_count(input: &[(String,usize)]) -> usize { +pub fn analyze_word_count(input: &[(String,usize)]) -> usize { let mut count = 0; input.iter().for_each(|(_,c)| { count+=c }); count } -fn analyze_unique_word_count(input: &[(String,usize)]) -> usize { +pub fn analyze_unique_word_count(input: &[(String,usize)]) -> usize { input.len() } -fn average_word_length(input: &[(String, usize)]) -> usize { +pub fn average_word_length(input: &[(String, usize)]) -> f64 { + if input.is_empty() { + return 0 as f64; + } let mut len = 0; let mut tokens = 0; input.iter().for_each(|(s,c)| { @@ -59,43 +70,113 @@ fn average_word_length(input: &[(String, usize)]) -> usize { tokens += c; }); - len/tokens + len as f64 /(tokens as f64) } -fn top_n_most_used(input: &[(String, usize)], n: usize) -> &[(String, usize)] { +pub fn top_n_most_used(input: &[(String, usize)], n: usize) -> &[(String, usize)] { let n = n.min(input.len()); &input[..n] } -fn print(input: &[(String, usize)]) { +// todo: option +pub fn longest_word(input: &[(String, usize)]) -> usize { + let mut max = 0; + input.iter().for_each(|(w,_)| if w.len() > max { max = w.len() }); + max +} + +pub fn num_digits(n: usize) -> usize { + let mut n = n; + let mut count = 0; + 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); - // todo: clean this up a bit - println!("Word Count % of total"); - println!("---------------------"); + let col1 = longest_word(&counts).max(5); + let col2 = digits.max(5); + let col3 = 5; + let end = col1 + col2 + col3.max(11) + 2; + println!("{: