1use std::{
5 hash::Hash,
6 sync::atomic::{AtomicU64, Ordering},
7};
8
9use dashmap::DashMap;
10
11pub struct LruCache<K, V> {
16 map: DashMap<K, Entry<V>>,
17 capacity: usize,
18 access_counter: AtomicU64,
19}
20
21struct Entry<V> {
22 value: V,
23 last_access: u64,
24}
25
26impl<K: Hash + Eq + Clone, V: Clone> LruCache<K, V> {
27 pub fn new(capacity: usize) -> Self {
28 assert!(capacity > 0, "LRU cache capacity must be greater than 0");
29 Self {
30 map: DashMap::with_capacity(capacity),
31 capacity,
32 access_counter: AtomicU64::new(0),
33 }
34 }
35
36 pub fn get(&self, key: &K) -> Option<V> {
38 if let Some(mut entry) = self.map.get_mut(key) {
39 entry.last_access = self.access_counter.fetch_add(1, Ordering::Relaxed);
40 Some(entry.value.clone())
41 } else {
42 None
43 }
44 }
45
46 pub fn put(&self, key: K, value: V) -> Option<V> {
48 let access = self.access_counter.fetch_add(1, Ordering::Relaxed);
49
50 if let Some(mut entry) = self.map.get_mut(&key) {
52 let old_value = std::mem::replace(&mut entry.value, value);
53 entry.last_access = access;
54 return Some(old_value);
55 }
56
57 if self.map.len() >= self.capacity {
59 self.evict_lru();
60 }
61
62 self.map.insert(
63 key,
64 Entry {
65 value,
66 last_access: access,
67 },
68 );
69 None
70 }
71
72 pub fn remove(&self, key: &K) -> Option<V> {
74 self.map.remove(key).map(|(_, entry)| entry.value)
75 }
76
77 pub fn contains_key(&self, key: &K) -> bool {
79 self.map.contains_key(key)
80 }
81
82 pub fn clear(&self) {
84 self.map.clear();
85 }
86
87 pub fn len(&self) -> usize {
89 self.map.len()
90 }
91
92 pub fn is_empty(&self) -> bool {
94 self.map.is_empty()
95 }
96
97 pub fn capacity(&self) -> usize {
99 self.capacity
100 }
101
102 fn evict_lru(&self) {
104 let mut oldest_key: Option<K> = None;
105 let mut oldest_access = u64::MAX;
106
107 for entry in self.map.iter() {
108 if entry.last_access < oldest_access {
109 oldest_access = entry.last_access;
110 oldest_key = Some(entry.key().clone());
111 }
112 }
113
114 if let Some(key) = oldest_key {
115 self.map.remove(&key);
116 }
117 }
118}
119
120#[cfg(test)]
121pub mod tests {
122
123 mod lru {
124 use crate::util::lru::LruCache;
125
126 #[test]
127 fn test_basic_operations() {
128 let cache = LruCache::new(2);
129
130 assert_eq!(cache.put(1, "a"), None);
131 assert_eq!(cache.put(2, "b"), None);
132 assert_eq!(cache.get(&1), Some("a"));
133 assert_eq!(cache.get(&2), Some("b"));
134 assert_eq!(cache.len(), 2);
135 }
136
137 #[test]
138 fn test_eviction() {
139 let cache = LruCache::new(2);
140
141 cache.put(1, "a");
142 cache.put(2, "b");
143 let evicted = cache.put(3, "c");
144
145 assert_eq!(evicted, None);
148 assert_eq!(cache.get(&1), None);
149 assert_eq!(cache.get(&2), Some("b"));
150 assert_eq!(cache.get(&3), Some("c"));
151 }
152
153 #[test]
154 fn test_lru_order() {
155 let cache = LruCache::new(2);
156
157 cache.put(1, "a");
158 cache.put(2, "b");
159 cache.get(&1); cache.put(3, "c"); assert_eq!(cache.get(&1), Some("a"));
163 assert_eq!(cache.get(&2), None);
164 assert_eq!(cache.get(&3), Some("c"));
165 }
166
167 #[test]
168 fn test_update_existing() {
169 let cache = LruCache::new(2);
170
171 cache.put(1, "a");
172 let old = cache.put(1, "b");
173
174 assert_eq!(old, Some("a"));
175 assert_eq!(cache.get(&1), Some("b"));
176 assert_eq!(cache.len(), 1);
177 }
178
179 #[test]
180 fn test_remove() {
181 let cache = LruCache::new(2);
182
183 cache.put(1, "a");
184 cache.put(2, "b");
185 let removed = cache.remove(&1);
186
187 assert_eq!(removed, Some("a"));
188 assert_eq!(cache.get(&1), None);
189 assert_eq!(cache.len(), 1);
190 }
191
192 #[test]
193 fn test_clear() {
194 let cache = LruCache::new(2);
195
196 cache.put(1, "a");
197 cache.put(2, "b");
198 cache.clear();
199
200 assert_eq!(cache.len(), 0);
201 assert!(cache.is_empty());
202 }
203
204 #[test]
205 fn test_contains_key() {
206 let cache = LruCache::new(2);
207
208 cache.put(1, "a");
209 assert!(cache.contains_key(&1));
210 assert!(!cache.contains_key(&2));
211 }
212 }
213}