Filter items in collection depending on field in struct

Hello!
I have written the code to filter out items in vector depends on field.
Is it possible to simplify this code?
And is it possible to avoid dyn traits to make filtering faster? As I know dyn traits is not free.

trait Cmp {
    fn eq(&self, item: &Item) -> bool;
}

struct CmpField1 {
    pattern: String,
}

impl Cmp for CmpField1 {
    fn eq(&self, item: &Item) -> bool {
        item.field1.cmp(&self.pattern).is_eq()
    }
}

struct CmpField2 {
    pattern: usize,
}

impl Cmp for CmpField2 {
    fn eq(&self, item: &Item) -> bool {
        item.field2.cmp(&self.pattern).is_eq()
    }
}

#[derive(Debug)]
struct Item {
    field1: String,
    field2: usize,
}

enum Pattern {
    I(usize),
    S(String),
}

fn factory(p: Pattern) -> Box<dyn Cmp> {
    match p {
        Pattern::S(v) => Box::new(CmpField1 { pattern: v }),
        Pattern::I(v) => Box::new(CmpField2 { pattern: v }),
    }
}

fn main() {
    let v = vec![
        Item {
            field1: String::from("a"),
            field2: 1,
        },
        Item {
            field1: String::from("b"),
            field2: 2,
        },
    ];

    let cmp = factory(Pattern::I(1));
    for item in v.iter().filter(|&x| cmp.eq(x)) {
        println!("{:?}", item);
    }

    let cmp = factory(Pattern::S("b".to_string()));
    for item in v.iter().filter(|&x| cmp.eq(x)) {
        println!("{:?}", item);
    }
}

Well, since you didn't specify your requirements, I propose the following simplification.


    for item in v.iter().filter(|&x| x.field2 == 1) {
        println!("{:?}", item);
    }

    for item in v.iter().filter(|&x| x.field1 == "b") {
        println!("{:?}", item);
    }

Ok. I have enough long expression to work with items in array and in order to not duplicate it I would like to create one object or one closure to filter out appropriate items.
For example,
vec.iter().filter(my_filter_closure).sorted_by(my_sort_closure).skip(offset).take(limit)
Compare two different approch:

First one

fn search(input: Vec<Item>, offset: usize, limit:usize, pattern: Pattern) -> Vec<Item> {
    match pattern {
        Pattern::S(v) => input.iter().filter(|x| x.field1.eq(&v)).sorted_by(...).map(|x| x.clone()).collect(),
        Pattern::I(v) => input.iter().filter(|x| x.field2.eq(&v)).sorted_by(...).map(|x| x.clone()).collect(),
    }
}

Seconds one

fn search(input: Vec<Item>, offset: usize, limit:usize, pattern: Pattern) -> Vec<Item> {
    let cmp = factory(pattern);
    input.iter().filter(|x| cmp.eq(&x)).sorted_by(...).map(|x| x.clone()).collect(),
}

Moreover I need to closure for sorted_by where first approch becomes more complex to read and maintain

If I would like to add closure for sorted_by the code would look like

fn search(input: Vec<Item>, offset: usize, limit:usize, pattern: Pattern) -> Vec<Item> {
    match pattern {
        Pattern::S(v) => {
            match pattern {
                Pattern::S(v) => {
                    input.iter().filter(|x| x.field1.eq(&v)).sorted_by(...).map(|x| x.clone()).collect(),
                }
                Pattern::I(v) => {
                    input.iter().filter(|x| x.field1.eq(&v)).sorted_by(...).map(|x| x.clone()).collect(),
                }
            }
        }
        Pattern::I(v) => {
            match pattern {
                Pattern::S(v) => {
                    input.iter().filter(|x| x.field1.eq(&v)).sorted_by(...).map(|x| x.clone()).collect(),
                }
                Pattern::I(v) => {
                    input.iter().filter(|x| x.field1.eq(&v)).sorted_by(...).map(|x| x.clone()).collect(),
                }
            }
        }
    }
}

I'd give Pattern a method to create a filter from itself.

enum Pattern {
    I(usize),
    S(String),
}

impl Pattern {
    fn as_filter<'a>(&'a self) -> impl Fn(&&Item) -> bool + 'a {
        move |x| match self {
            Pattern::I(v) => x.field2 == *v,
            Pattern::S(v) => x.field1 == *v,
        }
    }
}

You'd use it like this:

    for item in v.iter().filter(Pattern::I(1).as_filter()) {
        println!("{:?}", item);
    }

    for item in v.iter().filter(Pattern::S("b".into()).as_filter()) {
        println!("{:?}", item);
    }
1 Like

Thank you. Good idea.
But every time during filtering the filter chooses from "match" which enum is specified

for item in v.iter().filter(Pattern::I(1).as_filter()) {
    println!("{:?}", item);
}

But may be that is faster then dyn trait

And your solution more readable than mine

The impact should be minimal as the same branch is taken for every iteration of a loop - the branch predictor should notice that.

The compiler will also inline and elide the check if it knows that only one value is possible.

2 Likes

Agree. Thank you.

Yes. If you want to allow filtering by different conditions based on a run-time value, then there is obviously no way to completely avoid doing something dynamically. The compiler can't predict what the user will choose at runtime, after all.

1 Like