vocab/vocab_store/
translation.rs

1use diesel::{Insertable, Queryable};
2
3use crate::schema::translations;
4use crate::VocabStoreError;
5
6#[derive(Debug, Default, Insertable, Queryable, PartialEq)]
7pub struct Translation {
8    pub local: String,
9    pub foreign: String,
10    pub guesses_local_total: i32,
11    pub guesses_local_correct: i32,
12    pub guesses_foreign_total: i32,
13    pub guesses_foreign_correct: i32,
14}
15
16fn normalised_percent(numerator: i32, denominator: i32) -> f64 {
17    if denominator > 0 {
18        numerator as f64 / denominator as f64
19    } else {
20        0.0
21    }
22}
23
24impl Translation {
25    pub fn new(local: &str, foreign: &str) -> Translation {
26        Translation {
27            local: local.to_lowercase(),
28            foreign: foreign.to_lowercase(),
29            ..Default::default()
30        }
31    }
32
33    pub fn get_total_percent(&self) -> f64 {
34        normalised_percent(
35            self.guesses_local_correct + self.guesses_foreign_correct,
36            self.guesses_local_total + self.guesses_foreign_total,
37        )
38    }
39
40    pub fn guess_local(&mut self, guess: &str) -> bool {
41        self.guesses_local_total += 1;
42        if self.local.to_lowercase() == guess.to_lowercase() {
43            self.guesses_local_correct += 1;
44            true
45        } else {
46            false
47        }
48    }
49
50    pub fn guess_foreign(&mut self, guess: &str) -> bool {
51        self.guesses_foreign_total += 1;
52        if self.foreign.to_lowercase() == guess.to_lowercase() {
53            self.guesses_foreign_correct += 1;
54            true
55        } else {
56            false
57        }
58    }
59
60    pub fn reconcile(self, other: Translation) -> Result<Translation, VocabStoreError> {
61        // Don't reconcile different translations
62        if self.local != other.local || self.foreign != other.foreign {
63            return Err(VocabStoreError::ReconciliationError);
64        }
65
66        // Take whichever side has most guesses
67        let more_local = self.guesses_local_total > other.guesses_local_total;
68        let more_foreign = self.guesses_foreign_total > other.guesses_foreign_total;
69
70        let (guesses_local_total, guesses_local_correct) = if more_local {
71            (self.guesses_local_total, self.guesses_local_correct)
72        } else {
73            (other.guesses_local_total, other.guesses_local_correct)
74        };
75        let (guesses_foreign_total, guesses_foreign_correct) = if more_foreign {
76            (self.guesses_foreign_total, self.guesses_foreign_correct)
77        } else {
78            (other.guesses_foreign_total, other.guesses_foreign_correct)
79        };
80
81        Ok(Translation {
82            local: self.local,
83            foreign: self.foreign,
84            guesses_local_total,
85            guesses_local_correct,
86            guesses_foreign_total,
87            guesses_foreign_correct,
88        })
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::Translation;
95    use crate::VocabStoreError;
96
97    #[test]
98    fn test_guess_local() {
99        let mut translation = Translation::new("yes", "はい");
100        assert_eq!(translation.guess_local("yEs"), true);
101        assert_eq!(translation.guesses_local_total, 1);
102        assert_eq!(translation.guesses_local_correct, 1);
103        assert_eq!(translation.guesses_foreign_total, 0);
104        assert_eq!(translation.guesses_foreign_correct, 0);
105        assert_eq!(translation.get_total_percent(), 1.0);
106
107        assert_eq!(translation.guess_local("no"), false);
108        assert_eq!(translation.guesses_local_total, 2);
109        assert_eq!(translation.guesses_local_correct, 1);
110        assert_eq!(translation.guesses_foreign_total, 0);
111        assert_eq!(translation.guesses_foreign_correct, 0);
112        assert_eq!(translation.get_total_percent(), 0.5);
113    }
114
115    #[test]
116    fn test_guess_foreign() {
117        let mut translation = Translation::new("yes", "はい");
118        assert_eq!(translation.guess_foreign("はい"), true);
119        assert_eq!(translation.guesses_local_total, 0);
120        assert_eq!(translation.guesses_local_correct, 0);
121        assert_eq!(translation.guesses_foreign_total, 1);
122        assert_eq!(translation.guesses_foreign_correct, 1);
123        assert_eq!(translation.get_total_percent(), 1.0);
124
125        assert_eq!(translation.guess_foreign("いいえ"), false);
126        assert_eq!(translation.guesses_local_total, 0);
127        assert_eq!(translation.guesses_local_correct, 0);
128        assert_eq!(translation.guesses_foreign_total, 2);
129        assert_eq!(translation.guesses_foreign_correct, 1);
130        assert_eq!(translation.get_total_percent(), 0.5);
131    }
132
133    #[test]
134    fn test_reconcile() {
135        let mut old_translation = Translation::new("yes", "はい");
136        old_translation.guesses_local_correct = 4;
137        old_translation.guesses_local_total = 5;
138        old_translation.guesses_foreign_correct = 3;
139        old_translation.guesses_foreign_total = 5;
140        let mut new_translation = Translation::new("yes", "はい");
141        new_translation.guesses_local_correct = 4;
142        new_translation.guesses_local_total = 4;
143        new_translation.guesses_foreign_correct = 6;
144        new_translation.guesses_foreign_total = 6;
145        let reconciled_translation = old_translation.reconcile(new_translation).unwrap();
146
147        assert_eq!(reconciled_translation.local, "yes");
148        assert_eq!(reconciled_translation.foreign, "はい");
149        assert_eq!(reconciled_translation.guesses_local_correct, 4);
150        assert_eq!(reconciled_translation.guesses_local_total, 5);
151        assert_eq!(reconciled_translation.guesses_foreign_correct, 6);
152        assert_eq!(reconciled_translation.guesses_foreign_total, 6);
153
154        let mut old_translation = Translation::new("no", "いいえ");
155        let mut new_translation = Translation::new("japan", "日本");
156        match old_translation.reconcile(new_translation) {
157            Err(VocabStoreError::ReconciliationError) => {}
158            _ => assert!(false, "VocabStore did not return ReconciliationError error"),
159        }
160    }
161}