Review request: Grouping operation while preserving the insertion order

Here is a small code which allows grouping of data, it's by no means production grade but using this to explore and understand some concepts in the language and the std-lib.
Any feedback or criticism around code quality is appreciated.

/// Provides a struct `OrderedGroup<K, V>` which groups items of type `V` into
/// buckets of type `K` while preserving the insertion order of `K` and each 
/// of the `V` into it's respective bucket.
///
/// # Example
///
/// ```
///        let mut ogroup: OrderedGroup<String, String> = OrderedGroup::new();
///        let data: [[String; 2]; 3] = [
///            [String::from("UK"), String::from("James")],
///            [String::from("Canada"), String::from("Michael")],
///            [String::from("UK"), String::from("Brad")],
///        ];
///
///        for row in data.iter() {
///            ogroup.insert(row[0].clone(), row[1].clone());
///        }
///
///        let mut og_iter = ogroup.into_iter();
///        // The iterator returns a tuple of grouping key <K> and
///        // a Vec<V> of values grouped under it.
///        let (country, people) = og_iter.next().unwrap();
///        // Since UK was inserted first, it should be returned first.
///        assert_eq!("UK", country);
///        assert_eq!(2, people.len());
///        // Order of James & Brad should match the order of their insertion
///        assert_eq!("James", people[0]);
///        assert_eq!("Brad", people[1]);
///        // Next should be Canada
///        let (country2, people2) = og_iter.next().unwrap();
///        assert_eq!("Canada", country2);
///        assert_eq!(1, people2.len());
///        assert_eq!("Michael", people2[0]);
/// ```
///
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;

#[derive(Debug, Default)]
pub struct OrderedGroup<K: Eq + Hash + Debug + Clone, V: Debug> {
    map: HashMap<K, Vec<V>>,
    key_ordering: Vec<K>,
}

pub struct OrderedGroupIterator<'a, K: Eq + Hash + Debug + Clone, V: Debug> {
    index: usize,
    group: &'a OrderedGroup<K, V>,
}

impl<'a, K, V> IntoIterator for &'a OrderedGroup<K, V>
where
    K: Debug + Eq + Hash + Clone,
    V: Debug,
{
    type Item = (&'a K, &'a Vec<V>);
    type IntoIter = OrderedGroupIterator<'a, K, V>;

    fn into_iter(self) -> Self::IntoIter {
        OrderedGroupIterator {
            index: 0,
            group: self,
        }
    }
}

impl<'a, K, V> Iterator for OrderedGroupIterator<'a, K, V>
where
    K: Debug + Eq + Hash + Clone,
    V: Debug,
{
    type Item = (&'a K, &'a Vec<V>);

    fn next(&mut self) -> Option<Self::Item> {
        if self.index >= self.group.key_ordering.len() {
            return None;
        }
        let key = &self.group.key_ordering[self.index];
        let val = self.group.map.get(key).unwrap();
        self.index += 1;
        Some((key, val))
    }
}

impl<K, V> OrderedGroup<K, V>
where
    K: Eq + Hash + Debug + Clone,
    V: Debug,
{
    pub fn new() -> OrderedGroup<K, V> {
        OrderedGroup {
            map: HashMap::new(),
            key_ordering: vec![],
        }
    }

    pub fn insert(&mut self, key: K, val: V) {
        if !self.map.contains_key(&key) {
            self.key_ordering.push(key.clone());
            self.map.insert(key, vec![val]);
        } else {
            self.map.get_mut(&key).unwrap().push(val);
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::ordered_group::OrderedGroup;

    #[test]
    fn test_iterates_preserving_insertion_order() {
        let mut ogroup: OrderedGroup<String, String> = OrderedGroup::new();
        let data: [[String; 2]; 7] = [
            [String::from("UK"), String::from("James")],
            [String::from("UK"), String::from("Monica")],
            [String::from("Canada"), String::from("Michael")],
            [String::from("Australia"), String::from("Brad")],
            [String::from("UK"), String::from("Tim")],
            [String::from("Argentina"), String::from("Mauricio")],
            [String::from("Canada"), String::from("Venkat")],
        ];

        for row in data.iter() {
            ogroup.insert(row[0].clone(), row[1].clone());
        }

        let mut og_iter = ogroup.into_iter();
        let (country, people) = og_iter.next().unwrap();
        assert_eq!("UK", country);
        assert_eq!(3, people.len());
        assert_eq!("James", people[0]);
        assert_eq!("Monica", people[1]);
        assert_eq!("Tim", people[2]);

        let (country2, people2) = og_iter.next().unwrap();
        assert_eq!("Canada", country2);
        assert_eq!(2, people2.len());
        assert_eq!("Michael", people2[0]);
        assert_eq!("Venkat", people2[1]);

        let (country3, people3) = og_iter.next().unwrap();
        assert_eq!("Australia", country3);
        assert_eq!(1, people3.len());
        assert_eq!("Brad", people3[0]);

        let (country4, people4) = og_iter.next().unwrap();
        assert_eq!("Argentina", country4);
        assert_eq!(1, people4.len());
        assert_eq!("Mauricio", people4[0]);
    }
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.