exms/exam/mod.rs
1mod parse;
2mod plot;
3mod statistics;
4mod student;
5
6use std::cmp::Ordering;
7use std::path::Path;
8
9use unidecode::unidecode;
10
11use crate::error::ParseError;
12use parse::parse_exam_file;
13use statistics::ExamStatistics;
14pub use student::Student;
15
16/// This type represents and exam.
17#[derive(Debug, Clone)]
18pub struct Exam {
19 title: Option<String>,
20 max_grade: f32,
21 students: Vec<Student>,
22 statistics: ExamStatistics,
23}
24
25impl Exam {
26 /// Creates a new `Exam` from a given set of students.
27 ///
28 /// # Examples
29 ///
30 /// ```
31 /// use exms::exam::Exam;
32 /// use exms::exam::Student;
33 ///
34 /// let students = &[
35 /// Student::new("Joan Beltrán Peris", 4.6),
36 /// Student::new("Jose Abad Martínez", 3.6),
37 /// Student::new("David Jiménez Hidalgo", 7.94),
38 /// ];
39 ///
40 /// let exam = Exam::new(students);
41 /// ```
42 pub fn new(students: impl Into<Vec<Student>>) -> Self {
43 let mut students = students.into();
44 let statistics = ExamStatistics::new(&mut students, 10.0);
45
46 Self {
47 title: None,
48 max_grade: 10.0,
49 students,
50 statistics,
51 }
52 }
53
54 /// Creates a new `Exam` from a given file.
55 /// The file formats suppported for the moment are only JSON and TOML files.
56 /// The file should have a student object with all the students and their
57 /// grades as key/value pairs. For more information about wich format
58 /// a file should follow, please see [exms](crate).
59 ///
60 /// # Examples
61 ///
62 /// ```no_run
63 /// use std::path::Path;
64 ///
65 /// use exms::error::ParseError;
66 /// use exms::exam::Exam;
67 ///
68 /// fn main() -> Result<(), ParseError> {
69 /// let file_path = Path::new("students.json");
70 /// let exam = Exam::from_file(&file_path)?;
71 ///
72 /// Ok(())
73 /// }
74 /// ```
75 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, ParseError> {
76 parse_exam_file(path.as_ref())
77 }
78
79 /// Sets the maximum achievable grade in the exam.
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use exms::exam::Exam;
85 /// use exms::exam::Student;
86 ///
87 /// let students = &[
88 /// Student::new("Joan Beltrán Peris", 4.6),
89 /// Student::new("Jose Abad Martínez", 3.6),
90 /// Student::new("David Jiménez Hidalgo", 7.94),
91 /// ];
92 ///
93 /// let mut exam = Exam::new(students);
94 /// exam.set_max_grade(6.0);
95 /// ```
96 pub fn set_max_grade(&mut self, max_grade: f32) {
97 self.max_grade = max_grade;
98 self.statistics = ExamStatistics::new(&mut self.students, max_grade);
99 }
100
101 /// Sets the title of the exam.
102 ///
103 /// # Examples
104 ///
105 /// ```
106 /// use exms::exam::Exam;
107 /// use exms::exam::Student;
108 ///
109 /// let students = &[
110 /// Student::new("Joan Beltrán Peris", 4.6),
111 /// Student::new("Jose Abad Martínez", 3.6),
112 /// Student::new("David Jiménez Hidalgo", 7.94),
113 /// ];
114 ///
115 /// let mut exam = Exam::new(students);
116 /// exam.set_title("Econometrics");
117 /// ```
118 pub fn set_title(&mut self, title: impl Into<String>) {
119 self.title = Some(title.into())
120 }
121
122 /// Sorts the exam students based on their grade in descending order.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// use exms::exam::Exam;
128 /// use exms::exam::Student;
129 ///
130 /// let students = &[
131 /// Student::new("Joan Beltrán Peris", 4.6),
132 /// Student::new("Jose Abad Martínez", 3.6),
133 /// Student::new("David Jiménez Hidalgo", 7.94),
134 /// ];
135 ///
136 /// let mut exam = Exam::new(students);
137 /// exam.sort_by_grade();
138 ///
139 /// assert_eq!(exam.students[0].grade, 7.94);
140 /// assert_eq!(exam.students[1].grade, 4.6);
141 /// assert_eq!(exam.students[2].grade, 3.6);
142 /// ```
143 pub fn sort_by_grade(&mut self) {
144 // Sort students by name so that students with the same grade are sorted
145 // alphabetically
146 Self::sort_by_alphabetic_order(self);
147
148 self.students
149 .sort_by(|a, b| b.grade.partial_cmp(&a.grade).unwrap_or(Ordering::Equal))
150 }
151
152 /// Sorts the exam students based on their name alphabetically.
153 ///
154 /// # Examples
155 ///
156 /// ```
157 /// use exms::exam::Exam;
158 /// use exms::exam::Student;
159 ///
160 /// let students = &[
161 /// Student::new("Joan Beltrán Peris", 4.6),
162 /// Student::new("Jose Abad Martínez", 3.6),
163 /// Student::new("David Jiménez Hidalgo", 7.94),
164 /// ];
165 ///
166 /// let mut exam = Exam::new(students);
167 /// exam.sort_by_alphabetic_order();
168 ///
169 /// assert_eq!(exam.students[0].name, "David Jiménez Hidalgo");
170 /// assert_eq!(exam.students[1].name, "Joan Beltrán Peris");
171 /// assert_eq!(exam.students[2].name, "Jose Abad Martínez");
172 /// ```
173 pub fn sort_by_alphabetic_order(&mut self) {
174 self.students
175 .sort_by_key(|s| unidecode(&s.name.to_lowercase()))
176 }
177
178 /// Filters the exam students yielding only the students which name contains
179 /// the given query
180 ///
181 /// # Examples
182 ///
183 /// ```
184 /// use exms::exam::Exam;
185 /// use exms::exam::Student;
186 ///
187 /// let students = &[
188 /// Student::new("Joan Beltrán Peris", 4.6),
189 /// Student::new("Jose Abad Martínez", 3.6),
190 /// Student::new("David Jiménez Hidalgo", 7.94),
191 /// ];
192 ///
193 /// let mut exam = Exam::new(students);
194 /// exam.filter_by_name(&["joan", "jorge", "jim"]);
195 ///
196 /// assert_eq!(exam.students.len(), 2);
197 /// assert_eq!(exam.students[0].name, "Joan Beltrán Peris");
198 /// assert_eq!(exam.students[1].name, "David Jiménez Hidalgo");
199 /// ```
200 pub fn filter_by_name<S: AsRef<str>>(&mut self, query: &[S]) {
201 self.students.retain(|student| {
202 query.iter().any(|name| {
203 student
204 .name
205 .to_lowercase()
206 .contains(&name.as_ref().to_lowercase())
207 })
208 });
209 }
210
211 /// Filters the exam students yielding only the students that are in the
212 /// given file. The file format should be the same as the one used in
213 /// [from_file](Exam::from_file).
214 ///
215 /// # Examples
216 ///
217 /// ```no_run
218 /// use std::path::Path;
219 ///
220 /// use exms::error::ParseError;
221 /// use exms::exam::Exam;
222 ///
223 /// fn main() -> Result<(), ParseError> {
224 /// let file_path = Path::new("students.json");
225 /// let mut exam = Exam::from_file(&file_path)?;
226 ///
227 /// let file_path = Path::new("students2.json");
228 /// exam.filter_by_file(&[file_path])?;
229 ///
230 /// Ok(())
231 /// }
232 /// ```
233 pub fn filter_by_file<P: AsRef<Path>>(&mut self, file_paths: &[P]) -> Result<(), ParseError> {
234 for path in file_paths {
235 let exam = parse_exam_file(path.as_ref())?;
236 let students = exam.students;
237
238 self.students.retain(|student| {
239 students
240 .iter()
241 .any(|s| s.name.to_lowercase() == student.name.to_lowercase())
242 });
243 }
244
245 Ok(())
246 }
247
248 /// Print the exam students in a well formatted table with some statistical
249 /// information about each student, like the percentile, the rank, etc...
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// use exms::exam::Exam;
255 /// use exms::exam::Student;
256 ///
257 /// let students = &[
258 /// Student::new("Joan Beltrán Peris", 4.6),
259 /// Student::new("Jose Abad Martínez", 3.6),
260 /// Student::new("David Jiménez Hidalgo", 7.94),
261 /// ];
262 ///
263 /// let mut exam = Exam::new(students);
264 /// exam.students();
265 /// ```
266 pub fn students(&self) {
267 self.statistics.students(&self.students)
268 }
269
270 /// Print statistical information about the exam in a well formatted table,
271 /// like the mean, total students, the exam pass percentage etc...
272 ///
273 /// # Examples
274 ///
275 /// ```
276 /// use exms::exam::Exam;
277 /// use exms::exam::Student;
278 ///
279 /// let students = &[
280 /// Student::new("Joan Beltrán Peris", 4.6),
281 /// Student::new("Jose Abad Martínez", 3.6),
282 /// Student::new("David Jiménez Hidalgo", 7.94),
283 /// ];
284 ///
285 /// let mut exam = Exam::new(students);
286 /// exam.summary();
287 /// ```
288 pub fn summary(&self) {
289 self.statistics.summary(&self.title)
290 }
291
292 /// Print a histogram of the exam grades.
293 ///
294 /// # Examples
295 ///
296 /// ```
297 /// use exms::exam::Exam;
298 /// use exms::exam::Student;
299 ///
300 /// let students = &[
301 /// Student::new("Joan Beltrán Peris", 4.6),
302 /// Student::new("Jose Abad Martínez", 3.6),
303 /// Student::new("David Jiménez Hidalgo", 7.94),
304 /// ];
305 ///
306 /// let mut exam = Exam::new(students);
307 /// exam.histogram();
308 /// ```
309 pub fn histogram(&self, step: Option<f64>) {
310 plot::histogram(&self.students, self.max_grade, step)
311 }
312}