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