Search code examples
rust

Clone any collection by selecting elements based on a criteria


I have a collection(for time being Vec) which I want to clone but having only the elements that specify a criteria. Basically I want the C++ functionality of std::copy_if. I want the below generic code to work for any collection type. If the input collection type is a Vec, I want the output collection type to be also a Vec.

fn clone_if<'a, List, ItemType, BinaryPredicate>(list: List, binary_predicate: BinaryPredicate) -> List
where
    List: Clone + IntoIterator<Item = &'a ItemType> + Copy,
    ItemType: 'a,
    BinaryPredicate: Fn(&ItemType) -> bool
{
    let mut res = list.clone();
    for item in res.into_iter() {
        if !binary_predicate(&item) {
            // res.remove();
        }
    }
    
    res
}

fn main() {
    let first_list = vec![1, 2, 3, 4, 5, 6];
    let second_list = clone_if(&first_list, |item| {
        (item & 1) == 0
    });
    
    assert_eq!(vec![2, 4, 6], *second_list);
}

Solution

  • The current function signature of clone_if is not compatible with what you want to achieve. So I rewrote it slightly to probably match what you actually want.

    Here you go:

    fn clone_if<List, ItemType, BinaryPredicate>(list: &List, binary_predicate: BinaryPredicate) -> List
    where
        for<'a> &'a List: IntoIterator<Item = &'a ItemType>,
        List: FromIterator<ItemType>,
        ItemType: Clone,
        BinaryPredicate: Fn(&ItemType) -> bool,
    {
        list.into_iter()
            .filter(|val| binary_predicate(*val))
            .cloned()
            .collect()
    }
    
    fn main() {
        let first_list = vec![1, 2, 3, 4, 5, 6];
        let second_list = clone_if(&first_list, |item| (item & 1) == 0);
    
        assert_eq!(vec![2, 4, 6], *second_list);
    }
    

    Explanation:

    • list: &List - to call it the way you do in main, you need a reference here.
    • for<'a> &'a List: IntoIterator<Item = &'a ItemType> - you want the list reference to produce references to its elements when iterated over, which is what &Vec is compatible with. That prevents unnecessary copying.
    • List: FromIterator<ItemType> - Needed to produce the output value with collect().
    • ItemType: Clone - you want to create clones of the input items in your output list.
    • All other Clone and Copys were removed because they were unnecessary.

    Then the algorithm itself:

    • .into_iter() - Create an Iterator<Item = &ItemType> from your input list
    • .filter(|val| binary_predicate(*val)) - Do the filtering. No copies are made yet. The reason why a closure and dereference is needed is because filter takes references to the items iterated over, which in this case will be &&ItemType. So a small wrapper is needed to convert &&ItemType to &ItemType, which is what binary_predicate needs.
    • .cloned() - Clone all the items iterated over. This will convert the Iterator<Item = &ItemType> to an Iterator<Item = ItemType>. Note that this is done after filter() to prevent copying items that get filtered out anyway.
    • .collect() use FromIterator to convert the Iterator<Item = ItemType> to List.