aeuo
August 27, 2023, 10:22am
1
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);
}
aeuo
August 27, 2023, 11:05am
3
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
aeuo
August 27, 2023, 11:23am
4
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
aeuo
August 27, 2023, 1:35pm
6
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
aeuo
August 27, 2023, 1:37pm
7
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
H2CO3
August 27, 2023, 9:04pm
10
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