pub trait CollectByKey<Item, Key, Output> {
    fn collect_by_key(self, f: impl Fn(&Item) -> Key) -> Output;
}

macro_rules! impl_for {
    ($map_type:ty, $list_type:ty, $add_to_list_method:ident) => {
        impl_for!($map_type, $list_type, $add_to_list_method, From::<I>);
    };

    ($map_type:ty, $list_type:ty, $add_to_list_method:ident, $item_type_bounds:expr) => {
        paste::paste! {
            impl<T: IntoIterator<Item = I>, I: $item_type_bounds, K: Eq + std::hash::Hash> CollectByKey<I, K, $map_type<K, $list_type<I>>> for T {
                fn collect_by_key(self, f: impl Fn(&I) -> K) -> $map_type<K, $list_type<I>> {
                    self.into_iter()
                        .fold($map_type::default(), |mut acc, curr| {
                            let key = f(&curr);
                            match acc.get_mut(&key) {
                                Some(list) => {
                                    list.$add_to_list_method(curr);
                                }
                                None => {
                                    let mut list = $list_type::default();
                                    list.$add_to_list_method(curr);
                                    acc.insert(key, list);
                                }
                            }
                            acc
                        })
                }
            }
        }
    };
}

impl_for!(std::collections::HashMap, Vec, push);
impl_for!(
    std::collections::HashMap,
    std::collections::HashSet,
    insert,
    Eq + std::hash::Hash
);
#[cfg(feature = "indexmap")]
impl_for!(
    std::collections::HashMap,
    indexmap::IndexSet,
    insert,
    Eq + std::hash::Hash
);

#[cfg(feature = "indexmap")]
impl_for!(indexmap::IndexMap, Vec, push);
#[cfg(feature = "indexmap")]
impl_for!(
    indexmap::IndexMap,
    std::collections::HashSet,
    insert,
    Eq + std::hash::Hash
);
#[cfg(feature = "indexmap")]
impl_for!(
    indexmap::IndexMap,
    indexmap::IndexSet,
    insert,
    Eq + std::hash::Hash
);
