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
24use ustr::Ustr;
25
26/// Represents a generic set-like container with members.
27pub trait SetLike {
28    /// The type of items stored in the set.
29    type Item: Hash + Eq + Display + Clone;
30
31    /// Returns `true` if the set contains the specified item.
32    fn contains(&self, item: &Self::Item) -> bool;
33    /// Returns `true` if the set is empty.
34    fn is_empty(&self) -> bool;
35}
36
37impl<T, S> SetLike for HashSet<T, S>
38where
39    T: Eq + Hash + Display + Clone,
40    S: std::hash::BuildHasher,
41{
42    type Item = T;
43
44    #[inline]
45    fn contains(&self, v: &T) -> bool {
46        Self::contains(self, v)
47    }
48
49    #[inline]
50    fn is_empty(&self) -> bool {
51        Self::is_empty(self)
52    }
53}
54
55impl<T, S> SetLike for indexmap::IndexSet<T, S>
56where
57    T: Eq + Hash + Display + Clone,
58    S: std::hash::BuildHasher,
59{
60    type Item = T;
61
62    #[inline]
63    fn contains(&self, v: &T) -> bool {
64        Self::contains(self, v)
65    }
66
67    #[inline]
68    fn is_empty(&self) -> bool {
69        Self::is_empty(self)
70    }
71}
72
73impl<T, S> SetLike for ahash::AHashSet<T, S>
74where
75    T: Eq + Hash + Display + Clone,
76    S: std::hash::BuildHasher,
77{
78    type Item = T;
79
80    #[inline]
81    fn contains(&self, v: &T) -> bool {
82        self.get(v).is_some()
83    }
84
85    #[inline]
86    fn is_empty(&self) -> bool {
87        self.len() == 0
88    }
89}
90
91/// Represents a generic map-like container with key-value pairs.
92pub trait MapLike {
93    /// The type of keys stored in the map.
94    type Key: Hash + Eq + Display + Clone;
95    /// The type of values stored in the map.
96    type Value: Debug;
97
98    /// Returns `true` if the map contains the specified key.
99    fn contains_key(&self, key: &Self::Key) -> bool;
100    /// Returns `true` if the map is empty.
101    fn is_empty(&self) -> bool;
102}
103
104impl<K, V, S> MapLike for HashMap<K, V, S>
105where
106    K: Eq + Hash + Display + Clone,
107    V: Debug,
108    S: std::hash::BuildHasher,
109{
110    type Key = K;
111    type Value = V;
112
113    #[inline]
114    fn contains_key(&self, k: &K) -> bool {
115        self.contains_key(k)
116    }
117
118    #[inline]
119    fn is_empty(&self) -> bool {
120        self.is_empty()
121    }
122}
123
124impl<K, V, S> MapLike for indexmap::IndexMap<K, V, S>
125where
126    K: Eq + Hash + Display + Clone,
127    V: Debug,
128    S: std::hash::BuildHasher,
129{
130    type Key = K;
131    type Value = V;
132
133    #[inline]
134    fn contains_key(&self, k: &K) -> bool {
135        self.get(k).is_some()
136    }
137
138    #[inline]
139    fn is_empty(&self) -> bool {
140        self.is_empty()
141    }
142}
143
144impl<K, V, S> MapLike for ahash::AHashMap<K, V, S>
145where
146    K: Eq + Hash + Display + Clone,
147    V: Debug,
148    S: std::hash::BuildHasher,
149{
150    type Key = K;
151    type Value = V;
152
153    #[inline]
154    fn contains_key(&self, k: &K) -> bool {
155        self.get(k).is_some()
156    }
157
158    #[inline]
159    fn is_empty(&self) -> bool {
160        self.len() == 0
161    }
162}
163
164/// Convert any iterator of string-like items into a `Vec<Ustr>`.
165#[must_use]
166pub fn into_ustr_vec<I, T>(iter: I) -> Vec<Ustr>
167where
168    I: IntoIterator<Item = T>,
169    T: AsRef<str>,
170{
171    let iter = iter.into_iter();
172    let (lower, _) = iter.size_hint();
173    let mut result = Vec::with_capacity(lower);
174
175    for item in iter {
176        result.push(Ustr::from(item.as_ref()));
177    }
178
179    result
180}
181
182////////////////////////////////////////////////////////////////////////////////
183// Tests
184////////////////////////////////////////////////////////////////////////////////
185#[cfg(test)]
186#[allow(clippy::unnecessary_to_owned)]
187mod tests {
188    use std::collections::{HashMap, HashSet};
189
190    use ahash::{AHashMap, AHashSet};
191    use indexmap::{IndexMap, IndexSet};
192    use rstest::*;
193    use ustr::Ustr;
194
195    use super::*;
196
197    #[rstest]
198    fn test_hashset_setlike() {
199        let mut set: HashSet<String> = HashSet::new();
200        set.insert("test".to_string());
201        set.insert("value".to_string());
202
203        assert!(set.contains(&"test".to_string()));
204        assert!(!set.contains(&"missing".to_string()));
205        assert!(!set.is_empty());
206
207        let empty_set: HashSet<String> = HashSet::new();
208        assert!(empty_set.is_empty());
209    }
210
211    #[rstest]
212    fn test_indexset_setlike() {
213        let mut set: IndexSet<String> = IndexSet::new();
214        set.insert("test".to_string());
215        set.insert("value".to_string());
216
217        assert!(set.contains(&"test".to_string()));
218        assert!(!set.contains(&"missing".to_string()));
219        assert!(!set.is_empty());
220
221        let empty_set: IndexSet<String> = IndexSet::new();
222        assert!(empty_set.is_empty());
223    }
224
225    #[rstest]
226    fn test_into_ustr_vec_from_strings() {
227        let items = vec!["foo".to_string(), "bar".to_string()];
228        let ustrs = super::into_ustr_vec(items);
229
230        assert_eq!(ustrs.len(), 2);
231        assert_eq!(ustrs[0], Ustr::from("foo"));
232        assert_eq!(ustrs[1], Ustr::from("bar"));
233    }
234
235    #[rstest]
236    fn test_into_ustr_vec_from_str_slices() {
237        let items = ["alpha", "beta", "gamma"];
238        let ustrs = super::into_ustr_vec(items);
239
240        assert_eq!(ustrs.len(), 3);
241        assert_eq!(ustrs[2], Ustr::from("gamma"));
242    }
243
244    #[rstest]
245    fn test_ahashset_setlike() {
246        let mut set: AHashSet<String> = AHashSet::new();
247        set.insert("test".to_string());
248        set.insert("value".to_string());
249
250        assert!(set.contains(&"test".to_string()));
251        assert!(!set.contains(&"missing".to_string()));
252        assert!(!set.is_empty());
253
254        let empty_set: AHashSet<String> = AHashSet::new();
255        assert!(empty_set.is_empty());
256    }
257
258    #[rstest]
259    fn test_hashmap_maplike() {
260        let mut map: HashMap<String, i32> = HashMap::new();
261        map.insert("key1".to_string(), 42);
262        map.insert("key2".to_string(), 100);
263
264        assert!(map.contains_key(&"key1".to_string()));
265        assert!(!map.contains_key(&"missing".to_string()));
266        assert!(!map.is_empty());
267
268        let empty_map: HashMap<String, i32> = HashMap::new();
269        assert!(empty_map.is_empty());
270    }
271
272    #[rstest]
273    fn test_indexmap_maplike() {
274        let mut map: IndexMap<String, i32> = IndexMap::new();
275        map.insert("key1".to_string(), 42);
276        map.insert("key2".to_string(), 100);
277
278        assert!(map.contains_key(&"key1".to_string()));
279        assert!(!map.contains_key(&"missing".to_string()));
280        assert!(!map.is_empty());
281
282        let empty_map: IndexMap<String, i32> = IndexMap::new();
283        assert!(empty_map.is_empty());
284    }
285
286    #[rstest]
287    fn test_ahashmap_maplike() {
288        let mut map: AHashMap<String, i32> = AHashMap::new();
289        map.insert("key1".to_string(), 42);
290        map.insert("key2".to_string(), 100);
291
292        assert!(map.contains_key(&"key1".to_string()));
293        assert!(!map.contains_key(&"missing".to_string()));
294        assert!(!map.is_empty());
295
296        let empty_map: AHashMap<String, i32> = AHashMap::new();
297        assert!(empty_map.is_empty());
298    }
299
300    #[rstest]
301    fn test_trait_object_setlike() {
302        let mut hashset: HashSet<String> = HashSet::new();
303        hashset.insert("test".to_string());
304
305        let mut indexset: IndexSet<String> = IndexSet::new();
306        indexset.insert("test".to_string());
307
308        let sets: Vec<&dyn SetLike<Item = String>> = vec![&hashset, &indexset];
309
310        for set in sets {
311            assert!(set.contains(&"test".to_string()));
312            assert!(!set.is_empty());
313        }
314    }
315
316    #[rstest]
317    fn test_trait_object_maplike() {
318        let mut hashmap: HashMap<String, i32> = HashMap::new();
319        hashmap.insert("key".to_string(), 42);
320
321        let mut indexmap: IndexMap<String, i32> = IndexMap::new();
322        indexmap.insert("key".to_string(), 42);
323
324        let maps: Vec<&dyn MapLike<Key = String, Value = i32>> = vec![&hashmap, &indexmap];
325
326        for map in maps {
327            assert!(map.contains_key(&"key".to_string()));
328            assert!(!map.is_empty());
329        }
330    }
331}