Skip to content

Commit

Permalink
Add Eytzinger search map
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Taillefer (from Dev Box) committed Dec 8, 2024
1 parent cdd9b79 commit e485cb0
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 8 deletions.
16 changes: 16 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,28 @@

- with strings, use unique substrings as the actual hash code when possible.

- Expand benchmarks to test the performance of the library with larger data sets.

- Introduce a partially inlined hashmap for use by the macros, when we know the number
of entries but can't hash them at build time.

## Features

- Consider adding support for case-insensitive strings.

- Extend the Scalar derive macro to support more varieties of enum types.

- Reintroduce the facades as a first class part of the library. This is to support
truly dynamic data where you don't know the keys up front. Could be just aliases, depending
on how this looks in the docs. If we do this, we should support initializing the collections
from an iterator.

- Consider removing the magnitude value from the HashMap & SparseScalarHashMap, and
only maintain this concept for the fixed-sized maps.

- Simplify the set types to hash/ordered/scalar/string sets with the maps as a
generic argument.

## Type System Nightmares

These are some things which I haven't done yet since I can't figure out how to express these things in the
Expand Down
1 change: 0 additions & 1 deletion frozen-collections-core/src/facade_maps/facade_hash_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ enum MapTypes<K, V, H> {
/// If your keys are strings, you should use the [`FacadeStringMap`](crate::facade_maps::FacadeStringMap) type instead. Both of these will
/// deliver better performance since they are specifically optimized for those key types.
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeHashMap<K, V, H = BridgeHasher> {
map_impl: MapTypes<K, V, H>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ enum MapTypes<K, V> {
/// If your keys are strings, you should use the [`FacadeStringMap`](crate::facade_maps::FacadeStringMap) type instead. Both of these will
/// deliver better performance since they are specifically optimized for those key types.
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeOrderedMap<K, V> {
map_impl: MapTypes<K, V>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ enum MapTypes<K, V> {
#[doc = include_str!("../doc_snippets/type_compat_warning.md")]
#[doc = include_str!("../doc_snippets/about.md")]
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeScalarMap<K, V> {
map_impl: MapTypes<K, V>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ enum MapTypes<V, BH> {
#[doc = include_str!("../doc_snippets/type_compat_warning.md")]
#[doc = include_str!("../doc_snippets/about.md")]
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeStringMap<V, BH = RandomState> {
map_impl: MapTypes<V, BH>,
}
Expand Down
1 change: 0 additions & 1 deletion frozen-collections-core/src/facade_sets/facade_hash_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use core::ops::{BitAnd, BitOr, BitXor, Sub};
/// If your values are strings, you should use the [`FacadeStringSet`](crate::facade_sets::FacadeStringSet) type instead. Both of these will
/// deliver better performance since they are specifically optimized for those value types.
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeHashSet<T, H = BridgeHasher> {
map: FacadeHashMap<T, (), H>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use core::ops::{BitAnd, BitOr, BitXor, Sub};
/// If your values are strings, you should use the [`FacadeStringSet`](crate::facade_sets::FacadeStringSet) type instead. Both of these will
/// deliver better performance since they are specifically optimized for those value types.
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeOrderedSet<T> {
map: FacadeOrderedMap<T, ()>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use crate::traits::{Len, MapIterator, Scalar, Set, SetIterator};
#[doc = include_str!("../doc_snippets/type_compat_warning.md")]
#[doc = include_str!("../doc_snippets/about.md")]
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeScalarSet<T> {
map: FacadeScalarMap<T, ()>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use core::ops::{BitAnd, BitOr, BitXor, Sub};
#[doc = include_str!("../doc_snippets/type_compat_warning.md")]
#[doc = include_str!("../doc_snippets/about.md")]
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct FacadeStringSet<BH = RandomState> {
map: FacadeStringMap<(), BH>,
}
Expand Down
59 changes: 59 additions & 0 deletions frozen-collections-core/src/maps/decl_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,64 @@ macro_rules! binary_search_core {
};
}

macro_rules! eytzinger_search_core {
() => {
#[doc = include_str!("../doc_snippets/get_method.md")]
#[inline]
#[must_use]
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: ?Sized + Ord,
{
if let Some(index) =
eytzinger_search_by_key(&self.entries, key, |entry| entry.0.borrow())
{
Some(&self.entries[index].1)
} else {
None
}
}

#[doc = include_str!("../doc_snippets/get_mut_method.md")]
#[inline]
#[must_use]
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: ?Sized + Ord,
{
if let Some(index) =
eytzinger_search_by_key(&self.entries, key, |entry| entry.0.borrow())
{
Some(&mut self.entries[index].1)
} else {
None
}
}

#[doc = include_str!("../doc_snippets/get_key_value_method.md")]
#[inline]
#[must_use]
pub fn get_key_value<Q>(&self, key: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q>,
Q: ?Sized + Ord,
{
if let Some(index) =
eytzinger_search_by_key(&self.entries, key, |entry| entry.0.borrow())
{
Some((&self.entries[index].0, &self.entries[index].1))
} else {
None
}
}

get_many_mut_fn!(Ord);
contains_key_fn!(Ord);
};
}

macro_rules! sparse_scalar_lookup_core {
() => {
#[doc = include_str!("../doc_snippets/get_method.md")]
Expand Down Expand Up @@ -573,6 +631,7 @@ pub(crate) use binary_search_core;
pub(crate) use contains_key_fn;
pub(crate) use debug_fn;
pub(crate) use dense_scalar_lookup_core;
pub(crate) use eytzinger_search_core;
pub(crate) use get_many_mut_body;
pub(crate) use get_many_mut_fn;
pub(crate) use hash_core;
Expand Down
150 changes: 150 additions & 0 deletions frozen-collections-core/src/maps/eytzinger_search_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::fmt::{Debug, Formatter, Result};
use core::ops::Index;

use crate::maps::decl_macros::{
contains_key_fn, debug_fn, eytzinger_search_core, get_many_mut_body, get_many_mut_fn, index_fn,
into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice,
map_iterator_boilerplate_for_slice, partial_eq_fn,
};
use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut};
use crate::traits::{Len, Map, MapIterator};
use crate::utils::{dedup_by_keep_last, eytzinger_search_by_key, eytzinger_sort};

/// A general purpose map implemented using binary search.
///
#[doc = include_str!("../doc_snippets/type_compat_warning.md")]
#[doc = include_str!("../doc_snippets/about.md")]
#[doc = include_str!("../doc_snippets/order_warning.md")]
///
#[derive(Clone)]
pub struct EytzingerSearchMap<K, V> {
entries: Box<[(K, V)]>,
}

impl<K, V> EytzingerSearchMap<K, V>
where
K: Ord,
{
/// Creates a frozen map.
#[must_use]
pub fn new(mut entries: Vec<(K, V)>) -> Self {
entries.sort_by(|x, y| x.0.cmp(&y.0));
dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0));
eytzinger_sort(&mut entries);
Self::new_raw(entries)
}

/// Creates a frozen map.
#[must_use]
pub(crate) fn new_raw(processed_entries: Vec<(K, V)>) -> Self {
Self {
entries: processed_entries.into_boxed_slice(),
}
}
}

impl<K, V> EytzingerSearchMap<K, V> {
eytzinger_search_core!();
}

impl<K, V> Len for EytzingerSearchMap<K, V> {
fn len(&self) -> usize {
self.entries.len()
}
}

impl<K, V> Debug for EytzingerSearchMap<K, V>
where
K: Debug,
V: Debug,
{
debug_fn!();
}

impl<K, V> Default for EytzingerSearchMap<K, V> {
fn default() -> Self {
Self {
entries: Box::default(),
}
}
}

impl<Q, K, V> Index<&Q> for EytzingerSearchMap<K, V>
where
K: Borrow<Q>,
Q: ?Sized + Ord,
{
index_fn!();
}

impl<K, V> IntoIterator for EytzingerSearchMap<K, V> {
into_iter_fn_for_slice!(entries);
}

impl<'a, K, V> IntoIterator for &'a EytzingerSearchMap<K, V> {
into_iter_ref_fn!();
}

impl<'a, K, V> IntoIterator for &'a mut EytzingerSearchMap<K, V> {
into_iter_mut_ref_fn!();
}

impl<K, V, MT> PartialEq<MT> for EytzingerSearchMap<K, V>
where
K: Ord,
V: PartialEq,
MT: Map<K, V>,
{
partial_eq_fn!();
}

impl<K, V> Eq for EytzingerSearchMap<K, V>
where
K: Ord,
V: Eq,
{
}

impl<K, V> MapIterator<K, V> for EytzingerSearchMap<K, V> {
type Iterator<'a>
= Iter<'a, K, V>
where
K: 'a,
V: 'a;

type KeyIterator<'a>
= Keys<'a, K, V>
where
K: 'a,
V: 'a;

type ValueIterator<'a>
= Values<'a, K, V>
where
K: 'a,
V: 'a;

type MutIterator<'a>
= IterMut<'a, K, V>
where
K: 'a,
V: 'a;

type ValueMutIterator<'a>
= ValuesMut<'a, K, V>
where
K: 'a,
V: 'a;

map_iterator_boilerplate_for_slice!(entries);
}

impl<K, V> Map<K, V> for EytzingerSearchMap<K, V>
where
K: Ord,
{
map_boilerplate_for_slice!(entries);
}
2 changes: 2 additions & 0 deletions frozen-collections-core/src/maps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub use binary_search_map::BinarySearchMap;
pub use dense_scalar_lookup_map::DenseScalarLookupMap;
pub use eytzinger_search_map::EytzingerSearchMap;
pub use hash_map::HashMap;
pub use iterators::*;
pub use ordered_scan_map::OrderedScanMap;
Expand All @@ -11,6 +12,7 @@ pub use sparse_scalar_lookup_map::SparseScalarLookupMap;
mod binary_search_map;
pub(crate) mod decl_macros;
mod dense_scalar_lookup_map;
mod eytzinger_search_map;
mod hash_map;
mod iterators;
mod ordered_scan_map;
Expand Down
2 changes: 2 additions & 0 deletions frozen-collections-core/src/utils/bitvec.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Simple bit vectors.
use alloc::vec;
use alloc::vec::Vec;

Expand Down
Loading

0 comments on commit e485cb0

Please sign in to comment.