use std::collections::HashSet;
use std::hash::Hash;
pub fn find_duplicates_by<T, K, F>(
items: impl IntoIterator<Item = T>,
mut key_selector: F,
) -> Vec<K>
where
K: Eq + Hash + Clone,
F: FnMut(&T) -> K,
{
let mut seen = HashSet::new();
let mut reported = HashSet::new();
let mut duplicates = Vec::new();
for item in items {
let key = key_selector(&item);
if !seen.insert(key.clone()) && reported.insert(key.clone()) {
duplicates.push(key);
}
}
duplicates
}
pub fn ensure_unique_by<T, K, F>(
items: impl IntoIterator<Item = T>,
mut key_selector: F,
) -> Result<(), K>
where
K: Eq + Hash + Clone,
F: FnMut(&T) -> K,
{
let mut seen = HashSet::new();
for item in items {
let key = key_selector(&item);
if !seen.insert(key.clone()) {
return Err(key);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{ensure_unique_by, find_duplicates_by};
#[test]
fn find_duplicates_reports_each_key_once_in_order() {
let items = vec![
"apple",
"avocado",
"banana",
"cherry",
"cantaloupe",
"apricot",
];
let dups = find_duplicates_by(&items, |s| s.chars().next().unwrap());
assert_eq!(dups, vec!['a', 'c']);
}
#[test]
fn find_duplicates_empty_when_all_unique() {
let items = vec!["a", "b", "c"];
let dups = find_duplicates_by(&items, |s| *s);
assert!(dups.is_empty());
}
#[test]
fn ensure_unique_ok_when_all_unique() {
assert!(ensure_unique_by(&["a", "b", "c"], |s| *s).is_ok());
}
#[test]
fn ensure_unique_returns_first_duplicate() {
let err: &str = ensure_unique_by(&["a", "b", "a", "b"], |s| *s).unwrap_err();
assert_eq!(err, "a");
}
}