use crate::error::Result;
use crate::string::FastStr;
use std::collections::HashMap;
#[derive(Debug)]
pub struct HashStrMap<V> {
map: HashMap<String, V>,
total_inserts: usize,
unique_keys: usize,
}
impl<V> HashStrMap<V> {
pub fn new() -> Self {
Self {
map: HashMap::new(),
total_inserts: 0,
unique_keys: 0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
map: HashMap::with_capacity(capacity),
total_inserts: 0,
unique_keys: 0,
}
}
pub fn insert(&mut self, key: &str, value: V) -> Result<Option<V>> {
let key_string = key.to_string();
let was_new = !self.map.contains_key(&key_string);
let result = self.map.insert(key_string, value);
self.total_inserts += 1;
if was_new {
self.unique_keys += 1;
}
Ok(result)
}
pub fn insert_string(&mut self, key: String, value: V) -> Result<Option<V>> {
let was_new = !self.map.contains_key(&key);
let result = self.map.insert(key, value);
self.total_inserts += 1;
if was_new {
self.unique_keys += 1;
}
Ok(result)
}
#[inline]
pub fn get(&self, key: &str) -> Option<&V> {
self.map.get(key)
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut V> {
self.map.get_mut(key)
}
pub fn remove(&mut self, key: &str) -> Option<V> {
self.map.remove(key)
}
pub fn contains_key(&self, key: &str) -> bool {
self.map.contains_key(key)
}
#[inline]
pub fn len(&self) -> usize {
self.map.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn is_interned(&self, s: &str) -> bool {
self.map.contains_key(s)
}
pub fn interning_ratio(&self) -> f64 {
if self.total_inserts == 0 {
0.0
} else {
1.0 - (self.unique_keys as f64 / self.total_inserts as f64)
}
}
pub fn string_memory_usage(&self) -> usize {
self.map.keys().map(|k| k.len()).sum()
}
pub fn get_by_fast_str(&self, key: &FastStr) -> Option<&V> {
if let Some(s) = key.as_str() {
self.get(s)
} else {
None
}
}
pub fn insert_fast_str(&mut self, key: FastStr, value: V) -> Result<Option<V>> {
if let Some(s) = key.as_str() {
self.insert(s, value)
} else {
self.insert(&String::from_utf8_lossy(key.as_bytes()), value)
}
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &V)> {
self.map.iter()
}
pub fn values(&self) -> impl Iterator<Item = &V> {
self.map.values()
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.map.keys()
}
pub fn clear(&mut self) {
self.map.clear();
self.total_inserts = 0;
self.unique_keys = 0;
}
pub fn clear_all(&mut self) {
self.clear();
}
pub fn shrink_to_fit(&mut self) {
self.map.shrink_to_fit();
}
pub fn statistics(&self) -> HashStrMapStats {
HashStrMapStats {
entries: self.len(),
total_strings: self.total_inserts,
unique_strings: self.unique_keys,
interning_ratio: self.interning_ratio(),
string_memory: self.string_memory_usage(),
map_memory: self.map.capacity()
* (std::mem::size_of::<String>() + std::mem::size_of::<V>()),
}
}
}
#[derive(Debug, Clone)]
pub struct HashStrMapStats {
pub entries: usize,
pub total_strings: usize,
pub unique_strings: usize,
pub interning_ratio: f64,
pub string_memory: usize,
pub map_memory: usize,
}
impl<V> Default for HashStrMap<V> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() -> Result<()> {
let mut map = HashStrMap::new();
assert_eq!(map.insert("key1", 42)?, None);
assert_eq!(map.insert("key2", 43)?, None);
assert_eq!(map.len(), 2);
assert_eq!(map.get("key1"), Some(&42));
assert_eq!(map.get("key2"), Some(&43));
assert_eq!(map.get("key3"), None);
assert!(map.contains_key("key1"));
assert!(!map.contains_key("key3"));
assert_eq!(map.insert("key1", 44)?, Some(42));
assert_eq!(map.get("key1"), Some(&44));
assert_eq!(map.remove("key1"), Some(44));
assert_eq!(map.get("key1"), None);
assert_eq!(map.len(), 1);
Ok(())
}
#[test]
fn test_string_tracking() -> Result<()> {
let mut map = HashStrMap::new();
map.insert("user/john", 1)?;
map.insert("user/jane", 2)?;
map.insert("user/john", 3)?; map.insert("admin/root", 4)?;
let stats = map.statistics();
assert_eq!(stats.entries, 3); assert_eq!(stats.total_strings, 4); assert_eq!(stats.unique_strings, 3);
Ok(())
}
#[test]
fn test_fast_str_operations() -> Result<()> {
let mut map = HashStrMap::new();
let key = FastStr::from_string("fast_key");
map.insert_fast_str(key, 42)?;
assert_eq!(map.get("fast_key"), Some(&42));
Ok(())
}
#[test]
fn test_iterators() -> Result<()> {
let mut map = HashStrMap::new();
map.insert("a", 1)?;
map.insert("b", 2)?;
map.insert("c", 3)?;
let mut keys: Vec<&str> = map.keys().map(|k| k.as_str()).collect();
keys.sort();
assert_eq!(keys, vec!["a", "b", "c"]);
let mut values: Vec<&i32> = map.values().collect();
values.sort();
assert_eq!(values, vec![&1, &2, &3]);
assert_eq!(map.iter().count(), 3);
Ok(())
}
#[test]
fn test_statistics() -> Result<()> {
let mut map = HashStrMap::new();
for i in 0..100 {
let key = format!("key_{}", i % 10); map.insert(&key, i)?;
}
let stats = map.statistics();
assert_eq!(stats.entries, 10); assert_eq!(stats.total_strings, 100); assert_eq!(stats.unique_strings, 10); assert!(stats.interning_ratio > 0.8);
Ok(())
}
}