improved API intuitiveness and encapsulation
This commit is contained in:
+99
-92
@@ -10,6 +10,72 @@
|
|||||||
// 3.d. count the average word length
|
// 3.d. count the average word length
|
||||||
// 4. (pretty) print the info
|
// 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
|
// todo: return Result for whitespace input
|
||||||
// CTRL chars are preserved in tokens, though whitespace ones will get dropped
|
// 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
|
// 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
|
// 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.
|
// 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
|
// perhaps unnecessary copy, but this preserves the original token list
|
||||||
let mut sorted = input.to_vec();
|
let mut sorted = input.to_vec();
|
||||||
sorted.sort(); // O(n log n)
|
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)
|
for w in sorted { // O(n)
|
||||||
match counts.last_mut() {
|
match counts.last_mut() {
|
||||||
Some((last, count)) if last == &w => *count += 1,
|
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.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 {
|
fn num_digits(n: usize) -> usize {
|
||||||
input.iter().map(|(_,c)| c).sum()
|
if n == 0 { return 1; }
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let mut n = n;
|
let mut n = n;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
while n > 0 {
|
while n > 0 { count += 1; n /= 10; }
|
||||||
count += 1;
|
|
||||||
n /= 10;
|
|
||||||
}
|
|
||||||
count
|
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() {
|
fn main() {
|
||||||
println!("playing around with vecs and slices");
|
println!("playing around with vecs and slices");
|
||||||
println!("use `cargo test`]\n");
|
println!("use `cargo test`]\n");
|
||||||
test_print();
|
test_print();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_print() {
|
fn test_print() {
|
||||||
let tokens = tokenize("Hellow, wod! hello hello hello ok bye then oa more plz");
|
let tokens = tokenize("Hellow, wod! hello hello hello ok bye then oa more plz");
|
||||||
let freq_vec = create_frequency_vec(&tokens);
|
let freq_vec = create_frequency_vec(&tokens);
|
||||||
print_analysis(&freq_vec);
|
freq_vec.print_analysis();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -142,21 +149,21 @@ pub mod test {
|
|||||||
assert_eq!(tokens.len(), 0);
|
assert_eq!(tokens.len(), 0);
|
||||||
|
|
||||||
let freq_vec = create_frequency_vec(&tokens);
|
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);
|
assert_eq!(wc, 0);
|
||||||
|
|
||||||
let uwc = analyze_unique_word_count(&freq_vec);
|
let uwc = freq_vec.analyze_unique_word_count();
|
||||||
assert_eq!(uwc, 0);
|
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);
|
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);
|
assert_eq!(top_n.len(), 0);
|
||||||
|
|
||||||
print_analysis(&freq_vec);
|
freq_vec.print_analysis();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -165,21 +172,21 @@ pub mod test {
|
|||||||
assert_eq!(tokens.len(), 3);
|
assert_eq!(tokens.len(), 3);
|
||||||
|
|
||||||
let freq_vec = create_frequency_vec(&tokens);
|
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);
|
assert_eq!(wc, 3);
|
||||||
|
|
||||||
let uwc = analyze_unique_word_count(&freq_vec);
|
let uwc = freq_vec.analyze_unique_word_count();
|
||||||
assert_eq!(uwc, 2);
|
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);
|
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");
|
assert_eq!(top_n[0].0, "hello");
|
||||||
|
|
||||||
print_analysis(&freq_vec);
|
freq_vec.print_analysis();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -188,21 +195,21 @@ pub mod test {
|
|||||||
assert_eq!(tokens.len(), 4);
|
assert_eq!(tokens.len(), 4);
|
||||||
|
|
||||||
let freq_vec = create_frequency_vec(&tokens);
|
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);
|
assert_eq!(wc, 4);
|
||||||
|
|
||||||
let uwc = analyze_unique_word_count(&freq_vec);
|
let uwc = freq_vec.analyze_unique_word_count();
|
||||||
assert_eq!(uwc, 4);
|
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);
|
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");
|
assert_eq!(top_n[0].0, "hello");
|
||||||
|
|
||||||
print_analysis(&freq_vec);
|
freq_vec.print_analysis();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -211,20 +218,20 @@ pub mod test {
|
|||||||
assert_eq!(tokens.len(), 1);
|
assert_eq!(tokens.len(), 1);
|
||||||
|
|
||||||
let freq_vec = create_frequency_vec(&tokens);
|
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);
|
assert_eq!(wc, 1);
|
||||||
|
|
||||||
let uwc = analyze_unique_word_count(&freq_vec);
|
let uwc = freq_vec.analyze_unique_word_count();
|
||||||
assert_eq!(uwc, 1);
|
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);
|
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é");
|
assert_eq!(top_n[0].0, "café");
|
||||||
|
|
||||||
print_analysis(&freq_vec);
|
freq_vec.print_analysis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user