mule_map/mule_map.rs
1use entry::{
2 Entry, OccupiedEntry, OccupiedHashMapEntry, OccupiedVecEntry, VacantEntry, VacantHashMapEntry,
3 VacantVecEntry,
4};
5use key::Key;
6use key::PrimInt;
7use sealed::sealed;
8use std::collections::HashMap;
9use std::fmt::Debug;
10use std::hash::BuildHasher;
11use std::num::NonZero;
12use std::ops::AddAssign;
13
14pub(crate) mod entry;
15pub(crate) mod iterators;
16pub(crate) mod key;
17
18/// [`MuleMap`] is a hybrid between a [`HashMap`] and a lookup table. It improves performance for frequently accessed
19/// keys in a known range. If a key (integer) is in the user specified range, then its value will be stored directly in
20/// the lookup table.
21///
22/// ## Differences between [`HashMap`] and [`MuleMap`]
23///
24/// - **The key, `K`, must be an integer type.** - The key is directly mapped to the index in the lookup, so it must be
25/// an integer.
26/// - **The key, `K`, is passed by value** - Because it is a primitive integer type.
27/// - **The hash builder, `S`, does not have a default** - You must specify your hash builder. The assumption being
28/// that if you need better performance you will likely also want to use a custom hash function.
29/// - **`TABLE_MIN_VALUE` and `TABLE_SIZE`** - If a key is between `TABLE_MIN_VALUE` and `TABLE_MIN_VALUE +
30/// TABLE_SIZE` (exclusive), then the value will be stored directly in the lookup table, instead of using the `HashMap`.
31///
32/// **NOTE:** Currently the type of a const generic can’t depend on another generic type argument, so
33/// `TABLE_MIN_VALUE` can’t use the same type as the key. Because of this, We are using [`i128`], but that means we
34/// can’t represent values near [`u128::MAX`]. Hopefully having frequent keys near [`u128::MAX`] is extremely rare.
35///
36/// ## Performance
37///
38/// Benchmarks (using random selection) start to show speed improvements when about 50% of the key accesses are in the
39/// lookup table. Performance is almost identical to `HashMap` with less than 50%.
40///
41/// ## Example
42///
43/// ```
44/// use mule_map::MuleMap;
45/// use std::num::NonZero;
46/// type Hash = fnv_rs::FnvBuildHasher; // Use whatever hash function you prefer
47///
48/// // Using Entry API
49/// let mut mule_map = MuleMap::<u32, usize, Hash>::new();
50/// assert_eq!(mule_map.get(5), None);
51/// let entry = mule_map.entry(5);
52/// entry.or_insert(10);
53/// assert_eq!(mule_map.get(5), Some(&10));
54///
55/// // Using NonZero and bump
56/// let mut mule_map_non_zero = MuleMap::<u32, NonZero<i32>, Hash>::default();
57///
58/// mule_map_non_zero.bump_non_zero(10);
59/// mule_map_non_zero.bump_non_zero(10);
60/// mule_map_non_zero.bump_non_zero(999_999);
61/// mule_map_non_zero.bump_non_zero(999_999);
62///
63/// assert_eq!(mule_map_non_zero.get(10), NonZero::<i32>::new(2).as_ref());
64/// assert_eq!(mule_map_non_zero.get(999_999),NonZero::<i32>::new(2).as_ref());
65/// ```
66#[derive(Debug, Clone)]
67pub struct MuleMap<
68 K,
69 V,
70 S,
71 const TABLE_MIN_VALUE: i128 = 0,
72 const TABLE_SIZE: usize = { u8::MAX as usize + 1 },
73> {
74 hash_map: HashMap<K, V, S>,
75 table: [Option<V>; TABLE_SIZE],
76}
77
78impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize> Default
79 for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
80where
81 K: Key<TABLE_MIN_VALUE>,
82 S: Default + BuildHasher,
83 <K as TryFrom<i128>>::Error: Debug,
84{
85 /// Creates an empty [`MuleMap`].
86 ///
87 /// # Example
88 ///
89 /// ```
90 /// type Hash = fnv_rs::FnvBuildHasher;
91 /// let mule_map = mule_map::MuleMap::<u32, usize, Hash>::default();
92 /// assert!(mule_map.is_empty());
93 /// ```
94 ///
95 /// See: [`MuleMap::with_capacity_and_hasher`]
96 ///
97 /// Analogous to [`HashMap::default`]
98 #[inline]
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize> PartialEq
105 for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
106where
107 K: Key<TABLE_MIN_VALUE>,
108 V: PartialEq,
109 S: BuildHasher,
110{
111 /// Tests for `self` and `other` values to be equal, and is used by `==`.
112 ///
113 /// # Example
114 ///
115 /// ```
116 /// let mut mule_map1 = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
117 /// mule_map1.bump_int(10);
118 /// mule_map1.bump_int(11);
119 /// mule_map1.bump_int(999_999);
120 /// mule_map1.bump_int(999_999);
121 ///
122 /// let mut mule_map2 = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
123 /// mule_map2.bump_int(10);
124 /// mule_map2.bump_int(11);
125 /// mule_map2.bump_int(999_999);
126 /// mule_map2.bump_int(999_999);
127 ///
128 /// assert!(mule_map1 == mule_map2)
129 /// ```
130 #[inline]
131 fn eq(&self, other: &MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>) -> bool {
132 self.hash_map == other.hash_map && self.table == other.table
133 }
134}
135
136impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize> Eq
137 for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
138where
139 K: Key<TABLE_MIN_VALUE>,
140 V: Eq,
141 S: BuildHasher,
142{
143}
144
145impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize> std::ops::Index<K>
146 for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
147where
148 K: Key<TABLE_MIN_VALUE>,
149 S: BuildHasher,
150{
151 type Output = V;
152
153 /// Returns a reference to the value corresponding to the supplied key.
154 ///
155 /// # Example
156 ///
157 /// ```
158 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
159 /// mule_map.bump_int(10);
160 /// mule_map.bump_int(999_999);
161 /// assert_eq!(mule_map[10], 1);
162 /// assert_eq!(mule_map[999_999], 1);
163 /// assert!(test_utils::catch_unwind_silent(|| mule_map[123]).is_err());
164 ///
165 /// ```
166 ///
167 /// # Panics
168 ///
169 /// Panics if the key is not present in the `MuleMap`.
170 #[inline]
171 fn index(&self, key: K) -> &V {
172 self.get(key).expect("No entry found for key")
173 }
174}
175
176impl<'a, K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize> Extend<(K, &'a V)>
177 for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
178where
179 K: Key<TABLE_MIN_VALUE>,
180 S: Default + BuildHasher,
181 V: Copy,
182{
183 /// Extends a collection with the contents of an iterator.
184 ///
185 /// # Example
186 ///
187 /// ```
188 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
189 /// mule_map.extend([(0, &10), (999_999, &3)].into_iter());
190 ///
191 /// let mut mule_map2 = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
192 /// mule_map2.insert(0, 10);
193 /// mule_map2.insert(999_999, 3);
194 /// assert_eq!(mule_map, mule_map2);
195 /// ```
196 #[inline]
197 fn extend<T: IntoIterator<Item = (K, &'a V)>>(&mut self, iter: T) {
198 for (key, val) in iter {
199 self.insert(key, *val);
200 }
201 }
202}
203
204impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize> Extend<(K, V)>
205 for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
206where
207 K: Key<TABLE_MIN_VALUE>,
208 S: BuildHasher,
209{
210 /// Extends a collection with the contents of an iterator.
211 ///
212 /// # Example
213 ///
214 /// ```
215 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
216 /// mule_map.extend([(0, 10), (999_999, 3)].into_iter());
217 ///
218 /// let mut mule_map2 = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
219 /// mule_map2.insert(0, 10);
220 /// mule_map2.insert(999_999, 3);
221 /// assert_eq!(mule_map, mule_map2);
222 /// ```
223 #[inline]
224 fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
225 for (key, val) in iter {
226 self.insert(key, val);
227 }
228 }
229}
230
231impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize, const N: usize>
232 From<[(K, V); N]> for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
233where
234 K: Key<TABLE_MIN_VALUE>,
235 S: BuildHasher + Default,
236 <K as TryFrom<i128>>::Error: Debug,
237{
238 /// Converts a `[(K, V); N]` into a `MuleMap<K, V>`.
239 ///
240 /// If any entries in the array have equal keys,
241 /// all but one of the corresponding values will be dropped.
242 ///
243 /// # Example
244 ///
245 /// ```
246 /// use mule_map::MuleMap;
247 ///
248 /// let mut mule_map = MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
249 /// mule_map.insert(1,2);
250 /// mule_map.insert(3,4);
251 ///
252 /// let map1 = MuleMap::<_, _, fnv_rs::FnvBuildHasher>::from([(1, 2), (3, 4)]);
253 /// let map2: MuleMap<_, _, fnv_rs::FnvBuildHasher> = [(1, 2), (3, 4)].into();
254 ///
255 /// assert_eq!(map1, mule_map);
256 /// assert_eq!(map2, mule_map);
257 /// ```
258 #[inline]
259 fn from(arr: [(K, V); N]) -> Self {
260 let mut map = Self::default();
261 for (key, val) in arr {
262 map.insert(key, val);
263 }
264 map
265 }
266}
267
268impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize> FromIterator<(K, V)>
269 for MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
270where
271 K: Key<TABLE_MIN_VALUE>,
272 S: BuildHasher + Default,
273 <K as TryFrom<i128>>::Error: Debug,
274{
275 /// Constructs a `MuleMap<K, V>` from an iterator of key-value pairs.
276 ///
277 /// If the iterator produces any pairs with equal keys,
278 /// all but one of the corresponding values will be dropped.
279 ///
280 /// # Example
281 ///
282 /// ```
283 /// use mule_map::MuleMap;
284 ///
285 /// let mut mule_map = MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
286 /// mule_map.insert(1,2);
287 /// mule_map.insert(3,4);
288 ///
289 /// let map1 = MuleMap::<_, _, fnv_rs::FnvBuildHasher>::from_iter([(1, 2), (3, 4)].into_iter());
290 ///
291 /// assert_eq!(map1, mule_map);
292 /// ```
293 #[inline]
294 fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
295 let mut map = Self::default();
296 map.extend(iter);
297 map
298 }
299}
300
301impl<K, V, S, const TABLE_MIN_VALUE: i128, const TABLE_SIZE: usize>
302 MuleMap<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE>
303where
304 K: Key<TABLE_MIN_VALUE>,
305 S: BuildHasher,
306{
307 // Hard limit, way beyond practical lookup table size. This makes it easier to calculate the key index
308 const STATIC_ASSERT_LIMIT_SIZE_TO_I32_MAX: () =
309 assert!((TABLE_SIZE as u128) <= i32::MAX as u128 + 1);
310
311 // NOTE: using `saturating_sub` to allow for when `TABLE_SIZE == 0`
312 const STATIC_ASSERT_SIZE_FITS_I128: () = assert!(
313 TABLE_MIN_VALUE
314 .checked_add(const { TABLE_SIZE.saturating_sub(1) as i128 })
315 .is_some()
316 );
317
318 const STATIC_ASSERT_ISIZE_FITS_I32: () = assert!(isize::MAX as u128 >= i32::MAX as u128);
319
320 #[must_use]
321 #[inline]
322 pub(crate) fn use_lookup_table(key: K) -> bool {
323 if const { TABLE_SIZE == 0 } {
324 return false;
325 }
326
327 let promoted_sum = K::add_promoted(
328 K::i128_as_promoted(TABLE_MIN_VALUE),
329 K::usize_as_promoted(const { TABLE_SIZE.saturating_sub(1) }),
330 );
331
332 // NOTE: `TABLE_MIN_VALUE + TABLE_SIZE - 1` and TABLE_MIN_VALUE must fit into a key type, K (with correct
333 // promotion during add for signed ints)
334 key <= promoted_sum && key >= K::i128_as_k(TABLE_MIN_VALUE)
335 }
336
337 #[inline]
338 pub(crate) fn check_bounds()
339 where
340 <K as TryFrom<i128>>::Error: Debug,
341 {
342 let () = Self::STATIC_ASSERT_LIMIT_SIZE_TO_I32_MAX;
343 let () = Self::STATIC_ASSERT_SIZE_FITS_I128;
344 let () = Self::STATIC_ASSERT_ISIZE_FITS_I32;
345
346 // NOTE: using `saturating_sub` to allow for when `TABLE_SIZE == 0`
347 // `TABLE_MIN_VALUE + TABLE_SIZE - 1` must fit in i128, because of `STATIC_ASSERT_SIZE_FITS_I128`
348 <i128 as TryInto<K>>::try_into(
349 TABLE_MIN_VALUE + const { TABLE_SIZE.saturating_sub(1) } as i128,
350 )
351 .unwrap_or_else(|_| {
352 panic!(
353 "TABLE_MIN_VALUE ({TABLE_MIN_VALUE:?}) + TABLE_SIZE ({TABLE_SIZE:?}) should fit into key type, K"
354 )
355 });
356
357 <i128 as TryInto<K>>::try_into(TABLE_MIN_VALUE)
358 .expect("TABLE_MIN_VALUE should fit into key type, K");
359 }
360
361 /// Creates an empty [`MuleMap`].
362 ///
363 /// # Example
364 ///
365 /// ```
366 /// type Hash = fnv_rs::FnvBuildHasher;
367 /// let mule_map = mule_map::MuleMap::<u32, usize, Hash>::new();
368 /// assert!(mule_map.is_empty());
369 /// ```
370 ///
371 /// See: [`MuleMap::with_capacity_and_hasher`]
372 ///
373 /// Analogous to [`HashMap::new`]
374 #[must_use]
375 #[inline]
376 pub fn new() -> Self
377 where
378 S: Default,
379 <K as TryFrom<i128>>::Error: Debug,
380 {
381 Self::with_capacity_and_hasher(0, S::default())
382 }
383
384 /// Creates an empty [`MuleMap`] with at least the provided capacity.
385 ///
386 /// # Example
387 ///
388 /// ```
389 /// type Hash = fnv_rs::FnvBuildHasher;
390 /// let mule_map = mule_map::MuleMap::<u32, usize, Hash>::with_capacity(100);
391 /// assert!(mule_map.is_empty());
392 /// ```
393 ///
394 /// See: [`MuleMap::with_capacity_and_hasher`]
395 ///
396 /// Analogous to [`HashMap::with_capacity`]
397 #[must_use]
398 #[inline]
399 pub fn with_capacity(capacity: usize) -> Self
400 where
401 S: Default,
402 <K as TryFrom<i128>>::Error: Debug,
403 {
404 Self::with_capacity_and_hasher(capacity, S::default())
405 }
406
407 /// Creates an empty [`MuleMap`] using `hash_builder`.
408 ///
409 /// # Example
410 ///
411 /// ```
412 /// type Hash = fnv_rs::FnvBuildHasher;
413 /// let mule_map = mule_map::MuleMap::<u32, usize, Hash>::with_hasher(Hash::default());
414 /// assert!(mule_map.is_empty());
415 /// ```
416 ///
417 /// See: [`MuleMap::with_capacity_and_hasher`]
418 ///
419 /// Analogous to [`HashMap::with_hasher`]
420 #[must_use]
421 #[inline]
422 pub fn with_hasher(hash_builder: S) -> Self
423 where
424 <K as TryFrom<i128>>::Error: Debug,
425 {
426 Self::with_capacity_and_hasher(0, hash_builder)
427 }
428
429 /// Creates an empty [`MuleMap`] with at least the provided capacity and using `hash_builder`.
430 ///
431 /// # Example
432 ///
433 /// ```
434 /// type Hash = fnv_rs::FnvBuildHasher;
435 /// let mule_map = mule_map::MuleMap::<u32, usize, Hash>::with_capacity_and_hasher(100, Hash::default());
436 /// assert!(mule_map.is_empty());
437 /// ```
438 ///
439 /// # Panics
440 ///
441 /// Panics if
442 /// - `TABLE_MIN_VALUE` or `TABLE_MIN_VALUE + TABLE_SIZE - 1` doesn't fit into key type, `K`.
443 ///
444 /// Analogous to [`HashMap::with_capacity_and_hasher`]
445 #[must_use]
446 #[inline]
447 pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self
448 where
449 <K as TryFrom<i128>>::Error: Debug,
450 {
451 Self::check_bounds();
452
453 MuleMap::<K, V, S, TABLE_MIN_VALUE, TABLE_SIZE> {
454 hash_map: HashMap::with_capacity_and_hasher(capacity, hash_builder),
455 table: [const { None }; TABLE_SIZE],
456 }
457 }
458
459 /// Returns capacity of the underlying hash map.
460 ///
461 /// # Example
462 ///
463 /// ```
464 /// type Hash = fnv_rs::FnvBuildHasher;
465 /// let mule_map = mule_map::MuleMap::<u32, usize, Hash>::with_capacity(100);
466 /// assert!(mule_map.capacity() >= 100);
467 /// ```
468 ///
469 /// See [`HashMap::capacity`]
470 #[must_use]
471 #[inline]
472 pub fn capacity(&self) -> usize {
473 self.hash_map.capacity()
474 }
475
476 /// Clears the map, removing all key-value pairs. Keeps the allocated memory for reuse.
477 ///
478 /// # Example
479 /// ```
480 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
481 /// mule_map.insert(1,2);
482 /// mule_map.insert(999_999,4);
483 /// mule_map.clear();
484 /// assert!(mule_map.is_empty());
485 /// ```
486 ///
487 /// See [`HashMap::clear`]
488 #[inline]
489 pub fn clear(&mut self) {
490 self.hash_map.clear();
491 self.table = [const { None }; TABLE_SIZE];
492 }
493
494 /// Returns true if the map contains a value for the specified key.
495 ///
496 /// # Example
497 /// ```
498 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
499 /// mule_map.insert(1,2);
500 /// mule_map.insert(999_999,4);
501 /// assert!(mule_map.contains_key(999_999));
502 /// assert!(mule_map.contains_key(1));
503 /// assert!(!mule_map.contains_key(2));
504 /// assert!(!mule_map.contains_key(999_998));
505 /// ```
506 /// Analogous to [`HashMap::contains_key`]
507 #[must_use]
508 #[inline]
509 pub fn contains_key(&self, key: K) -> bool {
510 if Self::use_lookup_table(key) {
511 self.table[key.key_index()].is_some()
512 } else {
513 self.hash_map.contains_key(&key)
514 }
515 }
516
517 /// Returns a reference to the value corresponding to the key.
518 ///
519 /// # Example
520 ///
521 /// ```
522 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
523 /// mule_map.insert(1,2);
524 /// mule_map.insert(999_999,4);
525 /// assert_eq!(mule_map.get(999_999), Some(&4));
526 /// assert_eq!(mule_map.get(1), Some(&2));
527 /// assert_eq!(mule_map.get(2), None);
528 /// assert_eq!(mule_map.get(999_998), None);
529 /// ```
530 ///
531 /// Analogous to [`HashMap::get`]
532 #[must_use]
533 #[inline]
534 pub fn get(&self, key: K) -> Option<&V> {
535 if Self::use_lookup_table(key) {
536 self.table[key.key_index()].as_ref()
537 } else {
538 self.hash_map.get(&key)
539 }
540 }
541
542 /// Returns the key-value pair corresponding to the supplied key.
543 ///
544 /// # Example
545 ///
546 /// ```
547 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
548 /// mule_map.insert(1,2);
549 /// mule_map.insert(999_999,4);
550 /// assert_eq!(mule_map.get_key_value(999_999), Some((999_999, &4)));
551 /// assert_eq!(mule_map.get_key_value(1), Some((1,&2)));
552 /// assert_eq!(mule_map.get_key_value(2), None);
553 /// assert_eq!(mule_map.get_key_value(999_998), None);
554 /// ```
555 ///
556 /// Analogous to [`HashMap::get_key_value`]
557 #[must_use]
558 #[inline]
559 pub fn get_key_value(&self, key: K) -> Option<(K, &V)> {
560 let result = Some(key);
561 result.zip(self.get(key))
562 }
563
564 /// Attempts to get mutable references to N values in the map at once.
565 ///
566 /// ...
567 /// # Example
568 ///
569 /// ```
570 /// let mut map = mule_map::MuleMap::<_, _, fnv_rs::FnvBuildHasher>::from([(1, 1602), (2, 1807), (999_999, 1691), (999_998, 1800)]);
571 ///
572 /// assert_eq!(map.get_disjoint_mut([1, 999_999]), [Some(&mut 1602), Some(&mut 1691)]);
573 /// assert_eq!(map.get_disjoint_mut([2, 4, 999_998, 999_990]), [Some(&mut 1807), None, Some(&mut 1800), None]);
574 /// ```
575 ///
576 /// # Panics
577 /// Panics if any keys are overlapping.
578 ///
579 /// Analogous to [`HashMap::get_disjoint_mut`]
580 #[allow(clippy::reversed_empty_ranges)]
581 pub fn get_disjoint_mut<const N: usize>(&mut self, ks: [K; N]) -> [Option<&'_ mut V>; N]
582 where
583 K: Key<TABLE_MIN_VALUE>,
584 {
585 let mut key_indices = [const { 0..0 }; N];
586 let references: [&K; N] = std::array::from_fn(|i| &ks[i]);
587
588 let mut result = self.hash_map.get_disjoint_mut(references);
589
590 for (i, &key) in ks.iter().enumerate() {
591 if Self::use_lookup_table(key) {
592 #[allow(clippy::range_plus_one)]
593 {
594 key_indices[i] = key.key_index()..key.key_index() + 1;
595 }
596 }
597 }
598
599 for (i, range) in self
600 .table
601 .get_disjoint_mut(key_indices)
602 .expect("keys should not overlap, or be out of bounds")
603 .into_iter()
604 .enumerate()
605 {
606 for v in range {
607 if v.is_some() {
608 result[i] = v.as_mut();
609 }
610 }
611 }
612
613 result
614 }
615
616 /// Attempts to get mutable references to `N` values in the map at once, without validating that the values are
617 /// unique.
618 ///
619 /// ...
620 /// # Example
621 ///
622 /// ```
623 /// let mut map = mule_map::MuleMap::<_, _, fnv_rs::FnvBuildHasher>::from([(1, 1602), (2, 1807), (999_999, 1691), (999_998, 1800)]);
624 ///
625 /// assert_eq!(unsafe {map.get_disjoint_unchecked_mut([1, 999_999])}, [Some(&mut 1602), Some(&mut 1691)]);
626 /// assert_eq!(unsafe {map.get_disjoint_unchecked_mut([2, 4, 999_998, 999_990])}, [Some(&mut 1807), None, Some(&mut 1800), None]);
627 /// ```
628 ///
629 /// # Safety
630 /// Calling this method with overlapping keys is undefined behavior even if the resulting references are not used.
631 /// This makes calls to [`HashMap::get_disjoint_unchecked_mut`] and [`slice::get_disjoint_unchecked_mut`]
632 ///
633 /// Analogous to [`HashMap::get_disjoint_unchecked_mut`]
634 #[allow(clippy::reversed_empty_ranges)]
635 pub unsafe fn get_disjoint_unchecked_mut<const N: usize>(
636 &mut self,
637 ks: [K; N],
638 ) -> [Option<&'_ mut V>; N]
639 where
640 K: Key<TABLE_MIN_VALUE>,
641 {
642 let mut key_indices = [const { 0..0 }; N];
643 let references: [&K; N] = std::array::from_fn(|i| &ks[i]);
644
645 let mut result = unsafe { self.hash_map.get_disjoint_unchecked_mut(references) };
646
647 for (i, &key) in ks.iter().enumerate() {
648 if Self::use_lookup_table(key) {
649 #[allow(clippy::range_plus_one)]
650 {
651 key_indices[i] = key.key_index()..key.key_index() + 1;
652 }
653 }
654 }
655
656 for (i, range) in unsafe {
657 self.table
658 .get_disjoint_unchecked_mut(key_indices)
659 .into_iter()
660 .enumerate()
661 } {
662 for v in range {
663 if v.is_some() {
664 result[i] = v.as_mut();
665 }
666 }
667 }
668
669 result
670 }
671
672 /// Returns a mutable reference to the value corresponding to the key.
673 ///
674 /// # Example
675 ///
676 /// ```
677 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
678 /// mule_map.insert(1,2);
679 /// mule_map.insert(999_999,4);
680 /// assert_eq!(mule_map.get_mut(999_999), Some(&mut 4));
681 /// assert_eq!(mule_map.get_mut(1), Some(&mut 2));
682 /// assert_eq!(mule_map.get_mut(2), None);
683 /// assert_eq!(mule_map.get_mut(999_998), None);
684 /// ```
685 ///
686 /// Analogous to [`HashMap::get_mut`]
687 #[must_use]
688 #[inline]
689 pub fn get_mut(&mut self, key: K) -> Option<&mut V> {
690 if Self::use_lookup_table(key) {
691 self.table[key.key_index()].as_mut()
692 } else {
693 self.hash_map.get_mut(&key)
694 }
695 }
696
697 /// Returns a reference to the map’s [`BuildHasher`].
698 ///
699 /// # Example
700 ///
701 /// ```
702 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
703 /// assert_eq!(&fnv_rs::FnvBuildHasher::default(), mule_map.hasher());
704 /// ```
705 ///
706 /// Analogous to [`HashMap::hasher`]
707 #[must_use]
708 #[inline]
709 pub fn hasher(&self) -> &S {
710 self.hash_map.hasher()
711 }
712
713 /// Inserts a key-value pair into the map. If the map did not have this key present, None is returned. If the map
714 /// did have this key present, the value is updated, and the old value is returned.
715 ///
716 /// # Example
717 ///
718 /// ```
719 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
720 /// mule_map.insert(1,2);
721 /// mule_map.insert(999_999,4);
722 /// assert_eq!(mule_map.get_key_value(999_999), Some((999_999, &4)));
723 /// assert_eq!(mule_map.get_key_value(1), Some((1,&2)));
724 /// assert_eq!(mule_map.get_key_value(2), None);
725 /// assert_eq!(mule_map.get_key_value(999_998), None);
726 /// ```
727 ///
728 /// Analogous to [`HashMap::insert`]
729 #[inline]
730 pub fn insert(&mut self, key: K, value: V) -> Option<V> {
731 if Self::use_lookup_table(key) {
732 self.table[key.key_index()].replace(value)
733 } else {
734 self.hash_map.insert(key, value)
735 }
736 }
737
738 /// Returns true if the map contains no elements. Checks both the lookup table and the hashmap. Note, there is no
739 /// tracking in the lookup table - in the worst case, we have to check all elements of the lookup table.
740 ///
741 /// # Example
742 ///
743 /// ```
744 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
745 /// assert!(mule_map.is_empty());
746 /// mule_map.insert(1,2);
747 /// assert!(!mule_map.is_empty());
748 /// mule_map.clear();
749 /// assert!(mule_map.is_empty());
750 /// mule_map.insert(999_999,4);
751 /// assert!(!mule_map.is_empty());
752 /// mule_map.clear();
753 /// assert!(mule_map.is_empty());
754 /// ```
755 ///
756 /// Analogous to [`HashMap::is_empty`]
757 #[must_use]
758 #[inline]
759 pub fn is_empty(&self) -> bool {
760 self.hash_map.is_empty() && !self.table.iter().any(Option::is_some)
761 }
762
763 /// Returns the number of elements in the map. Checks both the lookup table and the hashmap. Note, there is no
764 /// tracking in the lookup table, so this will scan the whole table.
765 ///
766 /// # Example
767 ///
768 /// ```
769 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
770 /// assert_eq!(mule_map.len(), 0);
771 /// mule_map.insert(1,2);
772 /// assert_eq!(mule_map.len(), 1);
773 /// mule_map.insert(999_999,4);
774 /// assert_eq!(mule_map.len(), 2);
775 /// mule_map.clear();
776 /// assert_eq!(mule_map.len(), 0);
777 /// ```
778 ///
779 /// Analogous to [`HashMap::len`]
780 #[must_use]
781 #[inline]
782 pub fn len(&self) -> usize {
783 self.hash_map.len() + self.table.iter().filter(|&x| x.is_some()).count()
784 }
785
786 /// Removes a key from the map, returning the value at the key if the key was previously in the map.
787 ///
788 /// # Example
789 ///
790 /// ```
791 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
792 /// mule_map.insert(1,2);
793 /// assert_eq!(mule_map.remove(0), None);
794 /// assert!(!mule_map.is_empty());
795 /// assert_eq!(mule_map.remove(1), Some(2));
796 /// assert!(mule_map.is_empty());
797 /// mule_map.insert(999_999,4);
798 /// assert_eq!(mule_map.remove(999_999), Some(4));
799 /// assert!(mule_map.is_empty());
800 /// ```
801 ///
802 /// Analogous to [`HashMap::remove`]
803 #[inline]
804 pub fn remove(&mut self, key: K) -> Option<V> {
805 if Self::use_lookup_table(key) {
806 self.table[key.key_index()].take()
807 } else {
808 self.hash_map.remove(&key)
809 }
810 }
811
812 /// Removes a key from the map, returning the stored key and value if the key was previously in the map.
813 ///
814 /// # Example
815 ///
816 /// ```
817 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
818 /// mule_map.insert(1,2);
819 /// assert_eq!(mule_map.remove_entry(0), None);
820 /// assert!(!mule_map.is_empty());
821 /// assert_eq!(mule_map.remove_entry(1), Some((1, 2)));
822 /// assert!(mule_map.is_empty());
823 /// mule_map.insert(999_999,4);
824 /// assert_eq!(mule_map.remove_entry(999_999), Some((999_999,4)));
825 /// assert!(mule_map.is_empty());
826 /// ```
827 ///
828 /// Analogous to [`HashMap::remove_entry`]
829 #[inline]
830 pub fn remove_entry(&mut self, key: K) -> Option<(K, V)> {
831 if Self::use_lookup_table(key) {
832 let result = Some(key);
833 result.zip(self.table[key.key_index()].take())
834 } else {
835 self.hash_map.remove_entry(&key)
836 }
837 }
838
839 /// Calls `reserve` on the underlying [`HashMap`]
840 ///
841 /// # Example
842 ///
843 /// ```
844 /// let mut mule_map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::default();
845 /// mule_map.reserve(100);
846 /// ```
847 ///
848 /// Analogous to [`HashMap::reserve`]
849 #[inline]
850 pub fn reserve(&mut self, additional: usize) {
851 self.hash_map.reserve(additional);
852 }
853
854 /// Calls `shrink_to` on the underlying [`HashMap`]
855 ///
856 /// # Example
857 ///
858 /// ```
859 /// let mut map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::with_capacity(100);
860 /// map.insert(999_999, 2);
861 /// map.insert(999_998, 4);
862 /// assert!(map.capacity() >= 100);
863 /// map.shrink_to(10);
864 /// assert!(map.capacity() >= 10);
865 /// map.shrink_to(0);
866 /// assert!(map.capacity() >= 2);
867 /// ```
868 ///
869 /// Analogous to [`HashMap::shrink_to`]
870 #[inline]
871 pub fn shrink_to(&mut self, min_capacity: usize) {
872 self.hash_map.shrink_to(min_capacity);
873 }
874
875 /// Calls `shrink_to_fit` on the underlying [`HashMap`]
876 ///
877 /// # Example
878 ///
879 /// ```
880 /// let mut map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::with_capacity(100);
881 /// map.insert(999_999, 2);
882 /// map.insert(999_998, 4);
883 /// assert!(map.capacity() >= 100);
884 /// map.shrink_to_fit();
885 /// assert!(map.capacity() >= 2);
886 /// ```
887 ///
888 /// Analogous to [`HashMap::shrink_to_fit`]
889 #[inline]
890 pub fn shrink_to_fit(&mut self) {
891 self.hash_map.shrink_to_fit();
892 }
893
894 /// Calls `try_reserve` on the underlying [`HashMap`]
895 ///
896 ///
897 /// # Example
898 ///
899 /// ```
900 /// let mut map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::new();
901 /// map.try_reserve(10).expect("Should have space to allocate 10 elements");
902 /// ```
903 ///
904 /// # Errors
905 ///
906 /// If the capacity overflows, or the allocator reports a failure, then an error is returned.
907 ///
908 /// Analogous to [`HashMap::try_reserve`]
909 #[inline]
910 pub fn try_reserve(
911 &mut self,
912 additional: usize,
913 ) -> Result<(), std::collections::TryReserveError> {
914 self.hash_map.try_reserve(additional)
915 }
916
917 /// Modify the values at location `key` by calling `f` on its value. If no value present, create a new value set to
918 /// `default`.
919 ///
920 /// # Example
921 ///
922 /// ```
923 /// let mut map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::new();
924 /// map.modify_or_insert(10, |x| *x += 1, 100);
925 /// assert!(map.get(10) == Some(&100));
926 /// map.modify_or_insert(10, |x| *x += 1, 100);
927 /// assert!(map.get(10) == Some(&101));
928 /// map.modify_or_insert(999_999, |x| *x += 1, 100);
929 /// assert!(map.get(999_999) == Some(&100));
930 /// map.modify_or_insert(999_999, |x| *x += 1, 100);
931 /// assert!(map.get(999_999) == Some(&101));
932 /// ```
933 ///
934 #[inline]
935 pub fn modify_or_insert<F>(&mut self, key: K, f: F, default: V)
936 where
937 F: FnOnce(&mut V),
938 {
939 if Self::use_lookup_table(key) {
940 let value = &mut self.table[key.key_index()];
941 match value {
942 Some(x) => f(x),
943 None => *value = Some(default),
944 }
945 } else {
946 self.hash_map.entry(key).and_modify(f).or_insert(default);
947 }
948 }
949
950 /// Adds 1 to the value stored at location `key`. If the value is not present, the value 1 will be set at that
951 /// location.
952 ///
953 /// *NOTE:* This method can only be called with values that implement `AddAssign`, like primitives. For `NonZero<T>`
954 /// values use [`MuleMap::bump_non_zero`] - It uses the niche optimization for better performance.
955 ///
956 /// # Example
957 ///
958 /// ```
959 /// let mut map = mule_map::MuleMap::<u32, i32, fnv_rs::FnvBuildHasher>::new();
960 /// map.bump_int(10);
961 /// assert!(map.get(10) == Some(&1));
962 /// map.bump_int(10);
963 /// assert!(map.get(10) == Some(&2));
964 /// map.bump_int(999_999);
965 /// assert!(map.get(999_999) == Some(&1));
966 /// map.bump_int(999_999);
967 /// assert!(map.get(999_999) == Some(&2));
968 /// ```
969 ///
970 /// # Panics
971 ///
972 /// May panics if adding 1 results in overflow.
973 #[inline]
974 pub fn bump_int(&mut self, key: K)
975 where
976 V: AddAssign<V> + num_traits::One + num_traits::Zero,
977 {
978 if Self::use_lookup_table(key) {
979 *self.table[key.key_index()].get_or_insert(V::zero()) += V::one();
980 } else {
981 self.hash_map
982 .entry(key)
983 .and_modify(|counter| *counter += V::one())
984 .or_insert(V::one());
985 }
986 }
987
988 /// Adds 1 to the value stored at location `key`. If the value is not present, the value 1 will be set at that
989 /// location. Uses the niche optimization for better performance with `Option<NonZero<T>>`.
990 ///
991 /// *NOTE:* This method can only be called with `NonZero<T>` values. For primitive values use [`MuleMap::bump_int`].
992 ///
993 /// # Example
994 ///
995 /// ```
996 /// use std::num::NonZero;
997 /// let mut map = mule_map::MuleMap::<u32, NonZero<i32>, fnv_rs::FnvBuildHasher>::new();
998 /// map.bump_non_zero(10);
999 ///
1000 /// assert!(map.get(10) == Some(&const { NonZero::new(1).expect("1 is not 0") }));
1001 /// map.bump_non_zero(10);
1002 /// assert!(map.get(10) == Some(&const { NonZero::new(2).expect("2 is not 0") }));
1003 /// map.bump_non_zero(999_999);
1004 /// assert!(map.get(999_999) == Some(&const { NonZero::new(1).expect("1 is not 0") }));
1005 /// map.bump_non_zero(999_999);
1006 /// assert!(map.get(999_999) == Some(&const { NonZero::new(2).expect("2 is not 0") }));
1007 /// ```
1008 ///
1009 /// # Panics
1010 ///
1011 /// Panics if adding 1 results in overflow.
1012 #[inline]
1013 pub fn bump_non_zero(&mut self, key: K)
1014 where
1015 V: NonZeroInt + bytemuck::PodInOption,
1016 <V as NonZeroInt>::UnderlyingType: AddAssign<V::UnderlyingType>,
1017 <V as NonZeroInt>::UnderlyingType: bytemuck::Pod + PrimInt,
1018 {
1019 use num_traits::One;
1020
1021 if Self::use_lookup_table(key) {
1022 *bytemuck::cast_mut::<Option<V>, V::UnderlyingType>(
1023 &mut self.table[key.key_index()],
1024 ) += V::UnderlyingType::one();
1025 } else {
1026 self.hash_map
1027 .entry(key)
1028 .and_modify(|counter| {
1029 *counter = counter
1030 .checked_add(V::UnderlyingType::one())
1031 .expect("Addition should not overflow");
1032 })
1033 .or_insert(V::ONE);
1034 }
1035 }
1036
1037 /// Gets the given key’s corresponding entry in the map for in-place manipulation.
1038 ///
1039 /// # Example
1040 ///
1041 /// ```
1042 /// let mut map = mule_map::MuleMap::<u32, usize, fnv_rs::FnvBuildHasher>::new();
1043 /// map.entry(5).or_insert(3);
1044 /// assert!(map.get(5) == Some(&3));
1045 /// map.entry(5).and_modify(|e| *e += 1).or_insert(1);
1046 /// assert!(map.get(5) == Some(&4));
1047 /// ```
1048 ///
1049 /// Analogous to [`HashMap::entry`]
1050 #[must_use]
1051 #[inline]
1052 pub fn entry(&mut self, key: K) -> Entry<'_, K, V> {
1053 if Self::use_lookup_table(key) {
1054 let value: &mut Option<V> = &mut self.table[key.key_index()];
1055 match value {
1056 Some(_) => {
1057 Entry::<K, V>::Occupied(OccupiedEntry::Vec(OccupiedVecEntry { value, key }))
1058 }
1059 None => Entry::<K, V>::Vacant(VacantEntry::Vec(VacantVecEntry { value, key })),
1060 }
1061 } else {
1062 match self.hash_map.entry(key) {
1063 std::collections::hash_map::Entry::Occupied(base) => {
1064 Entry::<K, V>::Occupied(OccupiedEntry::HashMap(OccupiedHashMapEntry { base }))
1065 }
1066 std::collections::hash_map::Entry::Vacant(base) => {
1067 Entry::<K, V>::Vacant(VacantEntry::HashMap(VacantHashMapEntry { base }))
1068 }
1069 }
1070 }
1071 }
1072}
1073
1074#[sealed]
1075#[doc(hidden)]
1076pub trait NonZeroInt {
1077 type UnderlyingType;
1078 const ONE: Self;
1079 #[must_use]
1080 #[inline]
1081 fn checked_add(self, other: Self::UnderlyingType) -> Option<Self>
1082 where
1083 Self::UnderlyingType: bytemuck::Pod,
1084 Self::UnderlyingType: AddAssign<Self::UnderlyingType>,
1085 Self: bytemuck::PodInOption,
1086 Self::UnderlyingType: PrimInt,
1087 {
1088 let mut result = Some(self);
1089 let x = bytemuck::cast_mut::<Option<Self>, Self::UnderlyingType>(&mut result);
1090 *x += other;
1091 result
1092 }
1093}
1094
1095macro_rules! impl_non_zero {
1096 (non_zero=$non_zero_type:ty, underlying=$underlying_type:ty) => {
1097 #[sealed]
1098 impl NonZeroInt for $non_zero_type {
1099 const ONE: $non_zero_type = const { NonZero::new(1).expect("1 is not 0") };
1100 type UnderlyingType = $underlying_type;
1101 }
1102 };
1103}
1104
1105impl_non_zero!(non_zero = std::num::NonZeroI8, underlying = i8);
1106impl_non_zero!(non_zero = std::num::NonZeroI16, underlying = i16);
1107impl_non_zero!(non_zero = std::num::NonZeroI32, underlying = i32);
1108impl_non_zero!(non_zero = std::num::NonZeroI64, underlying = i64);
1109impl_non_zero!(non_zero = std::num::NonZeroI128, underlying = i128);
1110impl_non_zero!(non_zero = std::num::NonZeroIsize, underlying = isize);
1111
1112impl_non_zero!(non_zero = std::num::NonZeroU8, underlying = u8);
1113impl_non_zero!(non_zero = std::num::NonZeroU16, underlying = u16);
1114impl_non_zero!(non_zero = std::num::NonZeroU32, underlying = u32);
1115impl_non_zero!(non_zero = std::num::NonZeroU64, underlying = u64);
1116impl_non_zero!(non_zero = std::num::NonZeroU128, underlying = u128);
1117impl_non_zero!(non_zero = std::num::NonZeroUsize, underlying = usize);
1118
1119#[cfg(test)]
1120mod tests {
1121 use super::*;
1122
1123 #[test]
1124 fn use_lookup_table() {
1125 type DefaultRange = MuleMap<i32, i32, fnv_rs::FnvBuildHasher, 0, { u8::MAX as usize + 1 }>;
1126 type NegRange = MuleMap<i32, i32, fnv_rs::FnvBuildHasher, -100, { u8::MAX as usize + 1 }>;
1127 type ZeroSize = MuleMap<i32, i32, fnv_rs::FnvBuildHasher, 0, { u8::MIN as usize }>;
1128
1129 assert!(DefaultRange::use_lookup_table(0));
1130 assert!(!DefaultRange::use_lookup_table(-1));
1131 assert!(DefaultRange::use_lookup_table(255));
1132 assert!(!DefaultRange::use_lookup_table(256));
1133
1134 assert!(NegRange::use_lookup_table(-100));
1135 assert!(!NegRange::use_lookup_table(-101));
1136 assert!(NegRange::use_lookup_table(155));
1137 assert!(!NegRange::use_lookup_table(156));
1138
1139 assert!(!ZeroSize::use_lookup_table(0));
1140 assert!(!ZeroSize::use_lookup_table(1));
1141 }
1142 #[test]
1143 fn check_table_range() {
1144 type BadRange = MuleMap<u8, i32, fnv_rs::FnvBuildHasher, 0, { u8::MAX as usize + 2 }>;
1145 type BadRange2 = MuleMap<u8, i32, fnv_rs::FnvBuildHasher, -1, { u8::MAX as usize }>;
1146 type OkRange = MuleMap<u8, i32, fnv_rs::FnvBuildHasher, 0, { u8::MAX as usize + 1 }>;
1147 type OkRange2 = MuleMap<u8, i32, fnv_rs::FnvBuildHasher, 1, { u8::MAX as usize }>;
1148 type ZeroSize = MuleMap<u8, i32, fnv_rs::FnvBuildHasher, 0, { u8::MIN as usize }>;
1149
1150 assert!(test_utils::catch_unwind_silent(BadRange::new).is_err());
1151 assert!(test_utils::catch_unwind_silent(BadRange2::new).is_err());
1152 assert!(test_utils::catch_unwind_silent(OkRange::new).is_ok());
1153 assert!(test_utils::catch_unwind_silent(OkRange2::new).is_ok());
1154 assert!(test_utils::catch_unwind_silent(ZeroSize::new).is_ok());
1155 }
1156}