This idea of storing ranges would be possible using hashbrown
s new safe HashTable
API.
This idea could also work with strings by using String
instead of Vec<K>
and &str
instead of &u8
use hashbrown::hash_table::Entry;
use hashbrown::{HashMap, HashTable};
use std::hash::{BuildHasher, Hash, RandomState};
use std::ops::Range;
type TableData<V> = (Range<usize>, V);
pub struct SliceMap<K, V, S> {
key_data: Vec<K>,
hash_table: HashTable<TableData<V>>,
hasher: S,
}
pub struct SliceMapVacantEntry<'a, V> {
range: Range<usize>,
data: hashbrown::hash_table::VacantEntry<'a, TableData<V>>,
}
impl<'a, V> SliceMapVacantEntry<'a, V> {
fn insert(mut self, v: V) -> &'a mut V {
&mut self.data.insert((self.range, v)).into_mut().1
}
}
pub enum SliceMapEntry<'a, V> {
Occupied(&'a mut V),
Vacant(SliceMapVacantEntry<'a, V>),
}
fn eq<'a, K: Eq, V>(key: &'a [K], key_data: &'a [K]) -> impl Fn(&TableData<V>) -> bool + 'a {
move |(range, _)| &key_data[range.clone()] == key
}
fn hash_fn<'a, K: Hash, V, S: BuildHasher>(
key_data: &'a [K],
hasher: &'a S,
) -> impl Fn(&TableData<V>) -> u64 + 'a {
move |(range, _)| hasher.hash_one(&key_data[range.clone()])
}
impl<K: Hash + Eq, V, S: BuildHasher> SliceMap<K, V, S> {
pub fn with_hasher(s: S, data: Vec<K>) -> Self {
SliceMap {
key_data: data,
hash_table: HashTable::new(),
hasher: s,
}
}
pub fn get(&self, k: &[K]) -> Option<&V> {
let hash = self.hasher.hash_one(k);
Some(&self.hash_table.find(hash, eq(k, &self.key_data))?.1)
}
pub fn entry<'a>(&'a mut self, range: Range<usize>) -> SliceMapEntry<'a, V> {
let k = &self.key_data[range.clone()];
let hash = self.hasher.hash_one(k);
match self.hash_table.entry(
hash,
eq(k, &self.key_data),
hash_fn(&self.key_data, &self.hasher),
) {
Entry::Occupied(occ) => SliceMapEntry::Occupied(&mut occ.into_mut().1),
Entry::Vacant(vac) => SliceMapEntry::Vacant(SliceMapVacantEntry { range, data: vac }),
}
}
pub fn iter(&self) -> impl Iterator<Item = (&[K], &V)> {
self.hash_table
.iter()
.map(|(range, v)| (&self.key_data[range.clone()], v))
}
}
#[test]
fn test() {
let data = "hello world".to_string();
let mut map = SliceMap::with_hasher(RandomState::default(), data.into_bytes());
match map.entry(0..5) {
SliceMapEntry::Occupied(_) => unreachable!(),
SliceMapEntry::Vacant(v) => v.insert(0),
};
match map.entry(6..11) {
SliceMapEntry::Occupied(_) => unreachable!(),
SliceMapEntry::Vacant(v) => v.insert(1),
};
match map.entry(0..5) {
SliceMapEntry::Occupied(o) => {
assert_eq!(*o, 0);
*o = 10;
}
SliceMapEntry::Vacant(_) => unreachable!(),
};
assert_eq!(*map.get("hello".as_bytes()).unwrap(), 10);
assert_eq!(*map.get("world".as_bytes()).unwrap(), 1);
let map2: HashMap<String, u32> = map
.iter()
.map(|(k, v)| (String::from_utf8_lossy(k).into_owned(), *v))
.collect();
assert_eq!(
map2,
HashMap::from_iter([("hello".to_string(), 10), ("world".to_string(), 1)])
);
}