rolldown-notify 10.4.0

Cross-platform filesystem notification library
Documentation
#![cfg_attr(
    not(any(target_os = "linux", target_os = "android")),
    expect(dead_code)
)]
use std::{
    borrow::Borrow,
    collections::HashMap,
    fmt::Debug,
    hash::{BuildHasher, Hash, RandomState},
};

#[derive(Debug, Clone)]
pub(crate) struct BiHashMap<L, R, V, S = RandomState> {
    left: HashMap<L, (R, V), S>,
    right: HashMap<R, L, S>,
}

impl<L, R, V> BiHashMap<L, R, V, RandomState> {
    #[cfg(test)]
    pub fn new() -> Self {
        Self::default()
    }
}

impl<L, R, V, S: BuildHasher> BiHashMap<L, R, V, S> {
    #[cfg(test)]
    pub fn iter(&self) -> impl Iterator<Item = (&L, &R, &V)> {
        self.left.iter().map(|(l, (r, v))| (l, r, v))
    }

    pub fn clear(&mut self) {
        self.left.clear();
        self.right.clear();
    }
}

impl<L, R, V, S> BiHashMap<L, R, V, S>
where
    L: Eq + Hash,
    R: Eq + Hash,
    S: BuildHasher,
{
    pub fn contains_right<Q>(&self, right: &Q) -> bool
    where
        R: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.right.contains_key(right)
    }

    pub fn get_by_left<Q>(&self, left: &Q) -> Option<(&R, &V)>
    where
        L: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        self.left.get(left.borrow()).map(|(right, v)| (right, v))
    }

    pub fn get_by_right<Q>(&self, right: &Q) -> Option<(&L, &V)>
    where
        R: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        self.right.get(right.borrow()).map(|left| {
            let (_, v) = &self.left[left];
            (left, v)
        })
    }

    pub fn insert(&mut self, left: L, right: R, value: V) -> Option<(L, R, V)>
    where
        L: Clone + Debug,
        R: Clone + Debug,
    {
        if let Some((old_right, old_value)) = self.left.insert(left.clone(), (right.clone(), value))
        {
            let old_left = self.right.remove(&old_right).unwrap();
            self.right.insert(right, left);
            Some((old_left, old_right, old_value))
        } else if let Some(old_left) = self.right.insert(right.clone(), left) {
            let (_, old_value) = self.left.remove(&old_left).unwrap();
            Some((old_left, right, old_value))
        } else {
            None
        }
    }

    pub fn remove_by_left<Q>(&mut self, left: &Q) -> Option<(R, V)>
    where
        L: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        if let Some((right, value)) = self.left.remove(left.borrow()) {
            self.right.remove(&right);
            Some((right, value))
        } else {
            None
        }
    }

    pub fn remove_by_right<Q>(&mut self, right: &Q) -> Option<(L, V)>
    where
        R: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        if let Some(left) = self.right.remove(right.borrow()) {
            let (_, value) = self.left.remove(&left).unwrap();
            Some((left, value))
        } else {
            None
        }
    }
}

impl<L, R, V, S: Clone> BiHashMap<L, R, V, S> {
    pub fn with_hasher(hasher: S) -> Self {
        Self {
            left: HashMap::with_hasher(hasher.clone()),
            right: HashMap::with_hasher(hasher),
        }
    }
}

impl<L, R, V, S: Default + Clone> Default for BiHashMap<L, R, V, S> {
    #[inline]
    fn default() -> Self {
        Self::with_hasher(Default::default())
    }
}

impl<L, R, V, S> IntoIterator for BiHashMap<L, R, V, S> {
    type Item = (L, R, V);
    type IntoIter = std::iter::Map<
        <HashMap<L, (R, V)> as std::iter::IntoIterator>::IntoIter,
        fn((L, (R, V))) -> (L, R, V),
    >;

    fn into_iter(self) -> Self::IntoIter {
        self.left.into_iter().map(|(l, (r, v))| (l, r, v))
    }
}

impl<'a, L, R, V, S> IntoIterator for &'a BiHashMap<L, R, V, S> {
    type Item = (&'a L, &'a R, &'a V);
    type IntoIter = std::iter::Map<
        std::collections::hash_map::Iter<'a, L, (R, V)>,
        fn((&'a L, &'a (R, V))) -> (&'a L, &'a R, &'a V),
    >;

    fn into_iter(self) -> Self::IntoIter {
        self.left.iter().map(|(l, (r, v))| (l, r, v))
    }
}

#[cfg(test)]
mod tests {
    use crate::bimap::BiHashMap;

    #[test]
    fn test_contains() {
        let mut b = BiHashMap::new();
        b.insert(1, 2, 3);
        assert!(b.contains_right(&2));
        assert!(!b.contains_right(&3));
    }

    #[test]
    fn test_get() {
        let mut b = BiHashMap::new();
        b.insert(1, 2, 3);
        assert_eq!(b.get_by_left(&1), Some((&2, &3)));
        assert_eq!(b.get_by_right(&2), Some((&1, &3)));
        assert_eq!(b.get_by_left(&4), None);
        assert_eq!(b.get_by_right(&5), None);
    }

    #[test]
    fn test_insert() {
        let mut b = BiHashMap::new();
        let result = b.insert(1, 2, 3);
        assert_eq!(result, None);
        assert!(b.contains_right(&2));

        let result = b.insert(1, 4, 5);
        assert_eq!(result, Some((1, 2, 3)));
        assert!(b.contains_right(&4));
        assert!(!b.contains_right(&2));

        let result = b.insert(6, 4, 7);
        assert_eq!(result, Some((1, 4, 5)));
        assert!(b.contains_right(&4));
        assert_eq!(b.get_by_left(&6), Some((&4, &7)));
        assert_eq!(b.get_by_right(&4), Some((&6, &7)));

        let result = b.insert(6, 8, 9);
        assert_eq!(result, Some((6, 4, 7)));
        assert!(b.contains_right(&8));
        assert_eq!(b.get_by_left(&6), Some((&8, &9)));
        assert_eq!(b.get_by_right(&8), Some((&6, &9)));
    }

    #[test]
    fn test_remove() {
        let mut b = BiHashMap::new();
        b.insert(1, 2, 3);
        assert!(b.contains_right(&2));

        let result = b.remove_by_left(&1);
        assert_eq!(result, Some((2, 3)));
        assert!(!b.contains_right(&2));

        b.insert(1, 2, 3);
        assert!(b.contains_right(&2));

        let result = b.remove_by_right(&2);
        assert_eq!(result, Some((1, 3)));
        assert!(!b.contains_right(&2));

        let result = b.remove_by_left(&1);
        assert_eq!(result, None);
        let result = b.remove_by_right(&2);
        assert_eq!(result, None);
    }
}