Tests for map lib, small and all passing.

This commit is contained in:
Christian Lincoln 2026-10-10 01:18:56 +01:00
parent 76a9c2259e
commit 6e9c1c1213
2 changed files with 96 additions and 33 deletions

View file

@ -94,7 +94,7 @@ pub fn get<E: TableEntry + Equivalent<K> ,K: Hashes>(map: &[Entry<E>], index: K)
if home.entry.matches(&index) {
Some(&home.entry)
} else {
for i in 1..home.displacement as usize {
for i in 1..=home.displacement as usize {
let neighbour = &map[(location + i) % map.len()];
if neighbour.entry.matches(&index) {
return Some(&neighbour.entry);
@ -110,10 +110,11 @@ impl<E: TableEntry> DisplacementSet<E> {
&mut self.map,
(0..len).map(|_| Entry::VACANT).collect(), // todo: I don't like that this may go over all memory
);
self.map_count = 0;
self.map_bounds = self.lower()..self.upper();
for Entry { entry, .. } in old {
if !entry.is_vacant() {
self.set(entry)
self.rawset(entry)
}
}
}
@ -136,47 +137,45 @@ impl<E: TableEntry> DisplacementSet<E> {
self.exchange(self.map.len() / 2)
}
}
pub fn set(&mut self, entry: E) {
#[cfg(feature = "assertions")]
assert!(!entry.is_vacant());
if !self.map_bounds.contains(&(self.map_count + 1)) {
// make sure the table is appropriately sized
self.ensure();
}
fn rawset(&mut self, entry: E) {
let range = self.map.len();
let home = entry.hashed() as usize % range;
macro_rules! insert {
($entry:expr) => {
$entry.home = home;
$entry.entry = entry;
self.map_count += 1;
}
}
macro_rules! replace {
($entry:expr) => {
$entry.entry = entry;
}
}
if self.map[home].entry.is_vacant() {
// attempt to place it directly in an empty space
// INSERT
self.map[home].home = home;
self.map[home].entry = entry;
self.map[home].displacement = self.map[home].displacement.max(0);
self.map_count += 1;
insert!(self.map[home]);
} else if self.map[home].entry.matches(&entry) {
// attempt to replace it directly
// REPLACE
self.map[home].entry = entry; // home.item.soft_drop()
replace!(self.map[home]);
} else {
// attempt to replace it in a collided neighbour location
for i in 1..self.map[home].displacement + 1 {
let neighbour = &mut self.map[(home + i as usize) % range]; // todo: is mod expensive?
if neighbour.entry.matches(&entry) {
// found where it was displaced to
// REPLACE
neighbour.entry = entry;
replace!(neighbour);
return;
}
}
// could not find a space!
// at this point it must be added, probe to place in an empty space
for j in self.map[home].displacement + 1..Displacement::MAX - 1 {
let neighbour = &mut self.map[(home + j as usize) % range];
if neighbour.entry.is_vacant() {
// new empty slot hooray!
// INSERT
neighbour.entry = entry;
neighbour.home = home;
insert!(neighbour);
self.map[home].displacement = j;
self.map_count += 1;
return;
}
}
@ -186,29 +185,34 @@ impl<E: TableEntry> DisplacementSet<E> {
let mut free: Option<usize> = None;
for k in 0..self.map.len() {
let neighbour = &mut self.map[k];
if neighbour.entry.is_vacant() && free.is_none() {
if neighbour.entry.is_vacant() {
free = Some(k);
} else if neighbour.entry.matches(&entry) {
// REPLACE
neighbour.entry = entry;
replace!(neighbour);
return;
}
}
if let Some(k) = free {
// INSERT
self.map[k].home = home;
self.map[k].entry = entry;
insert!(self.map[k]);
self.map[home].displacement = Displacement::MAX;
self.map_count += 1;
return;
}
// must resize
// must resize (also impossible)
#[cfg(feature = "messages")]
eprintln!("\n\tResizing...");
self.resize((self.map.len() + 4) * 2);
self.set(entry);
}
}
pub fn set(&mut self, entry: E) {
#[cfg(feature = "assertions")]
assert!(!entry.is_vacant());
if !self.map_bounds.contains(&(self.map_count + 1)) {
// make sure the table is appropriately sized
self.ensure();
}
self.rawset(entry)
}
pub fn remove<K>(&mut self, index: K)
where
E: Equivalent<K>,
@ -225,7 +229,7 @@ impl<E: TableEntry> DisplacementSet<E> {
} else {
if self.map[home].displacement != Displacement::MAX {
let mut largest = 0;
for i in 1..self.map[home].displacement {
for i in 1..=self.map[home].displacement {
let neighbour = &mut self.map[(home + i as usize) % range]; // todo: maybe mod slows this down
if neighbour.entry.matches(&index) {
neighbour.entry = E::VACANT;
@ -290,7 +294,11 @@ pub struct MapEntry<K: Hashes + PartialEq, V>(pub Option<(K, V)>);
impl<K: PartialEq + Hashes, V> Equivalent<K> for MapEntry<K, V> {
fn matches(&self, other: &K) -> bool {
&self.0.as_ref().unwrap().0 == other
if let Some((ref key,_)) = self.0 {
key == other
} else {
false
}
}
}
@ -341,4 +349,52 @@ impl<K: Debug + Hashes + PartialEq, V: Debug> Debug for Map<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Map")) // todo: debug and print the entire thing?
}
}
#[cfg(test)]
mod tests {
use crate::Map;
macro_rules! str {
($string:expr) => {
$string.to_string()
};
}
#[test]
fn simple() {
let mut map: Map<String,String> = Map::new();
map.set(str!("name"),str!("bob"));
map.set(str!("age"),str!("43"));
map.set(str!("species"),str!("unknown"));
assert_eq!(*map.get(str!("name")).unwrap(),str!("bob"));
assert_eq!(*map.get(str!("age")).unwrap(),str!("43"));
assert_eq!(*map.get(str!("species")).unwrap(),str!("unknown"));
assert!(map.get(str!("gender")).is_none());
map.remove(str!("species"));
assert!(map.get(str!("species")).is_none());
map.remove(str!("name"));
assert!(map.get(str!("name")).is_none());
assert!(map.get(str!("age")).is_some());
}
// to flag up
#[test]
fn load() {
let mut map: Map<String,i32> = Map::new();
let mut count = 0;
for i in 1..100000 {
let before = map.0.map_count;
map.set(i.to_string(),i);
assert_eq!(map.0.map_count,before+1);
count+=1;
}
for i in 1..100000 {
if i % 2 == 0 {
map.remove(i.to_string());
}
}
for i in 1..100000 {
assert_eq!(map.get(i.to_string()).is_none(), i % 2 == 0);
}
}
}

View file

@ -4,9 +4,10 @@ pub use map;
#[cfg(test)]
mod tests {
use map::{TableEntry,MapEntry,Entry};
use map::{Entry, MapEntry};
extern crate self as deimos;
// Makes a constant map
#[test]
fn simple() {
static MAP: &[Entry<deimos::map::MapEntry<&'static str,fn()>>] = &deimos::macros::map! {
@ -15,4 +16,10 @@ mod tests {
"greetings" => || println!("salutations"),
};
}
// Makes and destroys lots of elements
#[test]
fn maker() {
}
}