Skip to main content

commonware_utils/
priority_set.rs

1use std::{
2    cmp::Ordering,
3    collections::{BTreeSet, HashMap, HashSet},
4    hash::Hash,
5};
6
7/// An entry in the `PrioritySet`.
8#[derive(Eq, PartialEq)]
9struct Entry<I: Ord + Hash + Clone, P: Ord + Copy> {
10    item: I,
11    priority: P,
12}
13
14impl<I: Ord + Hash + Clone, P: Ord + Copy> Ord for Entry<I, P> {
15    fn cmp(&self, other: &Self) -> Ordering {
16        match self.priority.cmp(&other.priority) {
17            Ordering::Equal => self.item.cmp(&other.item),
18            other => other,
19        }
20    }
21}
22
23impl<I: Ord + Hash + Clone, V: Ord + Copy> PartialOrd for Entry<I, V> {
24    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
25        Some(self.cmp(other))
26    }
27}
28
29/// A set that offers efficient iteration over
30/// its elements in priority-ascending order.
31pub struct PrioritySet<I: Ord + Hash + Clone, P: Ord + Copy> {
32    entries: BTreeSet<Entry<I, P>>,
33    keys: HashMap<I, P>,
34}
35
36impl<I: Ord + Hash + Clone, P: Ord + Copy> PrioritySet<I, P> {
37    /// Creates an empty `PrioritySet`.
38    ///
39    /// To support efficient temporary replacement, this does not allocate heap storage.
40    #[allow(clippy::new_without_default)]
41    pub fn new() -> Self {
42        Self {
43            entries: BTreeSet::new(),
44            keys: HashMap::new(),
45        }
46    }
47
48    /// Insert an item with a priority, overwriting the previous priority if it exists.
49    pub fn put(&mut self, item: I, priority: P) {
50        // Remove old entry, if it exists
51        let entry = if let Some(old_priority) = self.keys.remove(&item) {
52            // Remove the item from the old priority's set
53            let mut old_entry = Entry {
54                item: item.clone(),
55                priority: old_priority,
56            };
57            self.entries.remove(&old_entry);
58
59            // We reuse the entry to avoid another item clone
60            old_entry.priority = priority;
61            old_entry
62        } else {
63            Entry { item, priority }
64        };
65
66        // Insert the entry
67        self.keys.insert(entry.item.clone(), entry.priority);
68        self.entries.insert(entry);
69    }
70
71    /// Get the current priority of an item.
72    pub fn get(&self, item: &I) -> Option<P> {
73        self.keys.get(item).cloned()
74    }
75
76    /// Remove an item from the set.
77    ///
78    /// Returns `true` if the item was present.
79    pub fn remove(&mut self, item: &I) -> bool {
80        let Some(entry) = self.keys.remove(item).map(|priority| Entry {
81            item: item.clone(),
82            priority,
83        }) else {
84            return false;
85        };
86        assert!(self.entries.remove(&entry));
87        true
88    }
89
90    /// Remove all previously inserted items not included in `keep`
91    /// and add any items not yet seen with a priority of `initial`.
92    pub fn reconcile(&mut self, keep: &[I], default: P) {
93        // Remove items not in keep
94        let mut retained: HashSet<_> = keep.iter().collect();
95        let to_remove = self
96            .keys
97            .keys()
98            .filter(|item| !retained.remove(*item))
99            .cloned()
100            .collect::<Vec<_>>();
101        for item in to_remove {
102            let priority = self.keys.remove(&item).unwrap();
103            let entry = Entry { item, priority };
104            self.entries.remove(&entry);
105        }
106
107        // Add any items not yet removed with the initial priority
108        for item in retained {
109            self.put(item.clone(), default);
110        }
111    }
112
113    /// Retains only the items where the key satisfies the predicate.
114    pub fn retain(&mut self, predicate: impl Fn(&I) -> bool) {
115        self.entries.retain(|entry| predicate(&entry.item));
116        self.keys.retain(|key, _| predicate(key));
117    }
118
119    /// Returns `true` if the set contains the item.
120    pub fn contains(&self, item: &I) -> bool {
121        self.keys.contains_key(item)
122    }
123
124    /// Returns the item with the highest priority.
125    pub fn peek(&self) -> Option<(&I, &P)> {
126        self.entries
127            .iter()
128            .next()
129            .map(|entry| (&entry.item, &entry.priority))
130    }
131
132    /// Removes and returns the item with the highest priority.
133    pub fn pop(&mut self) -> Option<(I, P)> {
134        self.entries.pop_first().map(|entry| {
135            self.keys.remove(&entry.item);
136            (entry.item, entry.priority)
137        })
138    }
139
140    /// Remove all items from the set.
141    pub fn clear(&mut self) {
142        self.entries.clear();
143        self.keys.clear();
144    }
145
146    /// Returns an iterator over all items in the set in priority-ascending order.
147    pub fn iter(&self) -> impl Iterator<Item = (&I, &P)> {
148        self.entries
149            .iter()
150            .map(|entry| (&entry.item, &entry.priority))
151    }
152
153    /// Returns the number of items in the set.
154    pub fn len(&self) -> usize {
155        self.entries.len()
156    }
157
158    /// Returns `true` if the set is empty.
159    pub fn is_empty(&self) -> bool {
160        self.entries.is_empty()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use std::time::Duration;
168
169    #[test]
170    fn test_put_remove_and_iter() {
171        // Create a new PrioritySet
172        let mut pq = PrioritySet::new();
173
174        // Add items with different priorities
175        let key1 = "key1";
176        let key2 = "key2";
177        pq.put(key1, Duration::from_secs(10));
178        pq.put(key2, Duration::from_secs(5));
179
180        // Verify iteration order
181        let entries: Vec<_> = pq.iter().collect();
182        assert_eq!(entries.len(), 2);
183        assert_eq!(*entries[0].0, key2);
184        assert_eq!(*entries[1].0, key1);
185
186        // Remove existing item
187        pq.remove(&key1);
188
189        // Verify new iteration order
190        let entries: Vec<_> = pq.iter().collect();
191        assert_eq!(entries.len(), 1);
192        assert_eq!(*entries[0].0, key2);
193
194        // Remove non-existing item
195        pq.remove(&key1);
196
197        // Verify iteration order is still the same
198        let entries: Vec<_> = pq.iter().collect();
199        assert_eq!(entries.len(), 1);
200        assert_eq!(*entries[0].0, key2);
201    }
202
203    #[test]
204    fn test_update() {
205        // Create a new PrioritySet
206        let mut pq = PrioritySet::new();
207
208        // Add an item with a priority and verify it can be retrieved
209        let key = "key";
210        pq.put(key, Duration::from_secs(10));
211        assert_eq!(pq.get(&key).unwrap(), Duration::from_secs(10));
212
213        // Update the priority and verify it has changed
214        pq.put(key, Duration::from_secs(5));
215        assert_eq!(pq.get(&key).unwrap(), Duration::from_secs(5));
216
217        // Verify updated priority is in the iteration
218        let entries: Vec<_> = pq.iter().collect();
219        assert_eq!(entries.len(), 1);
220        assert_eq!(*entries[0].1, Duration::from_secs(5));
221    }
222
223    #[test]
224    fn test_reconcile() {
225        // Create a new PrioritySet
226        let mut pq = PrioritySet::new();
227
228        // Add 2 items with different priorities
229        let key1 = "key1";
230        let key2 = "key2";
231        pq.put(key1, Duration::from_secs(10));
232        pq.put(key2, Duration::from_secs(5));
233
234        // Introduce a new item and remove an existing one
235        let key3 = "key3";
236        pq.reconcile(&[key1, key3], Duration::from_secs(2));
237
238        // Verify iteration over only the kept items
239        let entries: Vec<_> = pq.iter().collect();
240        assert_eq!(entries.len(), 2);
241        assert!(
242            entries
243                .iter()
244                .any(|e| *e.0 == key1 && *e.1 == Duration::from_secs(10))
245        );
246        assert!(
247            entries
248                .iter()
249                .any(|e| *e.0 == key3 && *e.1 == Duration::from_secs(2))
250        );
251    }
252
253    #[test]
254    fn test_retain() {
255        // Create a new PrioritySet
256        let mut pq = PrioritySet::new();
257
258        // Add items with different priorities
259        pq.put("key1", Duration::from_secs(10));
260        pq.put("key2", Duration::from_secs(5));
261        pq.put("item3", Duration::from_secs(15));
262
263        // Retain only items that start with "key"
264        pq.retain(|key| key.starts_with("key"));
265
266        // Verify that only "key1" and "key2" are present
267        assert_eq!(pq.len(), 2);
268        assert!(pq.contains(&"key1"));
269        assert!(pq.contains(&"key2"));
270        assert!(!pq.contains(&"item3"));
271
272        // Verify iteration order
273        let entries: Vec<_> = pq.iter().collect();
274        assert_eq!(entries.len(), 2);
275        assert_eq!(*entries[0].0, "key2");
276        assert_eq!(*entries[1].0, "key1");
277    }
278
279    #[test]
280    fn test_clear() {
281        // Create a new PrioritySet
282        let mut pq = PrioritySet::new();
283
284        // Add some items
285        pq.put("key1", Duration::from_secs(10));
286        pq.put("key2", Duration::from_secs(5));
287
288        // Clear the set
289        pq.clear();
290
291        // Verify the set is empty
292        assert_eq!(pq.len(), 0);
293        assert!(pq.is_empty());
294        assert!(pq.iter().next().is_none());
295        assert!(pq.peek().is_none());
296    }
297}