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}