grouper_lib/
lib.rs

1//
2////////////////////////
3// Handles os-based i/o
4////////////////////////
5pub mod files {
6    use crate::grouper;
7    use crate::models::{Student, StudentBuilder};
8    use csv::StringRecord;
9    use serde::Deserialize;
10    use std::io::Read;
11    use std::net::TcpStream;
12    use std::path::Path;
13    use std::{
14        env,
15        fs::{create_dir, read_dir, remove_file, File},
16        io::Write,
17    };
18
19    #[derive(Deserialize, Clone)]
20    pub struct FileHandler {
21        pub temp_path: String,
22    }
23
24    impl Default for FileHandler {
25        fn default() -> Self {
26            Self::new()
27        }
28    }
29
30    impl FileHandler {
31        pub fn new() -> FileHandler {
32            FileHandler {
33                temp_path: format!(
34                    "{}grouper-data",
35                    env::temp_dir().to_string_lossy().into_owned()
36                ),
37            }
38        }
39
40        //
41
42        pub fn init_base_dir(&self) -> std::io::Result<()> {
43            let path = self.get_temp_path();
44            create_dir(path)?;
45            Ok(())
46        }
47
48        //
49
50        pub fn read_and_return_json(
51            &self,
52            filename: &str,
53        ) -> Result<String, Box<dyn std::error::Error>> {
54            let full_path = self.get_full_path(filename);
55
56            let file = File::open(full_path).expect("Failed to read json file");
57
58            let file_contents = Self::file_to_string(file);
59            Ok(file_contents)
60        }
61
62        //
63
64        pub fn read_and_return_students(&self, filename: &str) -> Result<Vec<Student>, ()> {
65            let full_path = self.get_full_path(filename);
66            let file = File::open(full_path).expect("Failed to read json file");
67
68            let file_contents = Self::file_to_string(file);
69
70            if let Ok(result) = grouper::Utils::students_from_json(&file_contents) {
71                Ok(result)
72            } else {
73                Err(())
74            }
75        }
76
77        //
78
79        fn file_to_string(mut file: File) -> String {
80            let mut file_contents = String::new();
81
82            file.read_to_string(&mut file_contents)
83                .expect("Failed to read json file.");
84            file_contents
85        }
86
87        //
88
89        fn get_full_path(&self, filename: &str) -> String {
90            let temp_path = self.get_temp_path();
91            format!("{}\\{}", &temp_path, &filename)
92        }
93
94        //
95
96        pub fn write_json(
97            &self,
98            data: Vec<Student>,
99            filename: &str,
100        ) -> Result<String, Box<dyn std::error::Error>> {
101            if let false = self.check_for_dir() {
102                println!("Creating base directory for Grouper Desktop user.");
103                self.init_base_dir().unwrap();
104            } else {
105                println!("Base directory found. Proceeding to write file.");
106            }
107            let write_path = format!("{}\\{}.json", self.get_temp_path(), filename);
108            let mut file = File::create(&write_path)?;
109
110            let json = serde_json::to_string(&data)?;
111            file.write_all(json.as_bytes())?;
112
113            Ok(format!(
114                "SUCCESS writing JSON to temp directory ::: @ ::: {}",
115                write_path
116            ))
117        }
118
119        //
120
121        pub fn delete_file(&self, filename: &str) -> Result<String, Box<dyn std::error::Error>> {
122            let path = self.get_temp_path();
123            let full_path = format!("{}\\{}", &path, &filename);
124            match remove_file(full_path) {
125                Ok(_) => Ok("File deleted successfully".to_string()),
126                Err(e) => Err(format!("Error deleting file: {}", e).into()),
127            }
128        }
129
130        //
131
132        fn get_temp_path(&self) -> String {
133            self.temp_path.clone()
134        }
135
136        //
137
138        fn check_for_dir(&self) -> bool {
139            let temp = &self.get_temp_path();
140            let path = Path::new(temp);
141            path.is_dir()
142        }
143
144        //
145
146        pub fn read_directory(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
147            let path = self.get_temp_path();
148            let dir = read_dir(path).unwrap();
149
150            let mut file_list: Vec<String> = Vec::new();
151
152            for file in dir {
153                let file_name = file.unwrap().file_name();
154                println!("{:?}", file_name);
155                file_list.push(file_name.into_string().expect("Failed to parse file name."));
156            }
157
158            Ok(file_list)
159        }
160        //
161        pub fn student_from_record(idx: usize, row: Result<StringRecord, csv::Error>) -> Student {
162            let r = row.expect("Unable to parse string record");
163
164            fn parse_avg(r: StringRecord) -> f32 {
165                r.get(42).unwrap().parse::<f32>().unwrap_or(0.0_f32)
166            }
167
168            StudentBuilder::new()
169                .id(idx as u32)
170                .name(r.get(0).unwrap().to_string())
171                .email(r.get(2).unwrap().to_string())
172                .avg(parse_avg(r))
173                .group(0)
174                .build()
175        }
176        //
177
178        pub fn network_available() -> bool {
179            TcpStream::connect("8.8.8.8:53").is_ok()
180        }
181
182        //
183
184        pub fn temp_data_available(&self) -> bool {
185            let path = format!("{}{}", self.get_temp_path(), "\\grouper-students.json");
186            let file_path = Path::new(&path);
187            println!("{}", &path);
188            if let true = file_path.exists() {
189                println!("path found");
190                return true;
191            }
192            false
193        }
194    }
195}
196
197//////////////////
198/// Handles student group manipulations
199//////////////////
200pub mod grouper {
201
202    use crate::err_handle::{self};
203    use crate::models::Student;
204    use rand::Rng;
205    use std::collections::{BTreeMap, HashMap};
206
207    // Main Handler - Balancer
208
209    type Students = Vec<Student>;
210
211    //
212    // Collection Transformation State
213    //
214    #[derive(Debug, Clone)]
215    pub struct GroupsMap(BTreeMap<u16, Vec<Student>>);
216
217    impl GroupsMap {
218        pub fn new(num_students: u16, group_size: u16) -> Self {
219            let tree_map = BTreeMap::new();
220            let mut new_map = GroupsMap(tree_map);
221            new_map.populate(num_students, group_size);
222            new_map
223        }
224
225        fn populate(&mut self, num_students: u16, group_size: u16) -> &mut Self {
226            let num_groups: u16 = (num_students as f32 / group_size as f32).floor() as u16;
227
228            for num in 1..=num_groups {
229                self.0.insert(num, vec![]);
230            }
231            self
232        }
233    }
234
235    //
236    // Utilities / Auxiliaries
237    //
238    #[derive(Debug, Clone, Copy)]
239    pub struct Utils;
240
241    impl Utils {
242        //
243
244        fn rand_idx(vec_length: &usize) -> usize {
245            let mut rng = rand::thread_rng();
246            rng.gen_range(0..*vec_length)
247        }
248
249        //
250
251        pub fn num_groups(num_students: u16, group_size: u16) -> u16 {
252            let res: f32 = num_students as f32 / group_size as f32;
253            res.floor() as u16
254        }
255
256        //
257
258        fn mean(floats: &[f32]) -> f32 {
259            floats.iter().fold(0 as f32, |acc, n| acc + n) / floats.len() as f32
260        }
261
262        //
263
264        fn diffs(floats: &[f32], mean: &f32) -> Vec<f32> {
265            floats.iter().fold(vec![], |mut acc: Vec<f32>, &val| {
266                acc.push((val - mean).abs());
267                acc
268            })
269        }
270
271        //
272
273        fn square_all(floats: &[f32]) -> Vec<f32> {
274            floats.iter().map(|float| float.powi(2)).collect()
275        }
276
277        //
278
279        fn round_to_dec_count(value: f32, dec_count: i32) -> f32 {
280            let multi = 10.0_f32.powi(dec_count);
281            (value * multi).round() / multi
282        }
283
284        //
285
286        pub fn std_dev(floats: Vec<f32>) -> f32 {
287            // 1. Calculate the mean of the vector.
288            let mean = Self::mean(&floats);
289            // 2. Calculate the difference between each element of the vector and the mean.
290            let differences = Self::diffs(&floats, &mean);
291            // 3. Square the differences.
292            let all_squared: Vec<f32> = Self::square_all(&differences);
293            // 4. Calculate the mean of the squared differences.
294            let mean_of_squared: f32 = Self::mean(&all_squared);
295            // 5. Take the square root of the mean of the squared differences to get the standard deviation.
296            let sd: f32 = mean_of_squared.sqrt();
297
298            sd
299        }
300
301        //
302
303        pub fn sort_students(vec_of_students: &Students) -> Students {
304            let mut students = vec_of_students.clone();
305            students.sort_by(|a, b| a.avg.partial_cmp(&b.avg).unwrap());
306            students
307        }
308
309        //
310
311        pub fn group_avgs_map(groups: &GroupsMap) -> HashMap<u16, f32> {
312            let mut map = HashMap::new();
313
314            for (k, v) in groups.0.clone().into_iter() {
315                let group_avg = v.iter().fold(0 as f32, |mut acc, val| {
316                    acc += val.avg;
317                    acc
318                }) / v.len() as f32;
319                map.entry(k).or_insert(group_avg);
320            }
321
322            map
323        }
324
325        //
326
327        pub fn group_avgs_vec(map: HashMap<u16, f32>) -> Vec<f32> {
328            let mut avgs = vec![];
329
330            for (_, v) in map.into_iter() {
331                avgs.push(Self::round_to_dec_count(v, 2));
332            }
333
334            avgs
335        }
336
337        //
338
339        pub fn send_group_avgs(groups_json: String) -> Result<String, err_handle::Error> {
340            let data: Students =
341                serde_json::from_str(&groups_json).expect("Failed to parse vector from json ...");
342            println!("{:?}", data);
343
344            Ok("".into())
345        }
346
347        //
348
349        pub fn students_from_json(json_str: &str) -> Result<Vec<Student>, err_handle::Error> {
350            let people: Vec<Student> = serde_json::from_str(json_str)
351                .expect("Failed to parse students from json string ... ");
352            Ok(people)
353        }
354
355        //
356
357        pub fn treemap_to_json(
358            groups: BTreeMap<u16, Vec<Student>>,
359        ) -> Result<String, Box<dyn std::error::Error>> {
360            let json = serde_json::to_string(&groups)?;
361            Ok(json)
362        }
363
364        //
365
366        pub fn groups_from_json(
367            json_str: &str,
368        ) -> Result<BTreeMap<u16, Vec<Student>>, err_handle::Error> {
369            let groups: BTreeMap<u16, Vec<Student>> = serde_json::from_str(json_str)
370                .expect("Failed to parse groups from json string ... ");
371            Ok(groups)
372        }
373
374        //
375
376        fn get_random_student(students: &mut Students) -> (&mut Student, usize) {
377            let rand_idx = Self::rand_idx(&students.len());
378            let rand_student = &mut students[rand_idx];
379            (rand_student, rand_idx)
380        }
381
382        pub fn random_assignment(
383            current: u16,
384            mut students: Students,
385            mut groups_map: GroupsMap,
386            num_groups: u16,
387        ) -> GroupsMap {
388            if students.len() == 0 {
389                return groups_map;
390            };
391
392            let (random_student, rand_idx) = Self::get_random_student(&mut students);
393
394            let mut current_group = current;
395            random_student.set_group(current_group);
396
397            let mut new_vec = groups_map.0.get(&current_group).unwrap().clone();
398            new_vec.push(random_student.to_owned());
399
400            groups_map.0.insert(current_group, new_vec);
401
402            if current_group == num_groups {
403                current_group = 1;
404            } else {
405                current_group += 1;
406            }
407
408            students.remove(rand_idx);
409
410            Self::random_assignment(current_group, students, groups_map, num_groups)
411
412            //
413        }
414
415        //
416
417        fn balance(
418            students: Vec<Student>,
419            group_size: u16,
420            _target_sd: u8,
421        ) -> BTreeMap<u16, Vec<Student>> {
422            //
423            let groups_map = GroupsMap::new(students.len() as u16, group_size);
424            let sorted = Self::sort_students(&students);
425            let num_groups = Self::num_groups(sorted.len() as u16, group_size);
426            //
427
428            Self::random_assignment(1, sorted, groups_map, num_groups).0
429
430            //
431        }
432
433        //
434
435        pub fn multi_balance(
436            _num_workers: u8,
437            students: Vec<Student>,
438            group_size: u16,
439            target_sd: u8,
440        ) -> BTreeMap<u16, Vec<Student>> {
441            //
442            // TODO
443            // Implement multi-threading to batch job too large for single stack.
444            // - Assign the same work to all threads.
445            // - Use whichever result returns first and dispense with operations on busy threads?
446            // - Probably a bad idea ....
447
448            Self::balance(students, group_size, target_sd)
449
450            //
451        }
452
453        //
454    }
455
456    #[cfg(test)]
457    mod tests {
458        use super::Utils;
459
460        #[test]
461        fn test_standard_deviation() {
462            let test_vector = vec![
463                // Each group's average as f32
464                79.08, 83.15, 96.23, 85.11, 90.73, 77.79, 80.34,
465            ];
466            // 1. Calculate the mean of the vector.
467            let mean = Utils::mean(&test_vector);
468            // 2. Calculate the difference between each element of the vector and the mean.
469            let differences = Utils::diffs(&test_vector, &mean);
470            // 3. Square the differences.
471            let all_squared: Vec<f32> = Utils::square_all(&differences);
472            // 4. Calculate the mean of the squared differences.
473            let mean_of_squared: f32 = Utils::mean(&all_squared);
474            // 5. Take the square root of the mean of the squared differences to get the standard deviation.
475            let sd: f32 = mean_of_squared.sqrt();
476
477            assert_eq!(sd, 6.2126956 as f32);
478        }
479    }
480}
481
482//////////////////
483/// Builders
484//////////////////
485pub mod models {
486    use serde::{Deserialize, Serialize};
487
488    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
489    pub struct Student {
490        id: u32,
491        name: String,
492        pub avg: f32,
493        group: u16,
494        email: String,
495    }
496    impl Student {
497        pub fn set_group(&mut self, g: u16) {
498            self.group = g;
499        }
500    }
501
502    pub struct StudentBuilder {
503        id: Option<u32>,
504        name: Option<String>,
505        avg: Option<f32>,
506        group: Option<u16>,
507        email: Option<String>,
508    }
509
510    impl Default for StudentBuilder {
511        fn default() -> Self {
512            Self::new()
513        }
514    }
515
516    impl StudentBuilder {
517        pub fn new() -> Self {
518            StudentBuilder {
519                id: None,
520                name: None,
521                avg: None,
522                group: None,
523                email: None,
524            }
525        }
526
527        pub fn id(mut self, id: u32) -> Self {
528            self.id = Some(id);
529            self
530        }
531
532        pub fn name(mut self, name: String) -> Self {
533            self.name = Some(name);
534            self
535        }
536
537        pub fn avg(mut self, avg: f32) -> Self {
538            self.avg = Some(avg);
539            self
540        }
541
542        pub fn group(mut self, group: u16) -> Self {
543            self.group = Some(group);
544            self
545        }
546
547        pub fn email(mut self, email: String) -> Self {
548            self.email = Some(email);
549            self
550        }
551
552        pub fn build(self) -> Student {
553            Student {
554                id: self.id.unwrap(),
555                name: self.name.unwrap(),
556                avg: self.avg.unwrap(),
557                group: self.group.unwrap(),
558                email: self.email.unwrap(),
559            }
560        }
561    }
562}
563
564//////////////////
565/// Multithreading
566//////////////////
567pub mod mutant {}
568
569//////////////////
570/// Error Handling
571//////////////////
572pub mod err_handle {
573    use std::fmt;
574
575    #[derive(Debug, thiserror::Error)]
576
577    pub enum Error {
578        #[error(transparent)]
579        Io(#[from] std::io::Error),
580        Std(#[from] Box<dyn std::error::Error>),
581    }
582
583    impl serde::Serialize for Error {
584        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
585        where
586            S: serde::ser::Serializer,
587        {
588            serializer.serialize_str(self.to_string().as_ref())
589        }
590    }
591
592    impl fmt::Display for Error {
593        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
594            write!(f, "Error reading file ... ")
595        }
596    }
597}
598
599pub use err_handle::Error;
600pub use files::FileHandler;
601pub use grouper::{GroupsMap, Utils};
602pub use models::{Student, StudentBuilder};