Retrieve the two first elements that match a condition - c#

I'm sure that this can be easily done with Linq but I can't figure it out.
var ls1 = plotter.Model.Series.FirstOrDefault(x => x.IsSelected);
var ls2 = plotter.Model.Series.FirstOrDefault((x => x.IsSelected)&&(ls2!=ls1));
What I'm pretending to do is to obtain the two first objects that have their property IsSelected set to true.
I can't use the syntax written above because the compiler can't use "local variable ls2 before it is declared".

Use Where to filter only the selected results then use Take to select the first two e.g.
plotter.Model.Series.Where(x => x.IsSelected).Take(2);

Try this:
var ls1and2 = plotter.Model.Series.Where(x => x.IsSelected).Take(2);

var ls1 = plotter.Model.Series.Where(x => x.IsSelected).Take(2);

You should use the Take method and do this
var ls1 = plotter.Model.Series.Where(x => x.IsSelected).Take(2);

Related

LINQ list exclude item with latest datetime

I have a list of objects, the object contains a datetime.
I want to use LINQ to exclude the object with the latest datetime.
I'm trying something like:
var excludedList = testdata.Except(testdata.Max(c => c.registeredAt).FirstOrDefault()).ToList();
But it does not work, what should the correct linq be?
You could try something like this:
var excludedList = testdata.OrderByDescending(i => i.registeredAt).Skip(1).ToList();
This will sort the list by date, then skip the first item.
Here's a solution that doesn't perform a whole sort just to extract a single maximum value, and preserves the order of the sequence. It does still depend on the maximum value being unique.
var maxRegisteredAt = testdata.Max(c => c.registeredAt);
var excludedList = testdata
.Where(c => c.registeredAt != maxRegisteredAt)
.ToList();
This is one possible approach if duplicate registeredAt datetimes aren't possible:
var maxObj = testdata.OrderByDescending(x => x?.registeredAt).First();
var excludedList = testdata.FindAll(x => x != maxObj);
This keeps the original order.

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!
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.
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()
);
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());
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

why is this linq query return a boolean and not the first result of the select?

I have a string array with 5 items. How to get one of these 5 items by a linq query?
Code below returns only a boolean true.
string[] allWebTemplateSettings =SiteLidmaatschapSettings.Current.ProvisioningSettings;
var webTemplate = allWebTemplateSettings
.Select(x => x.StartsWith(string.Format("Template:{0}", web.WebTemplate)))
.FirstOrDefault();
Use Where instead of Select:
var webTemplate = allWebTemplateSettings.Where(x => x.StartsWith(string.Format("Template:{0}", web.WebTemplate))).FirstOrDefault();
Well, you're getting an IEnumerable of bools with your Select, then you pick the first one if there are any. That's why you're getting a bool as your answer.
I think what you actually want is this:
string[] allWebTemplateSettings = SiteLidmaatschapSettings.Current.ProvisioningSettings;
var prefix = string.Format("Template:{0}", web.WebTemplate);
var webTemplate = allWebTemplateSettings
.FirstOrDefault(x => x.StartsWith(prefix));
I've moved the string formatting operation out of the predicate since it is wasteful to recompute it for each element in your collection (especially if the collection is long).
You are confusing Select, which selects a new value based on each existing value of a sequence, with Where, which filters a sequence so it only contains items where a condition is met.
The simplest change is to replace your usage of Select with Where.
string[] allWebTemplateSettings = SiteLidmaatschapSettings.Current.ProvisioningSettings;
var webTemplate = allWebTemplateSettings
.Where(x => x.StartsWith(string.Format("Template:{0}", web.WebTemplate)))
.FirstOrDefault();
The other answers have rolled this usage of Where into FirstOrDefault without explaining your underlying confusion.
That's because StartsWith returns a bool and you're saying Select that bool based on whether it starts with that value or not. So actually, you're not even filtering on that value at all because you're not using a filter expression.
Actually, you only need FirstOrDefault as the list is already a List<string>
string[] allWebTemplateSettings = SiteLidmaatschapSettings.Current.ProvisioningSettings;
var webTemplate = allWebTemplateSettings
.FirstOrDefault(x => x.StartsWith(string.Format("Template:{0}", web.WebTemplate)));

two foreach in linq?

...I've tried something but I got the "Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator." exception.
I'm talking about this:
Query = Query.Where(t => this.SysTextBox.Text.CSL2Array().All(ss => t.SysName.Contains(ss)));
I'm kind of new at this, but I kept trying to make it work. Thank you in advance!
You should be able to do the query after ToList:
Query = Query.ToList<TagsDeleted>()
.Where(t => this.SubSystemTextBox.Text.CSL2Array().All(ss => t.SubsystemName.Contains(ss)))
.AsQueryable<TagsDeleted>();
But that is not easy to read, I would refactor to the following to make it clear that "I create a list, and want to remove some items":
var words = this.SubSystemTextBox.Text.CSL2Array();
var list = Query.ToList<TagsDeleted>();
list.RemoveAll(t => !words.All(word => t.SubsystemName.Contains(word)));
Query = list.AsQueryable<TagsDeleted>();
var arr = this.SubSystemTextBox.Text.CSL2Array();
var notContained = Query.Where(td=>arr.Any(ss => !td.SubsystemName.Contains(ss)));
Query = Query.Except(notContained);
You can figure this out how to make it in one line, this was just to be clear.

How to assign to a variable the results of Find

I want to assign a variable from a Find method. Something like this:
object a = Collection.Find(x => x.propertie == whatever).propertie
The problem here is if my find query doesn't find anything. I just wanted to know if there was a way to do it only with one line.
What you can do is use LINQ to project your sequence of zero to n items into a property of that sequence. This will only apply the projection if the item exists:
var a = collection.Select(x => x.Property)
.FirstOrDefault(value => value == whatever);

Categories