nautilus_core/
collections.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Abstraction layer over common hash-based containers.
17
18use std::{
19    collections::{HashMap, HashSet},
20    fmt::{Debug, Display},
21    hash::Hash,
22};
23
24/// Represents a generic set-like container with members.
25pub trait SetLike {
26    /// The type of items stored in the set.
27    type Item: Hash + Eq + Display + Clone;
28
29    /// Returns `true` if the set contains the specified item.
30    fn contains(&self, item: &Self::Item) -> bool;
31    /// Returns `true` if the set is empty.
32    fn is_empty(&self) -> bool;
33}
34
35impl<T, S> SetLike for HashSet<T, S>
36where
37    T: Eq + Hash + Display + Clone,
38    S: std::hash::BuildHasher,
39{
40    type Item = T;
41
42    #[inline]
43    fn contains(&self, v: &T) -> bool {
44        Self::contains(self, v)
45    }
46
47    #[inline]
48    fn is_empty(&self) -> bool {
49        Self::is_empty(self)
50    }
51}
52
53impl<T, S> SetLike for indexmap::IndexSet<T, S>
54where
55    T: Eq + Hash + Display + Clone,
56    S: std::hash::BuildHasher,
57{
58    type Item = T;
59
60    #[inline]
61    fn contains(&self, v: &T) -> bool {
62        Self::contains(self, v)
63    }
64
65    #[inline]
66    fn is_empty(&self) -> bool {
67        Self::is_empty(self)
68    }
69}
70
71impl<T, S> SetLike for ahash::AHashSet<T, S>
72where
73    T: Eq + Hash + Display + Clone,
74    S: std::hash::BuildHasher,
75{
76    type Item = T;
77
78    #[inline]
79    fn contains(&self, v: &T) -> bool {
80        self.get(v).is_some()
81    }
82
83    #[inline]
84    fn is_empty(&self) -> bool {
85        self.len() == 0
86    }
87}
88
89/// Represents a generic map-like container with key-value pairs.
90pub trait MapLike {
91    /// The type of keys stored in the map.
92    type Key: Hash + Eq + Display + Clone;
93    /// The type of values stored in the map.
94    type Value: Debug;
95
96    /// Returns `true` if the map contains the specified key.
97    fn contains_key(&self, key: &Self::Key) -> bool;
98    /// Returns `true` if the map is empty.
99    fn is_empty(&self) -> bool;
100}
101
102impl<K, V, S> MapLike for HashMap<K, V, S>
103where
104    K: Eq + Hash + Display + Clone,
105    V: Debug,
106    S: std::hash::BuildHasher,
107{
108    type Key = K;
109    type Value = V;
110
111    #[inline]
112    fn contains_key(&self, k: &K) -> bool {
113        self.contains_key(k)
114    }
115
116    #[inline]
117    fn is_empty(&self) -> bool {
118        self.is_empty()
119    }
120}
121
122impl<K, V, S> MapLike for indexmap::IndexMap<K, V, S>
123where
124    K: Eq + Hash + Display + Clone,
125    V: Debug,
126    S: std::hash::BuildHasher,
127{
128    type Key = K;
129    type Value = V;
130
131    #[inline]
132    fn contains_key(&self, k: &K) -> bool {
133        self.get(k).is_some()
134    }
135
136    #[inline]
137    fn is_empty(&self) -> bool {
138        self.is_empty()
139    }
140}
141
142impl<K, V, S> MapLike for ahash::AHashMap<K, V, S>
143where
144    K: Eq + Hash + Display + Clone,
145    V: Debug,
146    S: std::hash::BuildHasher,
147{
148    type Key = K;
149    type Value = V;
150
151    #[inline]
152    fn contains_key(&self, k: &K) -> bool {
153        self.get(k).is_some()
154    }
155
156    #[inline]
157    fn is_empty(&self) -> bool {
158        self.len() == 0
159    }
160}
161
162////////////////////////////////////////////////////////////////////////////////
163// Tests
164////////////////////////////////////////////////////////////////////////////////
165#[cfg(test)]
166#[allow(clippy::unnecessary_to_owned)]
167mod tests {
168    use std::collections::{HashMap, HashSet};
169
170    use ahash::{AHashMap, AHashSet};
171    use indexmap::{IndexMap, IndexSet};
172    use rstest::*;
173
174    use super::*;
175
176    #[rstest]
177    fn test_hashset_setlike() {
178        let mut set: HashSet<String> = HashSet::new();
179        set.insert("test".to_string());
180        set.insert("value".to_string());
181
182        assert!(set.contains(&"test".to_string()));
183        assert!(!set.contains(&"missing".to_string()));
184        assert!(!set.is_empty());
185
186        let empty_set: HashSet<String> = HashSet::new();
187        assert!(empty_set.is_empty());
188    }
189
190    #[rstest]
191    fn test_indexset_setlike() {
192        let mut set: IndexSet<String> = IndexSet::new();
193        set.insert("test".to_string());
194        set.insert("value".to_string());
195
196        assert!(set.contains(&"test".to_string()));
197        assert!(!set.contains(&"missing".to_string()));
198        assert!(!set.is_empty());
199
200        let empty_set: IndexSet<String> = IndexSet::new();
201        assert!(empty_set.is_empty());
202    }
203
204    #[rstest]
205    fn test_ahashset_setlike() {
206        let mut set: AHashSet<String> = AHashSet::new();
207        set.insert("test".to_string());
208        set.insert("value".to_string());
209
210        assert!(set.contains(&"test".to_string()));
211        assert!(!set.contains(&"missing".to_string()));
212        assert!(!set.is_empty());
213
214        let empty_set: AHashSet<String> = AHashSet::new();
215        assert!(empty_set.is_empty());
216    }
217
218    #[rstest]
219    fn test_hashmap_maplike() {
220        let mut map: HashMap<String, i32> = HashMap::new();
221        map.insert("key1".to_string(), 42);
222        map.insert("key2".to_string(), 100);
223
224        assert!(map.contains_key(&"key1".to_string()));
225        assert!(!map.contains_key(&"missing".to_string()));
226        assert!(!map.is_empty());
227
228        let empty_map: HashMap<String, i32> = HashMap::new();
229        assert!(empty_map.is_empty());
230    }
231
232    #[rstest]
233    fn test_indexmap_maplike() {
234        let mut map: IndexMap<String, i32> = IndexMap::new();
235        map.insert("key1".to_string(), 42);
236        map.insert("key2".to_string(), 100);
237
238        assert!(map.contains_key(&"key1".to_string()));
239        assert!(!map.contains_key(&"missing".to_string()));
240        assert!(!map.is_empty());
241
242        let empty_map: IndexMap<String, i32> = IndexMap::new();
243        assert!(empty_map.is_empty());
244    }
245
246    #[rstest]
247    fn test_ahashmap_maplike() {
248        let mut map: AHashMap<String, i32> = AHashMap::new();
249        map.insert("key1".to_string(), 42);
250        map.insert("key2".to_string(), 100);
251
252        assert!(map.contains_key(&"key1".to_string()));
253        assert!(!map.contains_key(&"missing".to_string()));
254        assert!(!map.is_empty());
255
256        let empty_map: AHashMap<String, i32> = AHashMap::new();
257        assert!(empty_map.is_empty());
258    }
259
260    #[rstest]
261    fn test_trait_object_setlike() {
262        let mut hashset: HashSet<String> = HashSet::new();
263        hashset.insert("test".to_string());
264
265        let mut indexset: IndexSet<String> = IndexSet::new();
266        indexset.insert("test".to_string());
267
268        let sets: Vec<&dyn SetLike<Item = String>> = vec![&hashset, &indexset];
269
270        for set in sets {
271            assert!(set.contains(&"test".to_string()));
272            assert!(!set.is_empty());
273        }
274    }
275
276    #[rstest]
277    fn test_trait_object_maplike() {
278        let mut hashmap: HashMap<String, i32> = HashMap::new();
279        hashmap.insert("key".to_string(), 42);
280
281        let mut indexmap: IndexMap<String, i32> = IndexMap::new();
282        indexmap.insert("key".to_string(), 42);
283
284        let maps: Vec<&dyn MapLike<Key = String, Value = i32>> = vec![&hashmap, &indexmap];
285
286        for map in maps {
287            assert!(map.contains_key(&"key".to_string()));
288            assert!(!map.is_empty());
289        }
290    }
291}