Lance McCarthy
Lance McCarthy

Reputation: 1917

LINQ Intersect on inner collection

I have a list of Stores (of type ObservableCollection<Store>) and the Store object has a property called Features ( of type List<Feature> ). and the Feature object has a Name property (of type string).

To recap, a list of Stores that has a list of Features

I have a second collection of DesiredFeatures (of type List<string> ).

I need to use LINQ to give me results of only the stores that have all the DesiredFeatures. So far, I've only been able to come up with a query that gives me an OR result instead of AND.

Here's what that looks like:

var q = Stores.Where(s=> s.Features.Any(f=> DesiredFeatures.Contains(f.name)));

I know Intersect can help, and here's how I've used it:

var q = Stores.Where(s => s.Features.Intersect<Feature>(DesiredFeatures));

This is where I'm stuck, Intersect wants a Feature object, what I need to intersect is on the Feature.Name.

The goal is to end up with an ObservableCollection where each Store has all of the DesiredFeatures.

Thank you!

Upvotes: 2

Views: 192

Answers (4)

Ivan Stoev
Ivan Stoev

Reputation: 205589

I need to use LINQ to give me results of only the stores that have all the DesiredFeatures.

In other words, each desired feature must have a matching store feature.

I don't see how Intersect can help in this case. The direct translation of the above criteria to LINQ is like this:

var q = Stores.Where(s =>
    DesiredFeatures.All(df => s.Features.Any(f => f.Name == df))
);

A more efficient way could be to use a GroupJoin for performing the match:

var q = Stores.Where(s => 
    DesiredFeatures.GroupJoin(s.Features,
        df => df, sf => sf.Name, (df, sf) => sf.Any()
    ).All(match => match)
);

or Except to check for unmatched items:

var q = Stores.Where(s =>
    !DesiredFeatures.Except(s.Features.Select(sf => sf.Name)).Any()
);

Upvotes: 1

Hari Prasad
Hari Prasad

Reputation: 16956

Two things you want your code to perform.

var q = Stores.Where(s=> s.Features.All(f=> DesiredFeatures.Contains(f.name)) && 
                         s.Features.Count() == DesiredFeatures.Count()); // Incude Distinct in the comparison if Features list is not unique
  • Ensure that every Feature is DesiredFeature
  • Store contains all Desired features.

Code above assumes uniqueness in Features collection as well as DesiredFeatures, modify code as stated in comment line if this is not right

Upvotes: 0

Kody
Kody

Reputation: 965

Going on your intersect idea, the only way I thought of making this work was by using Select to get the Store.Features (List<Feature>) as a list of Feature Names (List<string>) and intersect that with DesiredFeatures.

Updated Answer:

var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures).Any());

or

var q = Stores.Where(s => DesiredFeatures.Intersect(s.Features.Select(f => f.Name)).Any());

Old Answer (if DesiredFeatures is a List<Feature>):

var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures.Select(df => df.Name)).Any());

Upvotes: 1

Ivan Gritsenko
Ivan Gritsenko

Reputation: 4236

You've almost done what you need. A small refine would be to swap DesiredFeatures and s.Features.

var q = Stores.Where(s => DesiredFeatures.All(df => s.Features.Contains(df)));

It means take only those stores where desired features are all contained in features of the store.

Upvotes: 2

Related Questions