commonware_utils/
priority_set.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use std::{
    cmp::Ordering,
    collections::{BTreeSet, HashMap, HashSet},
    hash::Hash,
};

/// An entry in the `PrioritySet`.
#[derive(Eq, PartialEq)]
struct Entry<I: Ord + Hash + Clone, P: Ord + Copy> {
    item: I,
    priority: P,
}

impl<I: Ord + Hash + Clone, P: Ord + Copy> Ord for Entry<I, P> {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.priority.cmp(&other.priority) {
            Ordering::Equal => self.item.cmp(&other.item),
            other => other,
        }
    }
}

impl<I: Ord + Hash + Clone, V: Ord + Copy> PartialOrd for Entry<I, V> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

/// A set that offers efficient iteration over
/// its elements in priority-ascending order.
pub struct PrioritySet<I: Ord + Hash + Clone, P: Ord + Copy> {
    entries: BTreeSet<Entry<I, P>>,
    keys: HashMap<I, P>,
}

impl<I: Ord + Hash + Clone, P: Ord + Copy> PrioritySet<I, P> {
    /// Create a new `PrioritySet`.
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        Self {
            entries: BTreeSet::new(),
            keys: HashMap::new(),
        }
    }

    /// Insert an item with a priority, overwriting the previous priority if it exists.
    pub fn put(&mut self, item: I, priority: P) {
        // Remove old entry, if it exists
        let entry = if let Some(old_priority) = self.keys.remove(&item) {
            // Remove the item from the old priority's set
            let mut old_entry = Entry {
                item: item.clone(),
                priority: old_priority,
            };
            self.entries.remove(&old_entry);

            // We reuse the entry to avoid another item clone
            old_entry.priority = priority;
            old_entry
        } else {
            Entry { item, priority }
        };

        // Insert the entry
        self.keys.insert(entry.item.clone(), entry.priority);
        self.entries.insert(entry);
    }

    /// Get the current priority of an item.
    pub fn get(&self, item: &I) -> Option<P> {
        self.keys.get(item).cloned()
    }

    /// Remove an item from the set.
    pub fn remove(&mut self, item: &I) {
        let Some(entry) = self.keys.remove(item).map(|priority| Entry {
            item: item.clone(),
            priority,
        }) else {
            return;
        };
        self.entries.remove(&entry);
    }

    /// Remove all previously inserted items not included in `keep`
    /// and add any items not yet seen with a priority of `initial`.
    pub fn reconcile(&mut self, keep: &[I], default: P) {
        // Remove items not in keep
        let mut retained: HashSet<_> = keep.iter().collect();
        let to_remove = self
            .keys
            .keys()
            .filter(|item| !retained.remove(*item))
            .cloned()
            .collect::<Vec<_>>();
        for item in to_remove {
            let priority = self.keys.remove(&item).unwrap();
            let entry = Entry { item, priority };
            self.entries.remove(&entry);
        }

        // Add any items not yet removed with the initial priority
        for item in retained {
            self.put(item.clone(), default);
        }
    }

    /// Returns an iterator over all items in the set in priority-ascending order.
    pub fn iter(&self) -> impl Iterator<Item = (&I, &P)> {
        self.entries
            .iter()
            .map(|entry| (&entry.item, &entry.priority))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn test_put_remove_and_iter() {
        // Create a new PrioritySet
        let mut pq = PrioritySet::new();

        // Add items with different priorities
        let key1 = "key1";
        let key2 = "key2";
        pq.put(key1, Duration::from_secs(10));
        pq.put(key2, Duration::from_secs(5));

        // Verify iteration order
        let entries: Vec<_> = pq.iter().collect();
        assert_eq!(entries.len(), 2);
        assert_eq!(*entries[0].0, key2);
        assert_eq!(*entries[1].0, key1);

        // Remove existing item
        pq.remove(&key1);

        // Verify new iteration order
        let entries: Vec<_> = pq.iter().collect();
        assert_eq!(entries.len(), 1);
        assert_eq!(*entries[0].0, key2);

        // Remove non-existing item
        pq.remove(&key1);

        // Verify iteration order is still the same
        let entries: Vec<_> = pq.iter().collect();
        assert_eq!(entries.len(), 1);
        assert_eq!(*entries[0].0, key2);
    }

    #[test]
    fn test_update() {
        // Create a new PrioritySet
        let mut pq = PrioritySet::new();

        // Add an item with a priority and verify it can be retrieved
        let key = "key";
        pq.put(key, Duration::from_secs(10));
        assert_eq!(pq.get(&key).unwrap(), Duration::from_secs(10));

        // Update the priority and verify it has changed
        pq.put(key, Duration::from_secs(5));
        assert_eq!(pq.get(&key).unwrap(), Duration::from_secs(5));

        // Verify updated priority is in the iteration
        let entries: Vec<_> = pq.iter().collect();
        assert_eq!(entries.len(), 1);
        assert_eq!(*entries[0].1, Duration::from_secs(5));
    }

    #[test]
    fn test_reconcile() {
        // Create a new PrioritySet
        let mut pq = PrioritySet::new();

        // Add 2 items with different priorities
        let key1 = "key1";
        let key2 = "key2";
        pq.put(key1, Duration::from_secs(10));
        pq.put(key2, Duration::from_secs(5));

        // Introduce a new item and remove an existing one
        let key3 = "key3";
        pq.reconcile(&[key1, key3], Duration::from_secs(2));

        // Verify iteration over only the kept items
        let entries: Vec<_> = pq.iter().collect();
        assert_eq!(entries.len(), 2);
        assert!(entries
            .iter()
            .any(|e| *e.0 == key1 && *e.1 == Duration::from_secs(10)));
        assert!(entries
            .iter()
            .any(|e| *e.0 == key3 && *e.1 == Duration::from_secs(2)));
    }
}