最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

rust - Clone any collection by selecting elements based on a criteria - Stack Overflow

programmeradmin0浏览0评论

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

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);
}
Share Improve this question edited Feb 2 at 8:34 cafce25 27.6k5 gold badges45 silver badges58 bronze badges asked Feb 2 at 7:01 HarryHarry 3,1421 gold badge24 silver badges46 bronze badges 4
  • 3 list.iter().filter (predicate).cloned().collect() – Jmb Commented Feb 2 at 7:19
  • @Jmb Thanks for the solution. How to achieve the same using my version of clone_if? – Harry Commented Feb 2 at 8:17
  • You cannot; your clone_if function is flawed as it is right now. You cannot return a reference to an item created inside of a function. You need to have ownership. So your return type needs to be some variant of Vec<T>, while your argument probably wants to be &Vec<T>, which is impossible to achieve with your current function signature. I'll propose a better signature. – Finomnis Commented Feb 2 at 8:23
  • Be aware that for most algorithms, collecting intermediate data into heap allocations might be a bottleneck. Leave the data inside of referencing iterators as long as possible without cloning and collecting, which will give you the smallest possible memory footprint and the fastest execution times. The Rust compiler is very good at optimizing chained iterators. – Finomnis Commented Feb 2 at 8:40
Add a comment  | 

1 Answer 1

Reset to default 1

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.
发布评论

评论列表(0)

  1. 暂无评论