#![doc = include_str!("../README.md")]
#[cfg(feature = "sync")]
pub mod _docs_sync {}
#[cfg(feature = "sharded")]
pub mod _docs_sharded {}
pub mod _sieve_algorithm {
}
pub mod _cache_implementation {
}
pub mod _implementation_choice {
}
fn _readme_examples_doctest() {
}
#[cfg(feature = "sync")]
pub mod _docs_sync_usage {
}
#[cfg(feature = "sharded")]
pub mod _docs_sharded_usage {
}
use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
#[cfg(feature = "sharded")]
mod sharded;
#[cfg(feature = "sync")]
mod sync;
#[cfg(feature = "sharded")]
pub use sharded::ShardedSieveCache;
#[cfg(feature = "sync")]
pub use sync::SyncSieveCache;
#[derive(Clone)]
struct Node<K: Eq + Hash + Clone, V> {
key: K,
value: V,
visited: bool,
}
impl<K: Eq + Hash + Clone, V> Node<K, V> {
fn new(key: K, value: V) -> Self {
Self {
key,
value,
visited: false,
}
}
}
#[cfg(feature = "sync")]
#[cfg(feature = "sharded")]
pub struct SieveCache<K: Eq + Hash + Clone, V> {
map: HashMap<K, usize>,
nodes: Vec<Node<K, V>>,
hand: Option<usize>,
capacity: usize,
}
impl<K: Eq + Hash + Clone, V> SieveCache<K, V> {
pub fn new(capacity: usize) -> Result<Self, &'static str> {
if capacity == 0 {
return Err("capacity must be greater than 0");
}
Ok(Self {
map: HashMap::with_capacity(capacity),
nodes: Vec::with_capacity(capacity),
hand: None,
capacity,
})
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
#[inline]
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
Q: Hash + Eq + ?Sized,
K: Borrow<Q>,
{
self.map.contains_key(key)
}
pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
where
Q: Hash + Eq + ?Sized,
K: Borrow<Q>,
{
if let Some(&idx) = self.map.get(key) {
self.nodes[idx].visited = true;
Some(&self.nodes[idx].value)
} else {
None
}
}
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
where
Q: Hash + Eq + ?Sized,
K: Borrow<Q>,
{
if let Some(&idx) = self.map.get(key) {
self.nodes[idx].visited = true;
Some(&mut self.nodes[idx].value)
} else {
None
}
}
pub fn insert(&mut self, key: K, value: V) -> bool {
if let Some(&idx) = self.map.get(&key) {
self.nodes[idx].visited = true;
self.nodes[idx].value = value;
return false;
}
if self.nodes.len() >= self.capacity {
let item = self.evict();
if item.is_none() {
let item = self.evict();
debug_assert!(
item.is_some(),
"evict() must remove one entry when at capacity"
);
}
}
let node = Node::new(key.clone(), value);
self.nodes.push(node);
let idx = self.nodes.len() - 1;
self.map.insert(key, idx);
debug_assert!(self.nodes.len() <= self.capacity);
true
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
let idx = self.map.remove(key)?;
if idx == self.nodes.len() - 1 {
return Some(self.nodes.pop().unwrap().value);
}
if let Some(hand_idx) = self.hand {
if hand_idx == idx {
self.hand = if idx > 0 {
Some(idx - 1)
} else {
Some(self.nodes.len() - 2)
};
} else if hand_idx == self.nodes.len() - 1 {
self.hand = Some(idx);
}
}
let last_node = self.nodes.pop().unwrap();
let removed_value = if idx < self.nodes.len() {
let last_key = last_node.key.clone(); let removed_node = std::mem::replace(&mut self.nodes[idx], last_node);
self.map.insert(last_key, idx); removed_node.value
} else {
last_node.value
};
Some(removed_value)
}
pub fn evict(&mut self) -> Option<V> {
if self.nodes.is_empty() {
return None;
}
let mut current_idx = self.hand.unwrap_or(self.nodes.len() - 1);
let start_idx = current_idx;
let mut wrapped = false;
let mut found_idx = None;
loop {
if !self.nodes[current_idx].visited {
found_idx = Some(current_idx);
break;
}
self.nodes[current_idx].visited = false;
current_idx = if current_idx > 0 {
current_idx - 1
} else {
if wrapped {
break;
}
wrapped = true;
self.nodes.len() - 1
};
if current_idx == start_idx {
break;
}
}
if let Some(idx) = found_idx {
self.hand = if idx > 0 {
Some(idx - 1)
} else if self.nodes.len() > 1 {
Some(self.nodes.len() - 2)
} else {
None
};
let key = &self.nodes[idx].key;
self.map.remove(key);
if idx == self.nodes.len() - 1 {
return Some(self.nodes.pop().unwrap().value);
} else {
let last_node = self.nodes.pop().unwrap();
let last_key = last_node.key.clone(); let removed_node = std::mem::replace(&mut self.nodes[idx], last_node);
self.map.insert(last_key, idx);
return Some(removed_node.value);
}
}
None
}
pub fn clear(&mut self) {
self.map.clear();
self.nodes.clear();
self.hand = None;
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.nodes.iter().map(|node| &node.key)
}
pub fn values(&self) -> impl Iterator<Item = &V> {
self.nodes.iter().map(|node| &node.value)
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
for node in &mut self.nodes {
node.visited = true;
}
self.nodes.iter_mut().map(|node| &mut node.value)
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.nodes.iter().map(|node| (&node.key, &node.value))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
for node in &mut self.nodes {
node.visited = true;
}
self.nodes
.iter_mut()
.map(|node| (&node.key, &mut node.value))
}
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&K, &V) -> bool,
{
let mut to_remove = Vec::new();
for (i, node) in self.nodes.iter().enumerate() {
if !f(&node.key, &node.value) {
to_remove.push(i);
}
}
to_remove.sort_unstable_by(|a, b| b.cmp(a));
for idx in to_remove {
self.map.remove(&self.nodes[idx].key);
if idx == self.nodes.len() - 1 {
self.nodes.pop();
} else {
let last_idx = self.nodes.len() - 1;
self.nodes.swap_remove(idx);
if idx < self.nodes.len() {
self.map.insert(self.nodes[idx].key.clone(), idx);
}
if let Some(hand_idx) = self.hand {
if hand_idx == idx {
self.hand = if idx > 0 {
Some(idx - 1)
} else if !self.nodes.is_empty() {
Some(self.nodes.len() - 1)
} else {
None
};
} else if hand_idx == last_idx {
self.hand = Some(idx);
}
}
}
}
}
pub fn recommended_capacity(
&self,
min_factor: f64,
max_factor: f64,
low_threshold: f64,
high_threshold: f64,
) -> usize {
if self.nodes.is_empty() {
return self.capacity;
}
let visited_count = self.nodes.iter().filter(|node| node.visited).count();
let utilization_ratio = visited_count as f64 / self.nodes.len() as f64;
let fill_ratio = self.nodes.len() as f64 / self.capacity as f64;
let low_fill_threshold = 0.1;
if fill_ratio < low_fill_threshold {
let fill_below_threshold = if fill_ratio > 0.0 {
(low_fill_threshold - fill_ratio) / low_fill_threshold
} else {
1.0
};
let scaling_factor = 1.0 - (1.0 - min_factor) * fill_below_threshold;
return std::cmp::max(1, (self.capacity as f64 * scaling_factor).round() as usize);
}
let scaling_factor = if utilization_ratio >= high_threshold {
let utilization_above_threshold =
(utilization_ratio - high_threshold) / (1.0 - high_threshold);
1.0 + (max_factor - 1.0) * utilization_above_threshold
} else if utilization_ratio <= low_threshold {
let utilization_below_threshold = (low_threshold - utilization_ratio) / low_threshold;
1.0 - (1.0 - min_factor) * utilization_below_threshold
} else {
1.0
};
std::cmp::max(1, (self.capacity as f64 * scaling_factor).round() as usize)
}
}
#[test]
fn test() {
let mut cache = SieveCache::new(3).unwrap();
assert!(cache.insert("foo".to_string(), "foocontent".to_string()));
assert!(cache.insert("bar".to_string(), "barcontent".to_string()));
cache.remove("bar");
assert!(cache.insert("bar2".to_string(), "bar2content".to_string()));
assert!(cache.insert("bar3".to_string(), "bar3content".to_string()));
assert_eq!(cache.get("foo"), Some(&"foocontent".to_string()));
assert_eq!(cache.get("bar"), None);
assert_eq!(cache.get("bar2"), Some(&"bar2content".to_string()));
assert_eq!(cache.get("bar3"), Some(&"bar3content".to_string()));
}
#[test]
fn test_visited_flag_update() {
let mut cache = SieveCache::new(2).unwrap();
cache.insert("key1".to_string(), "value1".to_string());
cache.insert("key2".to_string(), "value2".to_string());
cache.insert("key1".to_string(), "updated".to_string());
cache.insert("key3".to_string(), "value3".to_string());
assert_eq!(cache.get("key1"), Some(&"updated".to_string()));
}
#[test]
fn test_clear() {
let mut cache = SieveCache::new(10).unwrap();
cache.insert("key1".to_string(), "value1".to_string());
cache.insert("key2".to_string(), "value2".to_string());
assert_eq!(cache.len(), 2);
assert!(!cache.is_empty());
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.get("key1"), None);
assert_eq!(cache.get("key2"), None);
}
#[test]
fn test_iterators() {
let mut cache = SieveCache::new(10).unwrap();
cache.insert("key1".to_string(), "value1".to_string());
cache.insert("key2".to_string(), "value2".to_string());
let keys: Vec<&String> = cache.keys().collect();
assert_eq!(keys.len(), 2);
assert!(keys.contains(&&"key1".to_string()));
assert!(keys.contains(&&"key2".to_string()));
let values: Vec<&String> = cache.values().collect();
assert_eq!(values.len(), 2);
assert!(values.contains(&&"value1".to_string()));
assert!(values.contains(&&"value2".to_string()));
for value in cache.values_mut() {
*value = format!("{}_updated", value);
}
assert_eq!(cache.get("key1"), Some(&"value1_updated".to_string()));
assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
let entries: Vec<(&String, &String)> = cache.iter().collect();
assert_eq!(entries.len(), 2);
for (key, value) in cache.iter_mut() {
if key == "key1" {
*value = format!("{}_special", value);
}
}
assert_eq!(
cache.get("key1"),
Some(&"value1_updated_special".to_string())
);
assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
}
#[test]
fn test_retain() {
let mut cache = SieveCache::new(10).unwrap();
cache.insert("even1".to_string(), 2);
cache.insert("even2".to_string(), 4);
cache.insert("odd1".to_string(), 1);
cache.insert("odd2".to_string(), 3);
assert_eq!(cache.len(), 4);
cache.retain(|_, v| v % 2 == 0);
assert_eq!(cache.len(), 2);
assert!(cache.contains_key("even1"));
assert!(cache.contains_key("even2"));
assert!(!cache.contains_key("odd1"));
assert!(!cache.contains_key("odd2"));
cache.retain(|k, _| k.contains('1'));
assert_eq!(cache.len(), 1);
assert!(cache.contains_key("even1"));
assert!(!cache.contains_key("even2"));
}
#[test]
fn test_recommended_capacity() {
let cache = SieveCache::<String, u32>::new(100).unwrap();
assert_eq!(cache.recommended_capacity(0.5, 2.0, 0.3, 0.7), 100);
let mut cache = SieveCache::new(100).unwrap();
for i in 0..90 {
cache.insert(i.to_string(), i);
}
for i in 0..5 {
cache.get(&i.to_string()); }
let recommended = cache.recommended_capacity(0.5, 2.0, 0.1, 0.7); assert!(recommended < 100);
assert!(recommended >= 50);
let mut cache = SieveCache::new(100).unwrap();
for i in 0..90 {
cache.insert(i.to_string(), i);
if i % 10 != 0 {
cache.get(&i.to_string());
}
}
let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
assert!(recommended > 100);
assert!(recommended <= 200);
let mut cache = SieveCache::new(100).unwrap();
for i in 0..90 {
cache.insert(i.to_string(), i);
if i % 2 == 0 {
cache.get(&i.to_string());
}
}
let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
assert!(
recommended >= 95,
"With normal utilization, capacity should be close to original"
);
assert!(
recommended <= 100,
"With normal utilization, capacity should not exceed original"
);
let mut cache = SieveCache::new(2000).unwrap();
for i in 0..100 {
cache.insert(i.to_string(), i);
cache.get(&i.to_string());
}
let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
assert!(
recommended < 2000,
"With low fill ratio, capacity should be decreased despite high hit rate"
);
assert!(
recommended >= 1000, "Capacity should not go below min_factor of current capacity"
);
}
#[test]
fn insert_never_exceeds_capacity_when_all_visited() {
let mut c = SieveCache::new(2).unwrap();
c.insert("a".to_string(), 1);
c.insert("b".to_string(), 2);
assert!(c.get("a").is_some());
assert!(c.get("b").is_some());
c.insert("c".to_string(), 3);
assert!(c.len() <= c.capacity());
}