cranpose_core/collections/
perfect_map.rs1const BUCKETS: usize = 32;
11const SLOTS: usize = 128;
12const EMPTY: u8 = u8::MAX;
14const SEEDS: u64 = 64;
16
17pub const MAX_KEYS: usize = SLOTS * 3 / 4;
19
20pub struct PerfectMap<V: Copy + 'static, const N: usize> {
30 entries: [(&'static str, V); N],
31 seed: u64,
32 pilots: [u8; BUCKETS],
33 slots: [u8; SLOTS],
34}
35
36impl<V: Copy + 'static, const N: usize> PerfectMap<V, N> {
37 pub const fn new(entries: [(&'static str, V); N]) -> Self {
41 assert!(N <= MAX_KEYS, "a PerfectMap holds at most MAX_KEYS keys");
42 let mut seed = 0;
43 while seed < SEEDS {
44 seed += 1;
45 if let Some((pilots, slots)) = place(&entries, seed) {
46 return Self {
47 entries,
48 seed,
49 pilots,
50 slots,
51 };
52 }
53 }
54 panic!("no seed places these keys: is one of them there twice?");
55 }
56
57 #[inline]
59 pub fn get(&self, key: &str) -> Option<&V> {
60 let hash = lane_hash(self.seed, key.as_bytes());
61 let slot = self.slots[slot_of(hash, self.pilots[bucket_of(hash)])];
62 let (name, value) = self.entries.get(usize::from(slot))?;
63 (*name == key).then_some(value)
64 }
65}
66
67const fn lane_hash(seed: u64, bytes: &[u8]) -> u64 {
70 let mut hash = seed ^ (bytes.len() as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
71 let mut at = 0;
72 while at < bytes.len() {
73 let mut word = 0u64;
74 let mut index = 0;
75 while index < 8 && at + index < bytes.len() {
76 word |= (bytes[at + index] as u64) << (8 * index);
77 index += 1;
78 }
79 hash = (hash ^ word).wrapping_mul(0xbf58_476d_1ce4_e5b9);
80 hash ^= hash >> 31;
81 at += 8;
82 }
83 hash = (hash ^ (hash >> 32)).wrapping_mul(0x94d0_49bb_1331_11eb);
88 hash ^ (hash >> 29)
89}
90
91const fn bucket_of(hash: u64) -> usize {
93 hash.to_le_bytes()[0] as usize % BUCKETS
94}
95const _: () = assert!(256 % BUCKETS == 0, "a hash's low byte holds its bucket");
96
97const fn slot_of(hash: u64, pilot: u8) -> usize {
100 let mixed = (hash ^ (pilot as u64).wrapping_mul(0x94d0_49bb_1331_11eb))
101 .wrapping_mul(0x9e37_79b9_7f4a_7c15);
102 (mixed.to_be_bytes()[0] >> (8 - SLOTS.trailing_zeros())) as usize
104}
105const _: () = assert!(
106 SLOTS.is_power_of_two() && SLOTS <= 256,
107 "a hash's top byte holds its slot"
108);
109const _: () = assert!(MAX_KEYS < 256, "a key's index fits its slot's byte");
110
111const fn place<V: Copy, const N: usize>(
115 entries: &[(&'static str, V); N],
116 seed: u64,
117) -> Option<([u8; BUCKETS], [u8; SLOTS])> {
118 let mut hashes = [0u64; N];
119 let mut sizes = [0usize; BUCKETS];
120 let mut key = 0;
121 while key < N {
122 hashes[key] = lane_hash(seed, entries[key].0.as_bytes());
123 sizes[bucket_of(hashes[key])] += 1;
124 key += 1;
125 }
126 let mut pilots = [0u8; BUCKETS];
127 let mut slots = [EMPTY; SLOTS];
128 let mut size = N;
129 while size > 0 {
130 let mut bucket = 0;
131 while bucket < BUCKETS {
132 if sizes[bucket] == size {
133 match place_bucket(&hashes, bucket, &mut slots) {
134 Some(pilot) => pilots[bucket] = pilot,
135 None => return None,
136 }
137 }
138 bucket += 1;
139 }
140 size -= 1;
141 }
142 Some((pilots, slots))
143}
144
145const fn place_bucket<const N: usize>(
148 hashes: &[u64; N],
149 bucket: usize,
150 slots: &mut [u8; SLOTS],
151) -> Option<u8> {
152 let mut pilot = 0u8;
153 loop {
154 if bucket_fits(hashes, bucket, slots, pilot) {
155 let mut key = 0;
156 while key < N {
157 if bucket_of(hashes[key]) == bucket {
158 slots[slot_of(hashes[key], pilot)] = key.to_le_bytes()[0];
160 }
161 key += 1;
162 }
163 return Some(pilot);
164 }
165 if pilot == u8::MAX {
166 return None;
167 }
168 pilot += 1;
169 }
170}
171
172const fn bucket_fits<const N: usize>(
175 hashes: &[u64; N],
176 bucket: usize,
177 slots: &[u8; SLOTS],
178 pilot: u8,
179) -> bool {
180 let mut taken = [false; SLOTS];
181 let mut key = 0;
182 while key < N {
183 if bucket_of(hashes[key]) == bucket {
184 let slot = slot_of(hashes[key], pilot);
185 if slots[slot] != EMPTY || taken[slot] {
186 return false;
187 }
188 taken[slot] = true;
189 }
190 key += 1;
191 }
192 true
193}
194
195#[cfg(test)]
196#[path = "tests/perfect_map_tests.rs"]
197mod tests;