#![no_std]
mod murmur3;
#[inline]
const fn hash(seed: u8, s: &str) -> u32 {
murmur3::hash(s.as_bytes(), seed as u32)
}
#[inline]
const fn get_index(hash: u32, len: usize) -> usize {
hash as usize % len
}
#[inline]
const fn str_eq(a: &str, b: &str) -> bool {
let a_bytes = a.as_bytes();
let b_bytes = b.as_bytes();
if a_bytes.len() != b_bytes.len() {
return false;
}
let mut i = 0;
while i < a_bytes.len() {
if a_bytes[i] != b_bytes[i] {
return false;
}
i += 1;
}
true
}
#[doc(hidden)]
#[derive(Copy, Clone, Debug)]
pub struct StaticMapBuilder<T, const N: usize, const M: usize> {
seeds: [u8; M],
keys: [&'static str; N],
values: [T; N],
}
impl<T: Copy, const N: usize, const M: usize> StaticMapBuilder<T, N, M> {
pub const fn build(keys: [&'static str; N], values: [T; N]) -> Self {
assert!(N > 0, "Cannot build StaticMap with zero values");
assert!(M > 0, "Bucket count M must be greater than zero");
let mut seeds = [u8::MAX; M];
let mut placed = [false; N]; let mut output_keys: [&str; N] = [""; N];
let mut output_vals: [T; N] = [values[0]; N];
let mut bucket_sizes = [0usize; M];
let mut i = 0;
while i < N {
let bucket = get_index(hash(0, keys[i]), M);
bucket_sizes[bucket] += 1;
i += 1;
}
let mut processed_buckets = [false; M];
let mut buckets_done = 0;
while buckets_done < M {
let mut max_size = 0;
let mut max_bucket = 0;
let mut b = 0;
while b < M {
if !processed_buckets[b] && bucket_sizes[b] >= max_size {
max_size = bucket_sizes[b];
max_bucket = b;
}
b += 1;
}
processed_buckets[max_bucket] = true;
buckets_done += 1;
if max_size == 0 {
continue;
}
let mut bucket_key_indices: [usize; N] = [0; N];
let mut bucket_count = 0;
let mut k = 0;
while k < N {
if get_index(hash(0, keys[k]), M) == max_bucket {
bucket_key_indices[bucket_count] = k;
bucket_count += 1;
}
k += 1;
}
let mut seed = 0u8;
let found_seed = loop {
let mut candidate_positions: [usize; N] = [0; N];
let mut collision = false;
let mut j = 0;
while j < bucket_count {
let key_idx = bucket_key_indices[j];
let pos = get_index(hash(seed, keys[key_idx]), N);
if placed[pos] {
collision = true;
break;
}
let mut p = 0;
while p < j {
if candidate_positions[p] == pos {
collision = true;
break;
}
p += 1;
}
if collision {
break;
}
candidate_positions[j] = pos;
j += 1;
}
if !collision {
let mut j = 0;
while j < bucket_count {
let key_idx = bucket_key_indices[j];
let pos = candidate_positions[j];
placed[pos] = true;
output_keys[pos] = keys[key_idx];
output_vals[pos] = values[key_idx];
j += 1;
}
break seed;
}
seed += 1;
assert!(seed < u8::MAX, "Failed to find valid seed for StaticMap, try using more buckets");
};
seeds[max_bucket] = found_seed;
}
StaticMapBuilder {
seeds,
keys: output_keys,
values: output_vals,
}
}
}
impl<T, const N: usize, const M: usize> StaticMapBuilder<T, N, M> {
pub const fn as_ref(&'static self) -> StaticMap<T> {
StaticMap {
keys: StaticKeys {
seeds: &self.seeds,
keys: &self.keys,
},
values: &self.values,
}
}
}
#[macro_export]
macro_rules! static_map {
($vis:vis $name:ident: $T:ty; { $( $key:expr => $val:expr ),* $(,)? }) => {
$vis static $name: $crate::StaticMapBuilder<$T, {[$($key),*].len()}, {[$($key),*].len() / 2}> = $crate::StaticMapBuilder::build(
[$($key),*],
[$($val),*],
);
};
($vis:vis $name:ident: $T:ty, $M:expr; { $( $key:expr => $val:expr ),* $(,)? }) => {
$vis static $name: $crate::StaticMapBuilder<$T, {[$($key),*].len()}, $M> = $crate::StaticMapBuilder::build(
[$($key),*],
[$($val),*],
);
};
}
#[derive(Copy, Clone, Debug)]
struct StaticKeys {
seeds: &'static [u8],
keys: &'static [&'static str],
}
impl StaticKeys {
#[inline(never)]
fn get_index(&self, key: &str) -> usize {
if self.seeds.len() == 0 || self.keys.len() == 0 {
return usize::MAX;
}
let mut h = hash(0, key);
let bucket = get_index(h, self.seeds.len());
let seed = self.seeds[bucket];
if seed == u8::MAX {
return usize::MAX;
}
if seed != 0 {
h = hash(seed, key);
}
let index = get_index(h, self.keys.len());
if !str_eq(self.keys[index], key) {
return usize::MAX;
}
return index;
}
}
#[derive(Copy, Clone, Debug)]
pub struct StaticMap<T: 'static> {
keys: StaticKeys,
values: &'static [T],
}
impl<T> StaticMap<T> {
#[inline]
pub fn keys(&self) -> &'static [&'static str] {
self.keys.keys
}
#[inline]
pub fn values(&self) -> &'static [T] {
self.values
}
#[inline]
pub fn get(&self, key: &str) -> Option<&T> {
let index = self.keys.get_index(key);
self.values.get(index)
}
}
#[cfg(test)]
mod tests;