lru_st/collections/
hashset.rs

1use std::{collections::HashMap, hash::Hash};
2
3#[cfg(not(feature = "sync"))]
4use std::rc::{Rc, Weak};
5
6#[cfg(feature = "sync")]
7use {std::sync::Arc as Rc, std::sync::Weak};
8
9use crate::{Cursor, DoublyLinkedList, DoublyLinkedListIter};
10
11/// An iterator over the items of a [LruHashSet].
12pub struct Iter<'a, K>
13where
14    K: Hash + Eq,
15{
16    lru_iter: DoublyLinkedListIter<'a, Weak<K>>,
17}
18
19impl<'a, K> Iterator for Iter<'a, K>
20where
21    K: Hash + Eq,
22{
23    type Item = Rc<K>;
24    fn next(&mut self) -> Option<Self::Item> {
25        if let Some(item) = self.lru_iter.next() {
26            return item.upgrade();
27        }
28        None
29    }
30}
31
32impl<'a, K> DoubleEndedIterator for Iter<'a, K>
33where
34    K: Hash + Eq,
35{
36    fn next_back(&mut self) -> Option<Self::Item> {
37        if let Some(item) = self.lru_iter.next_back() {
38            return item.upgrade();
39        }
40        None
41    }
42}
43
44/// A LRU HashSet implementation
45pub struct LruHashSet<K> {
46    map: HashMap<Rc<K>, Cursor>,
47    lru: DoublyLinkedList<Weak<K>>,
48    max_entries: usize,
49}
50
51impl<K> LruHashSet<K>
52where
53    K: Hash + Eq,
54{
55    /// Creates a new [LruHashSet] with a given number of entries
56    pub fn with_max_entries(max_entries: usize) -> Self {
57        LruHashSet {
58            map: HashMap::with_capacity(max_entries),
59            lru: DoublyLinkedList::with_capacity(max_entries),
60            max_entries,
61        }
62    }
63
64    /// Returns a reference to the value in the set, if any, that is equal to the given value.
65    #[inline]
66    pub fn get(&mut self, k: &K) -> Option<Rc<K>> {
67        if let Some(k) = self.map.get(k).and_then(|cursor| {
68            self.lru.move_front(*cursor).unwrap();
69            self.lru.front()
70        }) {
71            return k.upgrade();
72        }
73        None
74    }
75
76    /// Returns an iterator over the values within the set
77    ///
78    /// # Warning
79    ///
80    /// Iterating does not modify the order of the LRU to prevent
81    /// any unwanted side effects due.
82    pub fn iter(&mut self) -> Iter<K> {
83        Iter {
84            lru_iter: self.lru.iter(),
85        }
86    }
87
88    /// Returns `true` if there is an element is in the set
89    #[inline]
90    pub fn contains(&mut self, k: &K) -> bool {
91        self.get(k).is_some()
92    }
93
94    /// Adds a value to the set.
95    ///
96    /// Returns whether the value was newly inserted. That is:
97    ///
98    /// - If the set did not previously contain this value, `true` is returned.
99    /// - If the set already contained this value, `false` is returned,
100    ///   and the set is not modified: original value is not replaced,
101    ///   and the value passed as argument is dropped.
102    #[inline]
103    pub fn insert(&mut self, k: K) -> bool {
104        // we update value if already there
105        if self.contains(&k) {
106            return false;
107        }
108
109        // if we arrive here the key/value needs to be inserted
110        // if we reached capacity, we must delete the last used entry first
111        if self.map.len() == self.max_entries {
112            if let Some(k) = self.lru.pop_back() {
113                // we put this in a block so that we can debug_assert latter
114                {
115                    let key = k.upgrade().expect("weak reference must be valid");
116                    self.map.remove(&key);
117                }
118                // Rc should have been dropped now because we have removed item from map
119                debug_assert!(k.upgrade().is_none());
120            };
121        }
122        let k = Rc::new(k);
123        let cursor = self.lru.push_front(Rc::downgrade(&k));
124        self.map.insert(k, cursor);
125        true
126    }
127
128    /// Removes an entry from the `LruHashSet` and returns `true`
129    /// if the entry was removed else `false`
130    #[inline]
131    pub fn remove(&mut self, k: &K) -> bool {
132        if let Some(c) = self.map.remove(k) {
133            if self.lru.pop(c).is_some() {
134                return true;
135            }
136        }
137        false
138    }
139
140    /// Returns the length of the `LruHashSet`
141    #[inline(always)]
142    pub fn len(&self) -> usize {
143        self.map.len()
144    }
145
146    /// Returns the capacity of the `LruHashSet`
147    #[inline(always)]
148    pub fn cap(&self) -> usize {
149        self.max_entries
150    }
151
152    /// Returns `true` if the `LruHashSet` is empty
153    #[inline(always)]
154    pub fn is_empty(&self) -> bool {
155        self.map.is_empty()
156    }
157
158    /// Returns `true` if the `LruHashSet` is full
159    #[inline(always)]
160    pub fn is_full(&self) -> bool {
161        self.max_entries == self.map.len()
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn basic_hashset_test() {
171        let max_entries = 1000;
172        let mut lru = LruHashSet::with_max_entries(max_entries);
173        assert!(lru.is_empty());
174
175        for i in 0..max_entries * 2 {
176            assert!(lru.insert(i));
177            assert!(lru.contains(&i));
178        }
179
180        // all values until max_entries must have been
181        // rotated in the LRU so not contained anymore
182        for i in 0..max_entries {
183            assert!(!lru.contains(&i));
184        }
185
186        // all values above max_entries must be contained
187        for i in max_entries + 1..max_entries * 2 {
188            assert!(lru.contains(&i));
189        }
190    }
191
192    #[test]
193    fn hashset_get_test() {
194        let mut lru = LruHashSet::with_max_entries(10);
195
196        lru.insert(42);
197        assert_eq!(lru.get(&42), Some(Rc::new(42)));
198    }
199
200    #[test]
201    #[allow(clippy::bool_assert_comparison)]
202    fn hashset_remove_test() {
203        let mut lru = LruHashSet::with_max_entries(10);
204
205        assert_eq!(lru.insert(42), true);
206        assert_eq!(lru.insert(42), false);
207        assert_eq!(lru.remove(&42), true);
208        assert_eq!(lru.remove(&42), false);
209    }
210
211    #[test]
212    fn hashset_iter() {
213        let mut lru = LruHashSet::with_max_entries(10);
214
215        lru.insert(41);
216        lru.insert(42);
217        lru.insert(43);
218        let mut iter = lru.iter();
219        assert_eq!(iter.next(), Some(Rc::new(43)));
220        assert_eq!(iter.next(), Some(Rc::new(42)));
221        assert_eq!(iter.next(), Some(Rc::new(41)));
222        assert_eq!(iter.next(), None);
223
224        // we remove one item
225        lru.remove(&42);
226        let mut iter = lru.iter().rev();
227        assert_eq!(iter.next(), Some(Rc::new(41)));
228        assert_eq!(iter.next(), Some(Rc::new(43)));
229        assert_eq!(iter.next(), None);
230    }
231}