improved API intuitiveness and encapsulation
This commit is contained in:
+99
-92
@@ -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!("{:<col1$} {:<col2$} {:<col3$}", "Word", "Count", "%_of_total");
|
||||
println!("{:-<end$}", "");
|
||||
for (w, c) in self.0.iter() {
|
||||
let pct = format!("{:.2}%", *c as f64 / wc as f64 * 100.0);
|
||||
println!("{:<col1$} {:<col2$} {:>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<String> {
|
||||
// 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!("{:<col1$} {:<col2$} {:<col3$}", "Word", "Count", "%_of_total");
|
||||
println!("{:-<end$}", "");
|
||||
for (w, c) in counts {
|
||||
println!("{:<col1$} {:<col2$} {:<col3$.2}%", w, c, (*c as f64/(wc as f64) * 100.0));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("playing around with vecs and slices");
|
||||
println!("use `cargo test`]\n");
|
||||
test_print();
|
||||
}
|
||||
|
||||
pub fn test_print() {
|
||||
fn test_print() {
|
||||
let tokens = tokenize("Hellow, wod! hello hello hello ok bye then oa more plz");
|
||||
let freq_vec = create_frequency_vec(&tokens);
|
||||
print_analysis(&freq_vec);
|
||||
freq_vec.print_analysis();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -142,21 +149,21 @@ pub mod test {
|
||||
assert_eq!(tokens.len(), 0);
|
||||
|
||||
let freq_vec = create_frequency_vec(&tokens);
|
||||
assert_eq!(freq_vec.len(), 0);
|
||||
assert_eq!(freq_vec.as_slice().len(), 0);
|
||||
|
||||
let wc = analyze_word_count(&freq_vec);
|
||||
let wc = freq_vec.analyze_word_count();
|
||||
assert_eq!(wc, 0);
|
||||
|
||||
let uwc = analyze_unique_word_count(&freq_vec);
|
||||
let uwc = freq_vec.analyze_unique_word_count();
|
||||
assert_eq!(uwc, 0);
|
||||
|
||||
let avg_len = average_word_length(&freq_vec);
|
||||
let avg_len = freq_vec.average_word_length();
|
||||
assert_eq!(avg_len, 0.0);
|
||||
|
||||
let top_n = top_n_most_used(&freq_vec, 4);
|
||||
let top_n = freq_vec.top_n(4);
|
||||
assert_eq!(top_n.len(), 0);
|
||||
|
||||
print_analysis(&freq_vec);
|
||||
freq_vec.print_analysis();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -165,21 +172,21 @@ pub mod test {
|
||||
assert_eq!(tokens.len(), 3);
|
||||
|
||||
let freq_vec = create_frequency_vec(&tokens);
|
||||
assert_eq!(freq_vec.len(), 2);
|
||||
assert_eq!(freq_vec.as_slice().len(), 2);
|
||||
|
||||
let wc = analyze_word_count(&freq_vec);
|
||||
let wc = freq_vec.analyze_word_count();
|
||||
assert_eq!(wc, 3);
|
||||
|
||||
let uwc = analyze_unique_word_count(&freq_vec);
|
||||
let uwc = freq_vec.analyze_unique_word_count();
|
||||
assert_eq!(uwc, 2);
|
||||
|
||||
let avg_len = average_word_length(&freq_vec);
|
||||
let avg_len = freq_vec.average_word_length();
|
||||
assert_eq!(avg_len, 5.0);
|
||||
|
||||
let top_n = top_n_most_used(&freq_vec, 4);
|
||||
let top_n = freq_vec.top_n(4);
|
||||
assert_eq!(top_n[0].0, "hello");
|
||||
|
||||
print_analysis(&freq_vec);
|
||||
freq_vec.print_analysis();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -188,21 +195,21 @@ pub mod test {
|
||||
assert_eq!(tokens.len(), 4);
|
||||
|
||||
let freq_vec = create_frequency_vec(&tokens);
|
||||
assert_eq!(freq_vec.len(), 4);
|
||||
assert_eq!(freq_vec.as_slice().len(), 4);
|
||||
|
||||
let wc = analyze_word_count(&freq_vec);
|
||||
let wc = freq_vec.analyze_word_count();
|
||||
assert_eq!(wc, 4);
|
||||
|
||||
let uwc = analyze_unique_word_count(&freq_vec);
|
||||
let uwc = freq_vec.analyze_unique_word_count();
|
||||
assert_eq!(uwc, 4);
|
||||
|
||||
let avg_len = average_word_length(&freq_vec);
|
||||
let avg_len = freq_vec.average_word_length();
|
||||
assert_eq!(avg_len, 4.25);
|
||||
|
||||
let top_n = top_n_most_used(&freq_vec, 4);
|
||||
let top_n = freq_vec.top_n(4);
|
||||
assert_eq!(top_n[0].0, "hello");
|
||||
|
||||
print_analysis(&freq_vec);
|
||||
freq_vec.print_analysis();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -211,20 +218,20 @@ pub mod test {
|
||||
assert_eq!(tokens.len(), 1);
|
||||
|
||||
let freq_vec = create_frequency_vec(&tokens);
|
||||
assert_eq!(freq_vec.len(), 1);
|
||||
assert_eq!(freq_vec.as_slice().len(), 1);
|
||||
|
||||
let wc = analyze_word_count(&freq_vec);
|
||||
let wc = freq_vec.analyze_word_count();
|
||||
assert_eq!(wc, 1);
|
||||
|
||||
let uwc = analyze_unique_word_count(&freq_vec);
|
||||
let uwc = freq_vec.analyze_unique_word_count();
|
||||
assert_eq!(uwc, 1);
|
||||
|
||||
let avg_len = average_word_length(&freq_vec);
|
||||
let avg_len = freq_vec.average_word_length();
|
||||
assert_eq!(avg_len, 4.0);
|
||||
|
||||
let top_n = top_n_most_used(&freq_vec, 4);
|
||||
let top_n = freq_vec.top_n(4);
|
||||
assert_eq!(top_n[0].0, "café");
|
||||
|
||||
print_analysis(&freq_vec);
|
||||
freq_vec.print_analysis();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user