dsa_rust/associative/probing_hash_table.rs
1/*! Safe, open addressing hash table with MAD compression and quadratic probing
2
3# About
4This hash map implementation uses open-addressing to take advantage of cache locality, and relies on a "multiply, add, and divide" (MAD) compression algorithm with quadratic probing to handle collisions.
5
6This structure provides the basis for several other structures in this library, including the [PriorityQueue<K, P>](crate::composite::priority_queue) and [HashSet\<T>](crate::associative::hash_set) implementations.
7
8# Design
9This structure uses Rust's [default hasher](std::hash::DefaultHasher) to hash keys. The default hasher currently represents a cryptographically strong [SipHash-1-3](https://en.wikipedia.org/wiki/SipHash) design as of the publication date (9/2025). In practice, hashing small key objects <= 128 bytes provide similar performance as ULID generation, but hashing arbitrarily large key objects may incur performance penalties.
10
11The primary [HashMap] struct uses a private byte mask (as a "control" list) to track positions with valid ("live"), empty, and removed ("tombstone") entries. Using a conventional free list and overwriting data for a marked slot would break the quadratic probing logic.
12
13```text
14+-------------------------------------------------+
15| ctrl | Option<Entry<K, V>> |
16|------+------------------------------------------|
17| 0 | None |
18| 0 | None |
19| 0 | None |
20| 0 | None |
21| 0 | None |
22| 1 | Some(Entry { key: "Dingus", value: 18 }) |
23| 1 | Some(Entry { key: "Brain", value: 37 }) |
24| 0 | None |
25| 0 | None |
26| 0 | None |
27| 0 | None |
28| 0 | None |
29| 0 | None |
30| 0 | None |
31| 1 | Some(Entry { key: "Remus", value: 22 }) |
32| 187 | None |
33| 0 | None |
34| 1 | Some(Entry { key: "Bobson", value: 36 }) |
35| 0 | None |
36| 0 | None |
37| 0 | None |
38| 187 | None |
39+-------------------------------------------------+
40```
41
42## Insertion
43The structure contains two primary ways to insert entries into the map.
44
45The [put()](crate::associative::probing_hash_table::HashMap::put) operation overwrites values for existing keys, but does not mutate the existing key. This keeps with the standard library's implementation where two keys can be `==` without being identical.
46
47```rust
48 use dsa_rust::associative::probing_hash_table::HashMap;
49
50 // Build a map with put()
51 let mut map = HashMap::<&str, u8>::new();
52 let mut names: Vec<&str> = vec!["Peter", "Brain", "Remus", "Bobson", "Dingus", "Dangus"];
53 let values: Vec<u8> = vec![39, 37, 22, 36, 18, 27];
54 for (k, v) in names.iter().zip(values.into_iter()) {
55 map.put(k, v);
56 }
57```
58
59The structure also provides a [mut_val_or()](crate::associative::probing_hash_table::HashMap::mut_val_or) operation for mutating the value(s) of the map or inserting a default value. This is useful with the structures iterators to make linear passes over source data during mapping.
60
61
62```rust
63 use dsa_rust::associative::probing_hash_table::HashMap;
64
65 // Build a map with mut_val_or()
66 let mut map = HashMap::<char, u8>::new();
67 let word: &str = "Hello, sickos!";
68 for k in word.chars() {
69 map.mut_val_or(k, |x| *x += 1, 1);
70 }
71
72```
73
74## Removal & Rehashing
75The structure contains a [remove()](crate::associative::probing_hash_table::HashMap::remove) operation that returns an owned entry if it exists in the map. The `remove()` operation marks the index with a tombstone, and uses [std::mem::take] to remove and return the value. Even though `take()` swaps out a `None` variant the index remains unusable until the calling code invokes a rehash operation. This is done in order to retain probing logic. For long-lived maps with many removals/tombstones, the structure provides two re-hashing operations. The [rehash()](crate::associative::probing_hash_table::HashMap::rehash) operation rehashes the entire map (freeing up tombstones) _at the current underlying structure's capacity_. This essentially allows you to permanently remove deleted entries and reduce the map's load factor. Conversely, the [shrink_to_fit()](crate::associative::probing_hash_table::HashMap::shrink_to_fit) operation rehashes the map (freeing up tombstones) down to the minimum backing capacity that still provides a load factor of _<= .5_. Both processes necessarily require _O(n)_ time where _n_ is the number of live entries in the map.
76
77Example of a map that has had many entries removed:
78
79```text
80+-------------------------------------------------+
81| ctrl | Option<Entry<K, V>> |
82|------+------------------------------------------|
83| 1 | Some(Entry { key: 'o', value: 2 }) |
84| 187 | None |
85| 0 | None |
86| 187 | None |
87| 1 | Some(Entry { key: 'l', value: 2 }) |
88| 187 | None |
89| 0 | None |
90| 0 | None |
91| 0 | None |
92| 187 | None |
93| 187 | None |
94| 0 | None |
95| 0 | None |
96| 0 | None |
97| 0 | None |
98| 1 | Some(Entry { key: 'H', value: 1 }) |
99| 0 | None |
100| 187 | None |
101| 0 | None |
102| 0 | None |
103| 1 | Some(Entry { key: 'e', value: 1 }) |
104| 187 | None |
105| 0 | None |
106+-------------------------------------------------+
107```
108
109This example illustrates how the map might look after a `rehash()`:
110
111```text
112+-------------------------------------------------+
113| ctrl | Option<Entry<K, V>> |
114|------+------------------------------------------|
115| 1 | Some(Entry { key: 'H', value: 1 }) |
116| 1 | Some(Entry { key: 'o', value: 2 }) |
117| 0 | None |
118| 0 | None |
119| 0 | None |
120| 0 | None |
121| 0 | None |
122| 0 | None |
123| 0 | None |
124| 0 | None |
125| 0 | None |
126| 0 | None |
127| 1 | Some(Entry { key: 'l', value: 2 }) |
128| 0 | None |
129| 0 | None |
130| 0 | None |
131| 0 | None |
132| 0 | None |
133| 0 | None |
134| 0 | None |
135| 1 | Some(Entry { key: 'e', value: 1 }) |
136| 0 | None |
137| 0 | None |
138+-------------------------------------------------+
139```
140
141This example illustrates how the map might look after a `shrink_to_fit()`:
142
143```text
144+-------------------------------------------------+
145| ctrl | Option<Entry<K, V>> |
146|------+------------------------------------------|
147| 1 | Some(Entry { key: 'H', value: 1 }) |
148| 0 | None |
149| 0 | None |
150| 1 | Some(Entry { key: 'l', value: 2 }) |
151| 1 | Some(Entry { key: 'o', value: 2 }) |
152| 0 | None |
153| 0 | None |
154| 1 | Some(Entry { key: 'e', value: 1 }) |
155| 0 | None |
156| 0 | None |
157| 0 | None |
158+-------------------------------------------------+
159```
160
161# Example
162
163```rust
164
165 use dsa_rust::associative::probing_hash_table::HashMap;
166
167 //Creates a new hash map with str keys and u8 values
168 let mut map = HashMap::<&str, u8>::new();
169
170 println!("Map stats: size: {}, capacity: {}, active entries: {}",
171 map.occupied(),
172 map.capacity(),
173 map.len()
174 );
175
176 // Puts some entries into the map
177 println!("Building the map...");
178 let mut names: Vec<&str> = vec!["Peter", "Brain", "Remus", "Bobson", "Dingus", "Dangus"];
179 let values: Vec<u8> = vec![39, 37, 22, 36, 18, 27];
180 for (k, v) in names.iter().zip(values.into_iter()) {
181 map.put(k, v);
182 }
183
184 // Checks that the map contains what we'd expect
185 if map.contains("Peter") == false {
186 panic!()
187 };
188 let val = map.get("Peter").unwrap();
189 println!("Peter is {val}");
190
191 // Replaces a value for a given key and
192 // checks that the new value took
193 let new = 41;
194
195 //let old = map.put("Peter", new).unwrap().value;
196 let old = map.get("Peter").unwrap().clone();
197 map.put("Peter", new);
198
199 println!("[Peter's age increased from {old} to {new}]");
200 let val = map.get("Peter").unwrap();
201 println!("Uhhhhh, I meant Peter is {val}");
202
203 // Shows the map and its data
204 println!(
205 "\nMap stats: size: {}, capacity: {}, active entries: {}",
206 map.occupied(),
207 map.capacity(),
208 map.len()
209 );
210 map.contents();
211
212 // Illustrates removing entries
213 println!("\nThere can be only one!");
214 names.remove(0);
215 for e in names {
216 let removed = map.remove(&e);
217 if let Some(entry) = removed {
218 println!("Remove: {}", entry.key());
219 }
220 }
221
222 // The final result
223 println!("\nMap stats: size: {}, capacity: {}, active entries: {}",
224 map.occupied(),
225 map.capacity(),
226 map.len()
227 );
228 map.contents();
229```
230
231*/
232
233use crate::associative::hash_lib;
234//use crate::maps::traits;
235use rand::Rng;
236use std::collections::hash_map::DefaultHasher;
237use std::fmt::Debug;
238use std::hash::{Hash, Hasher};
239
240#[derive(Debug, PartialEq)]
241// Contains the actual key:value pair
242pub struct Entry<K, V> {
243 key: K,
244 value: V,
245}
246impl<K, V> Entry<K, V>
247where
248 K: Debug + Hash + PartialEq,
249 V: Debug,
250{
251 /// Constructs a new Entry
252 fn new(key: K, value: V) -> Entry<K, V> {
253 Entry { key, value }
254 }
255
256 /// Returns the key from an Entry
257 pub fn key(&self) -> &K {
258 &self.key
259 }
260
261 /// Returns the value from an Entry
262 pub fn value(&self) -> &V {
263 &self.value
264 }
265}
266impl<K, V> Default for Entry<K, V>
267where
268 K: Default + Debug + Hash + PartialEq,
269 V: Debug + Default,
270{
271 fn default() -> Self {
272 Self {
273 key: K::default(),
274 value: V::default(),
275 }
276 }
277}
278
279// Byte masks were deemed more performant, but this is valuable historic pattern
280//#[derive(Debug, PartialEq)]
281//pub enum Entry<K, V> {
282// Live { key: K, value: V },
283// Tombstone,
284//}
285//impl<K, V> Entry<K, V>
286//where
287// K: Debug + Hash + PartialEq,
288// V: PartialEq,
289//{
290// /// Constructs a new Entry
291// fn new(key: K, value: V) -> Entry<K, V> {
292// Entry::Live { key, value }
293// }
294//
295// /// Returns the key from an Entry
296// pub fn key(&self) -> Option<&K> {
297// match self {
298// Entry::Live { key, .. } => Some(key),
299// Entry::Tombstone => None,
300// }
301// }
302//
303// /// Returns the value from an Entry
304// pub fn value(&self) -> Option<&V> {
305// match self {
306// Entry::Live { value, .. } => Some(value),
307// Entry::Tombstone => None,
308// }
309// }
310//
311// /// Entry adapter that takes a closure to mutate the value of an entry, if it exists.
312// pub fn mut_val<F>(&mut self, f: F)
313// where
314// F: FnOnce(&mut V),
315// {
316// if let Entry::Live { value, .. } = self {
317// f(value);
318// } else {
319// // No-op: Entry is a Tombstone and that would break the probing logic.
320// }
321// }
322//}
323
324#[derive(Debug)]
325pub struct HashMap<K, V> {
326 data: Vec<Option<Entry<K, V>>>, // The primary memory backing
327 // pub data: Vec<MaybeUninit<Entry<K, V>>>, // Gets rid of Option alignment bloat
328 ctrl: Vec<u8>, // A byte mask to identify available positions
329 size: usize, // The total number of entries in the map (live + deleted)
330 live: usize, // The number of "live" entries in the map
331
332 // NOTE: Prime, scale, and shift are used by the MAD compression algorithm.
333 // These randomly-generated values must remain static for the
334 // lifetime of the backing structure; The grow() operation changes these values
335 prime: usize,
336 scale: usize,
337 shift: usize,
338}
339impl<K, V> Default for HashMap<K, V>
340where
341 K: Debug + Default + Eq + Hash + PartialEq,
342 V: Default + PartialEq + std::fmt::Debug,
343{
344 fn default() -> Self {
345 Self::new()
346 }
347}
348impl<K, V> HashMap<K, V>
349where
350 K: Debug + Eq + Hash + PartialEq,
351 V: std::fmt::Debug,
352{
353 /// Constructor for an empty map with a default capacity of 2.
354 pub fn new() -> Self {
355 let new_capacity = 2;
356 let mut table = Self {
357 data: Vec::with_capacity(new_capacity),
358 ctrl: Vec::with_capacity(new_capacity),
359 prime: 13,
360 scale: 5,
361 shift: 7,
362 size: 0,
363 live: 0,
364 };
365 // Initialize storage to ensure access
366 table.data.resize_with(new_capacity, || None);
367 table.ctrl.resize_with(new_capacity, || 0x00);
368 table
369 }
370
371 /// Constructor for an empty map with a given capacity.
372 pub fn new_with_capacity(size: usize) -> Self {
373 let mut table = Self {
374 data: Vec::with_capacity(size),
375 ctrl: Vec::with_capacity(size),
376 prime: 13,
377 scale: 5,
378 shift: 7,
379 size: 0,
380 live: 0,
381 };
382 // Initialize storage to ensure access
383 table.data.resize_with(size, || None);
384 table.ctrl.resize_with(size, || 0x00);
385 table
386 }
387
388 /// Returns the number of active entries in the map in _O(1)_ time.
389 pub fn len(&self) -> usize {
390 self.live
391 }
392
393 /// Returns the total number of entries in the map (active + deleted entries) in _O(n)_ time.
394 pub fn occupied(&self) -> usize {
395 let mut occupied = 0;
396 for e in self.ctrl.iter() {
397 if *e == 1 || *e == 187 {
398 occupied += 1
399 }
400 }
401 occupied
402 }
403
404 /// Returns the total number of _deleted_ entries in the map in _O(n)_ time.
405 pub fn deleted(&self) -> usize {
406 let mut occupied = 0;
407 for e in self.ctrl.iter() {
408 if *e == 187 {
409 occupied += 1
410 }
411 }
412 occupied
413 }
414
415 /// Returns the total number of available slots in the map in _O(1)_ time.
416 ///
417 /// NOTE: The load factor is the quotient of `len() / capacity()`.
418 pub fn capacity(&self) -> usize {
419 self.data.len()
420 }
421
422 /// Returns true if the map is either empty or contains only empty slots and deleted entries.
423 pub fn is_empty(&self) -> bool {
424 self.live == 0 || self.data.is_empty()
425 }
426
427 /// Pretty-prints the map's contents.
428 pub fn contents(&self) {
429 for (e, m) in self.data.iter().zip(self.ctrl.iter()) {
430 println!(" {m:>3}: {e:?}")
431 }
432 }
433
434 /// Takes a key as a reference and returns a Boolean indicating whether its in
435 /// the map. The expected temporal complexity is _O(1)_, as the map
436 /// maintains a [laod factor](https://www.headyimage.com/cs/dsa/maps/#collision-handling-schemes) of <=.5.
437 pub fn contains<Q>(&self, key: &Q) -> bool
438 where
439 K: std::borrow::Borrow<Q>,
440 Q: Debug + Hash + Eq + ?Sized,
441 {
442 self.find_index(key) >= 0
443 }
444
445 /// Returns a reference to the value associated with a key, if the key exists.
446 pub fn get<Q>(&self, key: &Q) -> Option<&V>
447 //pub fn get(&self, key: &K) -> Option<&V> {
448 where
449 K: std::borrow::Borrow<Q>,
450 Q: Debug + Hash + Eq + ?Sized,
451 {
452 // find_index() uses quadratic probing
453 let location = self.find_index(key);
454 if location >= 0 {
455 let value = &self.data[location as usize].as_ref().unwrap().value;
456 Some(value)
457 } else {
458 None
459 }
460 }
461
462 /// Adds entry `(k, v)`, overwriting any value `v` associated with an
463 /// existing key `k`, returns old value. If a new addition increases
464 /// the map's load factor above the designated threshhold of 0.5
465 /// the map resizes.
466 pub fn put(&mut self, key: K, value: V) -> Option<Entry<K, V>>
467//where
468 // K: std::default::Default,
469 {
470 // Checks if the addition will bring the load factor above threshold
471 if ((self.size) as f64 + 1.0) / self.data.len() as f64 >= 0.5 {
472 self.grow();
473 }
474
475 let location = self.find_index(&key);
476
477 // Creates a new Entry and inserts it
478 let entry = Entry::new(key, value);
479 let mut old_entry: Option<Entry<K, V>> = None;
480 if location >= 0 {
481 // Replace an entry
482 //println!("COLLISION!!!! {:?}", &entry.key);
483 old_entry = self.data[location as usize].take();
484 self.data[location as usize] = Some(entry);
485 } else {
486 // Add a new entry
487 self.data[-(location + 1) as usize] = Some(entry);
488 self.ctrl[-(location + 1) as usize] = 0x01;
489 self.size += 1;
490 self.live += 1;
491 };
492 old_entry
493 }
494
495 // Attempts to add entry `(k, v)` to the map, if key `k` does not
496 // already exist. If a new addition increases
497 // the map's load factor above the designated threshhold of 0.5
498 // the map resizes.
499 pub fn try_put(&mut self, key: K, value: V) {
500 // Checks if the addition will bring the load factor above threshold
501 if ((self.size) as f64 + 1.0) / self.data.len() as f64 >= 0.5 {
502 self.grow();
503 }
504
505 let location = self.find_index(&key);
506
507 // Avoids duplicates all together
508 if location < 0 {
509 let entry = Entry::new(key, value);
510 self.data[-(location + 1) as usize] = Some(entry);
511 self.ctrl[-(location + 1) as usize] = 0x01;
512 self.size += 1;
513 self.live += 1;
514 }
515 }
516
517 /// Takes a key, a closure, and a default value. If the key is in the map, applies the closure
518 /// to the corresponding entry's value. If the key is not in the map, creates a new entry with
519 /// the provided value.
520 ///
521 /// Example:
522 /// ```rust
523 /// use dsa_rust::associative::probing_hash_table::HashMap;
524 /// let mut count: HashMap<char, u32> = HashMap::new();
525 /// let word: &str = "Hello";
526 /// for k in word.chars() {
527 /// count.mut_val_or(k, |x| *x += 1, 1);
528 /// }
529 /// ```
530 pub fn mut_val_or<F>(&mut self, key: K, f: F, default: V)
531 where
532 F: FnOnce(&mut V),
533 K: std::default::Default,
534 {
535 // Find the appropriate index
536 let index = self.find_index(&key);
537
538 if index >= 0 {
539 // Found existing key, mutate value
540 if let Some(Entry { value, .. }) = self.data[index as usize].as_mut() {
541 f(value);
542 }
543 } else {
544 // Create new entry
545 self.put(key, default);
546 }
547 }
548
549 /// Removes and returns an entry from the map for a given key, if it exists in the map.
550 pub fn remove<Q>(&mut self, key: &Q) -> Option<Entry<K, V>>
551 where
552 K: std::borrow::Borrow<Q>,
553 Q: Debug + Hash + Eq + ?Sized,
554 {
555 //pub fn remove(&mut self, key: K) -> Option<Entry<K, V>> {
556 let location = self.find_index(key);
557
558 let mut entry = None;
559 if location >= 0 {
560 //entry = self.data[location as usize].replace(Entry::default());
561 entry = self.data[location as usize].take();
562 self.ctrl[location as usize] = 0xBB; // Mark with tombstone
563 self.live -= 1;
564 }
565 entry
566 }
567
568 /// Consumes self and returns a new map of the same size, but without any tombstones.
569 /// Works like a cleanup operation in _O(capacity)_ time because `into_iter` checks all positions.
570 pub fn rehash(self) -> Self {
571 let mut new = Self::new_with_capacity(self.data.len());
572 for (k, v) in self.into_iter() {
573 new.put(k, v);
574 }
575 new
576 }
577
578 /// Rebuilds the map to eliminate accumulated tombstones thereby reducing
579 /// spatial bloat. This operation runs in _O(n)_ time and is intended for
580 /// long-lived maps that have undergone many deletions.
581 ///
582 /// The operation consumes the existing map and returns a new `HashMap`
583 /// with the same live entries.
584 pub fn shrink_to_fit(self) -> Self {
585 let mut new = Self::new();
586 for (k, v) in self.into_iter() {
587 new.put(k, v);
588 }
589 new
590 }
591
592 /// Returns an iterator over borrowed values. The resulting values
593 /// appear in random order.
594 ///
595 /// Example use:
596 /// ```rust
597 /// use dsa_rust::associative::probing_hash_table::HashMap;
598 /// let mut count: HashMap<char, u8> = HashMap::new();
599 /// let mut v = Vec::new();
600 /// for e in count.iter() {
601 /// v.push(*e.0);
602 /// }
603 /// ```
604 pub fn iter(&self) -> Iter<'_, K, V> {
605 Iter {
606 iter: self.data.iter(),
607 }
608 }
609
610 /// Returns an iterator over borrowed keys only. The keys
611 /// appear in random order.
612 ///
613 /// Example use:
614 /// ```text
615 /// for e in map.keys() {
616 /// println!("{e}");
617 /// }
618 /// ```
619 pub fn keys(&self) -> Keys<'_, K, V> {
620 Keys {
621 iter: self.data.iter(),
622 }
623 }
624
625 /// Returns an iterator over borrowed values only. The values
626 /// appear in random order.
627 ///
628 /// Example use:
629 /// ```text
630 /// for e in map.values() {
631 /// println!("{e}");
632 /// }
633 /// ```
634 pub fn values(&self) -> Values<'_, K, V> {
635 Values {
636 iter: self.data.iter(),
637 }
638 }
639
640 // UTILITY FUNCTIONS
641 ////////////////////
642
643 /// Finds the correct insertion location using probing
644 /// Searches the map for key:
645 /// if `>= 0`, overwrite the location and return the old Entry,
646 /// if `< 0`, insert new entry at that location, return None
647 fn find_index<Q>(&self, key: &Q) -> isize
648 where
649 K: std::borrow::Borrow<Q>,
650 Q: Debug + Hash + Eq + ?Sized,
651 {
652 let mut i: usize = 1;
653 let hash = Self::hash(&key);
654 let mut current_index = self.compress(hash);
655
656 // Quadratic probing logic
657 loop {
658 match &self.data[current_index] {
659 Some(val) => {
660 if val.key.borrow() == key {
661 return current_index as isize;
662 }
663 }
664 None => {
665 if self.ctrl[current_index] != 0xBB {
666 return -(current_index as isize + 1);
667 }
668 }
669 }
670 current_index = (current_index + i.pow(2)) % self.data.len();
671 i += 1;
672 }
673 //while let Some(entry) = &self.data[current_index] {
674 // if entry.key.borrow() == key && self.ctrl[current_index] != 0xBB {
675 // return current_index as isize;
676 // } else {
677 // current_index = (current_index + i.pow(2)) % self.data.len();
678 // i += 1;
679 // }
680 //}
681 //-(current_index as isize + 1)
682 }
683
684 /// Takes a reference to a type `T` and uses Rust's default hasher to return a
685 /// 64-bit digest.
686 fn hash<T: Hash + Debug + ?Sized>(key: &T) -> usize {
687 let mut hasher = DefaultHasher::new();
688 key.hash(&mut hasher); // Hash::hash()
689 hasher.finish() as usize // Hasher::finish()
690 }
691
692 // Compresses the hash using the MAD algorithm
693 fn compress(&self, hash: usize) -> usize {
694 (hash.wrapping_mul(self.scale)).wrapping_add(self.shift) % (self.prime) % (self.data.len())
695 }
696
697 /// Grows the base (storage) vector to the next prime larger than
698 /// double the length of the original vector, rehashes and compresses
699 /// hashes for new distribution.
700 fn grow(&mut self) {
701 // Create a new base vector with_capacity() and resize_with()
702 // to ensure all indexes exist, otherwise you could push to an
703 // index that doesn't exist causing a panic.
704 // NOTE: Vec::resize_with() may result in "hidden allocation"
705 // despite description that indicates that the function resizes
706 // "in place", initializes new_base with all None values.
707 let new_capacity = hash_lib::next_prime(self.data.len() * 2);
708 let mut new_base: Vec<Option<Entry<K, V>>> = Vec::with_capacity(new_capacity);
709 new_base.resize_with(new_capacity, || None);
710 let mut new_ctrl: Vec<u8> = Vec::with_capacity(new_capacity);
711 new_ctrl.resize_with(new_capacity, || 0x00);
712
713 //println!("Growing from {} to {}", self.data.len(), new_capacity);
714
715 // Reset the instance values for MAD compression
716 // Finds a prime > len, starting much larger to ensure even spread
717 self.prime = hash_lib::next_prime(new_capacity * 2);
718 let mut rng = rand::rng(); // Thread-local RNG
719 self.scale = rng.random_range(1..=self.prime - 1);
720 self.shift = rng.random_range(0..=self.prime - 1);
721
722 // Move entries from self.data into new_base
723 // For each Some entry in old table, calculate new location
724 // via hash/compression and insert the entry into new_base[location]
725 for e in &mut self.data {
726 if let Some(v) = e.take() {
727 // MAD compression algorithm
728 let mut location: usize = ((hash_lib::hash(&v.key)).wrapping_mul(self.scale))
729 //let mut location: usize = ((hash_lib::hash(&v.key().unwrap())).wrapping_mul(self.scale))
730 .wrapping_add(self.shift)
731 % (self.prime)
732 % (new_capacity);
733
734 // Handle potential collisions for new vec insert
735 let mut i: usize = 0;
736 while new_base[location].is_some() {
737 location = (location + i.pow(2)) % new_capacity;
738 i += 1;
739 }
740 new_base[location] = Some(v);
741 new_ctrl[location] = 0x01;
742 }
743 }
744
745 //Update the struct instance to the new collections
746 self.data = new_base;
747 self.ctrl = new_ctrl;
748 }
749}
750
751// Returns a tuple of borrowed key:value pairs (&K, &V)
752//pub struct Iter<'a, K, V> {
753// iter: std::slice::Iter<'a, Option<Entry<K, V>>>,
754//}
755//impl<'a, K, V> Iterator for Iter<'a, K, V>
756// where
757// K: Debug + Hash + PartialEq,
758// V: PartialEq,
759//{
760//
761// type Item = (&'a K, &'a V);
762//
763// fn next(&mut self) -> Option<Self::Item> {
764// for opt_entry in self.iter.by_ref() {
765// if let Some(entry) = opt_entry {
766// return Some((&entry.key, &entry.value));
767// }
768// }
769// None
770// }
771//}
772pub struct Iter<'a, K, V> {
773 iter: std::slice::Iter<'a, Option<Entry<K, V>>>,
774}
775
776impl<'a, K, V> Iterator for Iter<'a, K, V>
777where
778 K: Debug + Hash + PartialEq,
779 V: PartialEq,
780{
781 type Item = (&'a K, &'a V);
782
783 fn next(&mut self) -> Option<Self::Item> {
784 self.iter
785 .find_map(|opt_entry| opt_entry.as_ref().map(|entry| (&entry.key, &entry.value)))
786 }
787}
788
789// Returns a tuple of owned key:value pairs (K, V)
790pub struct IntoIter<K, V> {
791 inner: std::iter::Zip<std::vec::IntoIter<Option<Entry<K, V>>>, std::vec::IntoIter<u8>>,
792}
793impl<K, V> Iterator for IntoIter<K, V> {
794 type Item = (K, V);
795
796 fn next(&mut self) -> Option<Self::Item> {
797 for (slot, tag) in self.inner.by_ref() {
798 if tag == 1 {
799 if let Some(entry) = slot {
800 return Some((entry.key, entry.value));
801 }
802 }
803 }
804 None
805 }
806}
807
808// Allows caller to consume HashMap as an iterator
809impl<K, V> IntoIterator for HashMap<K, V> {
810 type Item = (K, V);
811 type IntoIter = IntoIter<K, V>;
812
813 fn into_iter(self) -> Self::IntoIter {
814 IntoIter {
815 inner: self.data.into_iter().zip(self.ctrl),
816 }
817 }
818}
819
820pub struct Keys<'a, K, V> {
821 iter: std::slice::Iter<'a, Option<Entry<K, V>>>,
822}
823impl<'a, K, V> Iterator for Keys<'a, K, V> {
824 type Item = &'a K;
825
826 fn next(&mut self) -> Option<Self::Item> {
827 if let Some(entry) = self.iter.by_ref().flatten().next() {
828 return Some(&entry.key);
829 }
830 None
831 }
832}
833
834pub struct Values<'a, K, V> {
835 iter: std::slice::Iter<'a, Option<Entry<K, V>>>,
836}
837impl<'a, K, V> Iterator for Values<'a, K, V> {
838 type Item = &'a V;
839
840 fn next(&mut self) -> Option<Self::Item> {
841 if let Some(entry) = self.iter.by_ref().flatten().next() {
842 return Some(&entry.value);
843 }
844 None
845 }
846}
847
848// Unit tests
849/////////////
850
851#[test]
852// Generic type test
853fn probing_hash_table_test() {
854 //Creates a new hash map
855 let mut map = HashMap::<&str, u8>::new();
856
857 assert_eq!(map.data.len(), 2);
858 assert_eq!(map.ctrl.len(), 2);
859
860 // Illustrates that put() and get() work
861 map.put("Peter", 40);
862 assert_eq!(map.size, 1);
863 assert_eq!(map.data.len(), 5);
864 assert_eq!(map.ctrl.len(), 5);
865
866 let fetch = map.get("Peter").unwrap();
867 //assert_eq!(*fetch, 40 as u8);
868 assert_eq!(*fetch, 40);
869
870 // Illustrates that the map grows correctly
871 map.put("Brain", 39); // Grows the map
872 assert_eq!(map.data.len(), 5);
873 map.put("Remus", 22);
874 map.put("Bobson", 36); // Grows the map
875 assert_eq!(map.data.len(), 11);
876 map.put("Dingus", 18);
877 map.put("Dangus", 27); // Grows the map
878 assert_eq!(map.size, 6);
879 assert_eq!(map.data.len(), 23);
880
881 // Illustrates that contains() works as intended
882 assert!(map.contains("Dingus"));
883
884 // Illustrates that put() returns old values and
885 // overwrites existing values upon collision...
886 let collision = map.put("Peter", 41).unwrap();
887 assert_eq!(collision.value, 40_u8);
888 //assert_eq!(*collision.value().unwrap(), 40_u8);
889 let new_val = map.get("Peter").unwrap();
890 //assert_eq!(*new_val, 41 as u8);
891 assert_eq!(*new_val, 41);
892 // Without increasing the list size
893 assert_eq!(map.size, 6);
894
895 // Illustrates that removes entries by key and returns the value
896 assert!(map.contains("Dangus"));
897 let removed = map.remove(&"Dangus").unwrap();
898 assert_eq!(
899 removed,
900 Entry {
901 key: "Dangus",
902 value: 27
903 }
904 );
905 assert!(!map.contains("Dangus"));
906}
907
908#[test]
909// Tests the custom update value function
910fn mut_val_test() {
911 //Creates a new hash map
912 let mut count = HashMap::<char, u8>::new();
913 let phrase: &str = "Hello, sickos";
914
915 // Seeds the map with just the characters and a default value
916 for char in phrase.chars() {
917 count.put(char, 0);
918 }
919
920 eprintln!("\nInitial state:");
921 count.contents();
922
923 // Iterates through the map again, updating each value based on its occurence
924 // NOTE: Can also be used as initial mapping operation with the same code,
925 // but was split here for illustrative purposes
926 for char in phrase.chars() {
927 count.mut_val_or(char, |x| *x += 1, 1);
928 }
929
930 // Pretty-prints the contents of the map
931 eprintln!("\nModified:");
932 count.contents();
933
934 // Uncomment to trigger debug print
935 //assert!(count.is_empty());
936}
937
938#[test]
939// Tests that the structure is iterable
940fn iter_test() {
941 //Creates a new hash map of the frequence letters in the given phrase
942 let mut map = HashMap::<char, u8>::new();
943 let phrase: &str = "Hello, sickos";
944 for char in phrase.chars() {
945 map.mut_val_or(char, |x| *x += 1, 1);
946 }
947
948 eprintln!("\nInitial map state:");
949 map.contents();
950
951 // Iterates through the map, pushing entries
952 // to a Vec as tuples returned from the iterator
953 eprintln!("\nCompact map: ");
954 let mut vec = Vec::new();
955 for e in map.iter() {
956 vec.push(*e.0);
957 eprintln!("{e:?}")
958 }
959
960 eprintln!("\nFinal map state:");
961 map.contents();
962
963 eprintln!("\nFinal vec state:");
964 for e in vec.iter() {
965 eprintln!("{:?}", *e)
966 }
967
968 // Proves that all entries are still valid
969 // Vec contains only keys
970 assert!(vec.contains(&'s'));
971 assert!(vec.contains(&'i'));
972 assert!(vec.contains(&'c'));
973 assert!(vec.contains(&'k'));
974 assert!(vec.contains(&'o'));
975
976 // Map contains whole entries
977 assert!(map.contains(&'s'));
978 assert!(map.contains(&'i'));
979 assert!(map.contains(&'c'));
980 assert!(map.contains(&'k'));
981 assert!(map.contains(&'o'));
982
983 eprintln!("\nPrinting just the keys:");
984 for key in map.keys() {
985 eprintln!("{key}");
986 }
987
988 eprintln!("\nPrinting just the values:");
989 for value in map.values() {
990 eprintln!("{value}");
991 }
992
993 // Consume the map and transfer it to a new map
994 let mut new_map = HashMap::<char, u8>::new();
995 for e in map.into_iter() {
996 new_map.put(e.0, e.1);
997 }
998 // Illegal, map is moved/consumed!
999 //assert!(map.is_empty());
1000
1001 // But new_map contains data now
1002 assert!(new_map.contains(&'s'));
1003 assert!(new_map.contains(&'i'));
1004 assert!(new_map.contains(&'c'));
1005 assert!(new_map.contains(&'k'));
1006 assert!(new_map.contains(&'o'));
1007
1008 // Uncomment to trigger debug print
1009 //assert!(map.is_empty());
1010}
1011
1012#[test]
1013// Tests that into_iter consumes the map,
1014// and that rehash and shrink_to_fit delete removed entries
1015fn rehash_test() {
1016 //Creates a new hash map
1017 let mut map = HashMap::<char, u8>::new();
1018 let word: &str = "Hello, sickos!";
1019 for k in word.chars() {
1020 map.mut_val_or(k, |x| *x += 1, 1);
1021 }
1022
1023 // Prints initial state of the map
1024 // and illustrates that values exist
1025 eprintln!("\nInitial state: ");
1026 map.contents();
1027 assert!(map.contains(&','));
1028 assert!(map.contains(&' '));
1029 assert!(map.contains(&'!'));
1030 assert!(map.contains(&'s'));
1031 assert_eq!(map.len(), 11);
1032 assert_eq!(map.data.len(), 23);
1033
1034 // Removes the values
1035 map.remove(&',');
1036 map.remove(&' ');
1037 map.remove(&'!');
1038 map.remove(&'s');
1039 map.remove(&'i');
1040 map.remove(&'c');
1041 map.remove(&'k');
1042
1043 // Checks that the values are removed
1044 eprintln!("\nModified: ");
1045 map.contents();
1046 assert!(!map.contains(&','));
1047 assert!(!map.contains(&' '));
1048 assert!(!map.contains(&'!'));
1049 assert!(!map.contains(&'s'));
1050 assert_eq!(map.len(), 4);
1051 assert_eq!(map.data.len(), 23);
1052
1053 // Rehashes to get rid of deleted items
1054 // map goes out of scope here
1055 let new_map = map.rehash();
1056
1057 // Illegal operation! into_iter has consumed map!
1058 // assert!(map.contains(&'H'));
1059
1060 // Checks that the values are removed
1061 // but the capacity remains the same as before
1062 eprintln!("\nRehash: ");
1063 new_map.contents();
1064 assert!(!new_map.contains(&','));
1065 assert!(!new_map.contains(&' '));
1066 assert!(!new_map.contains(&'!'));
1067 assert!(!new_map.contains(&'s'));
1068 assert_eq!(new_map.len(), 4);
1069 assert_eq!(new_map.data.len(), 23);
1070
1071 // Repeats the process for shrink_to_fit
1072 //
1073 //Creates a new hash map
1074 let mut map = HashMap::<char, u8>::new();
1075 let word: &str = "Hello, sickos!";
1076 for k in word.chars() {
1077 map.mut_val_or(k, |x| *x += 1, 1);
1078 }
1079
1080 // Prints initial state of the map
1081 // and illustrates that values exist
1082 eprintln!("\nInitial state: ");
1083 map.contents();
1084 assert!(map.contains(&','));
1085 assert!(map.contains(&' '));
1086 assert!(map.contains(&'!'));
1087 assert!(map.contains(&'s'));
1088 assert_eq!(map.len(), 11);
1089 assert_eq!(map.data.len(), 23);
1090
1091 // Removes the values
1092 map.remove(&',');
1093 map.remove(&' ');
1094 map.remove(&'!');
1095 map.remove(&'s');
1096 map.remove(&'i');
1097 map.remove(&'c');
1098 map.remove(&'k');
1099
1100 // Checks that the values are removed
1101 eprintln!("\nModified: ");
1102 map.contents();
1103 assert!(!map.contains(&','));
1104 assert!(!map.contains(&' '));
1105 assert!(!map.contains(&'!'));
1106 assert!(!map.contains(&'s'));
1107 assert_eq!(map.len(), 4);
1108 assert_eq!(map.data.len(), 23);
1109
1110 // Rehashes to get rid of deleted items
1111 // map goes out of scope here
1112 let new_map = map.shrink_to_fit();
1113
1114 // Checks that the values are removed
1115 // but the capacity remains the same as before
1116 eprintln!("\nRehash (shrink to fit): ");
1117 new_map.contents();
1118 assert!(!new_map.contains(&','));
1119 assert!(!new_map.contains(&' '));
1120 assert!(!new_map.contains(&'!'));
1121 assert!(!new_map.contains(&'s'));
1122 assert_eq!(new_map.len(), 4);
1123 assert_eq!(new_map.data.len(), 11);
1124
1125 // Uncomment to trigger debug print
1126 //panic!("MANUAL TEST FAILURE");
1127}
1128
1129// TODO: This belongs in either an external runner or in an integration test module
1130pub fn example() {
1131 //Creates a new hash map
1132 let mut map = HashMap::<&str, u8>::new();
1133
1134 let s = format!(
1135 "Map stats: size: {}, capacity: {}, active entries: {}",
1136 map.size,
1137 map.data.len(),
1138 map.live
1139 );
1140 let l = "=".repeat(s.len());
1141 println!("{l}\n{s}");
1142
1143 // Puts some entries into the map
1144 println!("Building the map...");
1145 let mut names: Vec<&str> = vec!["Peter", "Brain", "Remus", "Bobson", "Dingus", "Dangus"];
1146 let values: Vec<u8> = vec![39, 37, 22, 36, 18, 27];
1147 for (k, v) in names.iter().zip(values.into_iter()) {
1148 map.put(k, v);
1149 }
1150
1151 // Checks that the map contains what we'd expect
1152 if !map.contains("Peter") {
1153 panic!()
1154 };
1155 let val = map.get("Peter").unwrap();
1156 println!("Peter is {val}");
1157
1158 // Replaces a value for a given key and
1159 // checks that the new value took
1160 let new = 41;
1161 let old = map.put("Peter", new).unwrap().value;
1162 println!("[Peter's age increased from {old} to {new}]");
1163 let val = map.get("Peter").unwrap();
1164 println!("Uhhhhh, I meant Peter is {val}");
1165
1166 // Shows the map and its data
1167 println!(
1168 "\nMap stats: size: {}, capacity: {}, active entries: {}",
1169 map.size,
1170 map.data.len(),
1171 map.live
1172 );
1173 for (e, m) in map.data.iter().zip(map.ctrl.iter()) {
1174 println!("\t{m:>3}: {e:?}")
1175 }
1176
1177 // Illustrates removing entries
1178 println!("\nThere can be only one!");
1179 names.remove(0);
1180 for e in names {
1181 println!("Remove: {}", map.remove(&e).unwrap().key);
1182 }
1183
1184 // The final result
1185 let s = format!(
1186 "\nMap stats: size: {}, capacity: {}, active entries: {}",
1187 map.size,
1188 map.data.len(),
1189 map.live
1190 );
1191 println!("{s}");
1192 for (e, m) in map.data.iter().zip(map.ctrl.iter()) {
1193 println!("\t{m:>3}: {e:?}")
1194 }
1195
1196 let mut count: HashMap<char, u8> = HashMap::new();
1197 let word: &str = "The quick brown fox jumps over the lazy dog";
1198 for k in word.chars() {
1199 count.mut_val_or(k, |x| *x += 1, 1);
1200 }
1201 println!(
1202 "\n\nString: {word:?}
1203\nNOTE: There are 28 entries because T != t, and the space literal ' ' is included.
1204\nMap stats: size: {}, capacity: {}, active entries: {}",
1205 count.occupied(), // Total number of elements
1206 count.capacity(),
1207 count.len() // Active entries
1208 );
1209 for (e, m) in count.data.iter().zip(count.ctrl.iter()) {
1210 println!("\t{m:>3}: {e:?}")
1211 }
1212
1213 let l = "=".repeat(s.len());
1214 println!("\n{l}");
1215}