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]);
}
}