extern crate generic_matrix;
use generic_matrix::Matrix;
use std::io;
use std::time::{Duration, Instant};
mod words;
pub fn run(num_rounds: u32, num_words: u32) {
let mut mistake_count = 0;
let mut char_count = 0;
let mut word_count = 0;
let mut elapsed_time: Duration = Duration::from_secs(0);
for _ in 0..num_rounds {
let result = play_one_round(num_words);
mistake_count += result.mistakes;
char_count += result.answer.chars().count();
word_count += num_words;
elapsed_time += result.time;
if result.mistakes == 0 {
println!("Correct!\n");
} else {
println!("Number of mistakes: {}\n", result.mistakes);
}
}
print_results(mistake_count, char_count, word_count, elapsed_time);
}
fn play_one_round(num_words: u32) -> RoundResult {
let line = generate_string(num_words);
println!("{}", line);
let mut answer = String::new();
let begin = Instant::now();
io::stdin()
.read_line(&mut answer)
.expect("Failed to read from console.");
let end = Instant::now();
let answer = String::from(answer.trim());
let mistakes = edit_distance(line.trim(), &answer);
RoundResult {
answer,
mistakes,
time: end - begin,
}
}
struct RoundResult {
answer: String,
mistakes: u32,
time: Duration,
}
fn print_results(mistake_count: u32, char_count: usize, word_count: u32, elapsed_time: Duration) {
if word_count == 0 || elapsed_time.as_secs() == 0 {
println!("No data to calculate.");
return;
}
let secs = elapsed_time.as_secs() as f64;
println!(
"{:30}{:>.3}\n\
{:30}{:>.3}\n",
"Mistakes per word:",
mistake_count as f64 / word_count as f64,
"Characters per second:",
char_count as f64 / secs,
);
}
fn generate_string(num_words: u32) -> String {
(0..num_words)
.map(|_| words::get_word())
.collect::<Vec<_>>()
.join(" ")
}
fn edit_distance(a: &str, b: &str) -> u32 {
let a: Vec<char> = a.chars().collect();
let b: Vec<char> = b.chars().collect();
let mut distances = Matrix::from_fn(a.len() + 1, b.len() + 1, |_, _| 0);
for j in 1..=b.len() {
distances[(0, j)] = j as u32;
}
for i in 1..=a.len() {
distances[(i, 0)] = i as u32;
}
use std::cmp::min;
for i in 1..=a.len() {
for j in 1..=b.len() {
let compare = if a[i - 1] == b[j - 1] { 0 } else { 1 };
let distance = min(
distances[(i, j - 1)] + 1,
min(
distances[(i - 1, j)] + 1,
distances[(i - 1, j - 1)] + compare,
),
);
distances[(i, j)] = distance;
}
}
distances[(a.len(), b.len())]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edit_distance_short_cases() {
assert_eq!(0, edit_distance("", ""));
assert_eq!(0, edit_distance("a", "a"));
assert_eq!(1, edit_distance("x", ""));
assert_eq!(1, edit_distance("", "q"));
assert_eq!(1, edit_distance("p", "q"));
assert_eq!(1, edit_distance("ap", "p"));
assert_eq!(2, edit_distance("ap", "b"));
}
#[test]
fn test_edit_distance_normal_words() {
assert_eq!(3, edit_distance("hector", "extort"));
assert_eq!(4, edit_distance("abcd", "defg"));
}
#[test]
fn test_non_ascii_characters() {
assert_eq!(
4,
edit_distance("Добрый день", "Добрый вечер")
);
}
#[test]
fn test_edit_distance_big_difference() {
assert_eq!(10, edit_distance("", "1234567890"));
assert_eq!(10, edit_distance("abc", "1234567890abc"));
assert_eq!(10, edit_distance("abc", "abc1234567890"));
assert_eq!(13, edit_distance("abc", "abc1234567890abc"));
assert_eq!(10, edit_distance("1234567890", ""));
assert_eq!(10, edit_distance("1234567890abc", "abc"));
assert_eq!(10, edit_distance("abc1234567890", "abc"));
assert_eq!(13, edit_distance("abc1234567890abc", "abc"));
}
}