buddy_up_lib/algorithm/
history.rs

1use crate::BuddyError;
2use crate::Person;
3use glob::glob;
4use serde::Deserialize;
5use serde::Serialize;
6use std::collections::HashMap;
7use tracing::debug;
8
9/// Contains history of past pairings
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct History {
12    map: HashMap<(usize, usize), usize>,
13    #[serde(skip)]
14    stats: HistoryStats,
15}
16
17impl Default for History {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl History {
24    /// Usually the history is saved in some directory. Give it a directory name to read that
25    /// history. If you're just starting out, give it the desired directory. If it doesn't exist,
26    /// it will be created.
27    pub fn from_dir(dir: &str) -> Result<Self, BuddyError> {
28        let mut history = Self::new();
29
30        // read pairs from dir of files
31        let pattern = format!("{dir}/*.json");
32        for path in glob(&pattern)? {
33            debug!("Reading history file {path:?}");
34            let pairs = std::fs::read_to_string(path?)?;
35            let pairs: Vec<(Person, Person)> = serde_json::from_str(&pairs)?;
36            let pairs = pairs.iter().map(|p| (p.0.id, p.1.id)).collect();
37            history.stats.files_read += 1;
38            merge(&mut history, &pairs);
39        }
40        history.stats.pairs = history.len();
41        Ok(history)
42    }
43
44    #[allow(dead_code)]
45    fn max_iteration(&self) -> usize {
46        *self.map.values().max().unwrap_or(&0)
47    }
48
49    fn new() -> Self {
50        let scores = HashMap::new();
51        Self {
52            map: scores,
53            stats: HistoryStats::default(),
54        }
55    }
56
57    pub fn stats(&self) -> HistoryStats {
58        self.stats
59    }
60
61    fn insert(&mut self, pair: (usize, usize), iteration: usize) {
62        // if the first variation of the pair exists, insert it there; if not
63        // it doesn't really matter if the other one exists, just insert it as the other.
64        // Whether that one exists or not, it'll insert there.
65        if self.map.contains_key(&pair) {
66            self.map.insert(pair, iteration);
67        } else {
68            self.map.insert((pair.1, pair.0), iteration);
69        }
70    }
71
72    pub fn len(&self) -> usize {
73        self.map.len()
74    }
75
76    pub fn is_empty(&self) -> bool {
77        self.len() == 0
78    }
79
80    fn contains(&self, pair: &(usize, usize)) -> bool {
81        self.map.contains_key(pair) || self.map.contains_key(&(pair.1, pair.0))
82    }
83
84    pub fn get(&self, pair: (usize, usize)) -> Option<usize> {
85        Some(match self.map.get(&pair) {
86            Some(x) => *x,
87            None => *self.map.get(&(pair.1, pair.0))?,
88        })
89    }
90
91    pub fn min(&self) -> usize {
92        *self.map.values().min().unwrap_or(&0)
93    }
94    pub fn max(&self) -> usize {
95        *self.map.values().max().unwrap_or(&0)
96    }
97}
98
99fn merge(history: &mut History, pairs: &Vec<(usize, usize)>) {
100    for p in pairs {
101        if history.contains(p) {
102            let it = history.get(*p).unwrap();
103
104            history.insert(*p, it + 1);
105        } else {
106            history.insert(*p, 1);
107        }
108    }
109}
110
111/// Saves some stats about the [`History`].
112#[derive(Debug, Copy, Clone, Default)]
113pub struct HistoryStats {
114    /// How many files of history were read
115    pub files_read: usize,
116
117    /// How many existing [`Pairs`][crate::Pairs] are in the history.
118    pub pairs: usize,
119}
120
121#[cfg(test)]
122mod test {
123    use super::*;
124
125    #[test]
126    fn test_max_iteration_empty_history() {
127        let h = History::new();
128        assert_eq!(h.max_iteration(), 0);
129    }
130    #[test]
131    fn test_max_iteration() {
132        let mut h = History::new();
133        h.insert((1, 2), 4);
134        assert_eq!(h.max_iteration(), 4);
135    }
136
137    #[test]
138    fn test_merge() {
139        let mut h = History::new();
140        let pairs = vec![(1, 2)];
141        merge(&mut h, &pairs);
142        assert_eq!(h.max_iteration(), 1);
143        assert_eq!(h.len(), 1);
144
145        // if we insert it again, like for another run, we expect the iteration to be incremented
146        // by one
147        merge(&mut h, &pairs);
148        assert_eq!(h.max_iteration(), 2);
149        assert_eq!(h.len(), 1);
150    }
151
152    #[test]
153    fn test_merge_same() {
154        let mut h = History::new();
155
156        let pairs = vec![(1, 2)];
157        let pairs2 = vec![(2, 1)];
158        merge(&mut h, &pairs);
159        assert_eq!(h.max_iteration(), 1);
160        assert_eq!(h.len(), 1);
161        merge(&mut h, &pairs2);
162        assert_eq!(h.max_iteration(), 2);
163        assert_eq!(h.len(), 1);
164    }
165
166    #[test]
167    fn test_contains_either_order() {
168        let mut h = History::new();
169        let pairs = vec![(1, 2)];
170        merge(&mut h, &pairs);
171        assert_eq!(h.max_iteration(), 1);
172        assert_eq!(h.len(), 1);
173
174        // make both orders of pairs and see if they return the same value
175        let pair1 = h.contains(&(1, 2));
176        let pair2 = h.contains(&(2, 1));
177        assert!(pair1);
178        assert!(pair2);
179        assert_eq!(h.len(), 1);
180    }
181
182    #[test]
183    fn test_insert_same_pair() {
184        let mut h = History::new();
185        let pair1 = (1, 2);
186        let pair2 = (2, 1);
187        h.insert(pair1, 1);
188        assert_eq!(h.len(), 1);
189        h.insert(pair2, 2);
190        assert_eq!(h.len(), 1);
191    }
192
193    #[test]
194    fn test_get_either_order() {
195        let mut h = History::new();
196        let pairs = vec![(1, 2)];
197        merge(&mut h, &pairs);
198        assert_eq!(h.max_iteration(), 1);
199        assert_eq!(h.len(), 1);
200
201        // make both orders of pairs and see if they return the same value
202        let pair1 = h.get((1, 2));
203        let pair2 = h.get((2, 1));
204        assert_eq!(pair1, Some(1));
205        assert_eq!(pair2, Some(1));
206        assert_eq!(h.len(), 1);
207    }
208}