use rand::{thread_rng, Rng};
use std::collections::HashMap;
pub struct AutoIdMap<T> {
max_size: usize,
pub map: HashMap<usize, T>,
}
impl<T> AutoIdMap<T> {
pub fn new() -> AutoIdMap<T> {
Self::new_with_max_size(usize::MAX)
}
pub fn new_with_max_size(max_size: usize) -> AutoIdMap<T> {
AutoIdMap {
max_size,
map: HashMap::new(),
}
}
pub fn foreach_value<F: Fn(&T)>(&self, f: F) {
for i in self.map.values() {
f(i);
}
}
pub fn foreach_value_mut<F: Fn(&mut T)>(&mut self, f: F) {
for i in self.map.values_mut() {
f(i);
}
}
pub fn foreach<F: Fn(&usize, &T)>(&self, f: F) {
for i in &self.map {
f(i.0, i.1);
}
}
pub fn clear(&mut self) {
self.map.clear();
}
pub fn remove_values<F: Fn(&T) -> bool>(&mut self, f: F) -> Vec<T> {
let mut rems = vec![];
let mut rem_keys = vec![];
{
for i in self.map.iter() {
if f(i.1) {
rem_keys.push(*i.0);
}
}
}
for k in rem_keys {
rems.push(self.map.remove(&k).unwrap());
}
rems
}
pub fn contains_value<F: Fn(&T) -> bool>(&self, f: F) -> bool {
for v in self.map.values() {
if f(v) {
return true;
}
}
false
}
pub fn insert(&mut self, elem: T) -> usize {
self.try_insert(elem).expect("map is full")
}
pub fn try_insert(&mut self, elem: T) -> Result<usize, &str> {
if self.map.len() >= self.max_size {
Err("AutoIdMap is full")
} else {
let mut id = thread_rng().gen_range(0..self.max_size);
while self.map.contains_key(&id) {
if id >= self.max_size - 1 {
id = 0;
} else {
id += 1;
}
}
self.map.insert(id, elem);
Ok(id)
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn replace(&mut self, id: &usize, elem: T) {
if !self.contains_key(id) {
panic!("no entry to replace for {}", id);
}
self.map.insert(*id, elem);
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn get(&self, id: &usize) -> Option<&T> {
self.map.get(id)
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn get_mut(&mut self, id: &usize) -> Option<&mut T> {
self.map.get_mut(id)
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn remove(&mut self, id: &usize) -> T {
self.map.remove(id).expect("no such elem")
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn remove_opt(&mut self, id: &usize) -> Option<T> {
self.map.remove(id)
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.map.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn contains_key(&self, id: &usize) -> bool {
self.map.contains_key(id)
}
}
impl<T> Default for AutoIdMap<T> {
fn default() -> Self {
AutoIdMap::new()
}
}
#[cfg(test)]
pub mod tests {
use crate::auto_id_map::AutoIdMap;
#[test]
fn test_aim() {
let mut map = AutoIdMap::new_with_max_size(8);
for _x in 0..8 {
map.insert("foo");
}
assert_eq!(map.len(), 8);
map.remove(&5);
let free_id = map.insert("fail?");
assert_eq!(free_id, 5);
}
#[test]
fn test_aim_ms() {
let mut map = AutoIdMap::new_with_max_size(8);
for _x in 0..8 {
map.insert("foo");
}
assert_eq!(map.len(), 8);
map.remove(&5);
let free_id = map.insert("fail?");
assert_eq!(free_id, 5);
let res = map.try_insert("foobar");
assert!(res.is_err());
}
}