lowdash 0.5.3

A Lodash inspired utility library to manipulate array and object for Rust
Documentation
/// Find all duplicate elements in a collection based on a key generated by the iteratee function.
/// Each duplicate element (beyond the first occurrence) appears in the result.
///
/// # Arguments
/// * `collection` - A slice of items.
/// * `iteratee` - A function that takes an item and returns a key used for duplicate detection.
///
/// # Returns
/// * `Vec<T>` - A vector containing all duplicate elements beyond their first occurrence.
///
/// # Examples
/// ```rust
/// use lowdash::find_duplicates_by;
/// let numbers = vec![1, 2, 3, 4];
/// let result = find_duplicates_by(&numbers, |x| x % 2);
/// assert_eq!(result, vec![3, 4]); // Second occurrences of duplicated keys
/// ```
///
/// ```rust
/// use lowdash::find_duplicates_by;
///
/// #[derive(Debug, Clone, PartialEq)]
/// struct Person {
///     name: String,
///     age: u32,
/// }
///
/// let people = vec![
///     Person { name: "Alice".to_string(), age: 25 },
///     Person { name: "Bob".to_string(), age: 30 },
///     Person { name: "Carol".to_string(), age: 25 },
/// ];
///
/// let result = find_duplicates_by(&people, |p| p.age);
/// assert_eq!(result, vec![
///     Person { name: "Carol".to_string(), age: 25 },
/// ]);
/// ```
pub fn find_duplicates_by<T, U, F>(collection: &[T], iteratee: F) -> Vec<T>
where
    T: Clone,
    U: Eq + std::hash::Hash + Clone,
    F: Fn(&T) -> U,
{
    use std::collections::HashMap;

    // First pass: count occurrences of each key
    let mut key_counts: HashMap<U, usize> = HashMap::new();
    for item in collection {
        let key = iteratee(item);
        *key_counts.entry(key).or_insert(0) += 1;
    }

    let mut seen: HashMap<U, usize> = HashMap::new();
    let mut result = Vec::new();

    // Second pass: collect duplicates beyond the first occurrence
    for item in collection {
        let key = iteratee(item);
        if let Some(&count) = key_counts.get(&key) {
            if count > 1 {
                let entry = seen.entry(key.clone()).or_insert(0);
                if *entry >= 1 {
                    result.push(item.clone());
                }
                *entry += 1;
            }
        }
    }

    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_find_duplicates_by_remainder() {
        let numbers = vec![1, 2, 3, 4];
        let result = find_duplicates_by(&numbers, |x| x % 2);
        assert_eq!(result, vec![3, 4]); // Second occurrences of duplicated keys
    }

    #[test]
    fn test_find_duplicates_by_length() {
        let words = vec!["a", "ab", "cd", "ef", "abcd"];
        let result = find_duplicates_by(&words, |s| s.len());
        assert_eq!(result, vec!["cd", "ef"]); // Words with length 2 appear more than once
    }

    #[test]
    fn test_find_duplicates_by_empty() {
        let collection: Vec<i32> = vec![];
        let result = find_duplicates_by(&collection, |x| x % 2);
        assert_eq!(result, Vec::<i32>::new());
    }

    #[test]
    fn test_find_duplicates_by_with_struct() {
        #[derive(Debug, Clone, PartialEq)]
        struct Person {
            name: String,
            age: u32,
        }

        let people = vec![
            Person {
                name: "Alice".to_string(),
                age: 25,
            },
            Person {
                name: "Bob".to_string(),
                age: 30,
            },
            Person {
                name: "Carol".to_string(),
                age: 25,
            },
        ];

        // Test duplicates by age
        let result = find_duplicates_by(&people, |p| p.age);
        assert_eq!(
            result,
            vec![Person {
                name: "Carol".to_string(),
                age: 25,
            },]
        );

        // Test duplicates by name first letter
        let name_result = find_duplicates_by(&people, |p| p.name.chars().next().unwrap());
        assert_eq!(name_result, Vec::new()); // No duplicate first letters
    }

    #[test]
    fn test_find_duplicates_by_all_same() {
        let numbers = vec![1, 2, 3, 4];
        let result = find_duplicates_by(&numbers, |_| 1);
        assert_eq!(result, vec![2, 3, 4]); // All except first are duplicates
    }

    #[test]
    fn test_find_duplicates_by_floats() {
        let floats = vec![1.1, 2.2, 3.3, 2.2, 4.4, 1.1];
        let result = find_duplicates_by(&floats, |x: &f64| (*x).to_bits());
        assert_eq!(result, vec![2.2, 1.1]);
    }
}