hirofa_utils/
auto_id_map.rs

1use rand::{thread_rng, Rng};
2use std::collections::HashMap;
3
4/// AutoIdMap is a wrapper around HashMap which automatically creates a unique id for it's entries
5/// # Example
6/// ```no_run
7///
8/// use hirofa_utils::auto_id_map::AutoIdMap;
9/// let mut map = AutoIdMap::new();
10/// let id1 = map.insert("hi");
11/// let id2 = map.insert("hi2");
12/// assert_ne!(id1, id2);
13/// assert_eq!(map.len(), 2);
14/// let s1 = map.remove(&id1);
15/// assert_eq!(s1, "hi");
16/// assert_eq!(map.len(), 1);
17/// ```
18pub struct AutoIdMap<T> {
19    max_size: usize,
20    pub map: HashMap<usize, T>,
21}
22
23impl<T> AutoIdMap<T> {
24    /// create a new instance of the AutoIdMap
25    pub fn new() -> AutoIdMap<T> {
26        Self::new_with_max_size(usize::MAX)
27    }
28
29    pub fn new_with_max_size(max_size: usize) -> AutoIdMap<T> {
30        AutoIdMap {
31            max_size,
32            map: HashMap::new(),
33        }
34    }
35
36    pub fn foreach_value<F: Fn(&T)>(&self, f: F) {
37        for i in self.map.values() {
38            f(i);
39        }
40    }
41
42    pub fn foreach_value_mut<F: Fn(&mut T)>(&mut self, f: F) {
43        for i in self.map.values_mut() {
44            f(i);
45        }
46    }
47
48    pub fn foreach<F: Fn(&usize, &T)>(&self, f: F) {
49        for i in &self.map {
50            f(i.0, i.1);
51        }
52    }
53
54    pub fn clear(&mut self) {
55        self.map.clear();
56    }
57
58    pub fn remove_values<F: Fn(&T) -> bool>(&mut self, f: F) -> Vec<T> {
59        let mut rems = vec![];
60        let mut rem_keys = vec![];
61        {
62            for i in self.map.iter() {
63                if f(i.1) {
64                    rem_keys.push(*i.0);
65                }
66            }
67        }
68        for k in rem_keys {
69            rems.push(self.map.remove(&k).unwrap());
70        }
71        rems
72    }
73
74    pub fn contains_value<F: Fn(&T) -> bool>(&self, f: F) -> bool {
75        for v in self.map.values() {
76            if f(v) {
77                return true;
78            }
79        }
80        false
81    }
82
83    /// insert an element and return the new id
84    pub fn insert(&mut self, elem: T) -> usize {
85        self.try_insert(elem).expect("map is full")
86    }
87
88    /// insert an element and return the new id
89    pub fn try_insert(&mut self, elem: T) -> Result<usize, &str> {
90        if self.map.len() >= self.max_size {
91            Err("AutoIdMap is full")
92        } else {
93            let mut id = thread_rng().gen_range(0..self.max_size);
94
95            while self.map.contains_key(&id) {
96                if id >= self.max_size - 1 {
97                    id = 0;
98                } else {
99                    id += 1;
100                }
101            }
102
103            self.map.insert(id, elem);
104            Ok(id)
105        }
106    }
107
108    /// replace an element, this will panic if you pass an id that is not present
109    #[allow(clippy::trivially_copy_pass_by_ref)]
110    pub fn replace(&mut self, id: &usize, elem: T) {
111        // because we really don't want you to abuse this to insert your own id's :)
112        if !self.contains_key(id) {
113            panic!("no entry to replace for {}", id);
114        }
115        self.map.insert(*id, elem);
116    }
117
118    /// get an element based on it's id
119    #[allow(clippy::trivially_copy_pass_by_ref)]
120    pub fn get(&self, id: &usize) -> Option<&T> {
121        self.map.get(id)
122    }
123
124    /// get an element based on it's id
125    #[allow(clippy::trivially_copy_pass_by_ref)]
126    pub fn get_mut(&mut self, id: &usize) -> Option<&mut T> {
127        self.map.get_mut(id)
128    }
129
130    /// remove an element based on its id
131    #[allow(clippy::trivially_copy_pass_by_ref)]
132    pub fn remove(&mut self, id: &usize) -> T {
133        self.map.remove(id).expect("no such elem")
134    }
135
136    /// remove an element based on its id
137    #[allow(clippy::trivially_copy_pass_by_ref)]
138    pub fn remove_opt(&mut self, id: &usize) -> Option<T> {
139        self.map.remove(id)
140    }
141
142    /// get the size of the map
143    #[allow(dead_code)]
144    pub fn len(&self) -> usize {
145        self.map.len()
146    }
147
148    /// see if map is empty
149    #[allow(dead_code)]
150    pub fn is_empty(&self) -> bool {
151        self.map.is_empty()
152    }
153
154    /// check if a map contains a certain id
155    #[allow(clippy::trivially_copy_pass_by_ref)]
156    pub fn contains_key(&self, id: &usize) -> bool {
157        self.map.contains_key(id)
158    }
159}
160
161impl<T> Default for AutoIdMap<T> {
162    fn default() -> Self {
163        AutoIdMap::new()
164    }
165}
166
167#[cfg(test)]
168pub mod tests {
169    use crate::auto_id_map::AutoIdMap;
170
171    #[test]
172    fn test_aim() {
173        let mut map = AutoIdMap::new_with_max_size(8);
174        for _x in 0..8 {
175            map.insert("foo");
176        }
177        assert_eq!(map.len(), 8);
178        map.remove(&5);
179        let free_id = map.insert("fail?");
180
181        assert_eq!(free_id, 5);
182    }
183
184    #[test]
185    fn test_aim_ms() {
186        let mut map = AutoIdMap::new_with_max_size(8);
187        for _x in 0..8 {
188            map.insert("foo");
189        }
190        assert_eq!(map.len(), 8);
191        map.remove(&5);
192        let free_id = map.insert("fail?");
193
194        assert_eq!(free_id, 5);
195
196        let res = map.try_insert("foobar");
197        // should be full
198        assert!(res.is_err());
199    }
200}