#[doc(hidden)]
pub mod deps {
pub use paste::paste;
pub use slab::Slab;
pub use smallvec::SmallVec;
}
#[macro_export]
macro_rules! n_key_list {
{
$(#[$meta:meta])*
$vis:vis struct $mapname:ident $(<$($P:ident),*>)? for $V:ty
$( where [ $($constr:tt)+ ] )?
{
$($body:tt)+
}
} => {
n_key_list!{
$(#[$meta])*
$vis struct [$($($P),*)?] $mapname [$($($P),*)?] for $V
$( [ where $($constr)+ ] )?
{
$( $body )+
}
}
};
{
$(#[$meta:meta])*
$vis:vis struct [$($($G:tt)+)?] $mapname:ident [$($($P:tt)+)?] for $V:ty
$( [ where $($constr:tt)+ ])?
{
$( $(( $($flag:ident)+ ))? $key:ident : $KEY:ty $({ $($source:tt)+ })? ),+
$(,)?
}
} => {
$crate::n_key_list::deps::paste!{
$( #[$meta] )*
#[doc = concat!(
"A list of elements of type `", stringify!($V), "` whose members can be accessed by multiple keys."
)]
#[doc = $( "- `" $key "` (`" $KEY "`)" $(" (" $($flag)+ ")\n" )? )+]
$vis struct $mapname $(<$($G)*>)?
where
$( $KEY : std::hash::Hash + Eq + Clone , )+
$($($constr)+, )?
{
$([<$key _map>]: std::collections::HashMap<$KEY, $crate::n_key_list::deps::SmallVec<[usize; 4]>> , )+
values: $crate::n_key_list::deps::Slab<$V>,
}
#[allow(dead_code)] impl $(<$($G)*>)? $mapname $(<$($P)*>)?
where
$( $KEY : std::hash::Hash + Eq + Clone , )+
$($($constr)+)?
{
#[doc = "Construct a new [`" $mapname "`](Self)."]
$vis fn new() -> Self {
Self::with_capacity(0)
}
#[doc = "Construct a new [`" $mapname "`](Self) with a given capacity."]
$vis fn with_capacity(n: usize) -> Self {
Self {
$([<$key _map>]: std::collections::HashMap::with_capacity(n),)*
values: $crate::n_key_list::deps::Slab::with_capacity(n),
}
}
$(
#[doc = "Return an iterator of the elements whose `" $key "` is `key`."]
$vis fn [<by_ $key>] <BorrowAsKey_>(&self, key: &BorrowAsKey_) -> [<$mapname Iter>] <'_, $V>
where
$KEY : std::borrow::Borrow<BorrowAsKey_>,
BorrowAsKey_: std::hash::Hash + Eq + ?Sized,
{
[<$mapname Iter>] {
iter: self.[<$key _map>].get(key).map(|set| set.iter()).unwrap_or([].iter()),
values: &self.values,
}
}
#[doc = "Return `true` if this list contains an element whose `" $key "` is `key`."]
$vis fn [<contains_ $key>] <BorrowAsKey_>(&mut self, key: &BorrowAsKey_) -> bool
where
$KEY : std::borrow::Borrow<BorrowAsKey_>,
BorrowAsKey_: std::hash::Hash + Eq + ?Sized,
{
let Some(list) = self.[<$key _map>].get(key) else {
return false;
};
if list.is_empty() {
#[cfg(debug_assertions)]
panic!("Should not have an empty list");
#[cfg(not(debug_assertions))]
return false;
}
true
}
#[doc = "Remove and return the elements whose `" $key "` is `key`"]
$vis fn [<remove_by_ $key>] <BorrowAsKey_>(
&mut self,
key: &BorrowAsKey_,
mut filter: impl FnMut(&$V) -> bool,
) -> Vec<$V>
where
$KEY : std::borrow::Borrow<BorrowAsKey_>,
BorrowAsKey_: std::hash::Hash + Eq + ?Sized,
{
let idx_list: Vec<usize> = {
let Some(set) = self.[<$key _map>].get(key) else {
return Vec::new();
};
set
.iter()
.filter(|&&idx| filter(self.values.get(idx).expect("inconsistent state")))
.copied()
.collect()
};
let mut removed = Vec::with_capacity(idx_list.len());
for idx in idx_list {
removed.push(self.remove_at(idx).expect("inconsistent state"));
}
removed
}
)+
fn remove_at(&mut self, idx: usize) -> Option<$V> {
if let Some(removed) = self.values.try_remove(idx) {
$(
let $key = $crate::n_key_list!( @access(removed, ($($($flag)+)?) $key : $KEY $({$($source)+})?) );
if let Some($key) = $key {
let set = self.[<$key _map>].get_mut($key).expect("inconsistent state");
#[cfg(debug_assertions)]
let size_before_remove = set.len();
set.retain(|x| *x != idx);
#[cfg(debug_assertions)]
assert_ne!(set.len(), size_before_remove, "should have removed at least one element");
if set.is_empty() {
self.[<$key _map>].remove($key);
}
}
)*
Some(removed)
} else {
None
}
}
$vis fn values(&self) -> impl Iterator<Item=&$V> + '_ {
self.values.iter().map(|(_, v)| v)
}
$vis fn into_values(self) -> impl Iterator<Item=$V> {
self.values.into_iter().map(|(_, v)| v)
}
$vis fn try_insert(&mut self, value: $V) -> Result<(), $crate::n_key_list::Error> {
if self.capacity() > 32 && self.len() < self.capacity() / 4 {
self.compact()
}
let mut some_key_found = false;
$(
let $key = $crate::n_key_list!( @access(value, ($($($flag)+)?) $key : $KEY $({$($source)+})?) );
some_key_found |= $key.is_some();
)*
if !some_key_found {
return Err($crate::n_key_list::Error::NoKeys);
}
let idx = self.values.insert(value);
let value = self.values.get(idx).expect("inconsistent state");
$(
let $key = $crate::n_key_list!( @access(value, ($($($flag)+)?) $key : $KEY $({$($source)+})?) );
if let Some($key) = $key {
let set = self.[<$key _map>].entry($key.to_owned()).or_default();
set.push(idx);
if set.capacity() > 64 && set.len() < set.capacity() / 4 {
set.shrink_to_fit();
}
}
)*
Ok(())
}
$vis fn insert(&mut self, value: $V) {
self.try_insert(value)
.expect("tried to add a value with no key")
}
$vis fn len(&self) -> usize {
self.values.len()
}
$vis fn is_empty(&self) -> bool {
let is_empty = self.len() == 0;
#[cfg(debug_assertions)]
if is_empty {
$(assert!(self.[<$key _map>].is_empty());)*
}
is_empty
}
$vis fn capacity(&self) -> usize {
self.values.capacity()
}
$vis fn retain<F>(&mut self, mut pred: F)
where
F: FnMut(&$V) -> bool,
{
for idx in 0..self.values.capacity() {
if self.values.get(idx).map(&mut pred) == Some(false) {
self.remove_at(idx);
}
}
}
fn compact(&mut self) {
let old_value = std::mem::replace(self, Self::with_capacity(self.len()));
for item in old_value.into_values() {
self.insert(item);
}
}
fn check_consistency(&self) {
for (idx, value) in &self.values {
$(
let $key = $crate::n_key_list!( @access(value, ($($($flag)+)?) $key : $KEY $({$($source)+})?) );
if let Some($key) = $key {
let set = self.[<$key _map>].get($key).expect("inconsistent state");
assert!(set.contains(&idx));
for (_key, set) in self.[<$key _map>].iter().filter(|(key, _)| *key != $key) {
assert!(!set.contains(&idx));
}
} else {
for set in self.[<$key _map>].values() {
assert!(!set.contains(&idx));
}
}
)*
}
$(
for set in self.[<$key _map>].values() {
for idx in set {
assert!(self.values.contains(*idx));
}
let mut set_iter = set.iter();
while let Some(idx) = set_iter.next() {
assert!(!set_iter.clone().any(|x| x == idx));
}
assert!(!set.is_empty());
}
)*
$(
for (key, set) in &self.[<$key _map>] {
for idx in set {
let value = self.values.get(*idx).expect("inconsistent state");
let $key = $crate::n_key_list!( @access(value, ($($($flag)+)?) $key : $KEY $({$($source)+})?) );
let $key = $key.expect("inconsistent state");
assert!(key == $key);
}
}
)*
}
}
impl $(<$($G)*>)? Default for $mapname $(<$($P)*>)?
where
$( $KEY : std::hash::Hash + Eq + Clone , )+
$($($constr)+)?
{
fn default() -> Self {
$mapname::new()
}
}
impl $(<$($G)*>)? std::iter::FromIterator<$V> for $mapname $(<$($P)*>)?
where
$( $KEY : std::hash::Hash + Eq + Clone , )*
$($($constr)+)?
{
fn from_iter<IntoIter_>(iter: IntoIter_) -> Self
where
IntoIter_: std::iter::IntoIterator<Item = $V>,
{
let iter = iter.into_iter();
let mut list = Self::with_capacity(iter.size_hint().0);
for value in iter {
list.insert(value);
}
list
}
}
#[doc = "An iterator for [`" $mapname "`](" $mapname ")."]
$vis struct [<$mapname Iter>] <'a, T> {
iter: std::slice::Iter<'a, usize>,
values: &'a $crate::n_key_list::deps::Slab<T>,
}
impl<'a, T> std::iter::Iterator for [<$mapname Iter>] <'a, T> {
type Item = &'a T;
fn next(&mut self) -> std::option::Option<Self::Item> {
self.iter.next().map(|idx| self.values.get(*idx).expect("inconsistent state"))
}
#[inline]
fn size_hint(&self) -> (usize, std::option::Option<usize>) {
self.iter.size_hint()
}
}
impl<'a, T> std::iter::ExactSizeIterator for [<$mapname Iter>] <'a, T>
where
std::slice::Iter<'a, usize>: std::iter::ExactSizeIterator,
{
#[inline]
fn len(&self) -> usize {
self.iter.len()
}
}
impl<'a, T> std::default::Default for [<$mapname Iter>] <'a, T> {
fn default() -> Self {
[<$mapname Iter>] {
iter: [].iter(),
values: const { &$crate::n_key_list::deps::Slab::new() },
}
}
}
}
};
{ @access($ex:expr, (Option) $key:ident : $t:ty ) } => {
$ex.key()
};
{ @access($ex:expr, () $key:ident : $t:ty) } => {
Some($ex.key())
};
{ @access($ex:expr, (Option) $key:ident : $t:ty { . $field:tt } ) } => {
$ex.$field.as_ref()
};
{ @access($ex:expr, () $key:ident : $t:ty { . $field:tt } ) } => {
Some(&$ex.$field)
};
{ @access($ex:expr, (Option) $key:ident : $t:ty { $func:ident () } ) } => {
$ex.$func()
};
{ @access($ex:expr, () $key:ident : $t:ty { $func:ident () } ) } => {
Some($ex.$func())
};
}
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("Tried to insert a value with no keys")]
NoKeys,
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
fn sort<T: std::cmp::Ord>(i: impl Iterator<Item = T>) -> Vec<T> {
let mut v: Vec<_> = i.collect();
v.sort();
v
}
n_key_list! {
#[derive(Clone, Debug)]
struct Tuple2List<A,B> for (A,B) {
first: A { .0 },
second: B { .1 },
}
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn basic() {
let mut list = Tuple2List::new();
assert!(list.is_empty());
list.insert((0_u32, 99_u16));
assert_eq!(list.len(), 1);
assert_eq!(list.contains_first(&0), true);
assert_eq!(list.contains_second(&99), true);
assert_eq!(list.contains_first(&99), false);
assert_eq!(list.contains_second(&0), false);
assert_eq!(sort(list.by_first(&0)), [&(0, 99)]);
assert_eq!(sort(list.by_second(&99)), [&(0, 99)]);
assert_eq!(list.by_first(&99).len(), 0);
assert_eq!(list.by_second(&0).len(), 0);
list.check_consistency();
assert_eq!(list.by_first(&1000000).len(), 0);
assert_eq!(list.len(), 1);
list.insert((0_u32, 99_u16));
assert_eq!(list.len(), 2);
list.check_consistency();
list.insert((12, 34));
list.insert((0, 34));
assert_eq!(list.len(), 4);
assert!(list.capacity() >= 4);
assert_eq!(sort(list.by_first(&0)), [&(0, 34), &(0, 99), &(0, 99)]);
assert_eq!(sort(list.by_first(&12)), [&(12, 34)]);
list.check_consistency();
assert_eq!(
list.remove_by_first(&0, |(_, b)| *b == 99),
vec![(0, 99), (0, 99)]
);
assert_eq!(list.remove_by_first(&0, |_| true), vec![(0, 34)]);
assert_eq!(list.len(), 1);
list.check_consistency();
assert_eq!(sort(list.by_first(&12)), [&(12, 34)]);
list.insert((12, 123));
assert_eq!(list.len(), 2);
assert_eq!(sort(list.by_first(&12)), [&(12, 34), &(12, 123)]);
assert_eq!(sort(list.by_second(&34)), [&(12, 34)]);
assert_eq!(sort(list.by_second(&123)), [&(12, 123)]);
list.check_consistency();
list.insert((56, 78));
assert_eq!(sort(list.values()), [&(12, 34), &(12, 123), &(56, 78)]);
assert_eq!(sort(list.into_values()), [(12, 34), (12, 123), (56, 78)]);
}
#[test]
fn retain_and_compact() {
let mut list: Tuple2List<String, String> = (1..=1000)
.map(|idx| (format!("A={}", idx), format!("B={}", idx)))
.collect();
assert_eq!(list.len(), 1000);
let cap_orig = list.capacity();
assert!(cap_orig >= list.len());
list.check_consistency();
list.retain(|(a, _)| a.len() <= 3);
assert_eq!(list.len(), 9);
assert_eq!(list.capacity(), cap_orig);
list.check_consistency();
list.insert(("A=0".to_string(), "B=0".to_string()));
assert!(list.capacity() < cap_orig);
assert_eq!(list.len(), 10);
for idx in 0..=9 {
assert!(list.contains_first(&format!("A={}", idx)));
}
list.check_consistency();
}
n_key_list! {
#[derive(Clone, Debug)]
struct AllOptional<A,B> for (Option<A>,Option<B>) {
(Option) first: A { .0 },
(Option) second: B { .1 },
}
}
#[test]
fn optional() {
let mut list = AllOptional::<u8, u8>::new();
list.insert((Some(1), Some(2)));
list.insert((None, Some(2)));
list.insert((Some(1), None));
list.check_consistency();
assert_eq!(
sort(list.by_first(&1)),
[&(Some(1), None), &(Some(1), Some(2))],
);
assert!(matches!(
list.try_insert((None, None)),
Err(super::Error::NoKeys),
));
}
#[allow(dead_code)]
struct Weekday {
dow: u8,
name: &'static str,
lucky_number: Option<u16>,
}
#[allow(dead_code)]
impl Weekday {
fn dow(&self) -> &u8 {
&self.dow
}
fn name(&self) -> &str {
self.name
}
fn lucky_number(&self) -> Option<&u16> {
self.lucky_number.as_ref()
}
}
n_key_list! {
struct WeekdaySet for Weekday {
idx: u8 { dow() },
(Option) lucky: u16 { lucky_number() },
name: String { name() }
}
}
n_key_list! {
struct['a] ArrayMap['a] for (String, [&'a u32;10]) {
name: String { .0 }
}
}
n_key_list! {
struct['a, const N:usize] ArrayMap2['a, N] for (String, [&'a u32;N]) {
name: String { .0 }
}
}
}